-
Notifications
You must be signed in to change notification settings - Fork 0
/
hello_roller.go
118 lines (104 loc) · 3.47 KB
/
hello_roller.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package chained
import (
stderrors "errors"
"net"
"sync"
tls "github.com/refraction-networking/utls"
"github.com/getlantern/errors"
)
type helloSpec struct {
// If id == tls.HelloCustom, sample must be non-nil. In this case, we will use
// tls.FingerprintClientHello to generate a tls.ClientHelloSpec based on sample.
//
// We do this rather than store a tls.ClientHelloSpec on the hello type because (i)
// tls.ClientHelloSpec instances cannot be re-used and (ii) it is easier to keep the sample
// hello around than to deep-copy the tls.ClientHelloSpec on each use.
id tls.ClientHelloID
sample []byte
}
// This function is guaranteed to return one of the following:
// - A non-custom client hello ID (i.e. not utls.ClientHelloCustom)
// - utls.ClientHelloCustom and a non-nil utls.ClientHelloSpec.
// - An error (if the above is not possible)
func (hs helloSpec) utlsSpec() (tls.ClientHelloID, *tls.ClientHelloSpec, error) {
const tlsRecordHeaderLen = 5
if hs.id != tls.HelloCustom {
return hs.id, nil, nil
}
if hs.sample == nil {
return hs.id, nil, errors.New("illegal combination of HelloCustom and nil sample hello")
}
if len(hs.sample) < tlsRecordHeaderLen {
return hs.id, nil, errors.New("sample hello is too small")
}
spec, err := tls.FingerprintClientHello(hs.sample[tlsRecordHeaderLen:])
if err != nil {
return hs.id, nil, errors.New("failed to fingerprint sample hello: %v", err)
}
return hs.id, spec, nil
}
// An error is only returned in the case of an invalid custom hello (as specified by hs.sample).
func (hs helloSpec) uconn(transport net.Conn, cfg *tls.Config) (*tls.UConn, error) {
id, spec, err := hs.utlsSpec()
if err != nil {
return nil, errors.Wrap(err)
}
uconn := tls.UClient(transport, cfg, id)
if id != tls.HelloCustom {
return uconn, nil
}
if err := uconn.ApplyPreset(spec); err != nil {
return nil, errors.New("failed to apply custom hello: %v", err)
}
return uconn, nil
}
type helloRoller struct {
hellos []helloSpec
index, advances int
sync.Mutex
}
// Not concurrency safe.
func (hr *helloRoller) current() helloSpec {
if len(hr.hellos) < 1 {
panic("empty hello roller is invalid")
}
if hr.index >= len(hr.hellos) {
hr.index = 0
}
return hr.hellos[hr.index]
}
// Not concurrency safe.
func (hr *helloRoller) advance() {
hr.index++
if hr.index >= len(hr.hellos) {
hr.index = 0
}
hr.advances++
}
// Updates hr iff the input roller, 'other' has been advanced more than hr. It is safe to call this
// method concurrently for multiple references to hr, but not for multiple references to 'other'.
func (hr *helloRoller) updateTo(other *helloRoller) {
hr.Lock()
if other.advances > hr.advances {
hr.index = other.index
hr.advances = other.advances
}
hr.Unlock()
}
// Concurrency safe.
func (hr *helloRoller) getCopy() *helloRoller {
hr.Lock()
defer hr.Unlock()
hellos := make([]helloSpec, len(hr.hellos))
copy(hellos, hr.hellos)
return &helloRoller{hellos, hr.index, hr.advances, sync.Mutex{}}
}
// Utility function for users of the helloRoller. If an error is returned by the handshake function
// and that error is related to the ClientHello, we should roll to the next one.
func isHelloErr(err error) bool {
// We assume that everything other than timeouts might be related to the hello. This may be
// aggressive, but it's better that the client is willing to try other hellos, rather than
// get stuck in a loop on a bad one.
var netErr net.Error
return !(stderrors.As(err, &netErr) && netErr.Timeout())
}