github.com/kelleygo/clashcore@v1.0.2/component/tls/utls.go (about) 1 package tls 2 3 import ( 4 "crypto/tls" 5 "net" 6 7 "github.com/kelleygo/clashcore/log" 8 9 "github.com/mroth/weightedrand/v2" 10 utls "github.com/sagernet/utls" 11 ) 12 13 type UConn struct { 14 *utls.UConn 15 } 16 17 type UClientHelloID struct { 18 *utls.ClientHelloID 19 } 20 21 var initRandomFingerprint UClientHelloID 22 var initUtlsClient string 23 24 func UClient(c net.Conn, config *tls.Config, fingerprint UClientHelloID) *UConn { 25 utlsConn := utls.UClient(c, copyConfig(config), utls.ClientHelloID{ 26 Client: fingerprint.Client, 27 Version: fingerprint.Version, 28 Seed: fingerprint.Seed, 29 }) 30 return &UConn{UConn: utlsConn} 31 } 32 33 func GetFingerprint(ClientFingerprint string) (UClientHelloID, bool) { 34 if ClientFingerprint == "none" { 35 return UClientHelloID{}, false 36 } 37 38 if initRandomFingerprint.ClientHelloID == nil { 39 initRandomFingerprint, _ = RollFingerprint() 40 } 41 42 if ClientFingerprint == "random" { 43 log.Debugln("use initial random HelloID:%s", initRandomFingerprint.Client) 44 return initRandomFingerprint, true 45 } 46 47 fingerprint, ok := Fingerprints[ClientFingerprint] 48 if ok { 49 log.Debugln("use specified fingerprint:%s", fingerprint.Client) 50 return fingerprint, ok 51 } else { 52 log.Warnln("wrong ClientFingerprint:%s", ClientFingerprint) 53 return UClientHelloID{}, false 54 } 55 } 56 57 func RollFingerprint() (UClientHelloID, bool) { 58 chooser, _ := weightedrand.NewChooser( 59 weightedrand.NewChoice("chrome", 6), 60 weightedrand.NewChoice("safari", 3), 61 weightedrand.NewChoice("ios", 2), 62 weightedrand.NewChoice("firefox", 1), 63 ) 64 initClient := chooser.Pick() 65 log.Debugln("initial random HelloID:%s", initClient) 66 fingerprint, ok := Fingerprints[initClient] 67 return fingerprint, ok 68 } 69 70 var Fingerprints = map[string]UClientHelloID{ 71 "chrome": {&utls.HelloChrome_Auto}, 72 "chrome_psk": {&utls.HelloChrome_100_PSK}, 73 "chrome_psk_shuffle": {&utls.HelloChrome_106_Shuffle}, 74 "chrome_padding_psk_shuffle": {&utls.HelloChrome_114_Padding_PSK_Shuf}, 75 "chrome_pq": {&utls.HelloChrome_115_PQ}, 76 "chrome_pq_psk": {&utls.HelloChrome_115_PQ_PSK}, 77 "firefox": {&utls.HelloFirefox_Auto}, 78 "safari": {&utls.HelloSafari_Auto}, 79 "ios": {&utls.HelloIOS_Auto}, 80 "android": {&utls.HelloAndroid_11_OkHttp}, 81 "edge": {&utls.HelloEdge_Auto}, 82 "360": {&utls.Hello360_Auto}, 83 "qq": {&utls.HelloQQ_Auto}, 84 "random": {nil}, 85 "randomized": {nil}, 86 } 87 88 func init() { 89 weights := utls.DefaultWeights 90 weights.TLSVersMax_Set_VersionTLS13 = 1 91 weights.FirstKeyShare_Set_CurveP256 = 0 92 randomized := utls.HelloRandomized 93 randomized.Seed, _ = utls.NewPRNGSeed() 94 randomized.Weights = &weights 95 Fingerprints["randomized"] = UClientHelloID{&randomized} 96 } 97 98 func copyConfig(c *tls.Config) *utls.Config { 99 return &utls.Config{ 100 RootCAs: c.RootCAs, 101 ServerName: c.ServerName, 102 InsecureSkipVerify: c.InsecureSkipVerify, 103 VerifyPeerCertificate: c.VerifyPeerCertificate, 104 } 105 } 106 107 // BuildWebsocketHandshakeState it will only send http/1.1 in its ALPN. 108 // Copy from https://github.com/XTLS/Xray-core/blob/main/transport/internet/tls/tls.go 109 func (c *UConn) BuildWebsocketHandshakeState() error { 110 // Build the handshake state. This will apply every variable of the TLS of the 111 // fingerprint in the UConn 112 if err := c.BuildHandshakeState(); err != nil { 113 return err 114 } 115 // Iterate over extensions and check for utls.ALPNExtension 116 hasALPNExtension := false 117 for _, extension := range c.Extensions { 118 if alpn, ok := extension.(*utls.ALPNExtension); ok { 119 hasALPNExtension = true 120 alpn.AlpnProtocols = []string{"http/1.1"} 121 break 122 } 123 } 124 if !hasALPNExtension { // Append extension if doesn't exists 125 c.Extensions = append(c.Extensions, &utls.ALPNExtension{AlpnProtocols: []string{"http/1.1"}}) 126 } 127 // Rebuild the client hello 128 if err := c.BuildHandshakeState(); err != nil { 129 return err 130 } 131 return nil 132 } 133 134 func SetGlobalUtlsClient(Client string) { 135 initUtlsClient = Client 136 } 137 138 func HaveGlobalFingerprint() bool { 139 return len(initUtlsClient) != 0 && initUtlsClient != "none" 140 } 141 142 func GetGlobalFingerprint() string { 143 return initUtlsClient 144 }