refactor(simplequicping): use step-by-step (#852)
See https://github.com/ooni/probe/issues/2159 and https://github.com/ooni/spec/pull/254
This commit is contained in:
parent
fbd7cd2b7e
commit
69602abe8a
16 changed files with 922 additions and 26 deletions
315
internal/measurexlite/quic_test.go
Normal file
315
internal/measurexlite/quic_test.go
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
package measurexlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/ooni/probe-cli/v3/internal/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/model/mocks"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite"
|
||||
"github.com/ooni/probe-cli/v3/internal/testingx"
|
||||
)
|
||||
|
||||
func TestNewQUICListener(t *testing.T) {
|
||||
t.Run("NewQUICListenerTrace creates a wrapped listener", func(t *testing.T) {
|
||||
underlying := &mocks.QUICListener{}
|
||||
zeroTime := time.Now()
|
||||
trace := NewTrace(0, zeroTime)
|
||||
listenert := trace.WrapQUICListener(underlying).(*quicListenerTrace)
|
||||
if listenert.QUICListener != underlying {
|
||||
t.Fatal("invalid quic dialer")
|
||||
}
|
||||
if listenert.tx != trace {
|
||||
t.Fatal("invalid trace")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Listen works as intended", func(t *testing.T) {
|
||||
t.Run("with error", func(t *testing.T) {
|
||||
zeroTime := time.Now()
|
||||
trace := NewTrace(0, zeroTime)
|
||||
mockedErr := errors.New("mocked")
|
||||
mockListener := &mocks.QUICListener{
|
||||
MockListen: func(addr *net.UDPAddr) (model.UDPLikeConn, error) {
|
||||
return nil, mockedErr
|
||||
},
|
||||
}
|
||||
listener := trace.WrapQUICListener(mockListener)
|
||||
pconn, err := listener.Listen(&net.UDPAddr{})
|
||||
if !errors.Is(err, mockedErr) {
|
||||
t.Fatal("unexpected err", err)
|
||||
}
|
||||
if pconn != nil {
|
||||
t.Fatal("expected nil conn")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("without error", func(t *testing.T) {
|
||||
zeroTime := time.Now()
|
||||
trace := NewTrace(0, zeroTime)
|
||||
mockConn := &mocks.UDPLikeConn{}
|
||||
mockListener := &mocks.QUICListener{
|
||||
MockListen: func(addr *net.UDPAddr) (model.UDPLikeConn, error) {
|
||||
return mockConn, nil
|
||||
},
|
||||
}
|
||||
listener := trace.WrapQUICListener(mockListener)
|
||||
pconn, err := listener.Listen(&net.UDPAddr{})
|
||||
if err != nil {
|
||||
t.Fatal("unexpected err", err)
|
||||
}
|
||||
conn := pconn.(*udpLikeConnTrace)
|
||||
if conn.UDPLikeConn != mockConn {
|
||||
t.Fatal("invalid conn")
|
||||
}
|
||||
if conn.tx != trace {
|
||||
t.Fatal("invalid trace")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewQUICDialerWithoutResolver(t *testing.T) {
|
||||
t.Run("NewQUICDialerWithoutResolver creates a wrapped dialer", func(t *testing.T) {
|
||||
underlying := &mocks.QUICDialer{}
|
||||
zeroTime := time.Now()
|
||||
trace := NewTrace(0, zeroTime)
|
||||
trace.NewQUICDialerWithoutResolverFn = func(listener model.QUICListener, dl model.DebugLogger) model.QUICDialer {
|
||||
return underlying
|
||||
}
|
||||
listener := &mocks.QUICListener{}
|
||||
dialer := trace.NewQUICDialerWithoutResolver(listener, model.DiscardLogger)
|
||||
dt := dialer.(*quicDialerTrace)
|
||||
if dt.qd != underlying {
|
||||
t.Fatal("invalid quic dialer")
|
||||
}
|
||||
if dt.tx != trace {
|
||||
t.Fatal("invalid trace")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DialContext calls the underlying dialer with context-based tracing", func(t *testing.T) {
|
||||
expectedErr := errors.New("mocked err")
|
||||
zeroTime := time.Now()
|
||||
trace := NewTrace(0, zeroTime)
|
||||
var hasCorrectTrace bool
|
||||
underlying := &mocks.QUICDialer{
|
||||
MockDialContext: func(ctx context.Context, network, address string, tlsConfig *tls.Config,
|
||||
quicConfig *quic.Config) (quic.EarlyConnection, error) {
|
||||
gotTrace := netxlite.ContextTraceOrDefault(ctx)
|
||||
hasCorrectTrace = (gotTrace == trace)
|
||||
return nil, expectedErr
|
||||
},
|
||||
}
|
||||
trace.NewQUICDialerWithoutResolverFn = func(listener model.QUICListener, dl model.DebugLogger) model.QUICDialer {
|
||||
return underlying
|
||||
}
|
||||
listener := &mocks.QUICListener{}
|
||||
dialer := trace.NewQUICDialerWithoutResolver(listener, model.DiscardLogger)
|
||||
ctx := context.Background()
|
||||
conn, err := dialer.DialContext(ctx, "udp", "1.1.1.1:443", &tls.Config{}, &quic.Config{})
|
||||
if !errors.Is(err, expectedErr) {
|
||||
t.Fatal("unexpected err", err)
|
||||
}
|
||||
if conn != nil {
|
||||
t.Fatal("expected nil conn")
|
||||
}
|
||||
if !hasCorrectTrace {
|
||||
t.Fatal("does not have the correct trace")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CloseIdleConnection is correctly forwarded", func(t *testing.T) {
|
||||
var called bool
|
||||
zeroTime := time.Now()
|
||||
trace := NewTrace(0, zeroTime)
|
||||
underlying := &mocks.QUICDialer{
|
||||
MockCloseIdleConnections: func() {
|
||||
called = true
|
||||
},
|
||||
}
|
||||
trace.NewQUICDialerWithoutResolverFn = func(listener model.QUICListener, dl model.DebugLogger) model.QUICDialer {
|
||||
return underlying
|
||||
}
|
||||
listener := &mocks.QUICListener{}
|
||||
dialer := trace.NewQUICDialerWithoutResolver(listener, model.DiscardLogger)
|
||||
dialer.CloseIdleConnections()
|
||||
if !called {
|
||||
t.Fatal("not called")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DialContext saves into trace", func(t *testing.T) {
|
||||
mockedErr := errors.New("mocked")
|
||||
zeroTime := time.Now()
|
||||
td := testingx.NewTimeDeterministic(zeroTime)
|
||||
trace := NewTrace(0, zeroTime)
|
||||
trace.TimeNowFn = td.Now // deterministic time tracking
|
||||
pconn := &mocks.UDPLikeConn{
|
||||
MockLocalAddr: func() net.Addr {
|
||||
return &net.UDPAddr{
|
||||
Port: 0,
|
||||
}
|
||||
},
|
||||
MockRemoteAddr: func() net.Addr {
|
||||
return &net.UDPAddr{
|
||||
Port: 0,
|
||||
}
|
||||
},
|
||||
MockSyscallConn: func() (syscall.RawConn, error) {
|
||||
return nil, mockedErr
|
||||
},
|
||||
MockClose: func() error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
listener := &mocks.QUICListener{
|
||||
MockListen: func(addr *net.UDPAddr) (model.UDPLikeConn, error) {
|
||||
return pconn, nil
|
||||
},
|
||||
}
|
||||
dialer := trace.NewQUICDialerWithoutResolver(listener, model.DiscardLogger)
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: "dns.cloudflare.com",
|
||||
}
|
||||
ctx := context.Background()
|
||||
qconn, err := dialer.DialContext(ctx, "udp", "1.1.1.1:443", tlsConfig, &quic.Config{})
|
||||
if !errors.Is(err, mockedErr) {
|
||||
t.Fatal("unexpected err", err)
|
||||
}
|
||||
if qconn != nil {
|
||||
t.Fatal("expected nil qconn")
|
||||
}
|
||||
|
||||
t.Run("QUICHandshake events", func(t *testing.T) {
|
||||
events := trace.QUICHandshakes()
|
||||
if len(events) != 1 {
|
||||
t.Fatal("expected to see single QUICHandshake event")
|
||||
}
|
||||
expectedFailure := "unknown_failure: mocked"
|
||||
expect := &model.ArchivalTLSOrQUICHandshakeResult{
|
||||
Network: "quic",
|
||||
Address: "1.1.1.1:443",
|
||||
CipherSuite: "",
|
||||
Failure: &expectedFailure,
|
||||
NegotiatedProtocol: "",
|
||||
NoTLSVerify: true,
|
||||
PeerCertificates: []model.ArchivalMaybeBinaryData{},
|
||||
ServerName: "dns.cloudflare.com",
|
||||
T: time.Second.Seconds(),
|
||||
Tags: []string{},
|
||||
TLSVersion: "",
|
||||
}
|
||||
got := events[0]
|
||||
if diff := cmp.Diff(expect, got); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Network events", func(t *testing.T) {
|
||||
events := trace.NetworkEvents()
|
||||
if len(events) != 2 {
|
||||
t.Fatal("expected to see three Network events")
|
||||
}
|
||||
|
||||
t.Run("quic_handshake_start", func(t *testing.T) {
|
||||
expect := &model.ArchivalNetworkEvent{
|
||||
Address: "",
|
||||
Failure: nil,
|
||||
NumBytes: 0,
|
||||
Operation: "quic_handshake_start",
|
||||
Proto: "",
|
||||
T: 0,
|
||||
Tags: []string{},
|
||||
}
|
||||
got := events[0]
|
||||
if diff := cmp.Diff(expect, got); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("quic_handshake_done", func(t *testing.T) {
|
||||
expect := &model.ArchivalNetworkEvent{
|
||||
Address: "",
|
||||
Failure: nil,
|
||||
NumBytes: 0,
|
||||
Operation: "quic_handshake_done",
|
||||
Proto: "",
|
||||
T: time.Second.Seconds(),
|
||||
Tags: []string{},
|
||||
}
|
||||
got := events[1]
|
||||
if diff := cmp.Diff(expect, got); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
t.Run("DialContext discards events when buffer is full", func(t *testing.T) {
|
||||
mockedErr := errors.New("mocked")
|
||||
zeroTime := time.Now()
|
||||
trace := NewTrace(0, zeroTime)
|
||||
trace.NetworkEvent = make(chan *model.ArchivalNetworkEvent) // no buffer
|
||||
trace.QUICHandshake = make(chan *model.ArchivalTLSOrQUICHandshakeResult) // no buffer
|
||||
pconn := &mocks.UDPLikeConn{
|
||||
MockLocalAddr: func() net.Addr {
|
||||
return &net.UDPAddr{
|
||||
Port: 0,
|
||||
}
|
||||
},
|
||||
MockRemoteAddr: func() net.Addr {
|
||||
return &net.UDPAddr{
|
||||
Port: 0,
|
||||
}
|
||||
},
|
||||
MockSyscallConn: func() (syscall.RawConn, error) {
|
||||
return nil, mockedErr
|
||||
},
|
||||
MockClose: func() error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
listener := &mocks.QUICListener{
|
||||
MockListen: func(addr *net.UDPAddr) (model.UDPLikeConn, error) {
|
||||
return pconn, nil
|
||||
},
|
||||
}
|
||||
dialer := trace.NewQUICDialerWithoutResolver(listener, model.DiscardLogger)
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: "dns.cloudflare.com",
|
||||
}
|
||||
ctx := context.Background()
|
||||
qconn, err := dialer.DialContext(ctx, "udp", "1.1.1.1:443", tlsConfig, &quic.Config{})
|
||||
if !errors.Is(err, mockedErr) {
|
||||
t.Fatal("unexpected err", err)
|
||||
}
|
||||
if qconn != nil {
|
||||
t.Fatal("expected nil qconn")
|
||||
}
|
||||
|
||||
t.Run("QUiCHandshake events", func(t *testing.T) {
|
||||
events := trace.QUICHandshakes()
|
||||
if len(events) != 0 {
|
||||
t.Fatal("expected to see no QUICHandshake events")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Network events", func(t *testing.T) {
|
||||
events := trace.NetworkEvents()
|
||||
if len(events) != 0 {
|
||||
t.Fatal("expected to see no network events")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
Loading…
Reference in a new issue