[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [meek/master] Integrate uTLS as an option for TLS camouflage.
commit b8fb876145cda3c14d335a3fc88b5e422a926150
Author: David Fifield <david@xxxxxxxxxxxxxxx>
Date: Tue Jan 22 23:38:26 2019 -0700
Integrate uTLS as an option for TLS camouflage.
This adapts a technique that Yawning used in obfs4proxy for meek_lite:
https://gitlab.com/yawning/obfs4/commit/4d453dab2120082b00bf6e63ab4aaeeda6b8d8a3
https://lists.torproject.org/pipermail/tor-dev/2019-January/013633.html
It's activated by the new utls= SOCKS arg or --utls command line option.
The argument is the name of a uTLS ClientHelloID; e.g.,
HelloChrome_Auto. We omit HelloCustom (not useful externally),
HelloGolang (just don't use utls), and HelloRandomized (may negotiate
HTTP/1.1 or HTTP/2 at different times, which is incompatible with the
way the integration works).
When using HTTP/1, we copy default timeouts etc. from the Go
net/http.DefaultTransport. Unfortunately I don't know a way to do the
same for HTTP/2. Configuring an http.Transport and then calling
http2.ConfigureTransport on it doesn't work; it leads to the same
problem of an HTTP/1 client speaking to an HTTP/2 server.
We recognize utls=none and utls=HelloGolang as an alias for omitting
utls=. This is for compatibility with obfs4proxy meek_lite.
https://bugs.torproject.org/29077#comment:13
---
meek-client/meek-client.go | 30 +++++-
meek-client/utls.go | 260 +++++++++++++++++++++++++++++++++++++++++++++
meek-client/utls_test.go | 232 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 518 insertions(+), 4 deletions(-)
diff --git a/meek-client/meek-client.go b/meek-client/meek-client.go
index 73ca214..74ef2c5 100644
--- a/meek-client/meek-client.go
+++ b/meek-client/meek-client.go
@@ -79,9 +79,9 @@ const (
helperWriteTimeout = 2 * time.Second
)
-// We use this RoundTripper to make all our requests when --helper is not
-// in effect. We use the defaults, except we take control of the Proxy setting
-// (notably, disabling the default ProxyFromEnvironment).
+// We use this RoundTripper to make all our requests when neither --helper nor
+// utls is in effect. We use the defaults, except we take control of the Proxy
+// setting (notably, disabling the default ProxyFromEnvironment).
var httpRoundTripper *http.Transport = http.DefaultTransport.(*http.Transport)
// We use this RoundTripper when --helper is in effect.
@@ -96,6 +96,7 @@ var options struct {
Front string
ProxyURL *url.URL
UseHelper bool
+ UTLSName string
}
// RequestInfo encapsulates all the configuration used for a requestâ??response
@@ -307,9 +308,29 @@ func handler(conn *pt.SocksConn) error {
info.URL.Host = front
}
- info.RoundTripper = httpRoundTripper
+ // First check utls= SOCKS arg, then --utls option.
+ utlsName, utlsOK := conn.Req.Args.Get("utls")
+ if utlsOK {
+ } else if options.UTLSName != "" {
+ utlsName = options.UTLSName
+ utlsOK = true
+ }
+
+ // First we check --helper: if it was specified, then we always use the
+ // helper, and utls is disallowed. Otherwise, we use utls if requested;
+ // or else fall back to native net/http.
if options.UseHelper {
+ if utlsOK {
+ return fmt.Errorf("cannot use utls with --helper")
+ }
info.RoundTripper = helperRoundTripper
+ } else if utlsOK {
+ info.RoundTripper, err = NewUTLSRoundTripper(utlsName, nil)
+ if err != nil {
+ return err
+ }
+ } else {
+ info.RoundTripper = httpRoundTripper
}
return copyLoop(conn, &info)
@@ -387,6 +408,7 @@ func main() {
flag.StringVar(&logFilename, "log", "", "name of log file")
flag.StringVar(&proxy, "proxy", "", "proxy URL")
flag.StringVar(&options.URL, "url", "", "URL to request if no url= SOCKS arg")
+ flag.StringVar(&options.UTLSName, "utls", "", "uTLS Client Hello ID")
flag.Parse()
ptInfo, err := pt.ClientSetup(nil)
diff --git a/meek-client/utls.go b/meek-client/utls.go
new file mode 100644
index 0000000..e8d0bc0
--- /dev/null
+++ b/meek-client/utls.go
@@ -0,0 +1,260 @@
+// Support code for TLS camouflage using uTLS.
+//
+// The goal is: provide an http.RoundTripper abstraction that retains the
+// features of http.Transport (e.g., persistent connections and HTTP/2 support),
+// while making TLS connections using uTLS in place of crypto/tls. The challenge
+// is: while http.Transport provides a DialTLS hook, setting it to non-nil
+// disables automatic HTTP/2 support in the client. Most of the uTLS
+// fingerprints contain an ALPN extension containing "h2"; i.e., they declare
+// support for HTTP/2. If the server also supports HTTP/2, then uTLS may
+// negotiate an HTTP/2 connection without the http.Transport knowing it, which
+// leads to an HTTP/1.1 client speaking to an HTTP/2 server, a protocol error.
+//
+// The code here uses an idea adapted from meek_lite in obfs4proxy:
+// https://gitlab.com/yawning/obfs4/commit/4d453dab2120082b00bf6e63ab4aaeeda6b8d8a3
+// Instead of setting DialTLS on an http.Transport and exposing it directly, we
+// expose a wrapper type, UTLSRoundTripper, that contains within it either an
+// http.Transport or an http2.Transport. The first time a caller calls RoundTrip
+// on the wrapper, we initiate a uTLS connection (bootstrapConn), then peek at
+// the ALPN-negotiated protocol: if "h2", create an internal http2.Transport;
+// otherwise, create an internal http.Transport. In either case, set DialTLS on
+// the created Transport to a function that dials using uTLS. As a special case,
+// the first time the DialTLS callback is called, it reuses bootstrapConn (the
+// one made to peek at the ALPN), rather than make a new connection.
+//
+// Subsequent calls to RoundTripper on the wrapper just pass the requests though
+// the previously created http.Transport or http2.Transport. We assume that in
+// future RoundTrips, the ALPN-negotiated protocol will remain the same as it
+// was in the initial RoundTrip. At this point it is the http.Transport or
+// http2.Transport calling DialTLS, not us, so we can't dynamically swap the
+// underlying transport based on the ALPN.
+//
+// https://bugs.torproject.org/29077
+// https://github.com/refraction-networking/utls/issues/16
+package main
+
+import (
+ "crypto/tls"
+ "fmt"
+ "net"
+ "net/http"
+ "net/url"
+ "reflect"
+ "strings"
+ "sync"
+
+ utls "github.com/refraction-networking/utls"
+ "golang.org/x/net/http2"
+)
+
+// Copy the public fields (fields for which CanSet is true) from src to dst.
+// src and dst must be pointers to the same type. We use this to make copies of
+// httpRoundTripper. We cannot use struct assignment, because http.Transport
+// contains private mutexes. The idea of using reflection to copy only the
+// public fields comes from a post by Nick Craig-Wood:
+// https://groups.google.com/d/msg/Golang-Nuts/SDiGYNVE8iY/89hRKTF4BAAJ
+func copyPublicFields(dst, src interface{}) {
+ if reflect.TypeOf(dst) != reflect.TypeOf(src) {
+ panic("unequal types")
+ }
+ dstValue := reflect.ValueOf(dst).Elem()
+ srcValue := reflect.ValueOf(src).Elem()
+ for i := 0; i < dstValue.NumField(); i++ {
+ if dstValue.Field(i).CanSet() {
+ dstValue.Field(i).Set(srcValue.Field(i))
+ }
+ }
+}
+
+// Extract a host:port address from a URL, suitable for passing to net.Dial.
+func addrForDial(url *url.URL) (string, error) {
+ host := url.Hostname()
+ // net/http would use golang.org/x/net/idna here, to convert a possible
+ // internationalized domain name to ASCII.
+ port := url.Port()
+ if port == "" {
+ // No port? Use the default for the scheme.
+ switch url.Scheme {
+ case "http":
+ port = "80"
+ case "https":
+ port = "443"
+ default:
+ return "", fmt.Errorf("unsupported URL scheme %q", url.Scheme)
+ }
+ }
+ return net.JoinHostPort(host, port), nil
+}
+
+// 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)
+ if err != nil {
+ return nil, err
+ }
+ uconn := utls.UClient(conn, cfg, *clientHelloID)
+ if cfg == nil || cfg.ServerName == "" {
+ serverName, _, err := net.SplitHostPort(addr)
+ if err != nil {
+ return nil, err
+ }
+ uconn.SetSNI(serverName)
+ }
+ err = uconn.Handshake()
+ if err != nil {
+ return nil, err
+ }
+ return uconn, nil
+}
+
+// A http.RoundTripper that uses uTLS (with a specified Client Hello ID) to make
+// TLS connections.
+//
+// Can only be reused among servers which negotiate the same ALPN.
+type UTLSRoundTripper struct {
+ sync.Mutex
+
+ clientHelloID *utls.ClientHelloID
+ config *utls.Config
+ rt http.RoundTripper
+}
+
+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)
+ case "https":
+ default:
+ return nil, fmt.Errorf("unsupported URL scheme %q", req.URL.Scheme)
+ }
+
+ rt.Lock()
+ defer rt.Unlock()
+
+ if rt.rt == nil {
+ // 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)
+ if err != nil {
+ return nil, err
+ }
+ }
+ // Forward the request to the internal http.Transport or http2.Transport.
+ return rt.rt.RoundTrip(req)
+}
+
+func makeRoundTripper(url *url.URL, clientHelloID *utls.ClientHelloID, cfg *utls.Config) (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.
+ dial := func(network, addr string) (*utls.UConn, error) {
+ return dialUTLS(network, addr, cfg, clientHelloID)
+ }
+
+ bootstrapConn, err := dial("tcp", addr)
+ if err != nil {
+ return nil, err
+ }
+
+ // Peek at what protocol we negotiated.
+ protocol := bootstrapConn.ConnectionState().NegotiatedProtocol
+
+ // Protects bootstrapConn.
+ var lock sync.Mutex
+ // This is the callback for future dials done by the internal
+ // http.Transport or http2.Transport.
+ dialTLS := func(network, addr string) (net.Conn, error) {
+ lock.Lock()
+ defer lock.Unlock()
+
+ // On the first dial, reuse bootstrapConn.
+ if bootstrapConn != nil {
+ uconn := bootstrapConn
+ bootstrapConn = nil
+ return uconn, nil
+ }
+
+ // Later dials make a new connection.
+ uconn, err := dial(network, addr)
+ if err != nil {
+ return nil, err
+ }
+ if uconn.ConnectionState().NegotiatedProtocol != protocol {
+ return nil, fmt.Errorf("unexpected switch from ALPN %q to %q",
+ protocol, uconn.ConnectionState().NegotiatedProtocol)
+ }
+
+ return uconn, nil
+ }
+
+ // Construct an http.Transport or http2.Transport depending on ALPN.
+ switch protocol {
+ case http2.NextProtoTLS:
+ // Unfortunately http2.Transport does not expose the same
+ // configuration options as http.Transport with regard to
+ // timeouts, etc., so we are at the mercy of the defaults.
+ // https://github.com/golang/go/issues/16581
+ return &http2.Transport{
+ DialTLS: func(network, addr string, _ *tls.Config) (net.Conn, error) {
+ // Ignore the *tls.Config parameter; use our
+ // static cfg instead.
+ return dialTLS(network, addr)
+ },
+ }, nil
+ default:
+ // With http.Transport, copy important default fields from
+ // http.DefaultTransport, such as TLSHandshakeTimeout and
+ // IdleConnTimeout.
+ tr := &http.Transport{}
+ copyPublicFields(tr, httpRoundTripper)
+ tr.DialTLS = dialTLS
+ return tr, nil
+ }
+}
+
+// When you update this map, also update the man page in doc/meek-client.1.txt.
+var clientHelloIDMap = map[string]*utls.ClientHelloID{
+ // No HelloCustom: not useful for external configuration.
+ // No HelloRandomized: doesn't negotiate consistent ALPN.
+ "none": nil, // special case: disable uTLS
+ "hellogolang": nil, // special case: disable uTLS
+ "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,
+}
+
+func NewUTLSRoundTripper(name string, cfg *utls.Config) (http.RoundTripper, error) {
+ // Lookup is case-insensitive.
+ clientHelloID, ok := clientHelloIDMap[strings.ToLower(name)]
+ if !ok {
+ return nil, fmt.Errorf("no uTLS Client Hello ID named %q", name)
+ }
+ if clientHelloID == nil {
+ // Special case for "none" and HelloGolang.
+ return httpRoundTripper, nil
+ }
+ return &UTLSRoundTripper{
+ clientHelloID: clientHelloID,
+ config: cfg,
+ }, nil
+}
diff --git a/meek-client/utls_test.go b/meek-client/utls_test.go
new file mode 100644
index 0000000..2eb72af
--- /dev/null
+++ b/meek-client/utls_test.go
@@ -0,0 +1,232 @@
+package main
+
+import (
+ "bytes"
+ "io"
+ "net"
+ "net/http"
+ "net/url"
+ "testing"
+
+ utls "github.com/refraction-networking/utls"
+)
+
+func TestCopyPublicFieldsHTTPTransport(t *testing.T) {
+ src := http.DefaultTransport.(*http.Transport)
+ dst := &http.Transport{}
+ copyPublicFields(dst, src)
+ // Test various fields that we might care about a copy of http.Transport
+ // having.
+ if dst.DisableKeepAlives != src.DisableKeepAlives {
+ t.Errorf("mismatch on DisableKeepAlives")
+ }
+ if dst.DisableCompression != src.DisableCompression {
+ t.Errorf("mismatch on DisableCompression")
+ }
+ if dst.MaxIdleConns != src.MaxIdleConns {
+ t.Errorf("mismatch on MaxIdleConns")
+ }
+ if dst.MaxIdleConnsPerHost != src.MaxIdleConnsPerHost {
+ t.Errorf("mismatch on MaxIdleConnsPerHost")
+ }
+ if dst.MaxConnsPerHost != src.MaxConnsPerHost {
+ t.Errorf("mismatch on MaxConnsPerHost")
+ }
+ if dst.IdleConnTimeout != src.IdleConnTimeout {
+ t.Errorf("mismatch on IdleConnTimeout")
+ }
+ if dst.ResponseHeaderTimeout != src.ResponseHeaderTimeout {
+ t.Errorf("mismatch on ResponseHeaderTimeout")
+ }
+ if dst.ExpectContinueTimeout != src.ExpectContinueTimeout {
+ t.Errorf("mismatch on ExpectContinueTimeout")
+ }
+ if dst.MaxResponseHeaderBytes != src.MaxResponseHeaderBytes {
+ t.Errorf("mismatch on MaxResponseHeaderBytes")
+ }
+}
+
+// Test that the name lookup of NewUTLSRoundTripper is case-insensitive.
+func TestNewUTLSRoundTripperCase(t *testing.T) {
+ mixed, err := NewUTLSRoundTripper("HelloFirefox_Auto", nil, nil)
+ if err != nil {
+ t.Fatalf("error on %q: %v", "HelloFirefox_Auto", err)
+ }
+ upper, err := NewUTLSRoundTripper("HELLOFIREFOX_AUTO", nil, nil)
+ if err != nil {
+ t.Fatalf("error on %q: %v", "HELLOFIREFOX_AUTO", err)
+ }
+ lower, err := NewUTLSRoundTripper("hellofirefox_auto", nil, nil)
+ if err != nil {
+ t.Fatalf("error on %q: %v", "hellofirefox_auto", err)
+ }
+ if mixed.(*UTLSRoundTripper).clientHelloID != upper.(*UTLSRoundTripper).clientHelloID ||
+ upper.(*UTLSRoundTripper).clientHelloID != lower.(*UTLSRoundTripper).clientHelloID {
+ t.Fatalf("mismatch %p %p %p",
+ mixed.(*UTLSRoundTripper).clientHelloID,
+ upper.(*UTLSRoundTripper).clientHelloID,
+ lower.(*UTLSRoundTripper).clientHelloID)
+ }
+}
+
+// Return a byte slice which is the ClientHello sent when rt does a RoundTrip.
+// Opens a temporary listener on an ephemeral port on localhost. The host you
+// provide can be an IP address like "127.0.0.1" or a name like "localhost", but
+// it has to resolve to localhost.
+func clientHelloResultingFromRoundTrip(t *testing.T, host string, rt *UTLSRoundTripper) ([]byte, error) {
+ ch := make(chan []byte, 1)
+
+ ln, err := net.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ return nil, err
+ }
+ defer ln.Close()
+
+ go func() {
+ defer func() {
+ close(ch)
+ }()
+ conn, err := ln.Accept()
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ defer conn.Close()
+ buf := make([]byte, 1024)
+ n, err := conn.Read(buf)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ ch <- buf[:n]
+ }()
+
+ _, port, err := net.SplitHostPort(ln.Addr().String())
+ if err != nil {
+ return nil, err
+ }
+ u := &url.URL{
+ Scheme: "https",
+ Host: net.JoinHostPort(host, port),
+ }
+ req, err := http.NewRequest("POST", u.String(), nil)
+ if err != nil {
+ return nil, err
+ }
+ // The RoundTrip fails because the goroutine "server" hangs up. So
+ // ignore an EOF error.
+ _, err = rt.RoundTrip(req)
+ if err != nil && err != io.EOF {
+ return nil, err
+ }
+
+ return <-ch, nil
+}
+
+// Test that a uTLS RoundTripper actually does something to the TLS Client
+// Hello. We don't check all the ClientHelloIDs; this is just a guard against a
+// catastrophic incompatibility or something else that makes uTLS stop working.
+func TestUTLSClientHello(t *testing.T) {
+ // We use HelloIOS_11_1 because its lengthy ALPN means we will not
+ // confuse it with a native Go fingerprint, and lack of GREASE means we
+ // do not have to account for many variations.
+ rt, err := NewUTLSRoundTripper("HelloIOS_11_1", &utls.Config{InsecureSkipVerify: true, ServerName: "localhost"}, nil)
+ if err != nil {
+ panic(err)
+ }
+
+ buf, err := clientHelloResultingFromRoundTrip(t, "127.0.0.1", rt.(*UTLSRoundTripper))
+ // A poor man's regexp matching because the regexp package only works on
+ // UTF-8â??encoded strings, not arbitrary byte slices. Every byte matches
+ // itself, except '.' which matches anything. NB '.' and '\x2e' are the
+ // same.
+ pattern := "" +
+ // Handshake, Client Hello, TLS 1.2, Client Random
+ "\x16\x03\x01\x01\x01\x01\x00\x00\xfd\x03\x03................................" +
+ // Session ID
+ "\x20................................" +
+ // Ciphersuites and compression methods
+ "\x00\x28\xc0\x2c\xc0\x2b\xc0\x24\xc0\x23\xc0\x0a\xc0\x09\xcc\xa9\xc0\x30\xc0\x2f\xc0\x28\xc0\x27\xc0\x14\xc0\x13\xcc\xa8\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x01\x00" +
+ // Extensions
+ "\x00\x8c\xff\x01\x00\x01\x00" +
+ "\x00\x00\x00\x0e\x00\x0c\x00\x00\x09localhost" +
+ "\x00\x17\x00\x00" +
+ "\x00\x0d\x00\x14\x00\x12\x04\x03\x08\x04\x04\x01\x05\x03\x08\x05\x05\x01\x08\x06\x06\x01\x02\x01" +
+ "\x00\x05\x00\x05\x01\x00\x00\x00\x00" +
+ "\x33\x74\x00\x00" +
+ "\x00\x12\x00\x00" +
+ "\x00\x10\x00\x30\x00\x2e\x02\x68\x32\x05\x68\x32\x2d\x31\x36\x05\x68\x32\x2d\x31\x35\x05\x68\x32\x2d\x31\x34\x08\x73\x70\x64\x79\x2f\x33\x2e\x31\x06\x73\x70\x64\x79\x2f\x33\x08\x68\x74\x74\x70\x2f\x31\x2e\x31" +
+ "\x00\x0b\x00\x02\x01\x00" +
+ "\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x18\x00\x19"
+ if len(buf) != len(pattern) {
+ t.Errorf("fingerprint was not as expected: %+q", buf)
+ }
+ for i := 0; i < len(pattern); i++ {
+ a := buf[i]
+ b := pattern[i]
+ if b != '.' && a != b {
+ t.Fatalf("fingerprint mismatch a position %v: %+q", i, buf)
+ }
+ }
+}
+
+func TestUTLSServerName(t *testing.T) {
+ const clientHelloIDName = "HelloFirefox_63"
+
+ // No ServerName, dial IP address. Results in an invalid server_name
+ // extension with a 0-length host_name. Not sure if that's what it
+ // should do, but check if the behavior ever changes.
+ rt, err := NewUTLSRoundTripper(clientHelloIDName, &utls.Config{InsecureSkipVerify: true}, nil)
+ if err != nil {
+ panic(err)
+ }
+ buf, err := clientHelloResultingFromRoundTrip(t, "127.0.0.1", rt.(*UTLSRoundTripper))
+ if err != nil {
+ panic(err)
+ }
+ if !bytes.Contains(buf, []byte("\x00\x00\x00\x05\x00\x03\x00\x00\x00")) {
+ t.Errorf("expected 0-length server_name extension with no ServerName and IP address dial")
+ }
+
+ // No ServerName, dial hostname. server_name extension should come from
+ // the dial address.
+ rt, err = NewUTLSRoundTripper(clientHelloIDName, &utls.Config{InsecureSkipVerify: true}, nil)
+ if err != nil {
+ panic(err)
+ }
+ buf, err = clientHelloResultingFromRoundTrip(t, "localhost", rt.(*UTLSRoundTripper))
+ if err != nil {
+ panic(err)
+ }
+ if !bytes.Contains(buf, []byte("\x00\x00\x00\x0e\x00\x0c\x00\x00\x09localhost")) {
+ t.Errorf("expected \"localhost\" server_name extension with no ServerName and hostname dial")
+ }
+
+ // Given ServerName, dial IP address. server_name extension should from
+ // the ServerName.
+ rt, err = NewUTLSRoundTripper(clientHelloIDName, &utls.Config{InsecureSkipVerify: true, ServerName: "test.example"}, nil)
+ if err != nil {
+ panic(err)
+ }
+ buf, err = clientHelloResultingFromRoundTrip(t, "127.0.0.1", rt.(*UTLSRoundTripper))
+ if err != nil {
+ panic(err)
+ }
+ if !bytes.Contains(buf, []byte("\x00\x00\x00\x11\x00\x0f\x00\x00\x0ctest.example")) {
+ t.Errorf("expected \"test.example\" server_name extension with given ServerName and IP address dial")
+ }
+
+ // Given ServerName, dial hostname. server_name extension should from
+ // the ServerName.
+ rt, err = NewUTLSRoundTripper(clientHelloIDName, &utls.Config{InsecureSkipVerify: true, ServerName: "test.example"}, nil)
+ if err != nil {
+ panic(err)
+ }
+ buf, err = clientHelloResultingFromRoundTrip(t, "localhost", rt.(*UTLSRoundTripper))
+ if err != nil {
+ panic(err)
+ }
+ if !bytes.Contains(buf, []byte("\x00\x00\x00\x11\x00\x0f\x00\x00\x0ctest.example")) {
+ t.Errorf("expected \"test.example\" server_name extension with given ServerName and hostname dial")
+ }
+}
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits