diff --git a/client.go b/client.go index a24aaa2..bfa47bf 100644 --- a/client.go +++ b/client.go @@ -85,6 +85,7 @@ type DialOptions struct { Chain *Chain Host string HeaderConfig map[string]string + H2Alpn bool } // DialOption allows a common way to set DialOptions. @@ -118,6 +119,13 @@ func HeaderConfigDialOption(HeaderConfig map[string]string) DialOption { } } +// H2AlpnDialOption specifies is use HTTP2 in ALPN for TLS ClientHello +func H2AlpnDialOption(useH2Alpn bool) DialOption { + return func(opts *DialOptions) { + opts.H2Alpn = useH2Alpn + } +} + // HandshakeOptions describes the options for handshake. type HandshakeOptions struct { Addr string @@ -131,6 +139,7 @@ type HandshakeOptions struct { KCPConfig *KCPConfig QUICConfig *QUICConfig SSHConfig *SSHConfig + H2Alpn bool } // HandshakeOption allows a common way to set HandshakeOptions. @@ -213,6 +222,13 @@ func SSHConfigHandshakeOption(config *SSHConfig) HandshakeOption { } } +// H2AlpnHandshakeOption specifies is use HTTP2 in ALPN for TLS ClientHello. +func H2AlpnHandshakeOption(useH2Alpn bool) HandshakeOption { + return func(opts *HandshakeOptions) { + opts.H2Alpn = useH2Alpn + } +} + // ConnectOptions describes the options for Connector.Connect. type ConnectOptions struct { Addr string diff --git a/cmd/gost/route.go b/cmd/gost/route.go index 9c67547..6ee2a3a 100644 --- a/cmd/gost/route.go +++ b/cmd/gost/route.go @@ -291,10 +291,17 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) { } + h2AlpnStr := node.Get("h2Alpn") + h2Alpn := true + if h2AlpnStr != "" { + h2Alpn = node.GetBool("h2Alpn") + } + node.DialOptions = append(node.DialOptions, gost.TimeoutDialOption(timeout), gost.HostDialOption(host), gost.HeaderConfigDialOption(headerCfg), + gost.H2AlpnDialOption(h2Alpn), ) node.ConnectOptions = []gost.ConnectOption{ @@ -322,6 +329,7 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) { gost.TimeoutHandshakeOption(timeout), gost.RetryHandshakeOption(node.GetInt("retry")), gost.SSHConfigHandshakeOption(sshConfig), + gost.H2AlpnHandshakeOption(h2Alpn), } node.Client = &gost.Client{ diff --git a/http2.go b/http2.go index d651e16..63d3964 100644 --- a/http2.go +++ b/http2.go @@ -161,7 +161,7 @@ func (tr *http2Transporter) Dial(addr string, options ...DialOption) (net.Conn, if err != nil { return nil, err } - return wrapTLSClient(conn, cfg, timeout) + return wrapTLSClient(conn, cfg, timeout, opts.H2Alpn) }, } client = &http.Client{ @@ -242,7 +242,7 @@ func (tr *h2Transporter) Dial(addr string, options ...DialOption) (net.Conn, err if tr.tlsConfig == nil { return conn, nil } - return wrapTLSClient(conn, cfg, timeout) + return wrapTLSClient(conn, cfg, timeout, opts.H2Alpn) }, } client = &http.Client{ diff --git a/tls.go b/tls.go index e71b431..9c50a8b 100644 --- a/tls.go +++ b/tls.go @@ -36,7 +36,7 @@ func (tr *tlsTransporter) Handshake(conn net.Conn, options ...HandshakeOption) ( timeout = HandshakeTimeout } - return wrapTLSClient(conn, opts.TLSConfig, timeout) + return wrapTLSClient(conn, opts.TLSConfig, timeout, opts.H2Alpn) } type mtlsTransporter struct { @@ -131,7 +131,7 @@ func (tr *mtlsTransporter) initSession(addr string, conn net.Conn, opts *Handsha if opts.TLSConfig == nil { opts.TLSConfig = &tls.Config{InsecureSkipVerify: true} } - conn, err := wrapTLSClient(conn, opts.TLSConfig, opts.Timeout) + conn, err := wrapTLSClient(conn, opts.TLSConfig, opts.Timeout, opts.H2Alpn) if err != nil { return nil, err } @@ -268,7 +268,7 @@ func (l *mtlsListener) Close() error { // // This code is taken from consul: // https://github.com/hashicorp/consul/blob/master/tlsutil/config.go -func wrapTLSClient(conn net.Conn, tlsConfig *tls.Config, timeout time.Duration) (net.Conn, error) { +func wrapTLSClient(conn net.Conn, tlsConfig *tls.Config, timeout time.Duration, h2Alpn bool) (net.Conn, error) { var err error if timeout <= 0 { @@ -279,7 +279,13 @@ func wrapTLSClient(conn net.Conn, tlsConfig *tls.Config, timeout time.Duration) defer conn.SetDeadline(time.Time{}) //tlsConn := tls.Client(conn, tlsConfig) - tlsConn := utls.UClient(conn, &utls.Config{InsecureSkipVerify: tlsConfig.InsecureSkipVerify, ServerName: tlsConfig.ServerName}, utls.HelloChrome_Auto) + var tlsConn *utls.UConn + if h2Alpn { + tlsConn = utls.UClient(conn, &utls.Config{InsecureSkipVerify: tlsConfig.InsecureSkipVerify, ServerName: tlsConfig.ServerName}, utls.HelloChrome_Auto) + } else { + tlsConn = utls.UClient(conn, &utls.Config{InsecureSkipVerify: tlsConfig.InsecureSkipVerify, ServerName: tlsConfig.ServerName}, utls.HelloCustom) + tlsConn.ApplyPreset(newWsSpec()) + } // Otherwise perform handshake, but don't verify the domain // diff --git a/ws.go b/ws.go index 5701d2d..66d7dc4 100644 --- a/ws.go +++ b/ws.go @@ -744,6 +744,79 @@ type websocketConn struct { rb []byte } +func newWsSpec() *utls.ClientHelloSpec { + return &utls.ClientHelloSpec{ + CipherSuites: []uint16{ + utls.GREASE_PLACEHOLDER, + utls.TLS_AES_128_GCM_SHA256, + utls.TLS_AES_256_GCM_SHA384, + utls.TLS_CHACHA20_POLY1305_SHA256, + utls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + utls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + utls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + utls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + utls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + utls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + utls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + utls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + utls.TLS_RSA_WITH_AES_128_GCM_SHA256, + utls.TLS_RSA_WITH_AES_256_GCM_SHA384, + utls.TLS_RSA_WITH_AES_128_CBC_SHA, + utls.TLS_RSA_WITH_AES_256_CBC_SHA, + }, + CompressionMethods: []byte{ + 0x00, // compressionNone + }, + Extensions: []utls.TLSExtension{ + &utls.UtlsGREASEExtension{}, + &utls.SNIExtension{}, + &utls.UtlsExtendedMasterSecretExtension{}, + &utls.RenegotiationInfoExtension{Renegotiation: utls.RenegotiateOnceAsClient}, + &utls.SupportedCurvesExtension{[]utls.CurveID{ + utls.GREASE_PLACEHOLDER, + utls.X25519, + utls.CurveP256, + utls.CurveP384, + }}, + &utls.SupportedPointsExtension{SupportedPoints: []byte{ + 0x00, // pointFormatUncompressed + }}, + &utls.SessionTicketExtension{}, + &utls.ALPNExtension{AlpnProtocols: []string{"http/1.1"}}, + &utls.StatusRequestExtension{}, + &utls.SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []utls.SignatureScheme{ + utls.ECDSAWithP256AndSHA256, + utls.PSSWithSHA256, + utls.PKCS1WithSHA256, + utls.ECDSAWithP384AndSHA384, + utls.PSSWithSHA384, + utls.PKCS1WithSHA384, + utls.PSSWithSHA512, + utls.PKCS1WithSHA512, + }}, + &utls.SCTExtension{}, + &utls.KeyShareExtension{[]utls.KeyShare{ + {Group: utls.CurveID(utls.GREASE_PLACEHOLDER), Data: []byte{0}}, + {Group: utls.X25519}, + }}, + &utls.PSKKeyExchangeModesExtension{[]uint8{ + utls.PskModeDHE, + }}, + &utls.SupportedVersionsExtension{[]uint16{ + utls.GREASE_PLACEHOLDER, + utls.VersionTLS13, + utls.VersionTLS12, + }}, + &utls.UtlsCompressCertExtension{[]utls.CertCompressionAlgo{ + utls.CertCompressionBrotli, + }}, + &utls.ApplicationSettingsExtension{SupportedProtocols: []string{"h2"}}, + &utls.UtlsGREASEExtension{}, + &utls.UtlsPaddingExtension{GetPaddingLen: utls.BoringPaddingStyle}, + }, + } +} + func websocketClientConn(url string, conn net.Conn, tlsConfig *tls.Config, options *WSOptions) (net.Conn, error) { if options == nil { options = &WSOptions{} @@ -765,76 +838,7 @@ func websocketClientConn(url string, conn net.Conn, tlsConfig *tls.Config, optio }, NetDialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) { client := utls.UClient(conn, &utls.Config{InsecureSkipVerify: tlsConfig.InsecureSkipVerify, ServerName: tlsConfig.ServerName}, utls.HelloCustom) - client.ApplyPreset(&utls.ClientHelloSpec{ - CipherSuites: []uint16{ - utls.GREASE_PLACEHOLDER, - utls.TLS_AES_128_GCM_SHA256, - utls.TLS_AES_256_GCM_SHA384, - utls.TLS_CHACHA20_POLY1305_SHA256, - utls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - utls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - utls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - utls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - utls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, - utls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, - utls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, - utls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - utls.TLS_RSA_WITH_AES_128_GCM_SHA256, - utls.TLS_RSA_WITH_AES_256_GCM_SHA384, - utls.TLS_RSA_WITH_AES_128_CBC_SHA, - utls.TLS_RSA_WITH_AES_256_CBC_SHA, - }, - CompressionMethods: []byte{ - 0x00, // compressionNone - }, - Extensions: []utls.TLSExtension{ - &utls.UtlsGREASEExtension{}, - &utls.SNIExtension{}, - &utls.UtlsExtendedMasterSecretExtension{}, - &utls.RenegotiationInfoExtension{Renegotiation: utls.RenegotiateOnceAsClient}, - &utls.SupportedCurvesExtension{[]utls.CurveID{ - utls.GREASE_PLACEHOLDER, - utls.X25519, - utls.CurveP256, - utls.CurveP384, - }}, - &utls.SupportedPointsExtension{SupportedPoints: []byte{ - 0x00, // pointFormatUncompressed - }}, - &utls.SessionTicketExtension{}, - &utls.ALPNExtension{AlpnProtocols: []string{"http/1.1"}}, - &utls.StatusRequestExtension{}, - &utls.SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []utls.SignatureScheme{ - utls.ECDSAWithP256AndSHA256, - utls.PSSWithSHA256, - utls.PKCS1WithSHA256, - utls.ECDSAWithP384AndSHA384, - utls.PSSWithSHA384, - utls.PKCS1WithSHA384, - utls.PSSWithSHA512, - utls.PKCS1WithSHA512, - }}, - &utls.SCTExtension{}, - &utls.KeyShareExtension{[]utls.KeyShare{ - {Group: utls.CurveID(utls.GREASE_PLACEHOLDER), Data: []byte{0}}, - {Group: utls.X25519}, - }}, - &utls.PSKKeyExchangeModesExtension{[]uint8{ - utls.PskModeDHE, - }}, - &utls.SupportedVersionsExtension{[]uint16{ - utls.GREASE_PLACEHOLDER, - utls.VersionTLS13, - utls.VersionTLS12, - }}, - &utls.UtlsCompressCertExtension{[]utls.CertCompressionAlgo{ - utls.CertCompressionBrotli, - }}, - &utls.ApplicationSettingsExtension{SupportedProtocols: []string{"h2"}}, - &utls.UtlsGREASEExtension{}, - &utls.UtlsPaddingExtension{GetPaddingLen: utls.BoringPaddingStyle}, - }, - }) + client.ApplyPreset(newWsSpec()) err := client.Handshake() if err != nil { return nil, err