[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