commit 70c28e34dca7125895921c4161033666148dc3f0 Author: David Fifield david@bamsoftware.com 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@lists.torproject.org