9ffa124511
* upgrade to our go.mod enabled of psiphon-tunnel-core such that we're now using v2.0.24 of the tunnel-core; * upgrade to the latest lucas-clemente/quic-go release; * upgrade to the latest ooni/oohttp release (which is based on go1.19 but the diff seems good enough to continue using go1.18.x as well); * upgrade to the latest ooni/oocrypto release (for which we can make the same remarks regarding using go1.18.x); * deal with changes in lucas-clemente/quic-go API as well as changes in what a go1.19 *tls.Conn compatible type should look like. Unfortunately, we cannot switch to go1.19 because psiphon forks quic-go and their fork's still not building using such a version of go. Part of ooni/probe#2211.
942 lines
25 KiB
Go
942 lines
25 KiB
Go
package netxlite
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"errors"
|
|
"io"
|
|
"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/model"
|
|
"github.com/ooni/probe-cli/v3/internal/model/mocks"
|
|
)
|
|
|
|
func TestNewQUICListener(t *testing.T) {
|
|
ql := NewQUICListener()
|
|
qew := ql.(*quicListenerErrWrapper)
|
|
_ = qew.QUICListener.(*quicListenerStdlib)
|
|
}
|
|
|
|
type extensionQUICDialerFirst struct {
|
|
model.QUICDialer
|
|
}
|
|
|
|
type quicDialerWrapperFirst struct{}
|
|
|
|
func (*quicDialerWrapperFirst) WrapQUICDialer(qd model.QUICDialer) model.QUICDialer {
|
|
return &extensionQUICDialerFirst{qd}
|
|
}
|
|
|
|
type extensionQUICDialerSecond struct {
|
|
model.QUICDialer
|
|
}
|
|
|
|
type quicDialerWrapperSecond struct {
|
|
model.QUICDialer
|
|
}
|
|
|
|
func (*quicDialerWrapperSecond) WrapQUICDialer(qd model.QUICDialer) model.QUICDialer {
|
|
return &extensionQUICDialerSecond{qd}
|
|
}
|
|
|
|
func TestNewQUICDialer(t *testing.T) {
|
|
ql := NewQUICListener()
|
|
extensions := []model.QUICDialerWrapper{
|
|
&quicDialerWrapperFirst{},
|
|
nil, // explicitly test for this documented case
|
|
&quicDialerWrapperSecond{},
|
|
}
|
|
dlr := NewQUICDialerWithoutResolver(ql, log.Log, extensions...)
|
|
logger := dlr.(*quicDialerLogger)
|
|
if logger.Logger != log.Log {
|
|
t.Fatal("invalid logger")
|
|
}
|
|
resolver := logger.Dialer.(*quicDialerResolver)
|
|
if _, okay := resolver.Resolver.(*NullResolver); !okay {
|
|
t.Fatal("invalid resolver type")
|
|
}
|
|
logger = resolver.Dialer.(*quicDialerLogger)
|
|
if logger.Logger != log.Log {
|
|
t.Fatal("invalid logger")
|
|
}
|
|
ext2 := logger.Dialer.(*extensionQUICDialerSecond)
|
|
ext1 := ext2.QUICDialer.(*extensionQUICDialerFirst)
|
|
errWrapper := ext1.QUICDialer.(*quicDialerErrWrapper)
|
|
handshakeCompleter := errWrapper.QUICDialer.(*quicDialerHandshakeCompleter)
|
|
base := handshakeCompleter.Dialer.(*quicDialerQUICGo)
|
|
if base.QUICListener != ql {
|
|
t.Fatal("invalid quic listener")
|
|
}
|
|
}
|
|
|
|
func TestParseUDPAddr(t *testing.T) {
|
|
t.Run("cannot split host and port", func(t *testing.T) {
|
|
addr, err := ParseUDPAddr("1.2.3.4")
|
|
if err == nil || !strings.HasSuffix(err.Error(), "missing port in address") {
|
|
t.Fatal("unexpected error", err)
|
|
}
|
|
if addr != nil {
|
|
t.Fatal("expected nil addr")
|
|
}
|
|
})
|
|
|
|
t.Run("with invalid IP addr", func(t *testing.T) {
|
|
addr, err := ParseUDPAddr("www.google.com:80")
|
|
if !errors.Is(err, ErrInvalidIP) {
|
|
t.Fatal("unexpected error", err)
|
|
}
|
|
if addr != nil {
|
|
t.Fatal("expected nil addr")
|
|
}
|
|
})
|
|
|
|
t.Run("with invalid port", func(t *testing.T) {
|
|
addr, err := ParseUDPAddr("8.8.8.8:www")
|
|
if err == nil || !strings.HasSuffix(err.Error(), "invalid syntax") {
|
|
t.Fatal("unexpected error", err)
|
|
}
|
|
if addr != nil {
|
|
t.Fatal("expected nil addr")
|
|
}
|
|
})
|
|
|
|
t.Run("with valid input", func(t *testing.T) {
|
|
addr, err := ParseUDPAddr("8.8.8.8:80")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if addr.IP.String() != "8.8.8.8" {
|
|
t.Fatal("invalid IP")
|
|
}
|
|
if addr.Port != 80 {
|
|
t.Fatal("invalid port")
|
|
}
|
|
if addr.Zone != "" {
|
|
t.Fatal("invalid zone")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestQUICDialerQUICGo(t *testing.T) {
|
|
t.Run("DialContext", func(t *testing.T) {
|
|
t.Run("cannot split host port", func(t *testing.T) {
|
|
tlsConfig := &tls.Config{
|
|
ServerName: "www.google.com",
|
|
}
|
|
systemdialer := quicDialerQUICGo{
|
|
QUICListener: &quicListenerStdlib{},
|
|
}
|
|
defer systemdialer.CloseIdleConnections() // just to see it running
|
|
ctx := context.Background()
|
|
qconn, err := systemdialer.DialContext(
|
|
ctx, "a.b.c.d", tlsConfig, &quic.Config{})
|
|
if err == nil || !strings.HasSuffix(err.Error(), "missing port in address") {
|
|
t.Fatal("not the error we expected", err)
|
|
}
|
|
if qconn != nil {
|
|
t.Fatal("expected nil connection here")
|
|
}
|
|
})
|
|
|
|
t.Run("with invalid port", func(t *testing.T) {
|
|
tlsConfig := &tls.Config{
|
|
ServerName: "www.google.com",
|
|
}
|
|
systemdialer := quicDialerQUICGo{
|
|
QUICListener: &quicListenerStdlib{},
|
|
}
|
|
ctx := context.Background()
|
|
qconn, err := systemdialer.DialContext(
|
|
ctx, "8.8.4.4:xyz", tlsConfig, &quic.Config{})
|
|
if err == nil || !strings.HasSuffix(err.Error(), "invalid syntax") {
|
|
t.Fatal("not the error we expected", err)
|
|
}
|
|
if qconn != nil {
|
|
t.Fatal("expected nil connection here")
|
|
}
|
|
})
|
|
|
|
t.Run("with invalid IP", func(t *testing.T) {
|
|
tlsConfig := &tls.Config{
|
|
ServerName: "www.google.com",
|
|
}
|
|
systemdialer := quicDialerQUICGo{
|
|
QUICListener: &quicListenerStdlib{},
|
|
}
|
|
ctx := context.Background()
|
|
qconn, err := systemdialer.DialContext(
|
|
ctx, "a.b.c.d:0", tlsConfig, &quic.Config{})
|
|
if !errors.Is(err, ErrInvalidIP) {
|
|
t.Fatal("not the error we expected", err)
|
|
}
|
|
if qconn != nil {
|
|
t.Fatal("expected nil connection here")
|
|
}
|
|
})
|
|
|
|
t.Run("with listen error", func(t *testing.T) {
|
|
expected := errors.New("mocked error")
|
|
tlsConfig := &tls.Config{
|
|
ServerName: "www.google.com",
|
|
}
|
|
systemdialer := quicDialerQUICGo{
|
|
QUICListener: &mocks.QUICListener{
|
|
MockListen: func(addr *net.UDPAddr) (model.UDPLikeConn, error) {
|
|
return nil, expected
|
|
},
|
|
},
|
|
}
|
|
ctx := context.Background()
|
|
qconn, err := systemdialer.DialContext(
|
|
ctx, "8.8.8.8:443", tlsConfig, &quic.Config{})
|
|
if !errors.Is(err, expected) {
|
|
t.Fatal("not the error we expected", err)
|
|
}
|
|
if qconn != nil {
|
|
t.Fatal("expected nil connection here")
|
|
}
|
|
})
|
|
|
|
t.Run("with handshake failure", func(t *testing.T) {
|
|
tlsConfig := &tls.Config{
|
|
ServerName: "dns.google",
|
|
}
|
|
systemdialer := quicDialerQUICGo{
|
|
QUICListener: &quicListenerStdlib{},
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel() // fail immediately
|
|
qconn, err := systemdialer.DialContext(
|
|
ctx, "8.8.8.8:443", tlsConfig, &quic.Config{})
|
|
if !errors.Is(err, context.Canceled) {
|
|
t.Fatal("not the error we expected", err)
|
|
}
|
|
if qconn != nil {
|
|
log.Fatal("expected nil connection here")
|
|
}
|
|
})
|
|
|
|
t.Run("TLS defaults for web", func(t *testing.T) {
|
|
expected := errors.New("mocked error")
|
|
var gotTLSConfig *tls.Config
|
|
tlsConfig := &tls.Config{
|
|
ServerName: "dns.google",
|
|
}
|
|
systemdialer := quicDialerQUICGo{
|
|
QUICListener: &quicListenerStdlib{},
|
|
mockDialEarlyContext: func(ctx context.Context, pconn net.PacketConn,
|
|
remoteAddr net.Addr, host string, tlsConfig *tls.Config,
|
|
quicConfig *quic.Config) (quic.EarlyConnection, error) {
|
|
gotTLSConfig = tlsConfig
|
|
return nil, expected
|
|
},
|
|
}
|
|
ctx := context.Background()
|
|
qconn, err := systemdialer.DialContext(
|
|
ctx, "8.8.8.8:443", tlsConfig, &quic.Config{})
|
|
if !errors.Is(err, expected) {
|
|
t.Fatal("not the error we expected", err)
|
|
}
|
|
if qconn != nil {
|
|
t.Fatal("expected nil connection here")
|
|
}
|
|
if tlsConfig.RootCAs != nil {
|
|
t.Fatal("tlsConfig.RootCAs should not have been changed")
|
|
}
|
|
if gotTLSConfig.RootCAs != defaultCertPool {
|
|
t.Fatal("invalid gotTLSConfig.RootCAs")
|
|
}
|
|
if tlsConfig.NextProtos != nil {
|
|
t.Fatal("tlsConfig.NextProtos should not have been changed")
|
|
}
|
|
if diff := cmp.Diff(gotTLSConfig.NextProtos, []string{"h3"}); diff != "" {
|
|
t.Fatal("invalid gotTLSConfig.NextProtos", diff)
|
|
}
|
|
if tlsConfig.ServerName != gotTLSConfig.ServerName {
|
|
t.Fatal("the ServerName field must match")
|
|
}
|
|
})
|
|
|
|
t.Run("TLS defaults for DoQ", func(t *testing.T) {
|
|
expected := errors.New("mocked error")
|
|
var gotTLSConfig *tls.Config
|
|
tlsConfig := &tls.Config{
|
|
ServerName: "dns.google",
|
|
}
|
|
systemdialer := quicDialerQUICGo{
|
|
QUICListener: &quicListenerStdlib{},
|
|
mockDialEarlyContext: func(ctx context.Context, pconn net.PacketConn,
|
|
remoteAddr net.Addr, host string, tlsConfig *tls.Config,
|
|
quicConfig *quic.Config) (quic.EarlyConnection, error) {
|
|
gotTLSConfig = tlsConfig
|
|
return nil, expected
|
|
},
|
|
}
|
|
ctx := context.Background()
|
|
qconn, err := systemdialer.DialContext(
|
|
ctx, "8.8.8.8:8853", tlsConfig, &quic.Config{})
|
|
if !errors.Is(err, expected) {
|
|
t.Fatal("not the error we expected", err)
|
|
}
|
|
if qconn != nil {
|
|
t.Fatal("expected nil connection here")
|
|
}
|
|
if tlsConfig.RootCAs != nil {
|
|
t.Fatal("tlsConfig.RootCAs should not have been changed")
|
|
}
|
|
if gotTLSConfig.RootCAs != defaultCertPool {
|
|
t.Fatal("invalid gotTLSConfig.RootCAs")
|
|
}
|
|
if tlsConfig.NextProtos != nil {
|
|
t.Fatal("tlsConfig.NextProtos should not have been changed")
|
|
}
|
|
if diff := cmp.Diff(gotTLSConfig.NextProtos, []string{"dq"}); diff != "" {
|
|
t.Fatal("invalid gotTLSConfig.NextProtos", diff)
|
|
}
|
|
if tlsConfig.ServerName != gotTLSConfig.ServerName {
|
|
t.Fatal("the ServerName field must match")
|
|
}
|
|
})
|
|
|
|
t.Run("returns a quicDialerOwnConn in case of success", func(t *testing.T) {
|
|
tlsConfig := &tls.Config{
|
|
ServerName: "dns.google",
|
|
}
|
|
fakeconn := &mocks.QUICEarlyConnection{}
|
|
systemdialer := quicDialerQUICGo{
|
|
QUICListener: &quicListenerStdlib{},
|
|
mockDialEarlyContext: func(ctx context.Context, pconn net.PacketConn,
|
|
remoteAddr net.Addr, host string, tlsConfig *tls.Config,
|
|
quicConfig *quic.Config) (quic.EarlyConnection, error) {
|
|
return fakeconn, nil
|
|
},
|
|
}
|
|
ctx := context.Background()
|
|
qconn, err := systemdialer.DialContext(
|
|
ctx, "8.8.8.8:443", tlsConfig, &quic.Config{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
connOwner := qconn.(*quicConnectionOwnsConn)
|
|
if connOwner.EarlyConnection != fakeconn {
|
|
t.Fatal("invalid underlying conn")
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestQUICDialerHandshakeCompleter(t *testing.T) {
|
|
t.Run("DialContext", func(t *testing.T) {
|
|
t.Run("in case of failure", func(t *testing.T) {
|
|
expected := errors.New("mocked error")
|
|
d := &quicDialerHandshakeCompleter{
|
|
Dialer: &mocks.QUICDialer{
|
|
MockDialContext: func(ctx context.Context, address string,
|
|
tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) {
|
|
return nil, expected
|
|
},
|
|
},
|
|
}
|
|
ctx := context.Background()
|
|
conn, err := d.DialContext(ctx, "8.8.8.8:443", &tls.Config{}, &quic.Config{})
|
|
if !errors.Is(err, expected) {
|
|
t.Fatal("unexpected err", err)
|
|
}
|
|
if conn != nil {
|
|
t.Fatal("expected nil conn")
|
|
}
|
|
})
|
|
|
|
t.Run("in case of context cancellation", func(t *testing.T) {
|
|
handshakeCtx, handshakeCancel := context.WithCancel(context.Background())
|
|
defer handshakeCancel()
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
var called bool
|
|
expected := &mocks.QUICEarlyConnection{
|
|
MockHandshakeComplete: func() context.Context {
|
|
cancel()
|
|
return handshakeCtx
|
|
},
|
|
MockCloseWithError: func(code quic.ApplicationErrorCode, reason string) error {
|
|
called = true
|
|
return nil
|
|
},
|
|
}
|
|
d := &quicDialerHandshakeCompleter{
|
|
Dialer: &mocks.QUICDialer{
|
|
MockDialContext: func(ctx context.Context, address string,
|
|
tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) {
|
|
return expected, nil
|
|
},
|
|
},
|
|
}
|
|
conn, err := d.DialContext(ctx, "8.8.8.8:443", &tls.Config{}, &quic.Config{})
|
|
if !errors.Is(err, context.Canceled) {
|
|
t.Fatal("unexpected err", err)
|
|
}
|
|
if conn != nil {
|
|
t.Fatal("expected nil conn")
|
|
}
|
|
if !called {
|
|
t.Fatal("not called")
|
|
}
|
|
})
|
|
|
|
t.Run("in case of success", func(t *testing.T) {
|
|
handshakeCtx, handshakeCancel := context.WithCancel(context.Background())
|
|
defer handshakeCancel()
|
|
expected := &mocks.QUICEarlyConnection{
|
|
MockHandshakeComplete: func() context.Context {
|
|
handshakeCancel()
|
|
return handshakeCtx
|
|
},
|
|
}
|
|
d := &quicDialerHandshakeCompleter{
|
|
Dialer: &mocks.QUICDialer{
|
|
MockDialContext: func(ctx context.Context, address string,
|
|
tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) {
|
|
return expected, nil
|
|
},
|
|
},
|
|
}
|
|
conn, err := d.DialContext(
|
|
context.Background(), "8.8.8.8:443", &tls.Config{}, &quic.Config{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if conn == nil {
|
|
t.Fatal("expected non-nil conn")
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("CloseIdleConnections", func(t *testing.T) {
|
|
var forDialer bool
|
|
d := &quicDialerHandshakeCompleter{
|
|
Dialer: &mocks.QUICDialer{
|
|
MockCloseIdleConnections: func() {
|
|
forDialer = true
|
|
},
|
|
},
|
|
}
|
|
d.CloseIdleConnections()
|
|
if !forDialer {
|
|
t.Fatal("not called")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestQUICConnectionOwnsConn(t *testing.T) {
|
|
var (
|
|
quicClose bool
|
|
udpClose bool
|
|
)
|
|
qconn := &mocks.QUICEarlyConnection{
|
|
MockCloseWithError: func(code quic.ApplicationErrorCode, reason string) error {
|
|
quicClose = true
|
|
return nil
|
|
},
|
|
}
|
|
pconn := &mocks.UDPLikeConn{
|
|
MockClose: func() error {
|
|
udpClose = true
|
|
return nil
|
|
},
|
|
}
|
|
conn := newQUICConnectionOwnsConn(qconn, pconn)
|
|
conn.CloseWithError(0, "")
|
|
if !quicClose {
|
|
t.Fatal("did not call qconn.CloseWithError")
|
|
}
|
|
if !udpClose {
|
|
t.Fatal("did not call pconn.Close")
|
|
}
|
|
}
|
|
|
|
func TestQUICDialerResolver(t *testing.T) {
|
|
t.Run("CloseIdleConnections", func(t *testing.T) {
|
|
var (
|
|
forDialer bool
|
|
forResolver bool
|
|
)
|
|
d := &quicDialerResolver{
|
|
Dialer: &mocks.QUICDialer{
|
|
MockCloseIdleConnections: func() {
|
|
forDialer = true
|
|
},
|
|
},
|
|
Resolver: &mocks.Resolver{
|
|
MockCloseIdleConnections: func() {
|
|
forResolver = true
|
|
},
|
|
},
|
|
}
|
|
d.CloseIdleConnections()
|
|
if !forDialer || !forResolver {
|
|
t.Fatal("not called")
|
|
}
|
|
})
|
|
|
|
t.Run("DialContext", func(t *testing.T) {
|
|
t.Run("with missing port", func(t *testing.T) {
|
|
tlsConfig := &tls.Config{}
|
|
dialer := &quicDialerResolver{
|
|
Resolver: NewStdlibResolver(log.Log),
|
|
Dialer: &quicDialerQUICGo{}}
|
|
qconn, err := dialer.DialContext(
|
|
context.Background(), "www.google.com",
|
|
tlsConfig, &quic.Config{})
|
|
if err == nil || !strings.HasSuffix(err.Error(), "missing port in address") {
|
|
t.Fatal("not the error we expected")
|
|
}
|
|
if qconn != nil {
|
|
t.Fatal("expected a nil connection here")
|
|
}
|
|
})
|
|
|
|
t.Run("with lookup host failure", func(t *testing.T) {
|
|
tlsConfig := &tls.Config{}
|
|
expected := errors.New("mocked error")
|
|
dialer := &quicDialerResolver{Resolver: &mocks.Resolver{
|
|
MockLookupHost: func(ctx context.Context, domain string) ([]string, error) {
|
|
return nil, expected
|
|
},
|
|
}}
|
|
qconn, err := dialer.DialContext(
|
|
context.Background(), "dns.google.com:853",
|
|
tlsConfig, &quic.Config{})
|
|
if !errors.Is(err, expected) {
|
|
t.Fatal("not the error we expected")
|
|
}
|
|
if qconn != nil {
|
|
t.Fatal("expected nil connection")
|
|
}
|
|
})
|
|
|
|
t.Run("with invalid, non-numeric port)", func(t *testing.T) {
|
|
// This test allows us to check for the case where every attempt
|
|
// to establish a connection leads to a failure
|
|
tlsConf := &tls.Config{}
|
|
dialer := &quicDialerResolver{
|
|
Resolver: NewStdlibResolver(log.Log),
|
|
Dialer: &quicDialerQUICGo{
|
|
QUICListener: &quicListenerStdlib{},
|
|
}}
|
|
qconn, err := dialer.DialContext(
|
|
context.Background(), "8.8.4.4:x",
|
|
tlsConf, &quic.Config{})
|
|
if err == nil {
|
|
t.Fatal("expected an error here")
|
|
}
|
|
if !strings.HasSuffix(err.Error(), "invalid syntax") {
|
|
t.Fatal("not the error we expected", err)
|
|
}
|
|
if qconn != nil {
|
|
t.Fatal("expected nil connection")
|
|
}
|
|
})
|
|
|
|
t.Run("we apply TLS defaults", func(t *testing.T) {
|
|
expected := errors.New("mocked error")
|
|
var gotTLSConfig *tls.Config
|
|
tlsConfig := &tls.Config{}
|
|
dialer := &quicDialerResolver{
|
|
Resolver: NewStdlibResolver(log.Log),
|
|
Dialer: &mocks.QUICDialer{
|
|
MockDialContext: func(ctx context.Context, address string,
|
|
tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) {
|
|
gotTLSConfig = tlsConfig
|
|
return nil, expected
|
|
},
|
|
}}
|
|
qconn, err := dialer.DialContext(
|
|
context.Background(), "8.8.4.4:443",
|
|
tlsConfig, &quic.Config{})
|
|
if !errors.Is(err, expected) {
|
|
t.Fatal("not the error we expected", err)
|
|
}
|
|
if qconn != nil {
|
|
t.Fatal("expected nil connection here")
|
|
}
|
|
if tlsConfig.ServerName != "" {
|
|
t.Fatal("should not have changed tlsConfig.ServerName")
|
|
}
|
|
if gotTLSConfig.ServerName != "8.8.4.4" {
|
|
t.Fatal("gotTLSConfig.ServerName has not been set")
|
|
}
|
|
})
|
|
|
|
t.Run("on success", func(t *testing.T) {
|
|
expectedQConn := &mocks.QUICEarlyConnection{}
|
|
dialer := &quicDialerResolver{
|
|
Resolver: NewStdlibResolver(log.Log),
|
|
Dialer: &mocks.QUICDialer{
|
|
MockDialContext: func(ctx context.Context, address string,
|
|
tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) {
|
|
return expectedQConn, nil
|
|
},
|
|
}}
|
|
qconn, err := dialer.DialContext(
|
|
context.Background(), "8.8.4.4:443",
|
|
&tls.Config{}, &quic.Config{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if qconn != expectedQConn {
|
|
t.Fatal("unexpected underlying qconn")
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("lookup host with address", func(t *testing.T) {
|
|
dialer := &quicDialerResolver{Resolver: &mocks.Resolver{
|
|
MockLookupHost: func(ctx context.Context, domain string) ([]string, error) {
|
|
// We should not arrive here and call this function but if we do then
|
|
// there is going to be an error that fails this test.
|
|
return nil, errors.New("mocked error")
|
|
},
|
|
}}
|
|
addrs, err := dialer.lookupHost(context.Background(), "1.1.1.1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(addrs) != 1 || addrs[0] != "1.1.1.1" {
|
|
t.Fatal("not the result we expected")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestQUICLoggerDialer(t *testing.T) {
|
|
t.Run("CloseIdleConnections", func(t *testing.T) {
|
|
var forDialer bool
|
|
d := &quicDialerLogger{
|
|
Dialer: &mocks.QUICDialer{
|
|
MockCloseIdleConnections: func() {
|
|
forDialer = true
|
|
},
|
|
},
|
|
}
|
|
d.CloseIdleConnections()
|
|
if !forDialer {
|
|
t.Fatal("not called")
|
|
}
|
|
})
|
|
|
|
t.Run("DialContext", func(t *testing.T) {
|
|
t.Run("on success", func(t *testing.T) {
|
|
var called int
|
|
lo := &mocks.Logger{
|
|
MockDebugf: func(format string, v ...interface{}) {
|
|
called++
|
|
},
|
|
}
|
|
d := &quicDialerLogger{
|
|
Dialer: &mocks.QUICDialer{
|
|
MockDialContext: func(ctx context.Context,
|
|
address string, tlsConfig *tls.Config,
|
|
quicConfig *quic.Config) (quic.EarlyConnection, error) {
|
|
return &mocks.QUICEarlyConnection{
|
|
MockCloseWithError: func(
|
|
code quic.ApplicationErrorCode, reason string) error {
|
|
return nil
|
|
},
|
|
}, nil
|
|
},
|
|
},
|
|
Logger: lo,
|
|
}
|
|
ctx := context.Background()
|
|
tlsConfig := &tls.Config{}
|
|
quicConfig := &quic.Config{}
|
|
qconn, err := d.DialContext(ctx, "8.8.8.8:443", tlsConfig, quicConfig)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := qconn.CloseWithError(0, ""); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if called != 2 {
|
|
t.Fatal("invalid number of calls")
|
|
}
|
|
})
|
|
|
|
t.Run("on failure", func(t *testing.T) {
|
|
var called int
|
|
lo := &mocks.Logger{
|
|
MockDebugf: func(format string, v ...interface{}) {
|
|
called++
|
|
},
|
|
}
|
|
expected := errors.New("mocked error")
|
|
d := &quicDialerLogger{
|
|
Dialer: &mocks.QUICDialer{
|
|
MockDialContext: func(ctx context.Context,
|
|
address string, tlsConfig *tls.Config,
|
|
quicConfig *quic.Config) (quic.EarlyConnection, error) {
|
|
return nil, expected
|
|
},
|
|
},
|
|
Logger: lo,
|
|
}
|
|
ctx := context.Background()
|
|
tlsConfig := &tls.Config{}
|
|
quicConfig := &quic.Config{}
|
|
qconn, err := d.DialContext(ctx, "8.8.8.8:443", tlsConfig, quicConfig)
|
|
if !errors.Is(err, expected) {
|
|
t.Fatal("not the error we expected", err)
|
|
}
|
|
if qconn != nil {
|
|
t.Fatal("expected nil connection")
|
|
}
|
|
if called != 2 {
|
|
t.Fatal("invalid number of calls")
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestNewSingleUseQUICDialer(t *testing.T) {
|
|
qconn := &mocks.QUICEarlyConnection{}
|
|
qd := NewSingleUseQUICDialer(qconn)
|
|
defer qd.CloseIdleConnections()
|
|
outconn, err := qd.DialContext(
|
|
context.Background(), "", &tls.Config{}, &quic.Config{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if qconn != outconn {
|
|
t.Fatal("invalid outconn")
|
|
}
|
|
for i := 0; i < 4; i++ {
|
|
outconn, err = qd.DialContext(
|
|
context.Background(), "", &tls.Config{}, &quic.Config{})
|
|
if !errors.Is(err, ErrNoConnReuse) {
|
|
t.Fatal("not the error we expected", err)
|
|
}
|
|
if outconn != nil {
|
|
t.Fatal("expected nil outconn here")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestQUICListenerErrWrapper(t *testing.T) {
|
|
t.Run("Listen", func(t *testing.T) {
|
|
t.Run("on success", func(t *testing.T) {
|
|
expectedConn := &mocks.UDPLikeConn{}
|
|
ql := &quicListenerErrWrapper{
|
|
QUICListener: &mocks.QUICListener{
|
|
MockListen: func(addr *net.UDPAddr) (model.UDPLikeConn, error) {
|
|
return expectedConn, nil
|
|
},
|
|
},
|
|
}
|
|
conn, err := ql.Listen(&net.UDPAddr{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ewconn := conn.(*quicErrWrapperUDPLikeConn)
|
|
if ewconn.UDPLikeConn != expectedConn {
|
|
t.Fatal("unexpected conn")
|
|
}
|
|
})
|
|
|
|
t.Run("on failure", func(t *testing.T) {
|
|
expectedErr := io.EOF
|
|
ql := &quicListenerErrWrapper{
|
|
QUICListener: &mocks.QUICListener{
|
|
MockListen: func(addr *net.UDPAddr) (model.UDPLikeConn, error) {
|
|
return nil, expectedErr
|
|
},
|
|
},
|
|
}
|
|
conn, err := ql.Listen(&net.UDPAddr{})
|
|
if err == nil || err.Error() != FailureEOFError {
|
|
t.Fatal("unexpected err", err)
|
|
}
|
|
if conn != nil {
|
|
t.Fatal("expected nil conn")
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestQUICErrWrapperUDPLikeConn(t *testing.T) {
|
|
t.Run("ReadFrom", func(t *testing.T) {
|
|
t.Run("on success", func(t *testing.T) {
|
|
expectedAddr := &net.UDPAddr{}
|
|
p := make([]byte, 128)
|
|
conn := &quicErrWrapperUDPLikeConn{
|
|
UDPLikeConn: &mocks.UDPLikeConn{
|
|
MockReadFrom: func(p []byte) (n int, addr net.Addr, err error) {
|
|
return len(p), expectedAddr, nil
|
|
},
|
|
},
|
|
}
|
|
count, addr, err := conn.ReadFrom(p)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if count != len(p) {
|
|
t.Fatal("unexpected count")
|
|
}
|
|
if addr != expectedAddr {
|
|
t.Fatal("unexpected addr")
|
|
}
|
|
})
|
|
|
|
t.Run("on failure", func(t *testing.T) {
|
|
p := make([]byte, 128)
|
|
expectedErr := io.EOF
|
|
conn := &quicErrWrapperUDPLikeConn{
|
|
UDPLikeConn: &mocks.UDPLikeConn{
|
|
MockReadFrom: func(p []byte) (n int, addr net.Addr, err error) {
|
|
return 0, nil, expectedErr
|
|
},
|
|
},
|
|
}
|
|
count, addr, err := conn.ReadFrom(p)
|
|
if err == nil || err.Error() != FailureEOFError {
|
|
t.Fatal("unexpected err", err)
|
|
}
|
|
if count != 0 {
|
|
t.Fatal("unexpected count")
|
|
}
|
|
if addr != nil {
|
|
t.Fatal("unexpected addr")
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("WriteTo", func(t *testing.T) {
|
|
t.Run("on success", func(t *testing.T) {
|
|
p := make([]byte, 128)
|
|
conn := &quicErrWrapperUDPLikeConn{
|
|
UDPLikeConn: &mocks.UDPLikeConn{
|
|
MockWriteTo: func(p []byte, addr net.Addr) (int, error) {
|
|
return len(p), nil
|
|
},
|
|
},
|
|
}
|
|
count, err := conn.WriteTo(p, &net.UDPAddr{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if count != len(p) {
|
|
t.Fatal("unexpected count")
|
|
}
|
|
})
|
|
|
|
t.Run("on failure", func(t *testing.T) {
|
|
p := make([]byte, 128)
|
|
expectedErr := io.EOF
|
|
conn := &quicErrWrapperUDPLikeConn{
|
|
UDPLikeConn: &mocks.UDPLikeConn{
|
|
MockWriteTo: func(p []byte, addr net.Addr) (int, error) {
|
|
return 0, expectedErr
|
|
},
|
|
},
|
|
}
|
|
count, err := conn.WriteTo(p, &net.UDPAddr{})
|
|
if err == nil || err.Error() != FailureEOFError {
|
|
t.Fatal("unexpected err", err)
|
|
}
|
|
if count != 0 {
|
|
t.Fatal("unexpected count")
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("Close", func(t *testing.T) {
|
|
t.Run("on success", func(t *testing.T) {
|
|
conn := &quicErrWrapperUDPLikeConn{
|
|
UDPLikeConn: &mocks.UDPLikeConn{
|
|
MockClose: func() error {
|
|
return nil
|
|
},
|
|
},
|
|
}
|
|
err := conn.Close()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
})
|
|
|
|
t.Run("on failure", func(t *testing.T) {
|
|
expectedErr := io.EOF
|
|
conn := &quicErrWrapperUDPLikeConn{
|
|
UDPLikeConn: &mocks.UDPLikeConn{
|
|
MockClose: func() error {
|
|
return expectedErr
|
|
},
|
|
},
|
|
}
|
|
err := conn.Close()
|
|
if err == nil || err.Error() != FailureEOFError {
|
|
t.Fatal("unexpected err", err)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestQUICDialerErrWrapper(t *testing.T) {
|
|
t.Run("CloseIdleConnections", func(t *testing.T) {
|
|
var called bool
|
|
d := &quicDialerErrWrapper{
|
|
QUICDialer: &mocks.QUICDialer{
|
|
MockCloseIdleConnections: func() {
|
|
called = true
|
|
},
|
|
},
|
|
}
|
|
d.CloseIdleConnections()
|
|
if !called {
|
|
t.Fatal("not called")
|
|
}
|
|
})
|
|
|
|
t.Run("DialContext", func(t *testing.T) {
|
|
t.Run("on success", func(t *testing.T) {
|
|
expectedConn := &mocks.QUICEarlyConnection{}
|
|
d := &quicDialerErrWrapper{
|
|
QUICDialer: &mocks.QUICDialer{
|
|
MockDialContext: func(ctx context.Context, address string, tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) {
|
|
return expectedConn, nil
|
|
},
|
|
},
|
|
}
|
|
ctx := context.Background()
|
|
qconn, err := d.DialContext(ctx, "", &tls.Config{}, &quic.Config{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if qconn != expectedConn {
|
|
t.Fatal("unexpected connection")
|
|
}
|
|
})
|
|
|
|
t.Run("on failure", func(t *testing.T) {
|
|
expectedErr := io.EOF
|
|
d := &quicDialerErrWrapper{
|
|
QUICDialer: &mocks.QUICDialer{
|
|
MockDialContext: func(ctx context.Context, address string, tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) {
|
|
return nil, expectedErr
|
|
},
|
|
},
|
|
}
|
|
ctx := context.Background()
|
|
qconn, err := d.DialContext(ctx, "", &tls.Config{}, &quic.Config{})
|
|
if err == nil || err.Error() != FailureEOFError {
|
|
t.Fatal("unexpected err", err)
|
|
}
|
|
if qconn != nil {
|
|
t.Fatal("unexpected connection")
|
|
}
|
|
})
|
|
})
|
|
}
|