chore: update utls to 1.7.0

This commit is contained in:
wwqgtxx 2025-04-21 12:07:33 +08:00
parent d5243adf89
commit 39d6a0d7ba
14 changed files with 96 additions and 81 deletions

View File

@ -19,7 +19,6 @@ import (
shadowtls "github.com/metacubex/mihomo/transport/sing-shadowtls" shadowtls "github.com/metacubex/mihomo/transport/sing-shadowtls"
v2rayObfs "github.com/metacubex/mihomo/transport/v2ray-plugin" v2rayObfs "github.com/metacubex/mihomo/transport/v2ray-plugin"
restlsC "github.com/3andne/restls-client-go"
shadowsocks "github.com/metacubex/sing-shadowsocks2" shadowsocks "github.com/metacubex/sing-shadowsocks2"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
@ -37,7 +36,7 @@ type ShadowSocks struct {
v2rayOption *v2rayObfs.Option v2rayOption *v2rayObfs.Option
gostOption *gost.Option gostOption *gost.Option
shadowTLSOption *shadowtls.ShadowTLSOption shadowTLSOption *shadowtls.ShadowTLSOption
restlsConfig *restlsC.Config restlsConfig *restls.Config
} }
type ShadowSocksOption struct { type ShadowSocksOption struct {
@ -265,7 +264,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
var gostOption *gost.Option var gostOption *gost.Option
var obfsOption *simpleObfsOption var obfsOption *simpleObfsOption
var shadowTLSOpt *shadowtls.ShadowTLSOption var shadowTLSOpt *shadowtls.ShadowTLSOption
var restlsConfig *restlsC.Config var restlsConfig *restls.Config
obfsMode := "" obfsMode := ""
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true}) decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
@ -350,7 +349,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err) return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err)
} }
restlsConfig, err = restlsC.NewRestlsConfig(restlsOpt.Host, restlsOpt.Password, restlsOpt.VersionHint, restlsOpt.RestlsScript, option.ClientFingerprint) restlsConfig, err = restls.NewRestlsConfig(restlsOpt.Host, restlsOpt.Password, restlsOpt.VersionHint, restlsOpt.RestlsScript, option.ClientFingerprint)
if err != nil { if err != nil {
return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err) return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err)
} }

View File

