[tor-commits] [pluggable-transports/obfs4] 01/03: Cherry-pick meek uTLS support

gitolite role git at cupani.torproject.org
Fri Nov 4 16:42:56 UTC 2022


This is an automated email from the git hooks/post-receive script.

meskio pushed a commit to branch main
in repository pluggable-transports/obfs4.

commit 68b17054153565c6fd938aecbbd0132e643207bb
Author: meskio <meskio at torproject.org>
AuthorDate: Wed Oct 19 13:02:45 2022 +0200

    Cherry-pick meek uTLS support
    
    Mostly from f01e92dd.
---
 go.mod                           |   5 +-
 go.sum                           |  18 ++++
 transports/meeklite/meek.go      |  29 ++++-
 transports/meeklite/transport.go | 222 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 267 insertions(+), 7 deletions(-)

diff --git a/go.mod b/go.mod
index 49e491b..0dea33e 100644
--- a/go.mod
+++ b/go.mod
@@ -4,9 +4,10 @@ require (
 	filippo.io/edwards25519 v1.0.0-rc.1.0.20210721174708-390f27c3be20
 	git.torproject.org/pluggable-transports/goptlib.git v1.0.0
 	github.com/dchest/siphash v1.2.1
+	github.com/refraction-networking/utls v1.1.5
 	gitlab.com/yawning/edwards25519-extra.git v0.0.0-20211229043746-2f91fcc9fbdb
-	golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
-	golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
+	golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
+	golang.org/x/net v0.0.0-20220909164309-bea034e7d591
 )
 
 go 1.16
diff --git a/go.sum b/go.sum
index b542a6b..dd093aa 100644
--- a/go.sum
+++ b/go.sum
@@ -2,17 +2,35 @@ filippo.io/edwards25519 v1.0.0-rc.1.0.20210721174708-390f27c3be20 h1:iJoUgXvhags
 filippo.io/edwards25519 v1.0.0-rc.1.0.20210721174708-390f27c3be20/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
 git.torproject.org/pluggable-transports/goptlib.git v1.0.0 h1:ElTwFFPKf/tA6x5nuIk9g49JZzS4T5WN+eTQTjqd00A=
 git.torproject.org/pluggable-transports/goptlib.git v1.0.0/go.mod h1:YT4XMSkuEXbtqlydr9+OxqFAyspUv0Gr9qhM3B++o/Q=
+github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
+github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
 github.com/dchest/siphash v1.2.1 h1:4cLinnzVJDKxTCl9B01807Yiy+W7ZzVHj/KIroQRvT4=
 github.com/dchest/siphash v1.2.1/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4=
+github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
+github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
+github.com/refraction-networking/utls v1.1.5 h1:JtrojoNhbUQkBqEg05sP3gDgDj6hIEAAVKbI9lx4n6w=
+github.com/refraction-networking/utls v1.1.5/go.mod h1:jRQxtYi7nkq1p28HF2lwOH5zQm9aC8rpK0O9lIIzGh8=
 gitlab.com/yawning/edwards25519-extra.git v0.0.0-20211229043746-2f91fcc9fbdb h1:qRSZHsODmAP5qDvb3YsO7Qnf3TRiVbGxNG/WYnlM4/o=
 gitlab.com/yawning/edwards25519-extra.git v0.0.0-20211229043746-2f91fcc9fbdb/go.mod h1:gvdJuZuO/tPZyhEV8K3Hmoxv/DWud5L4qEQxfYjEUTo=
 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
+golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI=
+golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
+golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/transports/meeklite/meek.go b/transports/meeklite/meek.go
index 17c7a67..8f9fe35 100644
--- a/transports/meeklite/meek.go
+++ b/transports/meeklite/meek.go
@@ -44,6 +44,8 @@ import (
 	"sync"
 	"time"
 
+	utls "github.com/refraction-networking/utls"
+
 	"git.torproject.org/pluggable-transports/goptlib.git"
 	"gitlab.com/yawning/obfs4.git/transports/base"
 )
