diff --git a/internal/engine/netx/netx.go b/internal/engine/netx/netx.go index a2cccd4..2352066 100644 --- a/internal/engine/netx/netx.go +++ b/internal/engine/netx/netx.go @@ -171,7 +171,7 @@ func NewQUICDialer(config Config) QUICDialer { var d quicdialer.ContextDialer = &netxlite.QUICDialerQUICGo{ QUICListener: ql, } - d = quicdialer.ErrorWrapperDialer{Dialer: d} + d = &errorsx.ErrorWrapperQUICDialer{Dialer: d} if config.TLSSaver != nil { d = quicdialer.HandshakeSaver{Saver: config.TLSSaver, Dialer: d} } diff --git a/internal/engine/netx/quicdialer/errorwrapper.go b/internal/engine/netx/quicdialer/errorwrapper.go deleted file mode 100644 index a3e82e9..0000000 --- a/internal/engine/netx/quicdialer/errorwrapper.go +++ /dev/null @@ -1,30 +0,0 @@ -package quicdialer - -import ( - "context" - "crypto/tls" - - "github.com/lucas-clemente/quic-go" - "github.com/ooni/probe-cli/v3/internal/errorsx" -) - -// ErrorWrapperDialer is a dialer that performs quic err wrapping -type ErrorWrapperDialer struct { - Dialer ContextDialer -} - -// DialContext implements ContextDialer.DialContext -func (d ErrorWrapperDialer) DialContext( - ctx context.Context, network string, host string, - tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlySession, error) { - sess, err := d.Dialer.DialContext(ctx, network, host, tlsCfg, cfg) - err = errorsx.SafeErrWrapperBuilder{ - Classifier: errorsx.ClassifyQUICFailure, - Error: err, - Operation: errorsx.QUICHandshakeOperation, - }.MaybeBuild() - if err != nil { - return nil, err - } - return sess, nil -} diff --git a/internal/engine/netx/quicdialer/errorwrapper_test.go b/internal/engine/netx/quicdialer/errorwrapper_test.go deleted file mode 100644 index 77259e2..0000000 --- a/internal/engine/netx/quicdialer/errorwrapper_test.go +++ /dev/null @@ -1,85 +0,0 @@ -package quicdialer_test - -import ( - "context" - "crypto/tls" - "errors" - "io" - "testing" - - "github.com/lucas-clemente/quic-go" - "github.com/ooni/probe-cli/v3/internal/engine/netx/quicdialer" - "github.com/ooni/probe-cli/v3/internal/errorsx" - "github.com/ooni/probe-cli/v3/internal/netxlite" -) - -func TestErrorWrapperFailure(t *testing.T) { - ctx := context.Background() - d := quicdialer.ErrorWrapperDialer{ - Dialer: MockDialer{Sess: nil, Err: io.EOF}} - sess, err := d.DialContext( - ctx, "udp", "www.google.com:443", &tls.Config{}, &quic.Config{}) - if sess != nil { - t.Fatal("expected a nil sess here") - } - errorWrapperCheckErr(t, err, errorsx.QUICHandshakeOperation) -} - -func errorWrapperCheckErr(t *testing.T, err error, op string) { - if !errors.Is(err, io.EOF) { - t.Fatal("expected another error here") - } - var errWrapper *errorsx.ErrWrapper - if !errors.As(err, &errWrapper) { - t.Fatal("cannot cast to ErrWrapper") - } - if errWrapper.Operation != op { - t.Fatal("unexpected Operation") - } - if errWrapper.Failure != errorsx.FailureEOFError { - t.Fatal("unexpected failure") - } -} - -func TestErrorWrapperInvalidCertificate(t *testing.T) { - nextprotos := []string{"h3"} - servername := "example.com" - tlsConf := &tls.Config{ - NextProtos: nextprotos, - ServerName: servername, - } - - dlr := quicdialer.ErrorWrapperDialer{Dialer: &netxlite.QUICDialerQUICGo{ - QUICListener: &netxlite.QUICListenerStdlib{}, - }} - // use Google IP - sess, err := dlr.DialContext(context.Background(), "udp", - "216.58.212.164:443", tlsConf, &quic.Config{}) - if err == nil { - t.Fatal("expected an error here") - } - if sess != nil { - t.Fatal("expected nil sess here") - } - if err.Error() != errorsx.FailureSSLInvalidCertificate { - t.Fatal("unexpected failure") - } -} - -func TestErrorWrapperSuccess(t *testing.T) { - ctx := context.Background() - tlsConf := &tls.Config{ - NextProtos: []string{"h3"}, - ServerName: "www.google.com", - } - d := quicdialer.ErrorWrapperDialer{Dialer: &netxlite.QUICDialerQUICGo{ - QUICListener: &netxlite.QUICListenerStdlib{}, - }} - sess, err := d.DialContext(ctx, "udp", "216.58.212.164:443", tlsConf, &quic.Config{}) - if err != nil { - t.Fatal(err) - } - if sess == nil { - t.Fatal("expected non-nil sess here") - } -} diff --git a/internal/errorsx/integration_test.go b/internal/errorsx/integration_test.go new file mode 100644 index 0000000..187e0d2 --- /dev/null +++ b/internal/errorsx/integration_test.go @@ -0,0 +1,54 @@ +package errorsx_test + +import ( + "context" + "crypto/tls" + "testing" + + "github.com/lucas-clemente/quic-go" + "github.com/ooni/probe-cli/v3/internal/errorsx" + "github.com/ooni/probe-cli/v3/internal/netxlite" +) + +func TestErrorWrapperQUICDialerInvalidCertificate(t *testing.T) { + nextprotos := []string{"h3"} + servername := "example.com" + tlsConf := &tls.Config{ + NextProtos: nextprotos, + ServerName: servername, + } + + dlr := &errorsx.ErrorWrapperQUICDialer{Dialer: &netxlite.QUICDialerQUICGo{ + QUICListener: &netxlite.QUICListenerStdlib{}, + }} + // use Google IP + sess, err := dlr.DialContext(context.Background(), "udp", + "216.58.212.164:443", tlsConf, &quic.Config{}) + if err == nil { + t.Fatal("expected an error here") + } + if sess != nil { + t.Fatal("expected nil sess here") + } + if err.Error() != errorsx.FailureSSLInvalidCertificate { + t.Fatal("unexpected failure") + } +} + +func TestErrorWrapperQUICDialerSuccess(t *testing.T) { + ctx := context.Background() + tlsConf := &tls.Config{ + NextProtos: []string{"h3"}, + ServerName: "www.google.com", + } + d := &errorsx.ErrorWrapperQUICDialer{Dialer: &netxlite.QUICDialerQUICGo{ + QUICListener: &netxlite.QUICListenerStdlib{}, + }} + sess, err := d.DialContext(ctx, "udp", "216.58.212.164:443", tlsConf, &quic.Config{}) + if err != nil { + t.Fatal(err) + } + if sess == nil { + t.Fatal("expected non-nil sess here") + } +} diff --git a/internal/errorsx/quic.go b/internal/errorsx/quic.go new file mode 100644 index 0000000..11a9f1f --- /dev/null +++ b/internal/errorsx/quic.go @@ -0,0 +1,38 @@ +package errorsx + +import ( + "context" + "crypto/tls" + + "github.com/lucas-clemente/quic-go" +) + +// QUICContextDialer is a dialer for QUIC using Context. +type QUICContextDialer interface { + // DialContext establishes a new QUIC session using the given + // network and address. The tlsConfig and the quicConfig arguments + // MUST NOT be nil. Returns either the session or an error. + DialContext(ctx context.Context, network, address string, + tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlySession, error) +} + +// ErrorWrapperQUICDialer is a dialer that performs quic err wrapping +type ErrorWrapperQUICDialer struct { + Dialer QUICContextDialer +} + +// DialContext implements ContextDialer.DialContext +func (d *ErrorWrapperQUICDialer) DialContext( + ctx context.Context, network string, host string, + tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlySession, error) { + sess, err := d.Dialer.DialContext(ctx, network, host, tlsCfg, cfg) + err = SafeErrWrapperBuilder{ + Classifier: ClassifyQUICFailure, + Error: err, + Operation: QUICHandshakeOperation, + }.MaybeBuild() + if err != nil { + return nil, err + } + return sess, nil +} diff --git a/internal/errorsx/quic_test.go b/internal/errorsx/quic_test.go new file mode 100644 index 0000000..da6c8f0 --- /dev/null +++ b/internal/errorsx/quic_test.go @@ -0,0 +1,39 @@ +package errorsx + +import ( + "context" + "crypto/tls" + "errors" + "io" + "testing" + + "github.com/lucas-clemente/quic-go" + "github.com/ooni/probe-cli/v3/internal/netxmocks" +) + +func TestErrorWrapperQUICDialerFailure(t *testing.T) { + ctx := context.Background() + d := &ErrorWrapperQUICDialer{Dialer: &netxmocks.QUICContextDialer{ + MockDialContext: func(ctx context.Context, network, address string, tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlySession, error) { + return nil, io.EOF + }, + }} + sess, err := d.DialContext( + ctx, "udp", "www.google.com:443", &tls.Config{}, &quic.Config{}) + if sess != nil { + t.Fatal("expected a nil sess here") + } + if !errors.Is(err, io.EOF) { + t.Fatal("expected another error here") + } + var errWrapper *ErrWrapper + if !errors.As(err, &errWrapper) { + t.Fatal("cannot cast to ErrWrapper") + } + if errWrapper.Operation != QUICHandshakeOperation { + t.Fatal("unexpected Operation") + } + if errWrapper.Failure != FailureEOFError { + t.Fatal("unexpected failure") + } +}