forked from ooni/probe-cli
-
Notifications
You must be signed in to change notification settings - Fork 0
/
tcpconnect.go
141 lines (121 loc) · 3.72 KB
/
tcpconnect.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
package main
//
// TCP connect (and optionally TLS handshake) measurements
//
import (
"context"
"crypto/tls"
"sync"
"time"
"github.com/ooni/probe-cli/v3/internal/measurexlite"
"github.com/ooni/probe-cli/v3/internal/model"
"github.com/ooni/probe-cli/v3/internal/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
}
// tcpConfig configures the TCP connect check.
type tcpConfig 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
}
// tcpDo performs the TCP check.
func tcpDo(ctx context.Context, config *tcpConfig) {
const timeout = 15 * time.Second
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
defer config.Wg.Done()
out := &tcpResultPair{
Address: config.Address,
Endpoint: config.Endpoint,
TCP: model.THTCPConnectResult{},
TLS: nil, // means: not measured
}
defer func() {
config.Out <- out
}()
ol := measurexlite.NewOperationLogger(
config.Logger,
"TCPConnect %s EnableTLS=%v SNI=%s",
config.Endpoint,
config.EnableTLS,
config.URLHostname,
)
dialer := config.NewDialer(config.Logger)
defer dialer.CloseIdleConnections()
conn, err := dialer.DialContext(ctx, "tcp", config.Endpoint)
out.TCP.Failure = tcpMapFailure(newfailure(err))
out.TCP.Status = err == nil
defer measurexlite.MaybeClose(conn)
if err != nil || !config.EnableTLS {
ol.Stop(err)
return
}
tlsConfig := &tls.Config{
NextProtos: []string{"h2", "http/1.1"},
RootCAs: netxlite.NewDefaultCertPool(),
ServerName: config.URLHostname,
}
thx := config.NewTSLHandshaker(config.Logger)
tlsConn, _, err := thx.Handshake(ctx, conn, tlsConfig)
ol.Stop(err)
out.TLS = &ctrlTLSResult{
ServerName: config.URLHostname,
Status: err == nil,
Failure: newfailure(err),
}
measurexlite.MaybeClose(tlsConn)
}
// 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
}
}
}