From ea6c102275c8839ab5360dceed924df2347b6061 Mon Sep 17 00:00:00 2001 From: Ali Mosajjal Date: Tue, 10 Jan 2023 08:47:09 +1300 Subject: [PATCH] mvp support of upstream socks5 (#22) --- httpproxy.go | 6 +++++- https.go | 32 ++++++++++++++++++++++---------- main.go | 29 +++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/httpproxy.go b/httpproxy.go index 361ba5e..131ab9c 100644 --- a/httpproxy.go +++ b/httpproxy.go @@ -122,8 +122,12 @@ func handle80(w http.ResponseWriter, r *http.Request) { rr.Header.Set("Host", hostPort) } + transport := http.Transport{ + Dial: c.dialer.Dial, + } + // Forward request to origin server - resp, err := http.DefaultTransport.RoundTrip(&rr) + resp, err := transport.RoundTrip(&rr) if err != nil { // TODO: Passthru more error information http.Error(w, "Could not reach origin server", 500) diff --git a/https.go b/https.go index 7a757dc..170b5f7 100644 --- a/https.go +++ b/https.go @@ -8,6 +8,7 @@ import ( "net" slog "golang.org/x/exp/slog" + "golang.org/x/net/proxy" ) var httpslog = slog.New(log.Handler().WithAttrs([]slog.Attr{{Key: "service", Value: slog.StringValue("https")}})) @@ -77,16 +78,27 @@ func handle443(conn net.Conn) error { "source_ip", conn.RemoteAddr().String(), "host", sni, ) - // with the manipulation of the soruce address, we can set the outbound interface - srcAddr := net.TCPAddr{ - IP: c.sourceAddr, - Port: 0, - } - target, err := net.DialTCP("tcp", &srcAddr, &net.TCPAddr{IP: rAddr, Port: rPort}) - if err != nil { - httpslog.Error("could not connect to target", err) - conn.Close() - return err + var target *net.TCPConn + if c.dialer == proxy.Direct { + // with the manipulation of the soruce address, we can set the outbound interface + srcAddr := net.TCPAddr{ + IP: c.sourceAddr, + Port: 0, + } + target, err = net.DialTCP("tcp", &srcAddr, &net.TCPAddr{IP: rAddr, Port: rPort}) + if err != nil { + httpslog.Error("could not connect to target", err) + conn.Close() + return err + } + } else { + tmp, err := c.dialer.Dial("tcp", fmt.Sprintf("%s:%d", rAddr, rPort)) + if err != nil { + httpslog.Error("could not connect to target", err) + conn.Close() + return err + } + target = tmp.(*net.TCPConn) } defer target.Close() c.proxiedHTTPS.Inc(1) diff --git a/main.go b/main.go index 472bc10..ed44f1a 100644 --- a/main.go +++ b/main.go @@ -2,9 +2,11 @@ package main import ( "encoding/json" + "fmt" "io" "net" "net/http" + "net/url" "os" "path/filepath" "strings" @@ -22,12 +24,14 @@ import ( "github.com/miekg/dns" slog "golang.org/x/exp/slog" + "golang.org/x/net/proxy" ) type runConfig struct { BindIP string `json:"bindIP"` PublicIP string `json:"publicIP"` UpstreamDNS string `json:"upstreamDNS"` + UpstreamSOCKS5 string `json:"upstreamSOCKS5"` DomainListPath string `json:"domainListPath"` DomainListRefreshInterval duration `json:"domainListRefreshInterval"` BindDNSOverTCP bool `json:"bindDnsOverTcp"` @@ -57,6 +61,7 @@ type runConfig struct { mmdb *maxminddb.Reader dnsClient DNSClient + dialer proxy.Dialer sourceAddr net.IP reverseProxySNI string @@ -162,6 +167,7 @@ func getPublicIPInner() (string, error) { func main() { flag.StringVar(&c.BindIP, "bindIP", "0.0.0.0", "Bind 443 and 80 to a Specific IP Address. Doesn't apply to DNS Server. DNS Server always listens on 0.0.0.0") flag.StringVar(&c.UpstreamDNS, "upstreamDNS", "udp://8.8.8.8:53", "Upstream DNS URI. examples: udp://1.1.1.1:53, tcp://1.1.1.1:53, tcp-tls://1.1.1.1:853, https://dns.google/dns-query") + flag.StringVar(&c.UpstreamSOCKS5, "upstreamSOCKS5", "socks5://admin:admin@127.0.0.1:1080", "Use a SOCKS proxy for upstream HTTP/HTTPS traffic.") flag.StringVar(&c.DomainListPath, "domainListPath", "", "Path to the domain list. eg: /tmp/domainlist.csv") flag.DurationVar(&c.DomainListRefreshInterval.Duration, "domainListRefreshInterval", 60*time.Minute, "Interval to re-fetch the domain list") flag.BoolVar(&c.AllDomains, "allDomains", false, "Route all HTTP(s) traffic through the SNI proxy") @@ -291,6 +297,29 @@ func main() { } + if c.UpstreamSOCKS5 != "" { + uri, err := url.Parse(c.UpstreamSOCKS5) + if err != nil { + log.Error("", err) + } + if uri.Scheme != "socks5" { + log.Error("only SOCKS5 is supported", nil) + return + } + + log.Info("Using an upstream SOCKS5 proxy", "address", uri.Host) + u := uri.User.Username() + p, _ := uri.User.Password() + socksAuth := proxy.Auth{User: u, Password: p} + c.dialer, err = proxy.SOCKS5("tcp", uri.Host, &socksAuth, proxy.Direct) + if err != nil { + fmt.Fprintln(os.Stderr, "can't connect to the proxy:", err) + os.Exit(1) + } + } else { + c.dialer = proxy.Direct + } + tmp, err := dnsclient.New(c.UpstreamDNS, true) if err != nil { log.Error("", err)