Skip to content

Commit ee5d77c

Browse files
committed
chore: cleanup tls clientFingerprint code
1 parent 936df90 commit ee5d77c

File tree

8 files changed

+193
-116
lines changed

8 files changed

+193
-116
lines changed

common/once/oncefunc.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package once
6+
7+
import "sync"
8+
9+
// OnceFunc returns a function that invokes f only once. The returned function
10+
// may be called concurrently.
11+
//
12+
// If f panics, the returned function will panic with the same value on every call.
13+
func OnceFunc(f func()) func() {
14+
var (
15+
once sync.Once
16+
valid bool
17+
p any
18+
)
19+
// Construct the inner closure just once to reduce costs on the fast path.
20+
g := func() {
21+
defer func() {
22+
p = recover()
23+
if !valid {
24+
// Re-panic immediately so on the first call the user gets a
25+
// complete stack trace into f.
26+
panic(p)
27+
}
28+
}()
29+
f()
30+
f = nil // Do not keep f alive after invoking it.
31+
valid = true // Set only if f does not panic.
32+
}
33+
return func() {
34+
once.Do(g)
35+
if !valid {
36+
panic(p)
37+
}
38+
}
39+
}
40+
41+
// OnceValue returns a function that invokes f only once and returns the value
42+
// returned by f. The returned function may be called concurrently.
43+
//
44+
// If f panics, the returned function will panic with the same value on every call.
45+
func OnceValue[T any](f func() T) func() T {
46+
var (
47+
once sync.Once
48+
valid bool
49+
p any
50+
result T
51+
)
52+
g := func() {
53+
defer func() {
54+
p = recover()
55+
if !valid {
56+
panic(p)
57+
}
58+
}()
59+
result = f()
60+
f = nil
61+
valid = true
62+
}
63+
return func() T {
64+
once.Do(g)
65+
if !valid {
66+
panic(p)
67+
}
68+
return result
69+
}
70+
}
71+
72+
// OnceValues returns a function that invokes f only once and returns the values
73+
// returned by f. The returned function may be called concurrently.
74+
//
75+
// If f panics, the returned function will panic with the same value on every call.
76+
func OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) {
77+
var (
78+
once sync.Once
79+
valid bool
80+
p any
81+
r1 T1
82+
r2 T2
83+
)
84+
g := func() {
85+
defer func() {
86+
p = recover()
87+
if !valid {
88+
panic(p)
89+
}
90+
}()
91+
r1, r2 = f()
92+
f = nil
93+
valid = true
94+
}
95+
return func() (T1, T2) {
96+
once.Do(g)
97+
if !valid {
98+
panic(p)
99+
}
100+
return r1, r2
101+
}
102+
}

component/tls/reality.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,8 @@ type RealityConfig struct {
3737
ShortID [RealityMaxShortIDLen]byte
3838
}
3939

