From c626c7cdcce20cba3b6260d4ea3e528c0987d72d Mon Sep 17 00:00:00 2001 From: Vladislav Yarmak Date: Mon, 13 Apr 2020 17:02:38 +0300 Subject: [PATCH 1/2] workaround hola blocked hosts --- go.mod | 6 +- go.sum | 60 +++++++++++++++++++ handler.go | 163 +++++++++++++++++++++++++++++++++++++++++++++------- main.go | 13 ++++- resolver.go | 81 ++++++++++++++++++++++++++ utils.go | 94 ++++++++++++++++++++++++++++++ 6 files changed, 393 insertions(+), 24 deletions(-) create mode 100644 resolver.go diff --git a/go.mod b/go.mod index 2b12847..158fef2 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,8 @@ module github.com/Snawoot/hola-proxy go 1.13 -require github.com/google/uuid v1.1.1 +require ( + github.com/AdguardTeam/dnsproxy v0.25.0 + github.com/google/uuid v1.1.1 + github.com/miekg/dns v1.1.29 +) diff --git a/go.sum b/go.sum index b864886..35f4c7d 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,62 @@ +github.com/AdguardTeam/dnsproxy v0.25.0 h1:BTUPrrwB01GeQW5d2Xx4pH5HOFXcZxN1MTeNXXuy6vQ= +github.com/AdguardTeam/dnsproxy v0.25.0/go.mod h1:z2EljVLJQXFGZcP9pWABftXm9UxpLNqls7H6bMcIvEY= +github.com/AdguardTeam/golibs v0.4.0 h1:4VX6LoOqFe9p9Gf55BeD8BvJD6M6RDYmgEiHrENE9KU= +github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= +github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= +github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= +github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw= +github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us= +github.com/ameshkov/dnscrypt v1.1.0 h1:2vAt5dD6ZmqlAxEAfzRcLBnkvdf8NI46Kn9InSwQbSI= +github.com/ameshkov/dnscrypt v1.1.0/go.mod h1:ikduAxNLCTEfd1AaCgpIA5TgroIVQ8JY3Vb095fiFJg= +github.com/ameshkov/dnsstamps v1.0.1 h1:LhGvgWDzhNJh+kBQd/AfUlq1vfVe109huiXw4JhnPug= +github.com/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A= +github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-test/deep v1.0.5/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/joomcode/errorx v1.0.1 h1:CalpDWz14ZHd68fIqluJasJosAewpz2TFaJALrUxjrk= +github.com/joomcode/errorx v1.0.1/go.mod h1:kgco15ekB6cs+4Xjzo7SPeXzx38PbJzBwbnu9qfVNHQ= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg= +github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c/go.mod h1:eMyUVp6f/5jnzM+3zahzl7q6UXLbgSc3MKg/+ow9QW0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200403201458-baeed622b8d8 h1:fpnn/HnJONpIu6hkXi1u/7rR0NzilgWr4T0JmWkEitk= +golang.org/x/crypto v0.0.0-20200403201458-baeed622b8d8/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d h1:nc5K6ox/4lTFbMVSL9WRR81ixkcwXThoiF6yf+R9scA= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/handler.go b/handler.go index 1cb7b8b..5662b97 100644 --- a/handler.go +++ b/handler.go @@ -6,9 +6,8 @@ import ( "net/http/httputil" "crypto/tls" "strings" - "context" - "time" "net/url" + "bufio" ) type AuthProvider func() string @@ -18,9 +17,10 @@ type ProxyHandler struct { upstream string logger *CondLogger httptransport http.RoundTripper + resolver *Resolver } -func NewProxyHandler(upstream string, auth AuthProvider, logger *CondLogger) *ProxyHandler { +func NewProxyHandler(upstream string, auth AuthProvider, resolver *Resolver, logger *CondLogger) *ProxyHandler { proxyurl, err := url.Parse("https://" + upstream) if err != nil { panic(err) @@ -33,56 +33,175 @@ func NewProxyHandler(upstream string, auth AuthProvider, logger *CondLogger) *Pr upstream: upstream, logger: logger, httptransport: httptransport, + resolver: resolver, } } func (s *ProxyHandler) ServeHTTP(wr http.ResponseWriter, req *http.Request) { s.logger.Info("Request: %v %v %v", req.RemoteAddr, req.Method, req.URL) - req.Header.Set("Proxy-Authorization", s.auth()) if strings.ToUpper(req.Method) == "CONNECT" { + req.Header.Set("Proxy-Authorization", s.auth()) rawreq, err := httputil.DumpRequest(req, false) if err != nil { s.logger.Error("Can't dump request: %v", err) http.Error(wr, "Can't dump request", http.StatusInternalServerError) return } + conn, err := tls.Dial("tcp", s.upstream, nil) if err != nil { s.logger.Error("Can't dial tls upstream: %v", err) - http.Error(wr, "Can't dial tls upstream", http.StatusInternalServerError) + http.Error(wr, "Can't dial tls upstream", http.StatusBadGateway) return - defer conn.Close() } - hj, ok := wr.(http.Hijacker) - if !ok { - s.logger.Critical("Webserver doesn't support hijacking") - http.Error(wr, "Webserver doesn't support hijacking", http.StatusInternalServerError) - return - } - localconn, _, err := hj.Hijack() + + _, err = conn.Write(rawreq) if err != nil { - s.logger.Error("Can't hijack client connection: %v", err) - http.Error(wr, "Can't hijack client connection", http.StatusInternalServerError) + s.logger.Error("Can't write tls upstream: %v", err) + http.Error(wr, "Can't write tls upstream", http.StatusBadGateway) + return + } + bufrd := bufio.NewReader(conn) + proxyResp, err := http.ReadResponse(bufrd, req) + responseBytes := make([]byte, 0) + if err != nil { + s.logger.Error("Can't read response from upstream: %v", err) + http.Error(wr, "Can't read response from upstream", http.StatusBadGateway) return } - var emptytime time.Time - err = localconn.SetDeadline(emptytime) + + if (proxyResp.StatusCode == http.StatusForbidden && + proxyResp.Header.Get("X-Hola-Error") == "Forbidden Host") { + s.logger.Info("Request %s denied by upstream. Rescuing it with resolve&rewrite workaround.", + req.URL.String()) + conn.Close() + conn, err = tls.Dial("tcp", s.upstream, nil) + if err != nil { + s.logger.Error("Can't dial tls upstream: %v", err) + http.Error(wr, "Can't dial tls upstream", http.StatusBadGateway) + return + } + defer conn.Close() + err = rewriteConnectReq(req, s.resolver) + if err != nil { + s.logger.Error("Can't rewrite request: %v", err) + http.Error(wr, "Can't rewrite request", http.StatusInternalServerError) + return + } + rawreq, err = httputil.DumpRequest(req, false) + if err != nil { + s.logger.Error("Can't dump request: %v", err) + http.Error(wr, "Can't dump request", http.StatusInternalServerError) + return + } + _, err = conn.Write(rawreq) + if err != nil { + s.logger.Error("Can't write tls upstream: %v", err) + http.Error(wr, "Can't write tls upstream", http.StatusBadGateway) + return + } + } else { + defer conn.Close() + responseBytes, err = httputil.DumpResponse(proxyResp, false) + if err != nil { + s.logger.Error("Can't dump response: %v", err) + http.Error(wr, "Can't dump response", http.StatusInternalServerError) + return + } + buffered := bufrd.Buffered() + if buffered > 0 { + trailer := make([]byte, buffered) + bufrd.Read(trailer) + responseBytes = append(responseBytes, trailer...) + } + } + bufrd = nil + + // Upgrade client connection + localconn, _, err := hijack(wr) if err != nil { - s.logger.Error("Can't clear deadlines on local connection: %v", err) - http.Error(wr, "Can't clear deadlines on local connection", http.StatusInternalServerError) + s.logger.Error("Can't hijack client connection: %v", err) + http.Error(wr, "Can't hijack client connection", http.StatusInternalServerError) return } - conn.Write(rawreq) - proxy(context.TODO(), localconn, conn) + defer localconn.Close() + + if len(responseBytes) > 0 { + _, err = localconn.Write(responseBytes) + if err != nil { + return + } + } + proxy(req.Context(), localconn, conn) } else { - req.RequestURI = "" delHopHeaders(req.Header) + orig_req := req.Clone(req.Context()) + req.RequestURI = "" + req.Header.Set("Proxy-Authorization", s.auth()) resp, err := s.httptransport.RoundTrip(req) if err != nil { s.logger.Error("HTTP fetch error: %v", err) http.Error(wr, "Server Error", http.StatusInternalServerError) return } + if (resp.StatusCode == http.StatusForbidden && + resp.Header.Get("X-Hola-Error") == "Forbidden Host") { + s.logger.Info("Request %s denied by upstream. Rescuing it with resolve&tunnel workaround.", + req.URL.String()) + resp.Body.Close() + + // Prepare tunnel request + proxyReq, err := makeConnReq(orig_req.RequestURI, s.resolver) + if err != nil { + s.logger.Error("Can't rewrite request: %v", err) + http.Error(wr, "Can't rewrite request", http.StatusInternalServerError) + return + } + proxyReq.Header.Set("Proxy-Authorization", s.auth()) + rawreq, _ := httputil.DumpRequest(proxyReq, false) + s.logger.Debug("Rewritten request: %s", string(rawreq)) + + // Prepare upstream TLS conn + conn, err := tls.Dial("tcp", s.upstream, nil) + if err != nil { + s.logger.Error("Can't dial tls upstream: %v", err) + http.Error(wr, "Can't dial tls upstream", http.StatusBadGateway) + return + } + defer conn.Close() + + // Send proxy request + _, err = conn.Write(rawreq) + + // Read proxy response + bufrd := bufio.NewReader(conn) + proxyResp, err := http.ReadResponse(bufrd, proxyReq) + if err != nil { + s.logger.Error("Can't read response from upstream: %v", err) + http.Error(wr, "Can't read response from upstream", http.StatusBadGateway) + return + } + if proxyResp.StatusCode != http.StatusOK { + delHopHeaders(proxyResp.Header) + copyHeader(wr.Header(), proxyResp.Header) + wr.WriteHeader(proxyResp.StatusCode) + } + + // Send tunneled request + orig_req.RequestURI = "" + orig_req.Header.Set("Connection", "close") + rawreq, _ = httputil.DumpRequest(orig_req, false) + s.logger.Debug("Tunneled request: %s", string(rawreq)) + _, err = conn.Write(rawreq) + + // Read tunneled response + resp, err = http.ReadResponse(bufrd, orig_req) + if err != nil { + s.logger.Error("Can't read response from upstream: %v", err) + http.Error(wr, "Can't read response from upstream", http.StatusBadGateway) + return + } + } defer resp.Body.Close() s.logger.Info("%v %v %v %v", req.RemoteAddr, req.Method, req.URL, resp.Status) delHopHeaders(resp.Header) diff --git a/main.go b/main.go index f2a6044..7ceb332 100644 --- a/main.go +++ b/main.go @@ -42,6 +42,7 @@ type CLIArgs struct { verbosity int timeout, rotate time.Duration proxy_type string + resolver string } @@ -57,6 +58,9 @@ func parse_args() CLIArgs { flag.DurationVar(&args.timeout, "timeout", 10 * time.Second, "timeout for network operations") flag.DurationVar(&args.rotate, "rotate", 1 * time.Hour, "rotate user ID once per given period") flag.StringVar(&args.proxy_type, "proxy-type", "direct", "proxy type: direct or peer") + flag.StringVar(&args.resolver, "resolver", "https://cloudflare-dns.com/dns-query", + "DNS/DoH/DoT resolver to workaround Hola blocked hosts. " + + "See https://github.com/ameshkov/dnslookup/ for upstream DNS URL format.") flag.Parse() if args.country == "" { arg_fail("Country can't be empty string.") @@ -88,9 +92,16 @@ func main() { proxyLogger := NewCondLogger(log.New(logWriter, "PROXY : ", log.LstdFlags | log.Lshortfile), args.verbosity) + mainLogger.Info("Constructing fallback DNS upstream...") + resolver, err := NewResolver(args.resolver, args.timeout) + if err != nil { + mainLogger.Critical("Unable to instantiate DNS resolver: %v", err) + os.Exit(6) + } mainLogger.Info("Initializing configuration provider...") auth, tunnels, err := CredService(args.rotate, args.timeout, args.country, credLogger) if err != nil { + mainLogger.Critical("Unable to instantiate credential service: %v", err) os.Exit(4) } endpoint, err := get_endpoint(tunnels, args.proxy_type) @@ -99,7 +110,7 @@ func main() { os.Exit(5) } mainLogger.Info("Starting proxy server...") - handler := NewProxyHandler(endpoint, auth, proxyLogger) + handler := NewProxyHandler(endpoint, auth, resolver, proxyLogger) err = http.ListenAndServe(args.bind_address, handler) mainLogger.Critical("Server terminated with a reason: %v", err) mainLogger.Info("Shutting down...") diff --git a/resolver.go b/resolver.go new file mode 100644 index 0000000..548c9dd --- /dev/null +++ b/resolver.go @@ -0,0 +1,81 @@ +package main + +import ( + "github.com/AdguardTeam/dnsproxy/upstream" + "github.com/miekg/dns" + "time" +) + +type Resolver struct { + upstream upstream.Upstream +} + +const DOT = 0x2e + +func NewResolver(address string, timeout time.Duration) (*Resolver, error) { + opts := upstream.Options{Timeout: timeout} + u, err := upstream.AddressToUpstream(address, opts) + if err != nil { + return nil, err + } + return &Resolver{upstream: u}, nil +} + +func (r *Resolver) ResolveA(domain string) []string { + res := make([]string, 0) + if len(domain) == 0 { + return res + } + if domain[len(domain)-1] != DOT { + domain = domain + "." + } + req := dns.Msg{} + req.Id = dns.Id() + req.RecursionDesired = true + req.Question = []dns.Question{ + {Name: domain, Qtype: dns.TypeA, Qclass: dns.ClassINET}, + } + reply, err := r.upstream.Exchange(&req) + if err != nil { + return res + } + for _, rr := range reply.Answer { + if a, ok := rr.(*dns.A); ok { + res = append(res, a.A.String()) + } + } + return res +} + +func (r *Resolver) ResolveAAAA(domain string) []string { + res := make([]string, 0) + if len(domain) == 0 { + return res + } + if domain[len(domain)-1] != DOT { + domain = domain + "." + } + req := dns.Msg{} + req.Id = dns.Id() + req.RecursionDesired = true + req.Question = []dns.Question{ + {Name: domain, Qtype: dns.TypeAAAA, Qclass: dns.ClassINET}, + } + reply, err := r.upstream.Exchange(&req) + if err != nil { + return res + } + for _, rr := range reply.Answer { + if a, ok := rr.(*dns.AAAA); ok { + res = append(res, a.AAAA.String()) + } + } + return res +} + +func (r *Resolver) Resolve(domain string) []string { + res := make([]string, 0) + res = append(res, r.ResolveA(domain)...) + res = append(res, r.ResolveAAAA(domain)...) + return res +} diff --git a/utils.go b/utils.go index f613e37..53bc5b8 100644 --- a/utils.go +++ b/utils.go @@ -14,6 +14,8 @@ import ( "strings" "strconv" "net/http" + "net/url" + "bufio" ) func basic_auth_header(login, password string) string { @@ -115,6 +117,7 @@ var hopHeaders = []string{ "Connection", "Keep-Alive", "Proxy-Authenticate", + "Proxy-Connection", "Te", // canonicalized version of "TE" "Trailers", "Transfer-Encoding", @@ -135,3 +138,94 @@ func delHopHeaders(header http.Header) { } } +func hijack(hijackable interface{}) (net.Conn, *bufio.ReadWriter, error) { + hj, ok := hijackable.(http.Hijacker) + if !ok { + return nil, nil, errors.New("Connection doesn't support hijacking") + } + conn, rw, err := hj.Hijack() + if err != nil { + return nil, nil, err + } + var emptytime time.Time + err = conn.SetDeadline(emptytime) + if err != nil { + conn.Close() + return nil, nil, err + } + return conn, rw, nil +} + +func rewriteConnectReq(req *http.Request, resolver *Resolver) error { + origHost := req.Host + origAddr, origPort, err := net.SplitHostPort(origHost) + if err == nil { + origHost = origAddr + } + addrs := resolver.Resolve(origHost) + if len(addrs) == 0 { + return errors.New("Can't resolve host") + } + if origPort == "" { + req.URL.Host = addrs[0] + req.Host = addrs[0] + req.RequestURI = addrs[0] + } else { + req.URL.Host = net.JoinHostPort(addrs[0], origPort) + req.Host = net.JoinHostPort(addrs[0], origPort) + req.RequestURI = net.JoinHostPort(addrs[0], origPort) + } + return nil +} + +func rewriteReq(req *http.Request, resolver *Resolver) error { + origHost := req.URL.Host + origAddr, origPort, err := net.SplitHostPort(origHost) + if err == nil { + origHost = origAddr + } + addrs := resolver.Resolve(origHost) + if len(addrs) == 0 { + return errors.New("Can't resolve host") + } + if origPort == "" { + req.URL.Host = addrs[0] + req.Host = addrs[0] + } else { + req.URL.Host = net.JoinHostPort(addrs[0], origPort) + req.Host = net.JoinHostPort(addrs[0], origPort) + } + req.Header.Set("Host", origHost) + return nil +} + +func makeConnReq(uri string, resolver *Resolver) (*http.Request, error) { + parsed_url, err := url.Parse(uri) + if err != nil { + return nil, err + } + origAddr, origPort, err := net.SplitHostPort(parsed_url.Host) + if err != nil { + origAddr = parsed_url.Host + switch strings.ToLower(parsed_url.Scheme) { + case "https": + origPort = "443" + case "http": + origPort = "80" + default: + return nil, errors.New("Unknown scheme") + } + } + addrs := resolver.Resolve(origAddr) + if len(addrs) == 0 { + return nil, errors.New("Can't resolve host") + } + new_uri := net.JoinHostPort(addrs[0], origPort) + req, err := http.NewRequest("CONNECT", "http://" + new_uri, nil) + if err != nil { + return nil, err + } + req.RequestURI = new_uri + req.Host = new_uri + return req, nil +} From 73cdcfb701521f340ffb4244f771492d0318e5ba Mon Sep 17 00:00:00 2001 From: Vladislav Yarmak Date: Mon, 13 Apr 2020 23:48:31 +0300 Subject: [PATCH 2/2] polish --- handler.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/handler.go b/handler.go index 5662b97..75e5f27 100644 --- a/handler.go +++ b/handler.go @@ -159,7 +159,6 @@ func (s *ProxyHandler) ServeHTTP(wr http.ResponseWriter, req *http.Request) { } proxyReq.Header.Set("Proxy-Authorization", s.auth()) rawreq, _ := httputil.DumpRequest(proxyReq, false) - s.logger.Debug("Rewritten request: %s", string(rawreq)) // Prepare upstream TLS conn conn, err := tls.Dial("tcp", s.upstream, nil) @@ -172,6 +171,11 @@ func (s *ProxyHandler) ServeHTTP(wr http.ResponseWriter, req *http.Request) { // Send proxy request _, err = conn.Write(rawreq) + if err != nil { + s.logger.Error("Can't write tls upstream: %v", err) + http.Error(wr, "Can't write tls upstream", http.StatusBadGateway) + return + } // Read proxy response bufrd := bufio.NewReader(conn) @@ -191,8 +195,12 @@ func (s *ProxyHandler) ServeHTTP(wr http.ResponseWriter, req *http.Request) { orig_req.RequestURI = "" orig_req.Header.Set("Connection", "close") rawreq, _ = httputil.DumpRequest(orig_req, false) - s.logger.Debug("Tunneled request: %s", string(rawreq)) _, err = conn.Write(rawreq) + if err != nil { + s.logger.Error("Can't write tls upstream: %v", err) + http.Error(wr, "Can't write tls upstream", http.StatusBadGateway) + return + } // Read tunneled response resp, err = http.ReadResponse(bufrd, orig_req)