-
Notifications
You must be signed in to change notification settings - Fork 15
/
tcptls.go
183 lines (144 loc) · 4.68 KB
/
tcptls.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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
package oohelperd
//
// TCP connect (and optionally TLS handshake) measurements
//
import (
"context"
"crypto/tls"
"sync"
"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"
)
// ctrlTCPResult is the result of the TCP check performed by the test helper.
type ctrlTCPResult = model.THTCPConnectResult
// ctrlTLSResult is the result of the TLS check performed by the test helper.
type ctrlTLSResult = model.THTLSHandshakeResult
// tcpResultPair contains the endpoint and the corresponding result.
type tcpResultPair struct {
// Address is the IP address we measured.
Address string
// Endpoint is the endpoint we measured.
Endpoint string
// TCP contains the TCP results.
TCP ctrlTCPResult
// TLS contains the TLS results
TLS *ctrlTLSResult
}
// tcpTLSConfig configures the TCP connect check.
type tcpTLSConfig struct {
// Address is the MANDATORY address to measure.
Address string
// EnableTLS OPTIONALLY enables TLS.
EnableTLS bool
// Endpoint is the MANDATORY endpoint to connect to.
Endpoint string
// Logger is the MANDATORY logger to use.
Logger model.Logger
// NewDialer is the MANDATORY factory for creating a new dialer.
NewDialer func(model.Logger) model.Dialer
// NewTSLHandshaker is the MANDATORY factory for creating a new handshaker.
NewTSLHandshaker func(model.Logger) model.TLSHandshaker
// Out is the MANDATORY where we'll post the TCP measurement results.
Out chan *tcpResultPair
// URLHostname is the MANDATORY URL.Hostname() to use.
URLHostname string
// Wg is MANDATORY and is used to sync with the parent.
Wg *sync.WaitGroup
}
// tcpTLSDo performs the TCP and (possibly) TLS checks.
func tcpTLSDo(ctx context.Context, config *tcpTLSConfig) {
// add an overall timeout for this task
const timeout = 15 * time.Second
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
// tell the parent when we're done
defer config.Wg.Done()
// assemble result and arrange for it to be written back
out := &tcpResultPair{
Address: config.Address,
Endpoint: config.Endpoint,
TCP: model.THTCPConnectResult{},
TLS: nil, // means: not measured
}
defer func() {
config.Out <- out
}()
// 1: TCP dial
ol := logx.NewOperationLogger(
config.Logger,
"TCPConnect %s EnableTLS=%v SNI=%s",
config.Endpoint,
config.EnableTLS,
config.URLHostname,
)
dialer := config.NewDialer(config.Logger)
defer dialer.CloseIdleConnections()
// save the time before we start connecting
tcpT0 := time.Now()
// establish a TCP connection
conn, err := dialer.DialContext(ctx, "tcp", config.Endpoint)
// publish the time required to connect
tcpElapsed := time.Since(tcpT0)
metricTCPTaskDurationSeconds.Observe(tcpElapsed.Seconds())
// make sure we fill the TCP stanza
out.TCP.Failure = tcpMapFailure(newfailure(err))
out.TCP.Status = err == nil
defer measurexlite.MaybeClose(conn)
if err != nil || !config.EnableTLS {
ol.Stop(err)
return
}
// 2: TLS handshake (if needed)
// 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.
tlsConfig := &tls.Config{ // #nosec G402 - we need to use a large TLS versions range for measuring
NextProtos: []string{"h2", "http/1.1"},
RootCAs: nil,
ServerName: config.URLHostname,
}
thx := config.NewTSLHandshaker(config.Logger)
// save time before handshake
tlsT0 := time.Now()
// perform the handshake
tlsConn, err := thx.Handshake(ctx, conn, tlsConfig)
_ = measurexlite.MaybeClose(tlsConn)
// publish time required to handshake
tlsElapsed := time.Since(tlsT0)
metricTLSTaskDurationSeconds.Observe(tlsElapsed.Seconds())
ol.Stop(err)
// we're good and we can fill the result
out.TLS = &ctrlTLSResult{
ServerName: config.URLHostname,
Status: err == nil,
Failure: newfailure(err),
}
}
// tcpMapFailure attempts to map netxlite failures to the strings
// used by the original OONI test helper.
//
// See https://github.com/ooni/backend/blob/6ec4fda5b18/oonib/testhelpers/http_helpers.py#L392
func tcpMapFailure(failure *string) *string {
switch failure {
case nil:
return nil
default:
switch *failure {
case netxlite.FailureGenericTimeoutError:
return failure // already using the same name
case netxlite.FailureConnectionRefused:
s := "connection_refused_error"
return &s
default:
// The definition of this error according to Twisted is
// "something went wrong when connecting". Because we are
// indeed basically just connecting here, it seems safe
// to map any other error to "connect_error" here.
s := "connect_error"
return &s
}
}
}