chore: cleanup tls clientFingerprint code

This commit is contained in:
wwqgtxx 2025-04-29 21:15:48 +08:00
parent 936df90ace
commit ee5d77cfd1
8 changed files with 193 additions and 116 deletions

102
common/once/oncefunc.go Normal file
View File

@ -0,0 +1,102 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package once
import "sync"
// OnceFunc returns a function that invokes f only once. The returned function
// may be called concurrently.
//
// If f panics, the returned function will panic with the same value on every call.
func OnceFunc(f func()) func() {
var (
once sync.Once
valid bool
p any
)
// Construct the inner closure just once to reduce costs on the fast path.
g := func() {
defer func() {
p = recover()
if !valid {
// Re-panic immediately so on the first call the user gets a
// complete stack trace into f.
panic(p)
}
}()
f()
f = nil // Do not keep f alive after invoking it.
valid = true // Set only if f does not panic.
}
return func() {
once.Do(g)
if !valid {
panic(p)
}
}
}
// OnceValue returns a function that invokes f only once and returns the value
// returned by f. The returned function may be called concurrently.
//
// If f panics, the returned function will panic with the same value on every call.
func OnceValue[T any](f func() T) func() T {
var (
once sync.Once
valid bool
p any
result T
)
g := func() {
defer func() {
p = recover()
if !valid {
panic(p)
}
}()
result = f()
f = nil
valid = true
}
return func() T {
once.Do(g)
if !valid {
panic(p)
}
return result
}
}
// OnceValues returns a function that invokes f only once and returns the values
// returned by f. The returned function may be called concurrently.
//
// If f panics, the returned function will panic with the same value on every call.
func OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) {
var (
once sync.Once
valid bool
p any
r1 T1
r2 T2
)
g := func() {
defer func() {
p = recover()
if !valid {
panic(p)
}
}()
r1, r2 = f()
f = nil
valid = true
}
return func() (T1, T2) {
once.Do(g)
if !valid {
panic(p)
}
return r1, r2
}
}

View File

