303 lines
8.3 KiB
Go
303 lines
8.3 KiB
Go
package measurexlite
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"math"
|
|
"net"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"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 TestNewDialerWithoutResolver(t *testing.T) {
|
|
t.Run("NewDialerWithoutResolver creates a wrapped dialer", func(t *testing.T) {
|
|
underlying := &mocks.Dialer{}
|
|
zeroTime := time.Now()
|
|
trace := NewTrace(0, zeroTime)
|
|
trace.NewDialerWithoutResolverFn = func(dl model.DebugLogger) model.Dialer {
|
|
return underlying
|
|
}
|
|
dialer := trace.NewDialerWithoutResolver(model.DiscardLogger)
|
|
dt := dialer.(*dialerTrace)
|
|
if dt.d != underlying {
|
|
t.Fatal("invalid 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.Dialer{
|
|
MockDialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
|
|
gotTrace := netxlite.ContextTraceOrDefault(ctx)
|
|
hasCorrectTrace = (gotTrace == trace)
|
|
return nil, expectedErr
|
|
},
|
|
}
|
|
trace.NewDialerWithoutResolverFn = func(dl model.DebugLogger) model.Dialer {
|
|
return underlying
|
|
}
|
|
dialer := trace.NewDialerWithoutResolver(model.DiscardLogger)
|
|
ctx := context.Background()
|
|
conn, err := dialer.DialContext(ctx, "tcp", "1.1.1.1:443")
|
|
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.Dialer{
|
|
MockCloseIdleConnections: func() {
|
|
called = true
|
|
},
|
|
}
|
|
trace.NewDialerWithoutResolverFn = func(dl model.DebugLogger) model.Dialer {
|
|
return underlying
|
|
}
|
|
dialer := trace.NewDialerWithoutResolver(model.DiscardLogger)
|
|
dialer.CloseIdleConnections()
|
|
if !called {
|
|
t.Fatal("not called")
|
|
}
|
|
})
|
|
|
|
t.Run("DialContext saves into the trace", func(t *testing.T) {
|
|
zeroTime := time.Now()
|
|
td := testingx.NewTimeDeterministic(zeroTime)
|
|
trace := NewTrace(0, zeroTime)
|
|
trace.TimeNowFn = td.Now // deterministic time tracking
|
|
dialer := trace.NewDialerWithoutResolver(model.DiscardLogger)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel() // we cancel immediately so connect is ~instantaneous
|
|
conn, err := dialer.DialContext(ctx, "tcp", "1.1.1.1:443")
|
|
if err == nil || err.Error() != netxlite.FailureInterrupted {
|
|
t.Fatal("unexpected err", err)
|
|
}
|
|
if conn != nil {
|
|
t.Fatal("expected nil conn")
|
|
}
|
|
|
|
expectedFailure := netxlite.FailureInterrupted
|
|
|
|
t.Run("for TCPConnect", func(t *testing.T) {
|
|
events := trace.TCPConnects()
|
|
if len(events) != 1 {
|
|
t.Fatal("expected to see single TCPConnect event")
|
|
}
|
|
expect := &model.ArchivalTCPConnectResult{
|
|
IP: "1.1.1.1",
|
|
Port: 443,
|
|
Status: model.ArchivalTCPConnectStatus{
|
|
Blocked: nil,
|
|
Failure: &expectedFailure,
|
|
Success: false,
|
|
},
|
|
T: time.Second.Seconds(),
|
|
}
|
|
got := events[0]
|
|
if diff := cmp.Diff(expect, got); diff != "" {
|
|
t.Fatal(diff)
|
|
}
|
|
})
|
|
|
|
t.Run("for NetworkEvents", func(t *testing.T) {
|
|
events := trace.NetworkEvents()
|
|
if len(events) != 1 {
|
|
t.Fatal("expected to see single NetworkEvent event")
|
|
}
|
|
expectedFailure := netxlite.FailureInterrupted
|
|
expect := &model.ArchivalNetworkEvent{
|
|
Address: "1.1.1.1:443",
|
|
Failure: &expectedFailure,
|
|
NumBytes: 0,
|
|
Operation: netxlite.ConnectOperation,
|
|
Proto: "tcp",
|
|
T0: 0,
|
|
T: time.Second.Seconds(),
|
|
TransactionID: 0,
|
|
Tags: []string{},
|
|
}
|
|
got := events[0]
|
|
if diff := cmp.Diff(expect, got); diff != "" {
|
|
t.Fatal(diff)
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("DialContext discards events when buffer is full", func(t *testing.T) {
|
|
zeroTime := time.Now()
|
|
trace := NewTrace(0, zeroTime)
|
|
trace.tcpConnect = make(chan *model.ArchivalTCPConnectResult) // no buffer
|
|
trace.networkEvent = make(chan *model.ArchivalNetworkEvent) // ditto
|
|
dialer := trace.NewDialerWithoutResolver(model.DiscardLogger)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel() // we cancel immediately so connect is ~instantaneous
|
|
conn, err := dialer.DialContext(ctx, "tcp", "1.1.1.1:443")
|
|
if err == nil || err.Error() != netxlite.FailureInterrupted {
|
|
t.Fatal("unexpected err", err)
|
|
}
|
|
if conn != nil {
|
|
t.Fatal("expected nil conn")
|
|
}
|
|
|
|
t.Run("for TCPConnect", func(t *testing.T) {
|
|
events := trace.TCPConnects()
|
|
if len(events) != 0 {
|
|
t.Fatal("expected to see no TCPConnect events")
|
|
}
|
|
})
|
|
|
|
t.Run("for NetworkEvents", func(t *testing.T) {
|
|
events := trace.NetworkEvents()
|
|
if len(events) != 0 {
|
|
t.Fatal("expected to see no NetworkEvent events")
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("DialContext ignores UDP connect attempts", func(t *testing.T) {
|
|
zeroTime := time.Now()
|
|
trace := NewTrace(0, zeroTime)
|
|
dialer := trace.NewDialerWithoutResolver(model.DiscardLogger)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel() // we cancel immediately so connect is ~instantaneous
|
|
conn, err := dialer.DialContext(ctx, "udp", "1.1.1.1:443")
|
|
if err == nil || err.Error() != netxlite.FailureInterrupted {
|
|
t.Fatal("unexpected err", err)
|
|
}
|
|
if conn != nil {
|
|
t.Fatal("expected nil conn")
|
|
}
|
|
|
|
t.Run("for TCP connect", func(t *testing.T) {
|
|
events := trace.TCPConnects()
|
|
if len(events) != 0 {
|
|
t.Fatal("expected to see no TCPConnect events")
|
|
}
|
|
})
|
|
|
|
t.Run("for NetworkEvents", func(t *testing.T) {
|
|
events := trace.NetworkEvents()
|
|
if len(events) != 0 {
|
|
t.Fatal("expected to see no NetworkEvent events")
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("DialContext uses a dialer without a resolver", func(t *testing.T) {
|
|
zeroTime := time.Now()
|
|
trace := NewTrace(0, zeroTime)
|
|
dialer := trace.NewDialerWithoutResolver(model.DiscardLogger)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel() // we cancel immediately so connect is ~instantaneous
|
|
conn, err := dialer.DialContext(ctx, "udp", "dns.google:443") // domain
|
|
if !errors.Is(err, netxlite.ErrNoResolver) {
|
|
t.Fatal("unexpected err", err)
|
|
}
|
|
if conn != nil {
|
|
t.Fatal("expected nil conn")
|
|
}
|
|
events := trace.TCPConnects()
|
|
if len(events) != 0 {
|
|
t.Fatal("expected to see no TCPConnect events")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestFirstTCPConnect(t *testing.T) {
|
|
t.Run("returns nil when buffer is empty", func(t *testing.T) {
|
|
zeroTime := time.Now()
|
|
trace := NewTrace(0, zeroTime)
|
|
got := trace.FirstTCPConnectOrNil()
|
|
if got != nil {
|
|
t.Fatal("expected nil event")
|
|
}
|
|
})
|
|
|
|
t.Run("return first non-nil TCPConnect", func(t *testing.T) {
|
|
filler := func(tx *Trace, events []*model.ArchivalTCPConnectResult) {
|
|
for _, ev := range events {
|
|
tx.tcpConnect <- ev
|
|
}
|
|
}
|
|
zeroTime := time.Now()
|
|
trace := NewTrace(0, zeroTime)
|
|
expect := []*model.ArchivalTCPConnectResult{{
|
|
IP: "1.1.1.1",
|
|
Port: 443,
|
|
Status: model.ArchivalTCPConnectStatus{
|
|
Blocked: nil,
|
|
Failure: nil,
|
|
Success: true,
|
|
},
|
|
}, {
|
|
IP: "0.0.0.0",
|
|
Port: 443,
|
|
Status: model.ArchivalTCPConnectStatus{
|
|
Blocked: nil,
|
|
Failure: nil,
|
|
Success: true,
|
|
},
|
|
}}
|
|
filler(trace, expect)
|
|
got := trace.FirstTCPConnectOrNil()
|
|
if diff := cmp.Diff(got, expect[0]); diff != "" {
|
|
t.Fatal(diff)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestArchivalSplitHostPort(t *testing.T) {
|
|
addr, port := archivalSplitHostPort("1.1.1.1") // missing port
|
|
if addr != "" {
|
|
t.Fatal("invalid addr", addr)
|
|
}
|
|
if port != "" {
|
|
t.Fatal("invalid port", port)
|
|
}
|
|
}
|
|
|
|
func TestArchivalPortToString(t *testing.T) {
|
|
t.Run("with invalid number", func(t *testing.T) {
|
|
port := archivalPortToString("antani")
|
|
if port != 0 {
|
|
t.Fatal("invalid port")
|
|
}
|
|
})
|
|
|
|
t.Run("with negative number", func(t *testing.T) {
|
|
port := archivalPortToString("-1")
|
|
if port != 0 {
|
|
t.Fatal("invalid port")
|
|
}
|
|
})
|
|
|
|
t.Run("with too-large positive number", func(t *testing.T) {
|
|
port := archivalPortToString(strconv.Itoa(math.MaxUint16 + 1))
|
|
if port != 0 {
|
|
t.Fatal("invalid port")
|
|
}
|
|
})
|
|
}
|