[tor-commits] [meek/master] socks5 proxy support for uTLS.
dcf at torproject.org
dcf at torproject.org
Thu May 9 19:37:25 UTC 2019
commit 70c28e34dca7125895921c4161033666148dc3f0
Author: David Fifield <david at 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)
+ }
+ }
+}
More information about the tor-commits
mailing list