diff --git a/internal/netxlite/quic.go b/internal/netxlite/quic.go index 3971842..5473953 100644 --- a/internal/netxlite/quic.go +++ b/internal/netxlite/quic.go @@ -145,6 +145,8 @@ type QUICDialerResolver struct { Resolver Resolver } +var _ QUICContextDialer = &QUICDialerResolver{} + // DialContext implements QUICContextDialer.DialContext. This function // will apply the following TLS defaults: // @@ -195,3 +197,28 @@ func (d *QUICDialerResolver) lookupHost(ctx context.Context, hostname string) ([ } return d.Resolver.LookupHost(ctx, hostname) } + +// QUICDialerLogger is a dialer with logging. +type QUICDialerLogger struct { + // Dialer is the underlying QUIC dialer. + Dialer QUICContextDialer + + // Logger is the underlying logger. + Logger Logger +} + +var _ QUICContextDialer = &QUICDialerLogger{} + +// DialContext implements QUICContextDialer.DialContext. +func (d *QUICDialerLogger) DialContext( + ctx context.Context, network, address string, + tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlySession, error) { + d.Logger.Debugf("quic %s/%s...", address, network) + sess, err := d.Dialer.DialContext(ctx, network, address, tlsConfig, quicConfig) + if err != nil { + d.Logger.Debugf("quic %s/%s... %s", address, network, err) + return nil, err + } + d.Logger.Debugf("quic %s/%s... ok", address, network) + return sess, nil +} diff --git a/internal/netxlite/quic_test.go b/internal/netxlite/quic_test.go index fde7ce5..e41ede4 100644 --- a/internal/netxlite/quic_test.go +++ b/internal/netxlite/quic_test.go @@ -4,11 +4,11 @@ import ( "context" "crypto/tls" "errors" - "log" "net" "strings" "testing" + "github.com/apex/log" "github.com/google/go-cmp/cmp" "github.com/lucas-clemente/quic-go" "github.com/ooni/probe-cli/v3/internal/netxmocks" @@ -125,7 +125,7 @@ func TestQUICDialerQUICGoWorksAsIntended(t *testing.T) { } <-sess.HandshakeComplete().Done() if err := sess.CloseWithError(0, ""); err != nil { - log.Fatal(err) + t.Fatal(err) } } @@ -331,3 +331,55 @@ func TestQUICDialerResolverApplyTLSDefaults(t *testing.T) { t.Fatal("gotTLSConfig.ServerName has not been set") } } + +func TestQUICDialerLoggerSuccess(t *testing.T) { + d := &QUICDialerLogger{ + Dialer: &netxmocks.QUICContextDialer{ + MockDialContext: func(ctx context.Context, network string, + address string, tlsConfig *tls.Config, + quicConfig *quic.Config) (quic.EarlySession, error) { + return &netxmocks.QUICEarlySession{ + MockCloseWithError: func( + code quic.ApplicationErrorCode, reason string) error { + return nil + }, + }, nil + }, + }, + Logger: log.Log, + } + ctx := context.Background() + tlsConfig := &tls.Config{} + quicConfig := &quic.Config{} + sess, err := d.DialContext(ctx, "udp", "8.8.8.8:443", tlsConfig, quicConfig) + if err != nil { + t.Fatal(err) + } + if err := sess.CloseWithError(0, ""); err != nil { + t.Fatal(err) + } +} + +func TestQUICDialerLoggerFailure(t *testing.T) { + expected := errors.New("mocked error") + d := &QUICDialerLogger{ + Dialer: &netxmocks.QUICContextDialer{ + MockDialContext: func(ctx context.Context, network string, + address string, tlsConfig *tls.Config, + quicConfig *quic.Config) (quic.EarlySession, error) { + return nil, expected + }, + }, + Logger: log.Log, + } + ctx := context.Background() + tlsConfig := &tls.Config{} + quicConfig := &quic.Config{} + sess, err := d.DialContext(ctx, "udp", "8.8.8.8:443", tlsConfig, quicConfig) + if !errors.Is(err, expected) { + t.Fatal("not the error we expected", err) + } + if sess != nil { + t.Fatal("expected nil session") + } +} diff --git a/internal/netxmocks/quic.go b/internal/netxmocks/quic.go index 765e2ef..6a38b52 100644 --- a/internal/netxmocks/quic.go +++ b/internal/netxmocks/quic.go @@ -29,3 +29,100 @@ func (qcd *QUICContextDialer) DialContext(ctx context.Context, network, address tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlySession, error) { return qcd.MockDialContext(ctx, network, address, tlsConfig, quicConfig) } + +// QUICEarlySession is a mockable quic.EarlySession. +type QUICEarlySession struct { + MockAcceptStream func(context.Context) (quic.Stream, error) + MockAcceptUniStream func(context.Context) (quic.ReceiveStream, error) + MockOpenStream func() (quic.Stream, error) + MockOpenStreamSync func(ctx context.Context) (quic.Stream, error) + MockOpenUniStream func() (quic.SendStream, error) + MockOpenUniStreamSync func(ctx context.Context) (quic.SendStream, error) + MockLocalAddr func() net.Addr + MockRemoteAddr func() net.Addr + MockCloseWithError func(code quic.ApplicationErrorCode, reason string) error + MockContext func() context.Context + MockConnectionState func() quic.ConnectionState + MockHandshakeComplete func() context.Context + MockNextSession func() quic.Session + MockSendMessage func(b []byte) error + MockReceiveMessage func() ([]byte, error) +} + +var _ quic.EarlySession = &QUICEarlySession{} + +// AcceptStream calls MockAcceptStream. +func (s *QUICEarlySession) AcceptStream(ctx context.Context) (quic.Stream, error) { + return s.MockAcceptStream(ctx) +} + +// AcceptUniStream calls MockAcceptUniStream. +func (s *QUICEarlySession) AcceptUniStream(ctx context.Context) (quic.ReceiveStream, error) { + return s.MockAcceptUniStream(ctx) +} + +// OpenStream calls MockOpenStream. +func (s *QUICEarlySession) OpenStream() (quic.Stream, error) { + return s.MockOpenStream() +} + +// OpenStreamSync calls MockOpenStreamSync. +func (s *QUICEarlySession) OpenStreamSync(ctx context.Context) (quic.Stream, error) { + return s.MockOpenStreamSync(ctx) +} + +// OpenUniStream calls MockOpenUniStream. +func (s *QUICEarlySession) OpenUniStream() (quic.SendStream, error) { + return s.MockOpenUniStream() +} + +// OpenUniStreamSync calls MockOpenUniStreamSync. +func (s *QUICEarlySession) OpenUniStreamSync(ctx context.Context) (quic.SendStream, error) { + return s.MockOpenUniStreamSync(ctx) +} + +// LocalAddr class MockLocalAddr. +func (c *QUICEarlySession) LocalAddr() net.Addr { + return c.MockLocalAddr() +} + +// RemoteAddr calls MockRemoteAddr. +func (c *QUICEarlySession) RemoteAddr() net.Addr { + return c.MockRemoteAddr() +} + +// CloseWithError calls MockCloseWithError. +func (c *QUICEarlySession) CloseWithError( + code quic.ApplicationErrorCode, reason string) error { + return c.MockCloseWithError(code, reason) +} + +// Context calls MockContext. +func (s *QUICEarlySession) Context() context.Context { + return s.MockContext() +} + +// ConnectionState calls MockConnectionState. +func (s *QUICEarlySession) ConnectionState() quic.ConnectionState { + return s.MockConnectionState() +} + +// HandshakeComplete calls MockHandshakeComplete. +func (s *QUICEarlySession) HandshakeComplete() context.Context { + return s.MockHandshakeComplete() +} + +// NextSession calls MockNextSession. +func (s *QUICEarlySession) NextSession() quic.Session { + return s.MockNextSession() +} + +// SendMessage calls MockSendMessage. +func (s *QUICEarlySession) SendMessage(b []byte) error { + return s.MockSendMessage(b) +} + +// ReceiveMessage calls MockReceiveMessage. +func (s *QUICEarlySession) ReceiveMessage() ([]byte, error) { + return s.MockReceiveMessage() +} diff --git a/internal/netxmocks/quic_test.go b/internal/netxmocks/quic_test.go index 1afc610..fd7e7e0 100644 --- a/internal/netxmocks/quic_test.go +++ b/internal/netxmocks/quic_test.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "errors" "net" + "reflect" "testing" "github.com/lucas-clemente/quic-go" @@ -44,3 +45,223 @@ func TestQUICContextDialerDialContext(t *testing.T) { t.Fatal("expected nil session") } } + +func TestQUICEarlySessionAcceptStream(t *testing.T) { + expected := errors.New("mocked error") + sess := &QUICEarlySession{ + MockAcceptStream: func(ctx context.Context) (quic.Stream, error) { + return nil, expected + }, + } + ctx := context.Background() + stream, err := sess.AcceptStream(ctx) + if !errors.Is(err, expected) { + t.Fatal("not the error we expected", err) + } + if stream != nil { + t.Fatal("expected nil stream here") + } +} + +func TestQUICEarlySessionAcceptUniStream(t *testing.T) { + expected := errors.New("mocked error") + sess := &QUICEarlySession{ + MockAcceptUniStream: func(ctx context.Context) (quic.ReceiveStream, error) { + return nil, expected + }, + } + ctx := context.Background() + stream, err := sess.AcceptUniStream(ctx) + if !errors.Is(err, expected) { + t.Fatal("not the error we expected", err) + } + if stream != nil { + t.Fatal("expected nil stream here") + } +} + +func TestQUICEarlySessionOpenStream(t *testing.T) { + expected := errors.New("mocked error") + sess := &QUICEarlySession{ + MockOpenStream: func() (quic.Stream, error) { + return nil, expected + }, + } + stream, err := sess.OpenStream() + if !errors.Is(err, expected) { + t.Fatal("not the error we expected", err) + } + if stream != nil { + t.Fatal("expected nil stream here") + } +} + +func TestQUICEarlySessionOpenStreamSync(t *testing.T) { + expected := errors.New("mocked error") + sess := &QUICEarlySession{ + MockOpenStreamSync: func(ctx context.Context) (quic.Stream, error) { + return nil, expected + }, + } + ctx := context.Background() + stream, err := sess.OpenStreamSync(ctx) + if !errors.Is(err, expected) { + t.Fatal("not the error we expected", err) + } + if stream != nil { + t.Fatal("expected nil stream here") + } +} + +func TestQUICEarlySessionOpenUniStream(t *testing.T) { + expected := errors.New("mocked error") + sess := &QUICEarlySession{ + MockOpenUniStream: func() (quic.SendStream, error) { + return nil, expected + }, + } + stream, err := sess.OpenUniStream() + if !errors.Is(err, expected) { + t.Fatal("not the error we expected", err) + } + if stream != nil { + t.Fatal("expected nil stream here") + } +} + +func TestQUICEarlySessionOpenUniStreamSync(t *testing.T) { + expected := errors.New("mocked error") + sess := &QUICEarlySession{ + MockOpenUniStreamSync: func(ctx context.Context) (quic.SendStream, error) { + return nil, expected + }, + } + ctx := context.Background() + stream, err := sess.OpenUniStreamSync(ctx) + if !errors.Is(err, expected) { + t.Fatal("not the error we expected", err) + } + if stream != nil { + t.Fatal("expected nil stream here") + } +} + +func TestQUICEarlySessionLocalAddr(t *testing.T) { + sess := &QUICEarlySession{ + MockLocalAddr: func() net.Addr { + return &net.UDPAddr{} + }, + } + addr := sess.LocalAddr() + if !reflect.ValueOf(addr).Elem().IsZero() { + t.Fatal("expected a zero address here") + } +} + +func TestQUICEarlySessionRemoteAddr(t *testing.T) { + sess := &QUICEarlySession{ + MockRemoteAddr: func() net.Addr { + return &net.UDPAddr{} + }, + } + addr := sess.RemoteAddr() + if !reflect.ValueOf(addr).Elem().IsZero() { + t.Fatal("expected a zero address here") + } +} + +func TestQUICEarlySessionCloseWithError(t *testing.T) { + expected := errors.New("mocked error") + sess := &QUICEarlySession{ + MockCloseWithError: func( + code quic.ApplicationErrorCode, reason string) error { + return expected + }, + } + err := sess.CloseWithError(0, "") + if !errors.Is(err, expected) { + t.Fatal("not the error we expected", err) + } +} + +func TestQUICEarlySessionContext(t *testing.T) { + ctx := context.Background() + sess := &QUICEarlySession{ + MockContext: func() context.Context { + return ctx + }, + } + out := sess.Context() + if !reflect.DeepEqual(ctx, out) { + t.Fatal("not the context we expected") + } +} + +func TestQUICEarlySessionConnectionState(t *testing.T) { + state := quic.ConnectionState{SupportsDatagrams: true} + sess := &QUICEarlySession{ + MockConnectionState: func() quic.ConnectionState { + return state + }, + } + out := sess.ConnectionState() + if !reflect.DeepEqual(state, out) { + t.Fatal("not the context we expected") + } +} + +func TestQUICEarlySessionHandshakeComplete(t *testing.T) { + ctx := context.Background() + sess := &QUICEarlySession{ + MockHandshakeComplete: func() context.Context { + return ctx + }, + } + out := sess.HandshakeComplete() + if !reflect.DeepEqual(ctx, out) { + t.Fatal("not the context we expected") + } +} + +func TestQUICEarlySessionNextSession(t *testing.T) { + next := &QUICEarlySession{} + sess := &QUICEarlySession{ + MockNextSession: func() quic.Session { + return next + }, + } + out := sess.NextSession() + if !reflect.DeepEqual(next, out) { + t.Fatal("not the context we expected") + } +} + +func TestQUICEarlySessionSendMessage(t *testing.T) { + expected := errors.New("mocked error") + sess := &QUICEarlySession{ + MockSendMessage: func(b []byte) error { + return expected + }, + } + b := make([]byte, 17) + err := sess.SendMessage(b) + if !errors.Is(err, expected) { + t.Fatal("not the error we expected", err) + } +} + +func TestQUICEarlySessionReceiveMessage(t *testing.T) { + expected := errors.New("mocked error") + sess := &QUICEarlySession{ + MockReceiveMessage: func() ([]byte, error) { + return nil, expected + }, + } + b, err := sess.ReceiveMessage() + if !errors.Is(err, expected) { + t.Fatal("not the error we expected", err) + } + if b != nil { + t.Fatal("expected nil buffer here") + } +} diff --git a/internal/netxmocks/tlsconn_test.go b/internal/netxmocks/tlsconn_test.go new file mode 100644 index 0000000..255e441 --- /dev/null +++ b/internal/netxmocks/tlsconn_test.go @@ -0,0 +1,34 @@ +package netxmocks + +import ( + "crypto/tls" + "errors" + "reflect" + "testing" +) + +func TestTLSConnConnectionState(t *testing.T) { + state := tls.ConnectionState{Version: tls.VersionTLS12} + c := &TLSConn{ + MockConnectionState: func() tls.ConnectionState { + return state + }, + } + out := c.ConnectionState() + if !reflect.DeepEqual(out, state) { + t.Fatal("not the result we expected") + } +} + +func TestTLSConnHandshake(t *testing.T) { + expected := errors.New("mocked error") + c := &TLSConn{ + MockHandshake: func() error { + return expected + }, + } + err := c.Handshake() + if !errors.Is(err, expected) { + t.Fatal("not the error we expected", err) + } +}