/
tracing.go
149 lines (138 loc) · 4.98 KB
/
tracing.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
145
146
147
148
149
package tlsmiddlebox
//
// Iterative network tracing
//
import (
"context"
"crypto/tls"
"errors"
"net"
"sort"
"sync"
"syscall"
"time"
"github.com/ooni/probe-engine/pkg/logx"
"github.com/ooni/probe-engine/pkg/measurexlite"
"github.com/ooni/probe-engine/pkg/model"
"github.com/ooni/probe-engine/pkg/netxlite"
utls "gitlab.com/yawning/utls.git"
)
// ClientIDs to map configurable inputs to uTLS fingerprints
// We use a non-zero index to map to each ClientID
var ClientIDs = map[int]*utls.ClientHelloID{
1: &utls.HelloGolang,
2: &utls.HelloChrome_Auto,
3: &utls.HelloFirefox_Auto,
4: &utls.HelloIOS_Auto,
}
// TLSTrace performs tracing using control and target SNI
func (m *Measurer) TLSTrace(ctx context.Context, index int64, zeroTime time.Time, logger model.Logger,
address string, targetSNI string, trace *CompleteTrace) {
// perform an iterative trace with the control SNI
trace.ControlTrace = m.startIterativeTrace(ctx, index, zeroTime, logger, address, m.config.snicontrol())
// perform an iterative trace with the target SNI
trace.TargetTrace = m.startIterativeTrace(ctx, index, zeroTime, logger, address, targetSNI)
}
// startIterativeTrace creates a Trace and calls iterativeTrace
func (m *Measurer) startIterativeTrace(ctx context.Context, index int64, zeroTime time.Time, logger model.Logger,
address string, sni string) (tr *IterativeTrace) {
tr = &IterativeTrace{
SNI: sni,
Iterations: []*Iteration{},
}
maxTTL := m.config.maxttl()
m.traceWithIncreasingTTLs(ctx, index, zeroTime, logger, address, sni, maxTTL, tr)
tr.Iterations = alignIterations(tr.Iterations)
return
}
// traceWithIncreasingTTLs performs iterative tracing with increasing TTL values
func (m *Measurer) traceWithIncreasingTTLs(ctx context.Context, index int64, zeroTime time.Time, logger model.Logger,
address string, sni string, maxTTL int64, trace *IterativeTrace) {
ticker := time.NewTicker(m.config.delay())
wg := new(sync.WaitGroup)
for i := int64(1); i <= maxTTL; i++ {
wg.Add(1)
go m.handshakeWithTTL(ctx, index, zeroTime, logger, address, sni, int(i), trace, wg)
<-ticker.C
}
wg.Wait()
}
// handshakeWithTTL performs the TLS Handshake using the passed ttl value
func (m *Measurer) handshakeWithTTL(ctx context.Context, index int64, zeroTime time.Time, logger model.Logger,
address string, sni string, ttl int, tr *IterativeTrace, wg *sync.WaitGroup) {
defer wg.Done()
trace := measurexlite.NewTrace(index, zeroTime)
// 1. Connect to the target IP
// TODO(DecFox, bassosimone): Do we need a trace for this TCP connect?
d := NewDialerTTLWrapper()
ol := logx.NewOperationLogger(logger, "Handshake Trace #%d TTL %d %s %s", index, ttl, address, sni)
conn, err := d.DialContext(ctx, "tcp", address)
if err != nil {
iteration := newIterationFromHandshake(ttl, err, nil, nil)
tr.addIterations(iteration)
ol.Stop(err)
return
}
defer conn.Close()
// 2. Set the TTL to the passed value
err = setConnTTL(conn, ttl)
if err != nil {
iteration := newIterationFromHandshake(ttl, err, nil, nil)
tr.addIterations(iteration)
ol.Stop(err)
return
}
// 3. Perform the handshake and extract the SO_ERROR value (if any)
// Note: we switch to a uTLS Handshaker if the configured ClientID is non-zero
thx := trace.NewTLSHandshakerStdlib(logger)
clientId := m.config.clientid()
if clientId > 0 {
thx = trace.NewTLSHandshakerUTLS(logger, ClientIDs[clientId])
}
_, err = thx.Handshake(ctx, conn, genTLSConfig(sni))
ol.Stop(err)
soErr := extractSoError(conn)
// 4. reset the TTL value to ensure that conn closes successfully
// Note: Do not check for errors here
_ = setConnTTL(conn, 64)
iteration := newIterationFromHandshake(ttl, nil, soErr, trace.FirstTLSHandshakeOrNil())
tr.addIterations(iteration)
}
// extractSoError fetches the SO_ERROR value and returns a non-nil error if
// it qualifies as a valid ICMP soft error
// Note: The passed conn must be of type dialerTTLWrapperConn
func extractSoError(conn net.Conn) error {
soErrno, err := getSoErr(conn)
if err != nil || errors.Is(soErrno, syscall.Errno(0)) {
return nil
}
soErr := netxlite.MaybeNewErrWrapper(netxlite.ClassifyGenericError, netxlite.TLSHandshakeOperation, soErrno)
return soErr
}
// genTLSConfig generates tls.Config from a given SNI
func genTLSConfig(sni string) *tls.Config {
// See https://github.com/ooni/probe/issues/2413 to understand
// why we're using nil to force netxlite to use the cached
// default Mozilla cert pool.
return &tls.Config{
RootCAs: nil,
ServerName: sni,
NextProtos: []string{"h2", "http/1.1"},
InsecureSkipVerify: true,
}
}
// alignIterEvents sorts the iterEvents according to increasing TTL
// and stops when we receive a nil or connection_reset
func alignIterations(in []*Iteration) (out []*Iteration) {
out = []*Iteration{}
sort.Slice(in, func(i int, j int) bool {
return in[i].TTL < in[j].TTL
})
for _, iter := range in {
out = append(out, iter)
if iter.Handshake.Failure == nil || *iter.Handshake.Failure == netxlite.FailureConnectionReset {
break
}
}
return out
}