diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index 610e252e..f0b067d8 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -379,6 +379,9 @@ func (c *TLSConfig) Build() (proto.Message, error) { config.CipherSuites = c.CipherSuites config.PreferServerCipherSuites = c.PreferServerCipherSuites config.Fingerprint = strings.ToLower(c.Fingerprint) + if config.Fingerprint != "" && tls.GetFingerprint(config.Fingerprint) == nil { + return nil, newError(`unknown fingerprint: `, config.Fingerprint) + } config.RejectUnknownSni = c.RejectUnknownSNI if c.PinnedPeerCertificateChainSha256 != nil { diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go index 04f81e34..afc270bd 100644 --- a/transport/internet/grpc/dial.go +++ b/transport/internet/grpc/dial.go @@ -122,7 +122,7 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in if tlsConfig != nil { var transportCredential credentials.TransportCredentials - if fingerprint, exists := tls.GetFingerprint(ctx, tlsConfig.Fingerprint); exists { + if fingerprint := tls.GetFingerprint(tlsConfig.Fingerprint); fingerprint != nil { transportCredential = tls.NewGrpcUtls(tlsConfig.GetTLSConfig(), fingerprint) } else { // Fallback to normal gRPC TLS transportCredential = credentials.NewTLS(tlsConfig.GetTLSConfig()) diff --git a/transport/internet/http/dialer.go b/transport/internet/http/dialer.go index 6fef71c5..a192bddd 100644 --- a/transport/internet/http/dialer.go +++ b/transport/internet/http/dialer.go @@ -75,7 +75,7 @@ func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *in } var cn tls.Interface - if fingerprint, ok := tls.GetFingerprint(ctx, tlsConfigs.Fingerprint); ok { + if fingerprint := tls.GetFingerprint(tlsConfigs.Fingerprint); fingerprint != nil { cn = tls.UClient(pconn, tlsConfig, fingerprint).(*tls.UConn) } else { cn = tls.Client(pconn, tlsConfig).(*tls.Conn) diff --git a/transport/internet/tcp/dialer.go b/transport/internet/tcp/dialer.go index b08fd4b2..5606cd8d 100644 --- a/transport/internet/tcp/dialer.go +++ b/transport/internet/tcp/dialer.go @@ -22,7 +22,7 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me if config := tls.ConfigFromStreamSettings(streamSettings); config != nil { tlsConfig := config.GetTLSConfig(tls.WithDestination(dest)) - if fingerprint, ok := tls.GetFingerprint(ctx, config.Fingerprint); ok { + if fingerprint := tls.GetFingerprint(config.Fingerprint); fingerprint != nil { conn = tls.UClient(conn, tlsConfig, fingerprint) if err := conn.(*tls.UConn).Handshake(); err != nil { return nil, err diff --git a/transport/internet/tls/tls.go b/transport/internet/tls/tls.go index 728480f8..b3cde801 100644 --- a/transport/internet/tls/tls.go +++ b/transport/internet/tls/tls.go @@ -1,7 +1,6 @@ package tls import ( - "context" "crypto/rand" "crypto/tls" "math/big" @@ -9,15 +8,12 @@ import ( utls "github.com/refraction-networking/utls" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/net" - "github.com/xtls/xray-core/common/session" ) //go:generate go run github.com/xtls/xray-core/common/errors/errorgen var _ buf.Writer = (*Conn)(nil) -var XrayRandom *utls.ClientHelloID - type Conn struct { *tls.Conn } @@ -118,58 +114,51 @@ func copyConfig(c *tls.Config) *utls.Config { } } -func GetFingerprint(ctx context.Context, config string) (*utls.ClientHelloID, bool) { - if XrayRandom == nil { - // lazy init - for k, v := range FingerprintsForRNG { - Fingerprints[k] = v - } - big, err := rand.Int(rand.Reader, big.NewInt(int64(len(FingerprintsForRNG)))) - if err != nil { - newError("failed to generate xray random fingerprint").Base(err).WriteToLog(session.ExportIDToError(ctx)) - } - var i = int(big.Int64()) - count := 0 - for k, v := range FingerprintsForRNG { - if count == i { - newError("xray random fingerprint: ", k).WriteToLog(session.ExportIDToError(ctx)) - XrayRandom = v - break - } - count++ +func init() { + bigInt, _ := rand.Int(rand.Reader, big.NewInt(int64(len(ModernFingerprints)))) + stopAt := int(bigInt.Int64()) + i := 0 + for _, v := range ModernFingerprints { + if i == stopAt { + PresetFingerprints["random"] = v + break } + i++ } - if config == "random" { - return XrayRandom, true - } - fingerprint, ok := Fingerprints[config] - return fingerprint, ok } -var Fingerprints = map[string]*utls.ClientHelloID{ +func GetFingerprint(name string) (fingerprint *utls.ClientHelloID) { + if name == "" { + return + } + if fingerprint = PresetFingerprints[name]; fingerprint != nil { + return + } + if fingerprint = ModernFingerprints[name]; fingerprint != nil { + return + } + if fingerprint = OtherFingerprints[name]; fingerprint != nil { + return + } + return +} + +var PresetFingerprints = map[string]*utls.ClientHelloID{ + // Recommended preset options in GUI clients "chrome": &utls.HelloChrome_Auto, "firefox": &utls.HelloFirefox_Auto, "safari": &utls.HelloSafari_Auto, + "ios": &utls.HelloIOS_Auto, + "android": &utls.HelloAndroid_11_OkHttp, + "edge": &utls.HelloEdge_Auto, + "360": &utls.Hello360_Auto, + "qq": &utls.HelloQQ_Auto, + "random": nil, "randomized": &utls.HelloRandomized, - // This is a bit lame, but it seems there is no good way to reflect variables from Golang package - // We don't RNG for go, randomized, or fingerprints that is more than 4 years old - "hellogolang": &utls.HelloGolang, - "hellorandomized": &utls.HelloRandomized, - "hellorandomizedalpn": &utls.HelloRandomizedALPN, - "hellorandomizednoalpn": &utls.HelloRandomizedNoALPN, - "hellofirefox_55": &utls.HelloFirefox_55, - "hellofirefox_56": &utls.HelloFirefox_56, - "hellofirefox_63": &utls.HelloFirefox_63, - "hellofirefox_65": &utls.HelloFirefox_65, - "hellochrome_58": &utls.HelloChrome_58, - "hellochrome_62": &utls.HelloChrome_62, - "hellochrome_70": &utls.HelloChrome_70, - "hellochrome_72": &utls.HelloChrome_72, - "helloios_11_1": &utls.HelloIOS_11_1, - "hello360_7_5": &utls.Hello360_7_5, } -var FingerprintsForRNG = map[string]*utls.ClientHelloID{ +var ModernFingerprints = map[string]*utls.ClientHelloID{ + // One of these will be chosen as `random` at startup "hellofirefox_auto": &utls.HelloFirefox_Auto, "hellofirefox_99": &utls.HelloFirefox_99, "hellofirefox_102": &utls.HelloFirefox_102, @@ -197,6 +186,24 @@ var FingerprintsForRNG = map[string]*utls.ClientHelloID{ "helloqq_11_1": &utls.HelloQQ_11_1, } +var OtherFingerprints = map[string]*utls.ClientHelloID{ + // Golang, randomized, and fingerprints that are more than 4 years old + "hellogolang": &utls.HelloGolang, + "hellorandomized": &utls.HelloRandomized, + "hellorandomizedalpn": &utls.HelloRandomizedALPN, + "hellorandomizednoalpn": &utls.HelloRandomizedNoALPN, + "hellofirefox_55": &utls.HelloFirefox_55, + "hellofirefox_56": &utls.HelloFirefox_56, + "hellofirefox_63": &utls.HelloFirefox_63, + "hellofirefox_65": &utls.HelloFirefox_65, + "hellochrome_58": &utls.HelloChrome_58, + "hellochrome_62": &utls.HelloChrome_62, + "hellochrome_70": &utls.HelloChrome_70, + "hellochrome_72": &utls.HelloChrome_72, + "helloios_11_1": &utls.HelloIOS_11_1, + "hello360_7_5": &utls.Hello360_7_5, +} + type Interface interface { net.Conn Handshake() error diff --git a/transport/internet/websocket/dialer.go b/transport/internet/websocket/dialer.go index a0ac6811..5017cb50 100644 --- a/transport/internet/websocket/dialer.go +++ b/transport/internet/websocket/dialer.go @@ -86,7 +86,7 @@ func dialWebSocket(ctx context.Context, dest net.Destination, streamSettings *in protocol = "wss" tlsConfig := config.GetTLSConfig(tls.WithDestination(dest), tls.WithNextProto("http/1.1")) dialer.TLSClientConfig = tlsConfig - if fingerprint, exists := tls.GetFingerprint(ctx, config.Fingerprint); exists { + if fingerprint := tls.GetFingerprint(config.Fingerprint); fingerprint != nil { dialer.NetDialTLSContext = func(_ context.Context, _, addr string) (gonet.Conn, error) { // Like the NetDial in the dialer pconn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)