@@ -51,6 +53,7 @@ import (
 const (
 	urlArg   = "url"
 	frontArg = "front"
+	utlsArg  = "utls"
 
 	maxChanBacklog = 16
 
@@ -73,6 +76,8 @@ var (
 type meekClientArgs struct {
 	url   *gourl.URL
 	front string
+
+	utls *utls.ClientHelloID
 }
 
 func (ca *meekClientArgs) Network() string {
@@ -104,13 +109,19 @@ func newClientArgs(args *pt.Args) (ca *meekClientArgs, err error) {
 	// Parse the (optional) front argument.
 	ca.front, _ = args.Get(frontArg)
 
+	// Parse the (optional) utls argument.
+	utlsOpt, _ := args.Get(utlsArg)
+	if ca.utls, err = parseClientHelloID(utlsOpt); err != nil {
+		return nil, err
+	}
+
 	return ca, nil
 }
 
 type meekConn struct {
-	args      *meekClientArgs
-	sessionID string
-	transport *http.Transport
+	args         *meekClientArgs
+	sessionID    string
+	roundTripper http.RoundTripper
 
 	closeOnce       sync.Once
 	workerWrChan    chan []byte
@@ -242,7 +253,7 @@ func (c *meekConn) roundTrip(sndBuf []byte) (recvBuf []byte, err error) {
 		req.Header.Set("X-Session-Id", c.sessionID)
 		req.Header.Set("User-Agent", "")
 
-		resp, err = c.transport.RoundTrip(req)
+		resp, err = c.roundTripper.RoundTrip(req)
 		if err != nil {
 			return nil, err
 		}
@@ -343,10 +354,18 @@ func newMeekConn(network, addr string, dialFn base.DialFunc, ca *meekClientArgs)
 		return nil, err
 	}
 
+	var rt http.RoundTripper
+	switch ca.utls {
+	case nil:
+		rt = &http.Transport{Dial: dialFn}
+	default:
+		rt = newRoundTripper(dialFn, ca.utls)
+	}
+
 	conn := &meekConn{
 		args:            ca,
 		sessionID:       id,
-		transport:       &http.Transport{Dial: dialFn},
+		roundTripper:    rt,
 		workerWrChan:    make(chan []byte, maxChanBacklog),
 		workerRdChan:    make(chan []byte, maxChanBacklog),
 		workerCloseChan: make(chan struct{}),
diff --git a/transports/meeklite/transport.go b/transports/meeklite/transport.go
new file mode 100644
index 0000000..82ae9fe
--- /dev/null
+++ b/transports/meeklite/transport.go
@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) 2019 Yawning Angel <yawning at schwanenlied dot me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package meeklite
+
+import (
+	"crypto/tls"
+	"errors"
+	"fmt"
+	"net"
+	"net/http"
+	"net/url"
+	"strconv"
+	"strings"
+	"sync"
+
+	utls "github.com/refraction-networking/utls"
+	"gitlab.com/yawning/obfs4.git/transports/base"
+	"golang.org/x/net/http2"
+)
+
+var (
+	errProtocolNegotiated = errors.New("meek_lite: protocol negotiated")
+
+	// This should be kept in sync with what is available in utls.
+	clientHelloIDMap = map[string]*utls.ClientHelloID{
+		"hellogolang":           nil, // Don't bother with utls.
+		"hellorandomized":       &utls.HelloRandomized,
+		"hellorandomizedalpn":   &utls.HelloRandomizedALPN,
+		"hellorandomizednoalpn": &utls.HelloRandomizedNoALPN,
+		"hellofirefox_auto":     &utls.HelloFirefox_Auto,
+		"hellofirefox_55":       &utls.HelloFirefox_55,
+		"hellofirefox_56":       &utls.HelloFirefox_56,
+		"hellofirefox_63":       &utls.HelloFirefox_63,
+		"hellochrome_auto":      &utls.HelloChrome_Auto,
+		"hellochrome_58":        &utls.HelloChrome_58,
+		"hellochrome_62":        &utls.HelloChrome_62,
+		"hellochrome_70":        &utls.HelloChrome_70,
+		"helloios_auto":         &utls.HelloIOS_Auto,
+		"helloios_11_1":         &utls.HelloIOS_11_1,
+	}
+	defaultClientHello = &utls.HelloFirefox_Auto
+)
+
+type roundTripper struct {
+	sync.Mutex
+
+	clientHelloID *utls.ClientHelloID
+	dialFn        base.DialFunc
+	transport     http.RoundTripper
+
+	initConn net.Conn
+}
+
+func (rt *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
+	// Note: This isn't protected with a lock, since the meeklite ioWorker
+	// serializes RoundTripper requests.
+	//
+	// This also assumes that req.URL.Host will remain constant for the
+	// lifetime of the roundTripper, which is a valid assumption for meeklite.
+	if rt.transport == nil {
+		if err := rt.getTransport(req); err != nil {
+			return nil, err
+		}
+	}
+	return rt.transport.RoundTrip(req)
+}
+
+func (rt *roundTripper) getTransport(req *http.Request) error {
+	switch strings.ToLower(req.URL.Scheme) {
+	case "http":
+		rt.transport = newHTTPTransport(rt.dialFn, nil)
+		return nil
+	case "https":
+	default:
+		return fmt.Errorf("meek_lite: invalid URL scheme: '%v'", req.URL.Scheme)
+	}
+
+	_, err := rt.dialTLS("tcp", getDialTLSAddr(req.URL))
+	switch err {
+	case errProtocolNegotiated:
+	case nil:
+		// Should never happen.
+		panic("meek_lite: dialTLS returned no error when determining transport")
+	default:
+		return err
+	}
+
+	return nil
+}
+
+func (rt *roundTripper) dialTLS(network, addr string) (net.Conn, error) {
+	// Unlike rt.transport, this is protected by a critical section
+	// since past the initial manual call from getTransport, the HTTP
+	// client will be the caller.
+	rt.Lock()
+	defer rt.Unlock()
+
+	// If we have the connection from when we determined the HTTPS
+	// transport to use, return that.
+	if conn := rt.initConn; conn != nil {
+		rt.initConn = nil
+		return conn, nil
+	}
+
+	rawConn, err := rt.dialFn(network, addr)
+	if err != nil {
+		return nil, err
+	}
+
+	var host string
+	if host, _, err = net.SplitHostPort(addr); err != nil {
+		host = addr
+	}
+
+	conn := utls.UClient(rawConn, &utls.Config{
+		ServerName: host,
+
+		// `crypto/tls` gradually ramps up the record size.  While this is
+		// a good optimization and is a relatively common server feature,
+		// neither Firefox nor Chromium appear to use such optimizations.
+		DynamicRecordSizingDisabled: true,
+	}, *rt.clientHelloID)
+
+	if err = conn.Handshake(); err != nil {
+		conn.Close()
+		return nil, err
+	}
+
+	if rt.transport != nil {
+		return conn, nil
+	}
+
+	// No http.Transport constructed yet, create one based on the results
+	// of ALPN.
+	switch conn.ConnectionState().NegotiatedProtocol {
+	case http2.NextProtoTLS:
+		// The remote peer is speaking HTTP 2 + TLS.
+		rt.transport = &http2.Transport{DialTLS: rt.dialTLSHTTP2}
+	default:
+		// Assume the remote peer is speaking HTTP 1.x + TLS.
+		rt.transport = newHTTPTransport(nil, rt.dialTLS)
+	}
+
+	// Stash the connection just established for use servicing the
+	// actual request (should be near-immediate).
+	rt.initConn = conn
+
+	return nil, errProtocolNegotiated
+}
+
+func (rt *roundTripper) dialTLSHTTP2(network, addr string, cfg *tls.Config) (net.Conn, error) {
+	return rt.dialTLS(network, addr)
+}
+
+func getDialTLSAddr(u *url.URL) string {
+	host, port, err := net.SplitHostPort(u.Host)
+	if err == nil {
+		return net.JoinHostPort(host, port)
+	}
+	pInt, _ := net.LookupPort("tcp", u.Scheme)
+
+	return net.JoinHostPort(u.Host, strconv.Itoa(pInt))
+}
+
+func newRoundTripper(dialFn base.DialFunc, clientHelloID *utls.ClientHelloID) http.RoundTripper {
+	return &roundTripper{
+		clientHelloID: clientHelloID,
+		dialFn:        dialFn,
+	}
+}
+
+func parseClientHelloID(s string) (*utls.ClientHelloID, error) {
+	s = strings.ToLower(s)
+	switch s {
+	case "none":
+		return nil, nil
+	case "":
+		return defaultClientHello, nil
+	default:
+		if ret := clientHelloIDMap[s]; ret != nil {
+			return ret, nil
+		}
+	}
+	return nil, fmt.Errorf("invalid ClientHelloID: '%v'", s)
+}
+
+func newHTTPTransport(dialFn, dialTLSFn base.DialFunc) *http.Transport {
+	base := (http.DefaultTransport).(*http.Transport)
+
+	return &http.Transport{
+		Dial:    dialFn,
+		DialTLS: dialTLSFn,
+
+		// Use default configuration values, taken from the runtime.
+		MaxIdleConns:          base.MaxIdleConns,
+		IdleConnTimeout:       base.IdleConnTimeout,
+		TLSHandshakeTimeout:   base.TLSHandshakeTimeout,
+		ExpectContinueTimeout: base.ExpectContinueTimeout,
+	}
+}
+
+func init() {
+	// Attempt to increase compatibility, there's an encrypted link
+	// underneath, and this doesn't (shouldn't) affect the external
+	// fingerprint.
+	utls.EnableWeakCiphers()
+}

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the tor-commits mailing list