@ -16,6 +16,17 @@ func Filter[T comparable](tSlice []T, filter func(t T) bool) []T {
return result return result
} }
func Map[T any, N any](arr []T, block func(it T) N) []N {
if arr == nil { // keep nil
return nil
}
retArr := make([]N, 0, len(arr))
for index := range arr {
retArr = append(retArr, block(arr[index]))
}
return retArr
}
func ToStringSlice(value any) ([]string, error) { func ToStringSlice(value any) ([]string, error) {
strArr := make([]string, 0) strArr := make([]string, 0)
switch reflect.TypeOf(value).Kind() { switch reflect.TypeOf(value).Kind() {

View File

@ -111,11 +111,7 @@ func convertFingerprint(fingerprint string) (*[32]byte, error) {
return (*[32]byte)(fpByte), nil return (*[32]byte)(fpByte), nil
} }
// GetTLSConfig specified fingerprint, customCA and customCAString func GetCertPool(customCA string, customCAString string) (*x509.CertPool, error) {
func GetTLSConfig(tlsConfig *tls.Config, fingerprint string, customCA string, customCAString string) (*tls.Config, error) {
if tlsConfig == nil {
tlsConfig = &tls.Config{}
}
var certificate []byte var certificate []byte
var err error var err error
if len(customCA) > 0 { if len(customCA) > 0 {
@ -131,17 +127,35 @@ func GetTLSConfig(tlsConfig *tls.Config, fingerprint string, customCA string, cu
if !certPool.AppendCertsFromPEM(certificate) { if !certPool.AppendCertsFromPEM(certificate) {
return nil, fmt.Errorf("failed to parse certificate:\n\n %s", certificate) return nil, fmt.Errorf("failed to parse certificate:\n\n %s", certificate)
} }
tlsConfig.RootCAs = certPool return certPool, nil
} else { } else {
tlsConfig.RootCAs = getCertPool() return getCertPool(), nil
} }
}
func NewFingerprintVerifier(fingerprint string) (func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error, error) {
fingerprintBytes, err := convertFingerprint(fingerprint)
if err != nil {
return nil, err
}
return verifyFingerprint(fingerprintBytes), nil
}
// GetTLSConfig specified fingerprint, customCA and customCAString
func GetTLSConfig(tlsConfig *tls.Config, fingerprint string, customCA string, customCAString string) (_ *tls.Config, err error) {
if tlsConfig == nil {
tlsConfig = &tls.Config{}
}
tlsConfig.RootCAs, err = GetCertPool(customCA, customCAString)
if err != nil {
return nil, err
}
if len(fingerprint) > 0 { if len(fingerprint) > 0 {
var fingerprintBytes *[32]byte tlsConfig.VerifyPeerCertificate, err = NewFingerprintVerifier(fingerprint)
fingerprintBytes, err = convertFingerprint(fingerprint)
if err != nil { if err != nil {
return nil, err return nil, err
} }
tlsConfig.VerifyPeerCertificate = verifyFingerprint(fingerprintBytes)
tlsConfig.InsecureSkipVerify = true tlsConfig.InsecureSkipVerify = true
} }
return tlsConfig, nil return tlsConfig, nil

View File

@ -4,15 +4,16 @@ import (
"crypto/tls" "crypto/tls"
"net" "net"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
utls "github.com/metacubex/utls" utls "github.com/metacubex/utls"
"github.com/mroth/weightedrand/v2" "github.com/mroth/weightedrand/v2"
) )
type UConn struct { type UConn = utls.UConn
*utls.UConn
} const VersionTLS13 = utls.VersionTLS13
type UClientHelloID struct { type UClientHelloID struct {
*utls.ClientHelloID *utls.ClientHelloID
@ -21,13 +22,8 @@ type UClientHelloID struct {
var initRandomFingerprint UClientHelloID var initRandomFingerprint UClientHelloID
var initUtlsClient string var initUtlsClient string
func UClient(c net.Conn, config *tls.Config, fingerprint UClientHelloID) *UConn { func UClient(c net.Conn, config *utls.Config, fingerprint UClientHelloID) *UConn {
utlsConn := utls.UClient(c, copyConfig(config), utls.ClientHelloID{ return utls.UClient(c, config, *fingerprint.ClientHelloID)
Client: fingerprint.Client,
Version: fingerprint.Version,
Seed: fingerprint.Seed,
})
return &UConn{UConn: utlsConn}
} }
func GetFingerprint(ClientFingerprint string) (UClientHelloID, bool) { func GetFingerprint(ClientFingerprint string) (UClientHelloID, bool) {
@ -95,18 +91,43 @@ func init() {
Fingerprints["randomized"] = UClientHelloID{&randomized} Fingerprints["randomized"] = UClientHelloID{&randomized}
} }
func copyConfig(c *tls.Config) *utls.Config { func UCertificates(it tls.Certificate) utls.Certificate {
return utls.Certificate{
Certificate: it.Certificate,
PrivateKey: it.PrivateKey,
SupportedSignatureAlgorithms: utils.Map(it.SupportedSignatureAlgorithms, func(it tls.SignatureScheme) utls.SignatureScheme {
return utls.SignatureScheme(it)
}),
OCSPStaple: it.OCSPStaple,
SignedCertificateTimestamps: it.SignedCertificateTimestamps,
Leaf: it.Leaf,
}
}
func UConfig(config *tls.Config) *utls.Config {
return &utls.Config{ return &utls.Config{
RootCAs: c.RootCAs, Rand: config.Rand,
ServerName: c.ServerName, Time: config.Time,
InsecureSkipVerify: c.InsecureSkipVerify, Certificates: utils.Map(config.Certificates, UCertificates),
VerifyPeerCertificate: c.VerifyPeerCertificate, VerifyPeerCertificate: config.VerifyPeerCertificate,
RootCAs: config.RootCAs,
NextProtos: config.NextProtos,
ServerName: config.ServerName,
InsecureSkipVerify: config.InsecureSkipVerify,
CipherSuites: config.CipherSuites,
MinVersion: config.MinVersion,
MaxVersion: config.MaxVersion,
CurvePreferences: utils.Map(config.CurvePreferences, func(it tls.CurveID) utls.CurveID {
return utls.CurveID(it)
}),
SessionTicketsDisabled: config.SessionTicketsDisabled,
Renegotiation: utls.RenegotiationSupport(config.Renegotiation),
} }
} }
// BuildWebsocketHandshakeState it will only send http/1.1 in its ALPN. // BuildWebsocketHandshakeState it will only send http/1.1 in its ALPN.
// Copy from https://github.com/XTLS/Xray-core/blob/main/transport/internet/tls/tls.go // Copy from https://github.com/XTLS/Xray-core/blob/main/transport/internet/tls/tls.go
func (c *UConn) BuildWebsocketHandshakeState() error { func BuildWebsocketHandshakeState(c *UConn) error {
// Build the handshake state. This will apply every variable of the TLS of the // Build the handshake state. This will apply every variable of the TLS of the
// fingerprint in the UConn // fingerprint in the UConn
if err := c.BuildHandshakeState(); err != nil { if err := c.BuildHandshakeState(); err != nil {

2
go.mod
View File

@ -32,7 +32,7 @@ require (
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422
github.com/metacubex/utls v1.6.8-alpha.6 github.com/metacubex/utls v1.7.0-alpha.1
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181
github.com/miekg/dns v1.1.63 github.com/miekg/dns v1.1.63
github.com/mroth/weightedrand/v2 v2.1.0 github.com/mroth/weightedrand/v2 v2.1.0

4
go.sum
View File

@ -129,8 +129,8 @@ github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 h1:Z6bNy0
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589/go.mod h1:4NclTLIZuk+QkHVCGrP87rHi/y8YjgPytxTgApJNMhc= github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589/go.mod h1:4NclTLIZuk+QkHVCGrP87rHi/y8YjgPytxTgApJNMhc=
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY= github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/utls v1.6.8-alpha.6 h1:5ZdZNiZFkKKgEcuPOOROIc8bA4dX2VJHoY3gajSnSaU= github.com/metacubex/utls v1.7.0-alpha.1 h1:oMFsPh2oTlALJ7vKXPJuqgy0YeiZ+q/LLw+ZdxZ80l4=
github.com/metacubex/utls v1.6.8-alpha.6/go.mod h1:MEZ5WO/VLKYs/s/dOzEK/mlXOQxc04ESeLzRgjmLYtk= github.com/metacubex/utls v1.7.0-alpha.1/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU=
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ= github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y= github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=

View File

@ -22,14 +22,13 @@ import (
mihomoVMess "github.com/metacubex/mihomo/transport/vmess" mihomoVMess "github.com/metacubex/mihomo/transport/vmess"
"github.com/metacubex/sing-vmess/vless" "github.com/metacubex/sing-vmess/vless"
utls "github.com/metacubex/utls"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/metadata"
) )
func init() { func init() {
vless.RegisterTLS(func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer unsafe.Pointer) { vless.RegisterTLS(func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer unsafe.Pointer) {
tlsConn, loaded := common.Cast[*reality.Conn](conn) tlsConn, loaded := common.Cast[*reality.Conn](conn) // *utls.Conn
if !loaded { if !loaded {
return return
} }
@ -37,15 +36,7 @@ func init() {
}) })
vless.RegisterTLS(func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer unsafe.Pointer) { vless.RegisterTLS(func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer unsafe.Pointer) {
tlsConn, loaded := common.Cast[*utls.UConn](conn) tlsConn, loaded := common.Cast[*tlsC.UConn](conn) // *utls.UConn
if !loaded {
return
}
return true, tlsConn.NetConn(), reflect.TypeOf(tlsConn.Conn).Elem(), unsafe.Pointer(tlsConn.Conn)
})
vless.RegisterTLS(func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer unsafe.Pointer) {
tlsConn, loaded := common.Cast[*tlsC.UConn](conn)
if !loaded { if !loaded {
return return
} }

View File

@ -244,7 +244,7 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, clientFingerprint stri
if len(clientFingerprint) != 0 { if len(clientFingerprint) != 0 {
if realityConfig == nil { if realityConfig == nil {
if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists { if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists {
utlsConn := tlsC.UClient(pconn, cfg, fingerprint) utlsConn := tlsC.UClient(pconn, tlsC.UConfig(cfg), fingerprint)
if err := utlsConn.HandshakeContext(ctx); err != nil { if err := utlsConn.HandshakeContext(ctx); err != nil {
pconn.Close() pconn.Close()
return nil, err return nil, err

View File

@ -19,8 +19,12 @@ func (r *Restls) Upstream() any {
return r.UConn.NetConn() return r.UConn.NetConn()
} }
type Config = tls.Config
var NewRestlsConfig = tls.NewRestlsConfig
// NewRestls return a Restls Connection // NewRestls return a Restls Connection
func NewRestls(ctx context.Context, conn net.Conn, config *tls.Config) (net.Conn, error) { func NewRestls(ctx context.Context, conn net.Conn, config *Config) (net.Conn, error) {
clientHellowID := tls.HelloChrome_Auto clientHellowID := tls.HelloChrome_Auto
if config != nil { if config != nil {
clientIDPtr := config.ClientID.Load() clientIDPtr := config.ClientID.Load()

View File

@ -11,7 +11,6 @@ import (
"github.com/metacubex/sing-shadowtls" "github.com/metacubex/sing-shadowtls"
utls "github.com/metacubex/utls" utls "github.com/metacubex/utls"
sing_common "github.com/sagernet/sing/common"
) )
const ( const (
@ -60,32 +59,15 @@ func NewShadowTLS(ctx context.Context, conn net.Conn, option *ShadowTLSOption) (
func uTLSHandshakeFunc(config *tls.Config, clientFingerprint string) shadowtls.TLSHandshakeFunc { func uTLSHandshakeFunc(config *tls.Config, clientFingerprint string) shadowtls.TLSHandshakeFunc {
return func(ctx context.Context, conn net.Conn, sessionIDGenerator shadowtls.TLSSessionIDGeneratorFunc) error { return func(ctx context.Context, conn net.Conn, sessionIDGenerator shadowtls.TLSSessionIDGeneratorFunc) error {
tlsConfig := &utls.Config{ tlsConfig := tlsC.UConfig(config)
Rand: config.Rand, tlsConfig.SessionIDGenerator = sessionIDGenerator
Time: config.Time,
VerifyPeerCertificate: config.VerifyPeerCertificate,
RootCAs: config.RootCAs,
NextProtos: config.NextProtos,
ServerName: config.ServerName,
InsecureSkipVerify: config.InsecureSkipVerify,
CipherSuites: config.CipherSuites,
MinVersion: config.MinVersion,
MaxVersion: config.MaxVersion,
CurvePreferences: sing_common.Map(config.CurvePreferences, func(it tls.CurveID) utls.CurveID {
return utls.CurveID(it)
}),
SessionTicketsDisabled: config.SessionTicketsDisabled,
Renegotiation: utls.RenegotiationSupport(config.Renegotiation),
SessionIDGenerator: sessionIDGenerator,
}
clientFingerprint := clientFingerprint clientFingerprint := clientFingerprint
if tlsC.HaveGlobalFingerprint() && len(clientFingerprint) == 0 { if tlsC.HaveGlobalFingerprint() && len(clientFingerprint) == 0 {
clientFingerprint = tlsC.GetGlobalFingerprint() clientFingerprint = tlsC.GetGlobalFingerprint()
} }
if len(clientFingerprint) != 0 { if len(clientFingerprint) != 0 {
if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists { if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists {
clientHelloID := *fingerprint.ClientHelloID tlsConn := tlsC.UClient(conn, tlsConfig, fingerprint)
tlsConn := utls.UClient(conn, tlsConfig, clientHelloID)
return tlsConn.HandshakeContext(ctx) return tlsConn.HandshakeContext(ctx)
} }
} }

View File

@ -12,10 +12,10 @@ import (
"github.com/metacubex/mihomo/common/buf" "github.com/metacubex/mihomo/common/buf"
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
tlsC "github.com/metacubex/mihomo/component/tls"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"github.com/gofrs/uuid/v5" "github.com/gofrs/uuid/v5"
utls "github.com/metacubex/utls"
) )
var ( var (
@ -187,8 +187,8 @@ func (vc *Conn) WriteBuffer(buffer *buf.Buffer) (err error) {
buffer.Release() buffer.Release()
return ErrNotTLS13 return ErrNotTLS13
} }
case *utls.UConn: case *tlsC.UConn:
if underlying.ConnectionState().Version != utls.VersionTLS13 { if underlying.ConnectionState().Version != tlsC.VersionTLS13 {
buffer.Release() buffer.Release()
return ErrNotTLS13 return ErrNotTLS13
} }

View File

@ -14,7 +14,6 @@ import (
tlsC "github.com/metacubex/mihomo/component/tls" tlsC "github.com/metacubex/mihomo/component/tls"
"github.com/gofrs/uuid/v5" "github.com/gofrs/uuid/v5"
utls "github.com/metacubex/utls"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
) )
@ -46,16 +45,10 @@ func NewConn(conn connWithUpstream, userUUID *uuid.UUID) (*Conn, error) {
c.tlsConn = underlying c.tlsConn = underlying
t = reflect.TypeOf(underlying).Elem() t = reflect.TypeOf(underlying).Elem()
p = unsafe.Pointer(underlying) p = unsafe.Pointer(underlying)
case *utls.UConn:
//log.Debugln("type *utls.UConn")
c.Conn = underlying.NetConn()
c.tlsConn = underlying
t = reflect.TypeOf(underlying.Conn).Elem()
p = unsafe.Pointer(underlying.Conn)
case *tlsC.UConn: case *tlsC.UConn:
//log.Debugln("type *tlsC.UConn") //log.Debugln("type *tlsC.UConn")
c.Conn = underlying.NetConn() c.Conn = underlying.NetConn()
c.tlsConn = underlying.UConn c.tlsConn = underlying
t = reflect.TypeOf(underlying.Conn).Elem() t = reflect.TypeOf(underlying.Conn).Elem()
//log.Debugln("t:%v", t) //log.Debugln("t:%v", t)
p = unsafe.Pointer(underlying.Conn) p = unsafe.Pointer(underlying.Conn)

View File

@ -39,7 +39,7 @@ func StreamTLSConn(ctx context.Context, conn net.Conn, cfg *TLSConfig) (net.Conn
if len(clientFingerprint) != 0 { if len(clientFingerprint) != 0 {
if cfg.Reality == nil { if cfg.Reality == nil {
if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists { if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists {
utlsConn := tlsC.UClient(conn, tlsConfig, fingerprint) utlsConn := tlsC.UClient(conn, tlsC.UConfig(tlsConfig), fingerprint)
err = utlsConn.HandshakeContext(ctx) err = utlsConn.HandshakeContext(ctx)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -360,8 +360,8 @@ func streamWebsocketConn(ctx context.Context, conn net.Conn, c *WebsocketConfig,
} }
if len(clientFingerprint) != 0 { if len(clientFingerprint) != 0 {
if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists { if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists {
utlsConn := tlsC.UClient(conn, config, fingerprint) utlsConn := tlsC.UClient(conn, tlsC.UConfig(config), fingerprint)
if err = utlsConn.BuildWebsocketHandshakeState(); err != nil { if err = tlsC.BuildWebsocketHandshakeState(utlsConn); err != nil {
return nil, fmt.Errorf("parse url %s error: %w", c.Path, err) return nil, fmt.Errorf("parse url %s error: %w", c.Path, err)
} }
conn = utlsConn conn = utlsConn