40-
func GetRealityConn(ctx context.Context, conn net.Conn, clientFingerprint string, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) {
41-
retry := 0
42-
for fingerprint, exists := GetFingerprint(clientFingerprint); exists; retry++ {
40+
func GetRealityConn(ctx context.Context, conn net.Conn, fingerprint UClientHelloID, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) {
41+
for retry := 0; ; retry++ {
4342
verifier := &realityVerifier{
4443
serverName: tlsConfig.ServerName,
4544
}
@@ -151,7 +150,6 @@ func GetRealityConn(ctx context.Context, conn net.Conn, clientFingerprint string
151150

152151
return uConn, nil
153152
}
154-
return nil, errors.New("unknown uTLS fingerprint")
155153
}
156154

157155
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {

component/tls/utls.go

Lines changed: 47 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,53 +4,52 @@ import (
44
"crypto/tls"
55
"net"
66

7+
"github.com/metacubex/mihomo/common/once"
78
"github.com/metacubex/mihomo/common/utils"
89
"github.com/metacubex/mihomo/log"
910

1011
utls "github.com/metacubex/utls"
1112
"github.com/mroth/weightedrand/v2"
1213
)
1314

15+
type Conn = utls.Conn
1416
type UConn = utls.UConn
17+
type UClientHelloID = utls.ClientHelloID
1518

1619
const VersionTLS13 = utls.VersionTLS13
1720

18-
type UClientHelloID struct {
19-
*utls.ClientHelloID
21+
func Client(c net.Conn, config *utls.Config) *Conn {
22+
return utls.Client(c, config)
2023
}
2124

22-
var initRandomFingerprint UClientHelloID
23-
var initUtlsClient string
24-
2525
func UClient(c net.Conn, config *utls.Config, fingerprint UClientHelloID) *UConn {
26-
return utls.UClient(c, config, *fingerprint.ClientHelloID)
26+
return utls.UClient(c, config, fingerprint)
2727
}
2828

29-
func GetFingerprint(ClientFingerprint string) (UClientHelloID, bool) {
30-
if ClientFingerprint == "none" {
31-
return UClientHelloID{}, false
29+
func GetFingerprint(clientFingerprint string) (UClientHelloID, bool) {
30+
if len(clientFingerprint) == 0 {
31+
clientFingerprint = globalFingerprint
3232
}
33-
34-
if initRandomFingerprint.ClientHelloID == nil {
35-
initRandomFingerprint, _ = RollFingerprint()
33+
if len(clientFingerprint) == 0 || clientFingerprint == "none" {
34+
return UClientHelloID{}, false
3635
}
3736

38-
if ClientFingerprint == "random" {
39-
log.Debugln("use initial random HelloID:%s", initRandomFingerprint.Client)
40-
return initRandomFingerprint, true
37+
if clientFingerprint == "random" {
38+
fingerprint := randomFingerprint()
39+
log.Debugln("use initial random HelloID:%s", fingerprint.Client)
40+
return fingerprint, true
4141
}
4242

43-
fingerprint, ok := Fingerprints[ClientFingerprint]
44-
if ok {
43+
if fingerprint, ok := fingerprints[clientFingerprint]; ok {
4544
log.Debugln("use specified fingerprint:%s", fingerprint.Client)
46-
return fingerprint, ok
45+
return fingerprint, true
4746
} else {
48-
log.Warnln("wrong ClientFingerprint:%s", ClientFingerprint)
47+
log.Warnln("wrong clientFingerprint:%s", clientFingerprint)
4948
return UClientHelloID{}, false
5049
}
5150
}
5251

53-
func RollFingerprint() (UClientHelloID, bool) {
52+
var randomFingerprint = once.OnceValue(func() UClientHelloID {
5453
chooser, _ := weightedrand.NewChooser(
5554
weightedrand.NewChoice("chrome", 6),
5655
weightedrand.NewChoice("safari", 3),
@@ -59,26 +58,29 @@ func RollFingerprint() (UClientHelloID, bool) {
5958
)
6059
initClient := chooser.Pick()
6160
log.Debugln("initial random HelloID:%s", initClient)
62-
fingerprint, ok := Fingerprints[initClient]
63-
return fingerprint, ok
64-
}
65-
66-
var Fingerprints = map[string]UClientHelloID{
67-
"chrome": {&utls.HelloChrome_Auto},
68-
"chrome_psk": {&utls.HelloChrome_100_PSK},
69-
"chrome_psk_shuffle": {&utls.HelloChrome_106_Shuffle},
70-
"chrome_padding_psk_shuffle": {&utls.HelloChrome_114_Padding_PSK_Shuf},
71-
"chrome_pq": {&utls.HelloChrome_115_PQ},
72-
"chrome_pq_psk": {&utls.HelloChrome_115_PQ_PSK},
73-
"firefox": {&utls.HelloFirefox_Auto},
74-
"safari": {&utls.HelloSafari_Auto},
75-
"ios": {&utls.HelloIOS_Auto},
76-
"android": {&utls.HelloAndroid_11_OkHttp},
77-
"edge": {&utls.HelloEdge_Auto},
78-
"360": {&utls.Hello360_Auto},
79-
"qq": {&utls.HelloQQ_Auto},
80-
"random": {nil},
81-
"randomized": {nil},
61+
fingerprint, ok := fingerprints[initClient]
62+
if !ok {
63+
log.Warnln("error in initial random HelloID:%s", initClient)
64+
}
65+
return fingerprint
66+
})
67+
68+
var fingerprints = map[string]UClientHelloID{
69+
"chrome": utls.HelloChrome_Auto,
70+
"chrome_psk": utls.HelloChrome_100_PSK,
71+
"chrome_psk_shuffle": utls.HelloChrome_106_Shuffle,
72+
"chrome_padding_psk_shuffle": utls.HelloChrome_114_Padding_PSK_Shuf,
73+
"chrome_pq": utls.HelloChrome_115_PQ,
74+
"chrome_pq_psk": utls.HelloChrome_115_PQ_PSK,
75+
"firefox": utls.HelloFirefox_Auto,
76+
"safari": utls.HelloSafari_Auto,
77+
"ios": utls.HelloIOS_Auto,
78+
"android": utls.HelloAndroid_11_OkHttp,
79+
"edge": utls.HelloEdge_Auto,
80+
"360": utls.Hello360_Auto,
81+
"qq": utls.HelloQQ_Auto,
82+
"random": {},
83+
"randomized": utls.HelloRandomized,
8284
}
8385

8486
func init() {
@@ -88,7 +90,7 @@ func init() {
8890
randomized := utls.HelloRandomized
8991
randomized.Seed, _ = utls.NewPRNGSeed()
9092
randomized.Weights = &weights
91-
Fingerprints["randomized"] = UClientHelloID{&randomized}
93+
fingerprints["randomized"] = randomized
9294
}
9395

9496
func UCertificates(it tls.Certificate) utls.Certificate {
@@ -154,14 +156,12 @@ func BuildWebsocketHandshakeState(c *UConn) error {
154156
return nil
155157
}
156158

157-
func SetGlobalUtlsClient(Client string) {
158-
initUtlsClient = Client
159-
}
159+
var globalFingerprint string
160160

161-
func HaveGlobalFingerprint() bool {
162-
return len(initUtlsClient) != 0 && initUtlsClient != "none"
161+
func SetGlobalFingerprint(fingerprint string) {
162+
globalFingerprint = fingerprint
163163
}
164164

165165
func GetGlobalFingerprint() string {
166-
return initUtlsClient
166+
return globalFingerprint
167167
}

hub/executor/executor.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,7 @@ func updateGeneral(general *config.General, logging bool) {
454454
mihomoHttp.SetUA(general.GlobalUA)
455455
resource.SetETag(general.ETagSupport)
456456

457-
tlsC.SetGlobalUtlsClient(general.GlobalClientFingerprint)
457+
tlsC.SetGlobalFingerprint(general.GlobalClientFingerprint)
458458
}
459459

460460
func updateUsers(users []auth.AuthUser) {

transport/gun/gun.go

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -237,25 +237,19 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, clientFingerprint stri
237237
return pconn, nil
238238
}
239239

240-
clientFingerprint := clientFingerprint
241-
if tlsC.HaveGlobalFingerprint() && len(clientFingerprint) == 0 {
242-
clientFingerprint = tlsC.GetGlobalFingerprint()
243-
}
244-
if len(clientFingerprint) != 0 {
240+
if clientFingerprint, ok := tlsC.GetFingerprint(clientFingerprint); ok {
245241
if realityConfig == nil {
246-
if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists {
247-
utlsConn := tlsC.UClient(pconn, tlsC.UConfig(cfg), fingerprint)
248-
if err := utlsConn.HandshakeContext(ctx); err != nil {
249-
pconn.Close()
250-
return nil, err
251-
}
252-
state := utlsConn.ConnectionState()
253-
if p := state.NegotiatedProtocol; p != http2.NextProtoTLS {
254-
utlsConn.Close()
255-
return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS)
256-
}
257-
return utlsConn, nil
242+
tlsConn := tlsC.UClient(pconn, tlsC.UConfig(cfg), clientFingerprint)
243+
if err := tlsConn.HandshakeContext(ctx); err != nil {
244+
pconn.Close()
245+
return nil, err
246+
}
247+
state := tlsConn.ConnectionState()
248+
if p := state.NegotiatedProtocol; p != http2.NextProtoTLS {
249+
tlsConn.Close()
250+
return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS)
258251
}
252+
return tlsConn, nil
259253
} else {
260254
realityConn, err := tlsC.GetRealityConn(ctx, pconn, clientFingerprint, cfg, realityConfig)
261255
if err != nil {

transport/sing-shadowtls/shadowtls.go

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"github.com/metacubex/mihomo/log"
1111

1212
"github.com/metacubex/sing-shadowtls"
13-
utls "github.com/metacubex/utls"
1413
"golang.org/x/exp/slices"
1514
)
1615

@@ -67,26 +66,21 @@ func uTLSHandshakeFunc(config *tls.Config, clientFingerprint string) shadowtls.T
6766
return func(ctx context.Context, conn net.Conn, sessionIDGenerator shadowtls.TLSSessionIDGeneratorFunc) error {
6867
tlsConfig := tlsC.UConfig(config)
6968
tlsConfig.SessionIDGenerator = sessionIDGenerator
70-
clientFingerprint := clientFingerprint
71-
if tlsC.HaveGlobalFingerprint() && len(clientFingerprint) == 0 {
72-
clientFingerprint = tlsC.GetGlobalFingerprint()
73-
}
7469
if config.MaxVersion == tls.VersionTLS12 { // for ShadowTLS v1
75-
clientFingerprint = ""
70+
tlsConn := tlsC.Client(conn, tlsConfig)
71+
return tlsConn.HandshakeContext(ctx)
7672
}
77-
if len(clientFingerprint) != 0 {
78-
if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists {
79-
tlsConn := tlsC.UClient(conn, tlsConfig, fingerprint)
80-
if slices.Equal(tlsConfig.NextProtos, WsALPN) {
81-
err := tlsC.BuildWebsocketHandshakeState(tlsConn)
82-
if err != nil {
83-
return err
84-
}
73+
if clientFingerprint, ok := tlsC.GetFingerprint(clientFingerprint); ok {
74+
tlsConn := tlsC.UClient(conn, tlsConfig, clientFingerprint)
75+
if slices.Equal(tlsConfig.NextProtos, WsALPN) {
76+
err := tlsC.BuildWebsocketHandshakeState(tlsConn)
77+
if err != nil {
78+
return err
8579
}
86-
return tlsConn.HandshakeContext(ctx)
8780
}
81+
return tlsConn.HandshakeContext(ctx)
8882
}
89-
tlsConn := utls.Client(conn, tlsConfig)
83+
tlsConn := tlsC.Client(conn, tlsConfig)
9084
return tlsConn.HandshakeContext(ctx)
9185
}
9286
}

transport/vmess/tls.go

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,14 @@ func StreamTLSConn(ctx context.Context, conn net.Conn, cfg *TLSConfig) (net.Conn
3232
return nil, err
3333
}
3434

35-
clientFingerprint := cfg.ClientFingerprint
36-
if tlsC.HaveGlobalFingerprint() && len(clientFingerprint) == 0 {
37-
clientFingerprint = tlsC.GetGlobalFingerprint()
38-
}
39-
if len(clientFingerprint) != 0 {
35+
if clientFingerprint, ok := tlsC.GetFingerprint(cfg.ClientFingerprint); ok {
4036
if cfg.Reality == nil {
41-
if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists {
42-
utlsConn := tlsC.UClient(conn, tlsC.UConfig(tlsConfig), fingerprint)
43-
err = utlsConn.HandshakeContext(ctx)
44-
if err != nil {
45-
return nil, err
46-
}
47-
return utlsConn, nil
37+
tlsConn := tlsC.UClient(conn, tlsC.UConfig(tlsConfig), clientFingerprint)
38+
err = tlsConn.HandshakeContext(ctx)
39+
if err != nil {
40+
return nil, err
4841
}
42+
return tlsConn, nil
4943
} else {
5044
return tlsC.GetRealityConn(ctx, conn, clientFingerprint, tlsConfig, cfg.Reality)
5145
}

0 commit comments

Comments
 (0)