-
Notifications
You must be signed in to change notification settings - Fork 0
/
hello_roller.go
144 lines (129 loc) · 4.09 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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
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.HelloCustom)
// - utls.HelloCustom 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")
}
fp := &tls.Fingerprinter{
AllowBluntMimicry: false,
AlwaysAddPadding: false,
}
spec, err := fp.FingerprintClientHello(hs.sample)
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
}
func (hs helloSpec) supportsSessionTickets() (bool, error) {
id, spec, err := hs.utlsSpec()
if err != nil {
return false, errors.New("failed to get hello spec: %v", err)
}
if spec == nil {
// this can happen if we're not using a custom spec
_spec, err := tls.UTLSIdToSpec(id)
if err != nil {
return false, errors.New("failed to get spec for hello %v: %v", id, err)
}
spec = &_spec
}
for _, extension := range spec.Extensions {
_, isSessionTicket := extension.(*tls.SessionTicketExtension)
if isSessionTicket {
return true, nil
}
}
return false, 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())
}