@ -37,9 +37,8 @@ type RealityConfig struct {
ShortID [RealityMaxShortIDLen]byte ShortID [RealityMaxShortIDLen]byte
} }
func GetRealityConn(ctx context.Context, conn net.Conn, clientFingerprint string, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) { func GetRealityConn(ctx context.Context, conn net.Conn, fingerprint UClientHelloID, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) {
retry := 0 for retry := 0; ; retry++ {
for fingerprint, exists := GetFingerprint(clientFingerprint); exists; retry++ {
verifier := &realityVerifier{ verifier := &realityVerifier{
serverName: tlsConfig.ServerName, serverName: tlsConfig.ServerName,
} }
@ -151,7 +150,6 @@ func GetRealityConn(ctx context.Context, conn net.Conn, clientFingerprint string
return uConn, nil return uConn, nil
} }
return nil, errors.New("unknown uTLS fingerprint")
} }
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) { func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {

View File

@ -4,6 +4,7 @@ import (
"crypto/tls" "crypto/tls"
"net" "net"
"github.com/metacubex/mihomo/common/once"
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
@ -11,46 +12,44 @@ import (
"github.com/mroth/weightedrand/v2" "github.com/mroth/weightedrand/v2"
) )
type Conn = utls.Conn
type UConn = utls.UConn type UConn = utls.UConn
type UClientHelloID = utls.ClientHelloID
const VersionTLS13 = utls.VersionTLS13 const VersionTLS13 = utls.VersionTLS13
type UClientHelloID struct { func Client(c net.Conn, config *utls.Config) *Conn {
*utls.ClientHelloID return utls.Client(c, config)
} }
var initRandomFingerprint UClientHelloID
var initUtlsClient string
func UClient(c net.Conn, config *utls.Config, fingerprint UClientHelloID) *UConn { func UClient(c net.Conn, config *utls.Config, fingerprint UClientHelloID) *UConn {
return utls.UClient(c, config, *fingerprint.ClientHelloID) return utls.UClient(c, config, fingerprint)
} }
func GetFingerprint(ClientFingerprint string) (UClientHelloID, bool) { func GetFingerprint(clientFingerprint string) (UClientHelloID, bool) {
if ClientFingerprint == "none" { if len(clientFingerprint) == 0 {
clientFingerprint = globalFingerprint
}
if len(clientFingerprint) == 0 || clientFingerprint == "none" {
return UClientHelloID{}, false return UClientHelloID{}, false
} }
if initRandomFingerprint.ClientHelloID == nil { if clientFingerprint == "random" {
initRandomFingerprint, _ = RollFingerprint() fingerprint := randomFingerprint()
log.Debugln("use initial random HelloID:%s", fingerprint.Client)
return fingerprint, true
} }
if ClientFingerprint == "random" { if fingerprint, ok := fingerprints[clientFingerprint]; ok {
log.Debugln("use initial random HelloID:%s", initRandomFingerprint.Client)
return initRandomFingerprint, true
}
fingerprint, ok := Fingerprints[ClientFingerprint]
if ok {
log.Debugln("use specified fingerprint:%s", fingerprint.Client) log.Debugln("use specified fingerprint:%s", fingerprint.Client)
return fingerprint, ok return fingerprint, true
} else { } else {
log.Warnln("wrong ClientFingerprint:%s", ClientFingerprint) log.Warnln("wrong clientFingerprint:%s", clientFingerprint)
return UClientHelloID{}, false return UClientHelloID{}, false
} }
} }
func RollFingerprint() (UClientHelloID, bool) { var randomFingerprint = once.OnceValue(func() UClientHelloID {
chooser, _ := weightedrand.NewChooser( chooser, _ := weightedrand.NewChooser(
weightedrand.NewChoice("chrome", 6), weightedrand.NewChoice("chrome", 6),
weightedrand.NewChoice("safari", 3), weightedrand.NewChoice("safari", 3),
@ -59,26 +58,29 @@ func RollFingerprint() (UClientHelloID, bool) {
) )
initClient := chooser.Pick() initClient := chooser.Pick()
log.Debugln("initial random HelloID:%s", initClient) log.Debugln("initial random HelloID:%s", initClient)
fingerprint, ok := Fingerprints[initClient] fingerprint, ok := fingerprints[initClient]
return fingerprint, ok if !ok {
} log.Warnln("error in initial random HelloID:%s", initClient)
}
return fingerprint
})
var Fingerprints = map[string]UClientHelloID{ var fingerprints = map[string]UClientHelloID{
"chrome": {&utls.HelloChrome_Auto}, "chrome": utls.HelloChrome_Auto,
"chrome_psk": {&utls.HelloChrome_100_PSK}, "chrome_psk": utls.HelloChrome_100_PSK,
"chrome_psk_shuffle": {&utls.HelloChrome_106_Shuffle}, "chrome_psk_shuffle": utls.HelloChrome_106_Shuffle,
"chrome_padding_psk_shuffle": {&utls.HelloChrome_114_Padding_PSK_Shuf}, "chrome_padding_psk_shuffle": utls.HelloChrome_114_Padding_PSK_Shuf,
"chrome_pq": {&utls.HelloChrome_115_PQ}, "chrome_pq": utls.HelloChrome_115_PQ,
"chrome_pq_psk": {&utls.HelloChrome_115_PQ_PSK}, "chrome_pq_psk": utls.HelloChrome_115_PQ_PSK,
"firefox": {&utls.HelloFirefox_Auto}, "firefox": utls.HelloFirefox_Auto,
"safari": {&utls.HelloSafari_Auto}, "safari": utls.HelloSafari_Auto,
"ios": {&utls.HelloIOS_Auto}, "ios": utls.HelloIOS_Auto,
"android": {&utls.HelloAndroid_11_OkHttp}, "android": utls.HelloAndroid_11_OkHttp,
"edge": {&utls.HelloEdge_Auto}, "edge": utls.HelloEdge_Auto,
"360": {&utls.Hello360_Auto}, "360": utls.Hello360_Auto,
"qq": {&utls.HelloQQ_Auto}, "qq": utls.HelloQQ_Auto,
"random": {nil}, "random": {},
"randomized": {nil}, "randomized": utls.HelloRandomized,
} }
func init() { func init() {
@ -88,7 +90,7 @@ func init() {
randomized := utls.HelloRandomized randomized := utls.HelloRandomized
randomized.Seed, _ = utls.NewPRNGSeed() randomized.Seed, _ = utls.NewPRNGSeed()
randomized.Weights = &weights randomized.Weights = &weights
Fingerprints["randomized"] = UClientHelloID{&randomized} fingerprints["randomized"] = randomized
} }
func UCertificates(it tls.Certificate) utls.Certificate { func UCertificates(it tls.Certificate) utls.Certificate {
@ -154,14 +156,12 @@ func BuildWebsocketHandshakeState(c *UConn) error {
return nil return nil
} }
func SetGlobalUtlsClient(Client string) { var globalFingerprint string
initUtlsClient = Client
}
func HaveGlobalFingerprint() bool { func SetGlobalFingerprint(fingerprint string) {
return len(initUtlsClient) != 0 && initUtlsClient != "none" globalFingerprint = fingerprint
} }
func GetGlobalFingerprint() string { func GetGlobalFingerprint() string {
return initUtlsClient return globalFingerprint
} }

View File

@ -454,7 +454,7 @@ func updateGeneral(general *config.General, logging bool) {
mihomoHttp.SetUA(general.GlobalUA) mihomoHttp.SetUA(general.GlobalUA)
resource.SetETag(general.ETagSupport) resource.SetETag(general.ETagSupport)
tlsC.SetGlobalUtlsClient(general.GlobalClientFingerprint) tlsC.SetGlobalFingerprint(general.GlobalClientFingerprint)
} }
func updateUsers(users []auth.AuthUser) { func updateUsers(users []auth.AuthUser) {

View File

@ -237,25 +237,19 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, clientFingerprint stri
return pconn, nil return pconn, nil
} }
clientFingerprint := clientFingerprint if clientFingerprint, ok := tlsC.GetFingerprint(clientFingerprint); ok {
if tlsC.HaveGlobalFingerprint() && len(clientFingerprint) == 0 {
clientFingerprint = tlsC.GetGlobalFingerprint()
}
if len(clientFingerprint) != 0 {
if realityConfig == nil { if realityConfig == nil {
if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists { tlsConn := tlsC.UClient(pconn, tlsC.UConfig(cfg), clientFingerprint)
utlsConn := tlsC.UClient(pconn, tlsC.UConfig(cfg), fingerprint) if err := tlsConn.HandshakeContext(ctx); err != nil {
if err := utlsConn.HandshakeContext(ctx); err != nil {
pconn.Close() pconn.Close()
return nil, err return nil, err
} }
state := utlsConn.ConnectionState() state := tlsConn.ConnectionState()
if p := state.NegotiatedProtocol; p != http2.NextProtoTLS { if p := state.NegotiatedProtocol; p != http2.NextProtoTLS {
utlsConn.Close() tlsConn.Close()
return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS) return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS)
} }
return utlsConn, nil return tlsConn, nil
}
} else { } else {
realityConn, err := tlsC.GetRealityConn(ctx, pconn, clientFingerprint, cfg, realityConfig) realityConn, err := tlsC.GetRealityConn(ctx, pconn, clientFingerprint, cfg, realityConfig)
if err != nil { if err != nil {

View File

@ -10,7 +10,6 @@ import (
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"github.com/metacubex/sing-shadowtls" "github.com/metacubex/sing-shadowtls"
utls "github.com/metacubex/utls"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
) )
@ -67,16 +66,12 @@ func uTLSHandshakeFunc(config *tls.Config, clientFingerprint string) shadowtls.T
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 := tlsC.UConfig(config) tlsConfig := tlsC.UConfig(config)
tlsConfig.SessionIDGenerator = sessionIDGenerator tlsConfig.SessionIDGenerator = sessionIDGenerator
clientFingerprint := clientFingerprint
if tlsC.HaveGlobalFingerprint() && len(clientFingerprint) == 0 {
clientFingerprint = tlsC.GetGlobalFingerprint()
}
if config.MaxVersion == tls.VersionTLS12 { // for ShadowTLS v1 if config.MaxVersion == tls.VersionTLS12 { // for ShadowTLS v1
clientFingerprint = "" tlsConn := tlsC.Client(conn, tlsConfig)
return tlsConn.HandshakeContext(ctx)
} }
if len(clientFingerprint) != 0 { if clientFingerprint, ok := tlsC.GetFingerprint(clientFingerprint); ok {
if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists { tlsConn := tlsC.UClient(conn, tlsConfig, clientFingerprint)
tlsConn := tlsC.UClient(conn, tlsConfig, fingerprint)
if slices.Equal(tlsConfig.NextProtos, WsALPN) { if slices.Equal(tlsConfig.NextProtos, WsALPN) {
err := tlsC.BuildWebsocketHandshakeState(tlsConn) err := tlsC.BuildWebsocketHandshakeState(tlsConn)
if err != nil { if err != nil {
@ -85,8 +80,7 @@ func uTLSHandshakeFunc(config *tls.Config, clientFingerprint string) shadowtls.T
} }
return tlsConn.HandshakeContext(ctx) return tlsConn.HandshakeContext(ctx)
} }
} tlsConn := tlsC.Client(conn, tlsConfig)
tlsConn := utls.Client(conn, tlsConfig)
return tlsConn.HandshakeContext(ctx) return tlsConn.HandshakeContext(ctx)
} }
} }

View File

@ -32,20 +32,14 @@ func StreamTLSConn(ctx context.Context, conn net.Conn, cfg *TLSConfig) (net.Conn
return nil, err return nil, err
} }
clientFingerprint := cfg.ClientFingerprint if clientFingerprint, ok := tlsC.GetFingerprint(cfg.ClientFingerprint); ok {
if tlsC.HaveGlobalFingerprint() && len(clientFingerprint) == 0 {
clientFingerprint = tlsC.GetGlobalFingerprint()
}
if len(clientFingerprint) != 0 {
if cfg.Reality == nil { if cfg.Reality == nil {
if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists { tlsConn := tlsC.UClient(conn, tlsC.UConfig(tlsConfig), clientFingerprint)
utlsConn := tlsC.UClient(conn, tlsC.UConfig(tlsConfig), fingerprint) err = tlsConn.HandshakeContext(ctx)
err = utlsConn.HandshakeContext(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return utlsConn, nil return tlsConn, nil
}
} else { } else {
return tlsC.GetRealityConn(ctx, conn, clientFingerprint, tlsConfig, cfg.Reality) return tlsC.GetRealityConn(ctx, conn, clientFingerprint, tlsConfig, cfg.Reality)
} }

View File

@ -351,31 +351,26 @@ func streamWebsocketConn(ctx context.Context, conn net.Conn, c *WebsocketConfig,
} }
if config.ServerName == "" && !config.InsecureSkipVerify { // users must set either ServerName or InsecureSkipVerify in the config. if config.ServerName == "" && !config.InsecureSkipVerify { // users must set either ServerName or InsecureSkipVerify in the config.
config = config.Clone() config = config.Clone()
config.ServerName = uri.Host config.ServerName = c.Host
} }
clientFingerprint := c.ClientFingerprint if clientFingerprint, ok := tlsC.GetFingerprint(c.ClientFingerprint); ok {
if tlsC.HaveGlobalFingerprint() && len(clientFingerprint) == 0 { tlsConn := tlsC.UClient(conn, tlsC.UConfig(config), clientFingerprint)
clientFingerprint = tlsC.GetGlobalFingerprint() if err = tlsC.BuildWebsocketHandshakeState(tlsConn); err != nil {
}
if len(clientFingerprint) != 0 {
if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists {
utlsConn := tlsC.UClient(conn, tlsC.UConfig(config), fingerprint)
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 err = tlsConn.HandshakeContext(ctx)
} if err != nil {
} else {
conn = tls.Client(conn, config)
}
if tlsConn, ok := conn.(interface {
HandshakeContext(ctx context.Context) error
}); ok {
if err = tlsConn.HandshakeContext(ctx); err != nil {
return nil, err return nil, err
} }
conn = tlsConn
} else {
tlsConn := tls.Client(conn, config)
err = tlsConn.HandshakeContext(ctx)
if err != nil {
return nil, err
}
conn = tlsConn
} }
} }