initial commit

This commit is contained in:
dasha_uwu 2025-08-03 18:56:54 +05:00
commit c9565782b4
5 changed files with 308 additions and 0 deletions

108
config.go Normal file
View file

@ -0,0 +1,108 @@
package main
import (
"crypto/sha256"
"net/url"
"os"
"github.com/goccy/go-yaml"
)
type listenerDef struct {
protocol string
address string
acceptProxy bool
}
var listenerDefs []listenerDef
type credentials struct {
username []byte
password []byte
}
type pathDef struct {
path string
targetURL *url.URL
credentials []credentials
}
var hostDefs map[string][]pathDef
func parseConfig(configFile string) {
configData, err := os.ReadFile(configFile)
if err != nil {
panic(err)
}
var config struct {
Listeners []struct {
Protocol string
Address string
AcceptProxy bool
}
Hosts []struct {
Host string
Paths []struct {
Path string
Address string
Credentials []struct {
Username string
Password string
}
}
}
}
err = yaml.Unmarshal(configData, &config)
if err != nil {
panic(err)
}
hostDefs = make(map[string][]pathDef, len(config.Hosts))
for _, host := range config.Hosts {
hostDef := make([]pathDef, len(host.Paths))
hostDefs[host.Host] = hostDef
for i, path := range host.Paths {
targetURL, err := url.Parse(path.Address)
if err != nil {
panic(err)
}
var pdCredentials []credentials
if path.Credentials != nil {
pdCredentials = make([]credentials, len(path.Credentials))
for j, cfgCredentials := range path.Credentials {
hash := sha256.New()
hash.Write([]byte(cfgCredentials.Username))
usernameSum := hash.Sum(nil)
hash = sha256.New()
hash.Write([]byte(cfgCredentials.Password))
passwordSum := hash.Sum(nil)
pdCredentials[j] = credentials{
username: usernameSum,
password: passwordSum,
}
}
}
hostDef[i] = pathDef{
path: path.Path,
targetURL: targetURL,
credentials: pdCredentials,
}
}
}
listenerDefs = make([]listenerDef, len(config.Listeners))
for i, listener := range config.Listeners {
listenerDef := listenerDef{
protocol: listener.Protocol,
address: listener.Address,
acceptProxy: listener.AcceptProxy,
}
listenerDefs[i] = listenerDef
}
}

8
go.mod Normal file
View file

@ -0,0 +1,8 @@
module git.linuxping.win/dgrp/v2
go 1.24.5
require (
github.com/armon/go-proxyproto v0.1.0 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect
)

4
go.sum Normal file
View file

@ -0,0 +1,4 @@
github.com/armon/go-proxyproto v0.1.0 h1:TWWcSsjco7o2itn6r25/5AqKBiWmsiuzsUDLT/MTl7k=
github.com/armon/go-proxyproto v0.1.0/go.mod h1:Xj90dce2VKbHzRAeiVQAMBtj4M5oidoXJ8lmgyW21mw=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=

49
log.go Normal file
View file

@ -0,0 +1,49 @@
package main
import (
"log"
"net/http"
"strings"
"time"
)
type responseWriter struct {
http.ResponseWriter
status int
wroteHeader bool
}
func (rw *responseWriter) WriteHeader(code int) {
if rw.wroteHeader {
return
}
rw.status = code
rw.ResponseWriter.WriteHeader(code)
rw.wroteHeader = true
return
}
func loghttpreq(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
method := r.Method
path := r.URL.EscapedPath()
endpoint := strings.Split(r.RemoteAddr, ":")
ip := endpoint[0]
wrapped := &responseWriter{
ResponseWriter: w,
}
start := time.Now()
next.ServeHTTP(wrapped, r)
responseTime := time.Since(start)
log.Printf(
"%-15v %14v %v %-7v %v",
ip,
responseTime,
wrapped.status,
method,
path,
)
})
}

139
main.go Normal file
View file

@ -0,0 +1,139 @@
package main
import (
"context"
"crypto/sha256"
"crypto/subtle"
"flag"
"net"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"sync"
"github.com/armon/go-proxyproto"
)
func main() {
var configFile string
flag.StringVar(&configFile, "config", "config.yaml", "Config file")
flag.Parse()
parseConfig(configFile)
handler := loghttpreq(proxy())
wg := sync.WaitGroup{}
for _, ld := range listenerDefs {
listener, err := net.Listen(ld.protocol, ld.address)
if err != nil {
panic(err)
}
if ld.acceptProxy {
listener = &proxyproto.Listener{
Listener: listener,
}
}
wg.Add(1)
go func() {
http.Serve(listener, handler)
wg.Done()
}()
}
wg.Wait()
}
func rewrite(proxyRequest *httputil.ProxyRequest) {
target, ok := proxyRequest.In.Context().Value("target").(*url.URL)
if !ok {
panic("unreachable")
}
proxyRequest.SetURL(target)
proxyRequest.SetXForwarded()
}
func proxy() http.Handler {
revproxy := &httputil.ReverseProxy{
Rewrite: rewrite,
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tmp := strings.Split(r.Host, ":")
if len(tmp) == 0 {
w.WriteHeader(400)
return
}
hst := tmp[0]
hd, ok := hostDefs[hst]
if !ok {
hd, ok = hostDefs[""]
if !ok {
w.WriteHeader(400)
return
}
}
var pathDef pathDef
matched := false
for _, hostPathDef := range hd {
if !strings.HasPrefix(r.URL.Path, hostPathDef.path) {
continue
}
if !matched || len(hostPathDef.path) > len(pathDef.path) {
matched = true
pathDef = hostPathDef
}
}
if !matched {
w.WriteHeader(400)
return
}
r.URL.Path = r.URL.Path[len(pathDef.path):]
if pathDef.credentials != nil {
match := false
username, password, ok := r.BasicAuth()
if ok {
hash := sha256.New()
hash.Write([]byte(username))
usernameSum := hash.Sum(nil)
hash = sha256.New()
hash.Write([]byte(password))
passwordSum := hash.Sum(nil)
for _, cred := range pathDef.credentials {
res1 := subtle.ConstantTimeCompare(cred.username, usernameSum) == 1
res2 := subtle.ConstantTimeCompare(cred.password, passwordSum) == 1
if res1 && res2 {
match = true
break
}
}
}
if !match {
w.Header().Set("WWW-Authenticate", "Basic")
w.WriteHeader(401)
return
}
}
ctx := r.Context()
ctx = context.WithValue(ctx, "target", pathDef.targetURL)
r = r.WithContext(ctx)
w.Header().Add("Server", "dgrp")
revproxy.ServeHTTP(w, r)
})
}