From 2e9f8ab8e9e60728bc52c48a3c680282e36f0f20 Mon Sep 17 00:00:00 2001 From: Page Fault Date: Fri, 15 May 2020 05:38:08 +0000 Subject: [PATCH] fix tls fingerprint --- conf/parse.go | 1 + docs/content/basic/config.md | 5 +- docs/content/basic/full-config.md | 12 +- protocol/trojan/outbound.go | 4 - proxy/client/tls.go | 398 +++++++++++++++++++++++------- 5 files changed, 319 insertions(+), 101 deletions(-) diff --git a/conf/parse.go b/conf/parse.go index 1a14cb678..8191183b0 100644 --- a/conf/parse.go +++ b/conf/parse.go @@ -349,6 +349,7 @@ func ParseJSON(data []byte) (*GlobalConfig, error) { VerifyHostname: true, SessionTicket: true, ReuseSession: true, + Fingerprint: "firefox", }, Mux: MuxConfig{ IdleTimeout: 60, diff --git a/docs/content/basic/config.md b/docs/content/basic/config.md index 2d9f899e8..57dcd977c 100644 --- a/docs/content/basic/config.md +++ b/docs/content/basic/config.md @@ -99,7 +99,6 @@ sudo ./trojan-go -autocert renew "your_awesome_password" ], "ssl": { - "fingerprint": "firefox", "sni": "your_domain_name" } } @@ -107,8 +106,6 @@ sudo ./trojan-go -autocert renew 这个客户端配置使Trojan-Go开启一个监听在本地1080端口的socks5/http代理(自动识别),远端服务器为your_awesome_server:443,your_awesome_server可以是IP或者域名。 -如果你在```remote_addr```中填写的是域名,```sni```可以省略。```sni```字段应当填写你申请证书的对应域名,或者你自己签发的证书的Common Name,而且必须一致。注意,```sni```字段目前的在TLS协议中是**明文传送**的(目的是使服务器提供相应证书)。GFW已经被证实具有SNI探测和阻断能力,所以不要填写类似```google.com```等已经被封锁的域名,否则很有可能导致你的服务器也被封锁。 - -```fingerprint```将设置Trojan-Go伪造Firefox浏览器的TLS请求指纹,使得Trojan-Go的流量混杂在正常的HTTPS流量中无法被识别。还可以设置为```ios```,```chrome```,```randomized```等。 +如果你在```remote_addr```中填写的是域名,```sni```可以省略。如果你在```remote_addr```填写的是IP地址,```sni```字段应当填写你申请证书的对应域名,或者你自己签发的证书的Common Name,而且必须一致。注意,```sni```字段目前的在TLS协议中是**明文传送**的(目的是使服务器提供相应证书)。GFW已经被证实具有SNI探测和阻断能力,所以不要填写类似```google.com```等已经被封锁的域名,否则很有可能导致你的服务器也被遭到锁。 更多关于配置文件的信息,可以在左侧导航栏中找到相应介绍。 diff --git a/docs/content/basic/full-config.md b/docs/content/basic/full-config.md index 0deb2e999..65e18d9b4 100644 --- a/docs/content/basic/full-config.md +++ b/docs/content/basic/full-config.md @@ -50,7 +50,7 @@ weight: 30 "reuse_session": true, "plain_http_response": "", "fallback_port": 0, - "fingerprint": "" + "fingerprint": "firefox" }, "tcp": { "no_delay": true, @@ -172,21 +172,19 @@ weight: 30 ```curves```指定TLS在ECDHE中偏好使用的椭圆曲线。只有你明确知道自己在做什么的情况下,才应该填写此项。曲线名称用分号(":")分隔。 -```fingerprint```用于指定TLS Client Hello指纹伪造类型,以抵抗GFW对于TLS Client Hello指纹的特征识别和阻断。trojan-go使用[utls](https://github.com/refraction-networking/utls)进行指纹伪造。合法的值有 +```fingerprint```用于指定TLS Client Hello指纹伪造类型,以抵抗GFW对于TLS Client Hello指纹的特征识别和阻断。trojan-go使用[utls](https://github.com/refraction-networking/utls)进行指纹伪造,默认伪造Firefox的指纹。合法的值有 - "",不使用指纹伪造 -- "auto",自动选择(推荐) +- "auto",自动尝试并选择 -- "firefox",伪造Firefox指纹 +- "firefox",伪造Firefox指纹(默认) - "chrome",伪造Chrome指纹 - "ios",伪造iOS指纹 -- "randomized",随机指纹 - -一旦指纹的值被设置,```cipher```,```curves```,```alpn```,```session_ticket```等有可能影响指纹的字段将使用该指纹的特定设置覆写。设置该选项有可能导致与服务器密钥协商失败,使用auto选项将自动尝试所有指纹并选出合适的一项。 +一旦指纹的值被设置,```cipher```,```curves```,```alpn```,```session_ticket```等有可能影响指纹的字段将使用该指纹的特定设置覆写。 ```plain_http_response```指定了当TLS握手失败时,明文发送的原始数据(原始TCP数据)。这个字段填入该文件路径。推荐使用```fallback_port```而不是该字段。 diff --git a/protocol/trojan/outbound.go b/protocol/trojan/outbound.go index a7a940499..bfcc2c3a9 100644 --- a/protocol/trojan/outbound.go +++ b/protocol/trojan/outbound.go @@ -24,10 +24,6 @@ type TrojanOutboundConnSession struct { trojanHeader []byte } -func (o *TrojanOutboundConnSession) SetMeter(meter stat.TrafficMeter) { - o.meter = meter -} - func (o *TrojanOutboundConnSession) Write(p []byte) (int, error) { if o.trojanHeader != nil { //send the payload after the trojan request header diff --git a/proxy/client/tls.go b/proxy/client/tls.go index d12594775..f8475acca 100644 --- a/proxy/client/tls.go +++ b/proxy/client/tls.go @@ -20,12 +20,242 @@ import ( type TLSManager struct { TransportManager - helloIDs []utls.ClientHelloID - helloIDLock sync.Mutex - workingHelloID *utls.ClientHelloID - utlsConfig *utls.Config - tlsConfig *tls.Config - config *conf.GlobalConfig + fingerprints []string + workingFingerprint string + fingerprintsLock sync.Mutex + config *conf.GlobalConfig + sessionCache tls.ClientSessionCache +} + +func (m *TLSManager) genClientSpec(name string) (*utls.ClientHelloSpec, error) { + var spec *utls.ClientHelloSpec + switch name { + case "chrome": + spec = &utls.ClientHelloSpec{ + TLSVersMin: utls.VersionTLS10, + TLSVersMax: utls.VersionTLS13, + 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, + utls.TLS_RSA_WITH_3DES_EDE_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.CurveID(utls.GREASE_PLACEHOLDER), + utls.X25519, + utls.CurveP256, + utls.CurveP384, + }}, + &utls.SupportedPointsExtension{SupportedPoints: []byte{ + 0x00, // pointFormatUncompressed + }}, + &utls.SessionTicketExtension{}, + &utls.ALPNExtension{AlpnProtocols: []string{"h2", "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.PKCS1WithSHA1, + }}, + &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.VersionTLS11, + utls.VersionTLS10, + }}, + &utls.FakeCertCompressionAlgsExtension{[]utls.CertCompressionAlgo{ + utls.CertCompressionBrotli, + }}, + &utls.UtlsGREASEExtension{}, + &utls.UtlsPaddingExtension{GetPaddingLen: utls.BoringPaddingStyle}, + }, + } + case "firefox": + spec = &utls.ClientHelloSpec{ + TLSVersMin: utls.VersionTLS10, + TLSVersMax: utls.VersionTLS13, + CipherSuites: []uint16{ + utls.TLS_AES_128_GCM_SHA256, + utls.TLS_CHACHA20_POLY1305_SHA256, + utls.TLS_AES_256_GCM_SHA384, + utls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + utls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + utls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + utls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + utls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + utls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + utls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + utls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + utls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + utls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + utls.FAKE_TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + utls.FAKE_TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + utls.TLS_RSA_WITH_AES_128_CBC_SHA, + utls.TLS_RSA_WITH_AES_256_CBC_SHA, + utls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + }, + CompressionMethods: []byte{ + 0, //compressionNone, + }, + Extensions: []utls.TLSExtension{ + &utls.SNIExtension{}, + &utls.UtlsExtendedMasterSecretExtension{}, + &utls.RenegotiationInfoExtension{Renegotiation: utls.RenegotiateOnceAsClient}, + &utls.SupportedCurvesExtension{[]utls.CurveID{ + utls.X25519, + utls.CurveP256, + utls.CurveP384, + utls.CurveP521, + utls.CurveID(utls.FakeFFDHE2048), + utls.CurveID(utls.FakeFFDHE3072), + }}, + &utls.SupportedPointsExtension{SupportedPoints: []byte{ + 0, //pointFormatUncompressed, + }}, + &utls.SessionTicketExtension{}, + &utls.ALPNExtension{AlpnProtocols: []string{"h2", "http/1.1"}}, + &utls.StatusRequestExtension{}, + &utls.KeyShareExtension{[]utls.KeyShare{ + {Group: utls.X25519}, + {Group: utls.CurveP256}, + }}, + &utls.SupportedVersionsExtension{[]uint16{ + utls.VersionTLS13, + utls.VersionTLS12, + utls.VersionTLS11, + utls.VersionTLS10}}, + &utls.SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []utls.SignatureScheme{ + utls.ECDSAWithP256AndSHA256, + utls.ECDSAWithP384AndSHA384, + utls.ECDSAWithP521AndSHA512, + utls.PSSWithSHA256, + utls.PSSWithSHA384, + utls.PSSWithSHA512, + utls.PKCS1WithSHA256, + utls.PKCS1WithSHA384, + utls.PKCS1WithSHA512, + utls.ECDSAWithSHA1, + utls.PKCS1WithSHA1, + }}, + &utls.PSKKeyExchangeModesExtension{[]uint8{utls.PskModeDHE}}, + &utls.FakeRecordSizeLimitExtension{0x4001}, + &utls.UtlsPaddingExtension{GetPaddingLen: utls.BoringPaddingStyle}, + }, + } + case "ios": + spec = &utls.ClientHelloSpec{ + TLSVersMin: utls.VersionTLS10, + TLSVersMax: utls.VersionTLS13, + CipherSuites: []uint16{ + utls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + utls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + utls.DISABLED_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, + utls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + utls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + utls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + utls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + utls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + utls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + utls.DISABLED_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, + utls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + utls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + utls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + utls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + utls.TLS_RSA_WITH_AES_256_GCM_SHA384, + utls.TLS_RSA_WITH_AES_128_GCM_SHA256, + utls.DISABLED_TLS_RSA_WITH_AES_256_CBC_SHA256, + utls.TLS_RSA_WITH_AES_128_CBC_SHA256, + utls.TLS_RSA_WITH_AES_256_CBC_SHA, + utls.TLS_RSA_WITH_AES_128_CBC_SHA, + 0xc008, + utls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, + utls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + }, + CompressionMethods: []byte{ + 0, //compressionNone, + }, + Extensions: []utls.TLSExtension{ + &utls.RenegotiationInfoExtension{Renegotiation: utls.RenegotiateOnceAsClient}, + &utls.SNIExtension{}, + &utls.UtlsExtendedMasterSecretExtension{}, + &utls.SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []utls.SignatureScheme{ + utls.ECDSAWithP256AndSHA256, + utls.PSSWithSHA256, + utls.PKCS1WithSHA256, + utls.ECDSAWithP384AndSHA384, + utls.ECDSAWithSHA1, + utls.PSSWithSHA384, + utls.PSSWithSHA384, + utls.PKCS1WithSHA384, + utls.PSSWithSHA512, + utls.PKCS1WithSHA512, + utls.PKCS1WithSHA1, + }}, + &utls.StatusRequestExtension{}, + &utls.NPNExtension{}, + &utls.SCTExtension{}, + &utls.ALPNExtension{AlpnProtocols: []string{"h2", "h2-16", "h2-15", "h2-14", "spdy/3.1", "spdy/3", "http/1.1"}}, + &utls.SupportedPointsExtension{SupportedPoints: []byte{ + 0, //pointFormatUncompressed, + }}, + &utls.SupportedCurvesExtension{[]utls.CurveID{ + utls.X25519, + utls.CurveP256, + utls.CurveP384, + utls.CurveP521, + }}, + }, + } + } + if spec == nil { + return nil, common.NewError("Invalid fingerprint:" + name) + } + if m.config.Websocket.Enabled { + for i := range spec.Extensions { + if alpn, ok := spec.Extensions[i].(*utls.ALPNExtension); ok { + alpn.AlpnProtocols = []string{"http/1.1"} + spec.Extensions[i] = alpn + log.Debug("Force http/1.1") + } + } + } + return spec, nil } func (m *TLSManager) printConnInfo(conn net.Conn) { @@ -37,7 +267,7 @@ func (m *TLSManager) printConnInfo(conn net.Conn) { tlsConn := conn.(*tls.Conn) state := tlsConn.ConnectionState() chain := state.VerifiedChains - log.Trace("TLS handshaked", "cipher:", tls.CipherSuiteName(state.CipherSuite), "resume:", state.DidResume) + log.Trace("TLS handshaked", tls.CipherSuiteName(state.CipherSuite), state.DidResume, state.NegotiatedProtocol) for i := range chain { for j := range chain[i] { log.Trace("Subject:", chain[i][j].Subject, "Issuer:", chain[i][j].Issuer) @@ -47,7 +277,7 @@ func (m *TLSManager) printConnInfo(conn net.Conn) { tlsConn := conn.(*utls.UConn) state := tlsConn.ConnectionState() chain := state.VerifiedChains - log.Trace("UTLS handshaked", "cipher:", tls.CipherSuiteName(state.CipherSuite), "resume:", state.DidResume) + log.Trace("uTLS handshaked", tls.CipherSuiteName(state.CipherSuite), state.DidResume, state.NegotiatedProtocol) for i := range chain { for j := range chain[i] { log.Trace("Subject:", chain[i][j].Subject, "Issuer:", chain[i][j].Issuer) @@ -88,54 +318,73 @@ func (m *TLSManager) dialTCP() (net.Conn, error) { } func (m *TLSManager) dialTLSWithFakeFingerprint() (*utls.UConn, error) { - helloIDs := make([]utls.ClientHelloID, len(m.helloIDs)) - copy(helloIDs, m.helloIDs) - rand.Shuffle(len(m.helloIDs), func(i, j int) { - helloIDs[i], helloIDs[j] = helloIDs[j], helloIDs[i] - }) - - m.helloIDLock.Lock() - workingHelloID := m.workingHelloID // keep using same helloID, if it works - m.helloIDLock.Unlock() - if workingHelloID != nil { - helloIDFound := false - for i, ID := range helloIDs { - if ID == *workingHelloID { - helloIDs[i] = helloIDs[0] - helloIDs[0] = *workingHelloID // push working hello ID first - helloIDFound = true - break - } + m.fingerprintsLock.Lock() + workingFingerprint := m.workingFingerprint + m.fingerprintsLock.Unlock() + + utlsConfig := &utls.Config{ + RootCAs: m.config.TLS.CertPool, + ServerName: m.config.TLS.SNI, + InsecureSkipVerify: !m.config.TLS.Verify, + } + if workingFingerprint != "" { + spec, err := m.genClientSpec(workingFingerprint) + if err != nil { + return nil, err + } + tcpConn, err := m.dialTCP() + if err != nil { + return nil, err // on tcp Dial failure return with error right away } - if !helloIDFound { - helloIDs = append([]utls.ClientHelloID{*workingHelloID}, helloIDs...) - helloIDs[0], helloIDs[len(helloIDs)-1] = helloIDs[len(helloIDs)-1], helloIDs[0] + tlsConn := utls.UClient(tcpConn, utlsConfig, utls.HelloCustom) + if err := tlsConn.ApplyPreset(spec); err != nil { + m.fingerprintsLock.Lock() + workingFingerprint = "" + m.fingerprintsLock.Unlock() + log.Error(common.NewError("Failed to set working fingerprint").Base(err)) + } else { + protocol.SetRandomizedTimeout(tlsConn) + err = tlsConn.Handshake() + protocol.CancelTimeout(tlsConn) + if err != nil { + log.Debug("Working hello id failed, err:", err) + } else { + return tlsConn, nil + } } } - for _, helloID := range helloIDs { + + for _, name := range m.fingerprints { + spec, err := m.genClientSpec(name) + if err != nil { + return nil, err + } + tcpConn, err := m.dialTCP() if err != nil { return nil, err // on tcp Dial failure return with error right away } - client := utls.UClient(tcpConn, m.utlsConfig, helloID) - if m.config.Websocket.Enabled { - // HACK disable alpn (http/1.1, h2) to support websocket - client.HandshakeState.Hello.AlpnProtocols = []string{} + tlsConn := utls.UClient(tcpConn, utlsConfig, utls.HelloCustom) + + if err := tlsConn.ApplyPreset(spec); err != nil { + log.Error(common.NewError("Failed to set " + name + " fingerprint").Base(err)) + continue } - protocol.SetRandomizedTimeout(client) - err = client.Handshake() - protocol.CancelTimeout(client) + + protocol.SetRandomizedTimeout(tlsConn) + err = tlsConn.Handshake() + protocol.CancelTimeout(tlsConn) if err != nil { - log.Debug("hello id", helloID.Str(), "failed, err:", err) - continue // on tls Dial error keep trying HelloIDs + log.Debug("Fingerprint", name, "failed, err:", err) + continue // on tls Dial error keep trying } - log.Debug("found avaliable hello id:", helloID.Str()) - m.helloIDLock.Lock() - m.workingHelloID = &client.ClientHelloID - m.helloIDLock.Unlock() - return client, err + log.Debug("Avaliable hello id found:", name) + m.fingerprintsLock.Lock() + m.workingFingerprint = name + m.fingerprintsLock.Unlock() + return tlsConn, err } return nil, common.NewError("All client hello IDs tried but failed") } @@ -156,7 +405,17 @@ func (m *TLSManager) DialToServer() (io.ReadWriteCloser, error) { if err != nil { return nil, err } - tlsConn := tls.Client(tcpConn, m.tlsConfig) + tlsConfig := &tls.Config{ + CipherSuites: m.config.TLS.CipherSuites, + RootCAs: m.config.TLS.CertPool, + ServerName: m.config.TLS.SNI, + InsecureSkipVerify: !m.config.TLS.Verify, + SessionTicketsDisabled: !m.config.TLS.SessionTicket, + CurvePreferences: m.config.TLS.CurvePreferences, + NextProtos: m.config.TLS.ALPN, + ClientSessionCache: m.sessionCache, + } + tlsConn := tls.Client(tcpConn, tlsConfig) err = tlsConn.Handshake() if err != nil { return nil, err @@ -176,51 +435,18 @@ func (m *TLSManager) DialToServer() (io.ReadWriteCloser, error) { } func NewTLSManager(config *conf.GlobalConfig) *TLSManager { - utlsConfig := &utls.Config{ - RootCAs: config.TLS.CertPool, - ServerName: config.TLS.SNI, - InsecureSkipVerify: !config.TLS.Verify, - } - tlsConfig := &tls.Config{ - CipherSuites: config.TLS.CipherSuites, - RootCAs: config.TLS.CertPool, - ServerName: config.TLS.SNI, - InsecureSkipVerify: !config.TLS.Verify, - SessionTicketsDisabled: !config.TLS.SessionTicket, - CurvePreferences: config.TLS.CurvePreferences, - NextProtos: config.TLS.ALPN, - ClientSessionCache: tls.NewLRUClientSessionCache(192), - } - m := &TLSManager{ - config: config, - utlsConfig: utlsConfig, - tlsConfig: tlsConfig, + config: config, } + if config.TLS.Fingerprint != "" { + m.fingerprints = []string{config.TLS.Fingerprint} + } if config.TLS.Fingerprint == "auto" { - m.helloIDs = []utls.ClientHelloID{ - utls.HelloChrome_Auto, - utls.HelloFirefox_Auto, - utls.HelloIOS_Auto, - utls.HelloRandomizedNoALPN, - } - } else if config.TLS.Fingerprint != "" { - table := map[string]*utls.ClientHelloID{ - "chrome": &utls.HelloChrome_Auto, - "firefox": &utls.HelloFirefox_Auto, - "ios": &utls.HelloIOS_Auto, - "randomized": &utls.HelloRandomizedNoALPN, - } - id, found := table[config.TLS.Fingerprint] - if found { - log.Debug("TLS fingerprint loaded:", id.Str()) - m.helloIDs = []utls.ClientHelloID{*id} - } else { - log.Warn("Invalid TLS fingerprint:", config.TLS.Fingerprint, ", using default fingerprint") - config.TLS.Fingerprint = "" - } + m.fingerprints = []string{"chrome", "firefox", "ios"} + rand.Shuffle(len(m.fingerprints), func(i, j int) { + m.fingerprints[i], m.fingerprints[j] = m.fingerprints[j], m.fingerprints[i] + }) } - return m }