Skip to content

Commit d0ea69d

Browse files
authored
refactor(model/netx.go): TLSHandhaker now returns a TLSConn (#1281)
I am making progress with ooni/probe#2531 and I want to reactor model/netx.go such that the TLSHandshaker returns a model.TLSConn rather than a net.Conn. Returning a net.Conn and documenting it is a model.TLSConn is bad compared to returning a model.TLSConn directly. Note that we cannot apply the same transformation to netxlite's TLSDialer.DialTLSContext because such a method must be assignable to net/http and github.com/ooni/oohttp's Transport function also called DialTLSContext. The fact that we need code to be assignable to the Transport function is what historically led the TLSHandshaker to return a net.Conn as well. But it was quite clear from the get go that this choice led to some quirks (and, in fact, this behavior was explicitly documented as such). While there, slightly refactor `internal/experiment/echcheck/utls.go` to avoid storing the conn inside the handshaker and make sure the test coverage does not drop for this experiment. While there, note that ooni/probe#2538 exists and commit a mitigation.
1 parent e146d99 commit d0ea69d

File tree

31 files changed

+252
-214
lines changed

31 files changed

+252
-214
lines changed

internal/dslx/tls.go

+3-8
Original file line numberDiff line numberDiff line change
@@ -124,27 +124,22 @@ func (f *tlsHandshakeFunc) Apply(
124124
defer cancel()
125125

126126
// handshake
127-
conn, tlsState, err := handshaker.Handshake(ctx, input.Conn, config)
127+
conn, err := handshaker.Handshake(ctx, input.Conn, config)
128128

129129
// possibly register established conn for late close
130130
f.Pool.MaybeTrack(conn)
131131

132132
// stop the operation logger
133133
ol.Stop(err)
134134

135-
var tlsConn netxlite.TLSConn
136-
if conn != nil {
137-
tlsConn = conn.(netxlite.TLSConn) // guaranteed to work
138-
}
139-
140135
state := &TLSConnection{
141136
Address: input.Address,
142-
Conn: tlsConn, // possibly nil
137+
Conn: conn, // possibly nil
143138
Domain: input.Domain,
144139
IDGenerator: input.IDGenerator,
145140
Logger: input.Logger,
146141
Network: input.Network,
147-
TLSState: tlsState,
142+
TLSState: netxlite.MaybeTLSConnectionState(conn),
148143
Trace: trace,
149144
ZeroTime: input.ZeroTime,
150145
}

internal/dslx/tls_test.go

+10-5
Original file line numberDiff line numberDiff line change
@@ -70,17 +70,22 @@ func TestTLSHandshake(t *testing.T) {
7070
return nil
7171
},
7272
}
73-
tlsConn := &mocks.TLSConn{Conn: tcpConn}
73+
tlsConn := &mocks.TLSConn{
74+
Conn: tcpConn,
75+
MockConnectionState: func() tls.ConnectionState {
76+
return tls.ConnectionState{}
77+
},
78+
}
7479

7580
eofHandshaker := &mocks.TLSHandshaker{
76-
MockHandshake: func(ctx context.Context, conn net.Conn, config *tls.Config) (net.Conn, tls.ConnectionState, error) {
77-
return nil, tls.ConnectionState{}, io.EOF
81+
MockHandshake: func(ctx context.Context, conn net.Conn, config *tls.Config) (model.TLSConn, error) {
82+
return nil, io.EOF
7883
},
7984
}
8085

8186
goodHandshaker := &mocks.TLSHandshaker{
82-
MockHandshake: func(ctx context.Context, conn net.Conn, config *tls.Config) (net.Conn, tls.ConnectionState, error) {
83-
return tlsConn, tls.ConnectionState{}, nil
87+
MockHandshake: func(ctx context.Context, conn net.Conn, config *tls.Config) (model.TLSConn, error) {
88+
return tlsConn, nil
8489
},
8590
}
8691

internal/experiment/echcheck/handshake.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,10 @@ func handshakeWithExtension(ctx context.Context, conn net.Conn, zeroTime time.Ti
4141
tracedHandshaker := handshakerConstructor(log.Log, &utls.HelloFirefox_Auto)
4242

4343
start := time.Now()
44-
_, connState, err := tracedHandshaker.Handshake(ctx, conn, tlsConfig)
44+
maybeTLSConn, err := tracedHandshaker.Handshake(ctx, conn, tlsConfig)
4545
finish := time.Now()
4646

47+
connState := netxlite.MaybeTLSConnectionState(maybeTLSConn)
4748
return measurexlite.NewArchivalTLSOrQUICHandshakeResult(0, start.Sub(zeroTime), "tcp", address, tlsConfig,
4849
connState, err, finish.Sub(zeroTime))
4950
}

internal/experiment/echcheck/measure_test.go

+3-5
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,10 @@ func TestMeasurementSuccess(t *testing.T) {
8484
}
8585

8686
summary, err := measurer.GetSummaryKeys(&model.Measurement{})
87-
87+
if err != nil {
88+
t.Fatal(err)
89+
}
8890
if summary.(SummaryKeys).IsAnomaly != false {
8991
t.Fatal("expected false")
9092
}
9193
}
92-
93-
func newsession() model.ExperimentSession {
94-
return &mockable.Session{MockableLogger: log.Log}
95-
}

internal/experiment/echcheck/utls.go

+9-9
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
)
1313

1414
type tlsHandshakerWithExtensions struct {
15-
conn *netxlite.UTLSConn
1615
extensions []utls.TLSExtension
1716
dl model.DebugLogger
1817
id *utls.ClientHelloID
@@ -32,18 +31,19 @@ func newHandshakerWithExtensions(extensions []utls.TLSExtension) func(dl model.D
3231
}
3332
}
3433

35-
func (t *tlsHandshakerWithExtensions) Handshake(ctx context.Context, conn net.Conn, tlsConfig *tls.Config) (
36-
net.Conn, tls.ConnectionState, error) {
37-
var err error
38-
t.conn, err = netxlite.NewUTLSConn(conn, tlsConfig, t.id)
34+
func (t *tlsHandshakerWithExtensions) Handshake(
35+
ctx context.Context, tcpConn net.Conn, tlsConfig *tls.Config) (model.TLSConn, error) {
36+
tlsConn, err := netxlite.NewUTLSConn(tcpConn, tlsConfig, t.id)
3937
runtimex.Assert(err == nil, "unexpected error when creating UTLSConn")
4038

4139
if t.extensions != nil && len(t.extensions) != 0 {
42-
t.conn.BuildHandshakeState()
43-
t.conn.Extensions = append(t.conn.Extensions, t.extensions...)
40+
tlsConn.BuildHandshakeState()
41+
tlsConn.Extensions = append(tlsConn.Extensions, t.extensions...)
4442
}
4543

46-
err = t.conn.Handshake()
44+
if err := tlsConn.Handshake(); err != nil {
45+
return nil, err
46+
}
4747

48-
return t.conn.NetConn(), t.conn.ConnectionState(), err
48+
return tlsConn, nil
4949
}
+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package echcheck
2+
3+
import (
4+
"context"
5+
"crypto/tls"
6+
"errors"
7+
"testing"
8+
9+
"github.com/ooni/probe-cli/v3/internal/mocks"
10+
"github.com/ooni/probe-cli/v3/internal/model"
11+
utls "gitlab.com/yawning/utls.git"
12+
)
13+
14+
func TestTLSHandshakerWithExtension(t *testing.T) {
15+
t.Run("when the TLS handshake fails", func(t *testing.T) {
16+
thx := &tlsHandshakerWithExtensions{
17+
extensions: []utls.TLSExtension{},
18+
dl: model.DiscardLogger,
19+
id: &utls.HelloChrome_70,
20+
}
21+
22+
expected := errors.New("mocked error")
23+
tcpConn := &mocks.Conn{
24+
MockWrite: func(b []byte) (int, error) {
25+
return 0, expected
26+
},
27+
}
28+
29+
tlsConfig := &tls.Config{
30+
InsecureSkipVerify: true,
31+
}
32+
33+
tlsConn, err := thx.Handshake(context.Background(), tcpConn, tlsConfig)
34+
if !errors.Is(err, expected) {
35+
t.Fatal(err)
36+
}
37+
if tlsConn != nil {
38+
t.Fatal("expected nil tls conn")
39+
}
40+
})
41+
}

internal/experiment/tlsmiddlebox/tracing.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ func (m *Measurer) handshakeWithTTL(ctx context.Context, index int64, zeroTime t
9797
if clientId > 0 {
9898
thx = trace.NewTLSHandshakerUTLS(logger, ClientIDs[clientId])
9999
}
100-
_, _, err = thx.Handshake(ctx, conn, genTLSConfig(sni))
100+
_, err = thx.Handshake(ctx, conn, genTLSConfig(sni))
101101
ol.Stop(err)
102102
soErr := extractSoError(conn)
103103
// 4. reset the TTL value to ensure that conn closes successfully

internal/experiment/tlsping/tlsping.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ func (m *Measurer) tlsConnectAndHandshake(ctx context.Context, index int64,
189189
RootCAs: nil,
190190
ServerName: sni,
191191
}
192-
_, _, err = thx.Handshake(ctx, conn, config)
192+
_, err = thx.Handshake(ctx, conn, config)
193193
ol.Stop(err)
194194
sp.TLSHandshake = trace.FirstTLSHandshakeOrNil() // record the first handshake from the buffer
195195
sp.NetworkEvents = trace.NetworkEvents()

internal/experiment/webconnectivitylte/secureflow.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -154,14 +154,15 @@ func (t *SecureFlow) Run(parentCtx context.Context, index int64) error {
154154
const tlsTimeout = 10 * time.Second
155155
tlsCtx, tlsCancel := context.WithTimeout(parentCtx, tlsTimeout)
156156
defer tlsCancel()
157-
tlsConn, tlsConnState, err := tlsHandshaker.Handshake(tlsCtx, tcpConn, tlsConfig)
157+
tlsConn, err := tlsHandshaker.Handshake(tlsCtx, tcpConn, tlsConfig)
158158
t.TestKeys.AppendTLSHandshakes(trace.TLSHandshakes()...)
159159
if err != nil {
160160
ol.Stop(err)
161161
return err
162162
}
163163
defer tlsConn.Close()
164164

165+
tlsConnState := netxlite.MaybeTLSConnectionState(tlsConn)
165166
alpn := tlsConnState.NegotiatedProtocol
166167

167168
// Determine whether we're allowed to fetch the webpage
@@ -177,8 +178,7 @@ func (t *SecureFlow) Run(parentCtx context.Context, index int64) error {
177178
httpTransport := netxlite.NewHTTPTransport(
178179
t.Logger,
179180
netxlite.NewNullDialer(),
180-
// note: netxlite guarantees that here tlsConn is a netxlite.TLSConn
181-
netxlite.NewSingleUseTLSDialer(tlsConn.(netxlite.TLSConn)),
181+
netxlite.NewSingleUseTLSDialer(tlsConn),
182182
)
183183

184184
// create HTTP request

internal/legacy/measurex/measurer.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -355,13 +355,12 @@ func (mx *Measurer) TLSConnectAndHandshakeWithDB(ctx context.Context,
355355
ctx, cancel := context.WithTimeout(ctx, timeout)
356356
defer cancel()
357357
th := mx.WrapTLSHandshaker(db, mx.TLSHandshaker)
358-
tlsConn, _, err := th.Handshake(ctx, conn, config)
358+
tlsConn, err := th.Handshake(ctx, conn, config)
359359
ol.Stop(err)
360360
if err != nil {
361361
return nil, err
362362
}
363-
// cast safe according to the docs of netxlite's handshaker
364-
return tlsConn.(netxlite.TLSConn), nil
363+
return tlsConn, nil
365364
}
366365

367366
// QUICHandshake connects and TLS handshakes with a QUIC endpoint.

internal/legacy/measurex/tls.go

+8-9
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"crypto/tls"
1212
"crypto/x509"
1313
"errors"
14-
"net"
1514
"time"
1615

1716
"github.com/ooni/probe-cli/v3/internal/model"
@@ -53,13 +52,13 @@ type QUICTLSHandshakeEvent struct {
5352
Started float64
5453
}
5554

56-
func (thx *tlsHandshakerDB) Handshake(ctx context.Context,
57-
conn Conn, config *tls.Config) (net.Conn, tls.ConnectionState, error) {
55+
func (thx *tlsHandshakerDB) Handshake(ctx context.Context, conn Conn, config *tls.Config) (model.TLSConn, error) {
5856
network := conn.RemoteAddr().Network()
5957
remoteAddr := conn.RemoteAddr().String()
6058
started := time.Since(thx.begin).Seconds()
61-
tconn, state, err := thx.TLSHandshaker.Handshake(ctx, conn, config)
59+
tconn, err := thx.TLSHandshaker.Handshake(ctx, conn, config)
6260
finished := time.Since(thx.begin).Seconds()
61+
tstate := netxlite.MaybeTLSConnectionState(tconn)
6362
thx.db.InsertIntoTLSHandshake(&QUICTLSHandshakeEvent{
6463
Network: network,
6564
RemoteAddr: remoteAddr,
@@ -70,12 +69,12 @@ func (thx *tlsHandshakerDB) Handshake(ctx context.Context,
7069
Finished: finished,
7170
Failure: NewFailure(err),
7271
Oddity: thx.computeOddity(err),
73-
TLSVersion: netxlite.TLSVersionString(state.Version),
74-
CipherSuite: netxlite.TLSCipherSuiteString(state.CipherSuite),
75-
NegotiatedProto: state.NegotiatedProtocol,
76-
PeerCerts: peerCerts(err, &state),
72+
TLSVersion: netxlite.TLSVersionString(tstate.Version),
73+
CipherSuite: netxlite.TLSCipherSuiteString(tstate.CipherSuite),
74+
NegotiatedProto: tstate.NegotiatedProtocol,
75+
PeerCerts: peerCerts(err, &tstate),
7776
})
78-
return tconn, state, err
77+
return tconn, err
7978
}
8079

8180
func (thx *tlsHandshakerDB) computeOddity(err error) Oddity {

internal/legacy/tracex/tls.go

+8-7
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func (s *Saver) WrapTLSHandshaker(thx model.TLSHandshaker) model.TLSHandshaker {
4242

4343
// Handshake implements model.TLSHandshaker.Handshake
4444
func (h *TLSHandshakerSaver) Handshake(
45-
ctx context.Context, conn net.Conn, config *tls.Config) (net.Conn, tls.ConnectionState, error) {
45+
ctx context.Context, conn net.Conn, config *tls.Config) (model.TLSConn, error) {
4646
proto := conn.RemoteAddr().Network()
4747
remoteAddr := conn.RemoteAddr().String()
4848
start := time.Now()
@@ -54,23 +54,24 @@ func (h *TLSHandshakerSaver) Handshake(
5454
TLSServerName: config.ServerName,
5555
Time: start,
5656
}})
57-
tlsconn, state, err := h.TLSHandshaker.Handshake(ctx, conn, config)
57+
tlsconn, err := h.TLSHandshaker.Handshake(ctx, conn, config)
5858
stop := time.Now()
59+
tstate := netxlite.MaybeTLSConnectionState(tlsconn)
5960
h.Saver.Write(&EventTLSHandshakeDone{&EventValue{
6061
Address: remoteAddr,
6162
Duration: stop.Sub(start),
6263
Err: NewFailureStr(err),
6364
NoTLSVerify: config.InsecureSkipVerify,
6465
Proto: proto,
65-
TLSCipherSuite: netxlite.TLSCipherSuiteString(state.CipherSuite),
66-
TLSNegotiatedProto: state.NegotiatedProtocol,
66+
TLSCipherSuite: netxlite.TLSCipherSuiteString(tstate.CipherSuite),
67+
TLSNegotiatedProto: tstate.NegotiatedProtocol,
6768
TLSNextProtos: config.NextProtos,
68-
TLSPeerCerts: tlsPeerCerts(state, err),
69+
TLSPeerCerts: tlsPeerCerts(tstate, err),
6970
TLSServerName: config.ServerName,
70-
TLSVersion: netxlite.TLSVersionString(state.Version),
71+
TLSVersion: netxlite.TLSVersionString(tstate.Version),
7172
Time: stop,
7273
}})
73-
return tlsconn, state, err
74+
return tlsconn, err
7475
}
7576

7677
var _ model.TLSHandshaker = &TLSHandshakerSaver{}

internal/legacy/tracex/tls_test.go

+7-8
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"github.com/google/go-cmp/cmp"
1212
"github.com/ooni/probe-cli/v3/internal/mocks"
13+
"github.com/ooni/probe-cli/v3/internal/model"
1314
)
1415

1516
func TestWrapTLSHandshaker(t *testing.T) {
@@ -98,9 +99,8 @@ func TestTLSHandshakerSaver(t *testing.T) {
9899
},
99100
}
100101
thx := saver.WrapTLSHandshaker(&mocks.TLSHandshaker{
101-
MockHandshake: func(ctx context.Context, conn net.Conn,
102-
config *tls.Config) (net.Conn, tls.ConnectionState, error) {
103-
return returnedConn, returnedConnState, nil
102+
MockHandshake: func(ctx context.Context, conn net.Conn, config *tls.Config) (model.TLSConn, error) {
103+
return returnedConn, nil
104104
},
105105
})
106106
ctx := context.Background()
@@ -121,7 +121,7 @@ func TestTLSHandshakerSaver(t *testing.T) {
121121
}
122122
},
123123
}
124-
conn, _, err := thx.Handshake(ctx, tcpConn, tlsConfig)
124+
conn, err := thx.Handshake(ctx, tcpConn, tlsConfig)
125125
if err != nil {
126126
t.Fatal(err)
127127
}
@@ -161,9 +161,8 @@ func TestTLSHandshakerSaver(t *testing.T) {
161161
expected := errors.New("mocked error")
162162
saver := &Saver{}
163163
thx := saver.WrapTLSHandshaker(&mocks.TLSHandshaker{
164-
MockHandshake: func(ctx context.Context, conn net.Conn,
165-
config *tls.Config) (net.Conn, tls.ConnectionState, error) {
166-
return nil, tls.ConnectionState{}, expected
164+
MockHandshake: func(ctx context.Context, conn net.Conn, config *tls.Config) (model.TLSConn, error) {
165+
return nil, expected
167166
},
168167
})
169168
ctx := context.Background()
@@ -184,7 +183,7 @@ func TestTLSHandshakerSaver(t *testing.T) {
184183
}
185184
},
186185
}
187-
conn, _, err := thx.Handshake(ctx, tcpConn, tlsConfig)
186+
conn, err := thx.Handshake(ctx, tcpConn, tlsConfig)
188187
if !errors.Is(err, expected) {
189188
t.Fatal("unexpected err", err)
190189
}

internal/measurexlite/tls.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ var _ model.TLSHandshaker = &tlsHandshakerTrace{}
3535

3636
// Handshake implements model.TLSHandshaker.Handshake.
3737
func (thx *tlsHandshakerTrace) Handshake(
38-
ctx context.Context, conn net.Conn, tlsConfig *tls.Config) (net.Conn, tls.ConnectionState, error) {
38+
ctx context.Context, conn net.Conn, tlsConfig *tls.Config) (model.TLSConn, error) {
3939
return thx.thx.Handshake(netxlite.ContextWithTrace(ctx, thx.tx), conn, tlsConfig)
4040
}
4141

0 commit comments

Comments
 (0)