commit 70c28e34dca7125895921c4161033666148dc3f0
Author: David Fifield <[email protected]>
Date:   Sat Feb 2 01:52:10 2019 -0700

    socks5 proxy support for uTLS.
---
 meek-client/meek-client.go |  2 +-
 meek-client/utls.go        | 80 ++++++++++++++++++++++++++++++++++++++--------
 meek-client/utls_test.go   | 49 ++++++++++++++++++++++++++++
 3 files changed, 116 insertions(+), 15 deletions(-)

diff --git a/meek-client/meek-client.go b/meek-client/meek-client.go
index 74ef2c5..d76ca98 100644
--- a/meek-client/meek-client.go
+++ b/meek-client/meek-client.go
@@ -325,7 +325,7 @@ func handler(conn *pt.SocksConn) error {
                }
                info.RoundTripper = helperRoundTripper
        } else if utlsOK {
-               info.RoundTripper, err = NewUTLSRoundTripper(utlsName, nil)
+               info.RoundTripper, err = NewUTLSRoundTripper(utlsName, nil, 
options.ProxyURL)
                if err != nil {
                        return err
                }
diff --git a/meek-client/utls.go b/meek-client/utls.go
index e8d0bc0..1cdeca1 100644
--- a/meek-client/utls.go
+++ b/meek-client/utls.go
@@ -45,6 +45,7 @@ import (
 
        utls "github.com/refraction-networking/utls"
        "golang.org/x/net/http2"
+       "golang.org/x/net/proxy"
 )
 
 // Copy the public fields (fields for which CanSet is true) from src to dst.
@@ -88,12 +89,8 @@ func addrForDial(url *url.URL) (string, error) {
 
 // Analogous to tls.Dial. Connect to the given address and initiate a TLS
 // handshake using the given ClientHelloID, returning the resulting connection.
-func dialUTLS(network, addr string, cfg *utls.Config, clientHelloID 
*utls.ClientHelloID) (*utls.UConn, error) {
-       if options.ProxyURL != nil {
-               return nil, fmt.Errorf("no proxy allowed with uTLS")
-       }
-
-       conn, err := net.Dial(network, addr)
+func dialUTLS(network, addr string, cfg *utls.Config, clientHelloID 
*utls.ClientHelloID, forward proxy.Dialer) (*utls.UConn, error) {
+       conn, err := forward.Dial(network, addr)
        if err != nil {
                return nil, err
        }
@@ -121,14 +118,18 @@ type UTLSRoundTripper struct {
 
        clientHelloID *utls.ClientHelloID
        config        *utls.Config
+       proxyDialer   proxy.Dialer
        rt            http.RoundTripper
+
+       // Transport for HTTP requests, which don't use uTLS.
+       httpRT *http.Transport
 }
 
 func (rt *UTLSRoundTripper) RoundTrip(req *http.Request) (*http.Response, 
error) {
        switch req.URL.Scheme {
        case "http":
-               // If http, we don't invoke uTLS; just pass it to the global 
http.Transport.
-               return httpRoundTripper.RoundTrip(req)
+               // If http, we don't invoke uTLS; just pass it to an ordinary 
http.Transport.
+               return rt.httpRT.RoundTrip(req)
        case "https":
        default:
                return nil, fmt.Errorf("unsupported URL scheme %q", 
req.URL.Scheme)
@@ -141,7 +142,7 @@ func (rt *UTLSRoundTripper) RoundTrip(req *http.Request) 
(*http.Response, error)
                // On the first call, make an http.Transport or http2.Transport
                // as appropriate.
                var err error
-               rt.rt, err = makeRoundTripper(req.URL, rt.clientHelloID, 
rt.config)
+               rt.rt, err = makeRoundTripper(req.URL, rt.clientHelloID, 
rt.config, rt.proxyDialer)
                if err != nil {
                        return nil, err
                }
@@ -150,16 +151,52 @@ func (rt *UTLSRoundTripper) RoundTrip(req *http.Request) 
(*http.Response, error)
        return rt.rt.RoundTrip(req)
 }
 
-func makeRoundTripper(url *url.URL, clientHelloID *utls.ClientHelloID, cfg 
*utls.Config) (http.RoundTripper, error) {
+// Unlike when using the native Go net/http (whose built-in proxy support we 
can
+// use by setting Proxy on an http.Transport), and unlike when using the 
browser
+// helper (the browser has its own proxy support), when using uTLS we have to
+// craft our own proxy connections.
+func makeProxyDialer(proxyURL *url.URL) (proxy.Dialer, error) {
+       var proxyDialer proxy.Dialer = proxy.Direct
+       if proxyURL == nil {
+               return proxyDialer, nil
+       }
+
+       proxyAddr, err := addrForDial(proxyURL)
+       if err != nil {
+               return nil, err
+       }
+
+       var auth *proxy.Auth
+       if userpass := proxyURL.User; userpass != nil {
+               auth = &proxy.Auth{
+                       User: userpass.Username(),
+               }
+               if password, ok := userpass.Password(); ok {
+                       auth.Password = password
+               }
+       }
+
+       switch proxyURL.Scheme {
+       case "socks5":
+               proxyDialer, err = proxy.SOCKS5("tcp", proxyAddr, auth, 
proxyDialer)
+       default:
+               return nil, fmt.Errorf("cannot use proxy scheme %q with uTLS", 
proxyURL.Scheme)
+       }
+
+       return proxyDialer, err
+}
+
+func makeRoundTripper(url *url.URL, clientHelloID *utls.ClientHelloID, cfg 
*utls.Config, proxyDialer proxy.Dialer) (http.RoundTripper, error) {
        addr, err := addrForDial(url)
        if err != nil {
                return nil, err
        }
 
-       // Connect to the given address and initiate a TLS handshake using
-       // the given ClientHelloID. Return the resulting connection.
+       // Connect to the given address, through a proxy if requested, and
+       // initiate a TLS handshake using the given ClientHelloID. Return the
+       // resulting connection.
        dial := func(network, addr string) (*utls.UConn, error) {
-               return dialUTLS(network, addr, cfg, clientHelloID)
+               return dialUTLS(network, addr, cfg, clientHelloID, proxyDialer)
        }
 
        bootstrapConn, err := dial("tcp", addr)
@@ -243,7 +280,7 @@ var clientHelloIDMap = map[string]*utls.ClientHelloID{
        "helloios_11_1":         &utls.HelloIOS_11_1,
 }
 
-func NewUTLSRoundTripper(name string, cfg *utls.Config) (http.RoundTripper, 
error) {
+func NewUTLSRoundTripper(name string, cfg *utls.Config, proxyURL *url.URL) 
(http.RoundTripper, error) {
        // Lookup is case-insensitive.
        clientHelloID, ok := clientHelloIDMap[strings.ToLower(name)]
        if !ok {
@@ -253,8 +290,23 @@ func NewUTLSRoundTripper(name string, cfg *utls.Config) 
(http.RoundTripper, erro
                // Special case for "none" and HelloGolang.
                return httpRoundTripper, nil
        }
+
+       proxyDialer, err := makeProxyDialer(proxyURL)
+       if err != nil {
+               return nil, err
+       }
+
+       // This special-case RoundTripper is used for HTTP requests, which don't
+       // use uTLS but should use the specified proxy.
+       httpRT := &http.Transport{}
+       copyPublicFields(httpRT, httpRoundTripper)
+       httpRT.Proxy = http.ProxyURL(proxyURL)
+
        return &UTLSRoundTripper{
                clientHelloID: clientHelloID,
                config:        cfg,
+               proxyDialer:   proxyDialer,
+               // rt will be set in the first call to RoundTrip.
+               httpRT: httpRT,
        }, nil
 }
diff --git a/meek-client/utls_test.go b/meek-client/utls_test.go
index 2eb72af..0e62dac 100644
--- a/meek-client/utls_test.go
+++ b/meek-client/utls_test.go
@@ -230,3 +230,52 @@ func TestUTLSServerName(t *testing.T) {
                t.Errorf("expected \"test.example\" server_name extension with 
given ServerName and hostname dial")
        }
 }
+
+// Test that HTTP requests (which don't go through the uTLS code path) still 
use
+// any proxy that's configured on the UTLSRoundTripper.
+func TestUTLSHTTPWithProxy(t *testing.T) {
+       // Make a web server that we should *not* be able to reach.
+       server := &http.Server{
+               Handler: http.NotFoundHandler(),
+       }
+       serverLn, err := net.Listen("tcp", "127.0.0.1:0")
+       if err != nil {
+               panic(err)
+       }
+       defer serverLn.Close()
+       go server.Serve(serverLn)
+
+       // Make a non-functional proxy server.
+       proxyLn, err := net.Listen("tcp", "127.0.0.1:0")
+       if err != nil {
+               panic(err)
+       }
+       defer proxyLn.Close()
+       go func() {
+               for {
+                       conn, err := proxyLn.Accept()
+                       if err == nil {
+                               conn.Close() // go away
+                       }
+               }
+       }()
+
+       // Try to access the web server through the non-functional proxy.
+       for _, proxyURL := range []url.URL{
+               url.URL{Scheme: "socks5", Host: proxyLn.Addr().String()},
+       } {
+               rt, err := NewUTLSRoundTripper("HelloFirefox_63", 
&utls.Config{InsecureSkipVerify: true}, &proxyURL)
+               if err != nil {
+                       panic(err)
+               }
+               fetchURL := url.URL{Scheme: "http", Host: 
serverLn.Addr().String()}
+               req, err := http.NewRequest("GET", fetchURL.String(), nil)
+               if err != nil {
+                       panic(err)
+               }
+               _, err = rt.RoundTrip(req)
+               if err == nil {
+                       t.Errorf("fetch of %s through %s proxy should have 
failed", &fetchURL, proxyURL.Scheme)
+               }
+       }
+}



_______________________________________________
tor-commits mailing list
[email protected]
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits

Reply via email to