From bdad392b6188cf6fe64824de31954477bbe26c2f Mon Sep 17 00:00:00 2001 From: Simone Basso Date: Mon, 6 Sep 2021 21:34:14 +0200 Subject: [PATCH] feat(netxlite): write factories for quic code (#470) Part of https://github.com/ooni/probe/issues/1591 --- internal/netxlite/dialer.go | 21 +++++++--- internal/netxlite/quic.go | 73 +++++++++++++++++++++++++++------- internal/netxlite/quic_test.go | 33 +++++++++++++++ 3 files changed, 107 insertions(+), 20 deletions(-) diff --git a/internal/netxlite/dialer.go b/internal/netxlite/dialer.go index 2b40679..0489a58 100644 --- a/internal/netxlite/dialer.go +++ b/internal/netxlite/dialer.go @@ -22,8 +22,9 @@ func NewDialerWithResolver(logger Logger, resolver Resolver) Dialer { return &dialerLogger{ Dialer: &dialerResolver{ Dialer: &dialerLogger{ - Dialer: &dialerSystem{}, - Logger: logger, + Dialer: &dialerSystem{}, + Logger: logger, + operationSuffix: "_address", }, Resolver: resolver, }, @@ -120,21 +121,31 @@ type dialerLogger struct { // Logger is the underlying logger. Logger Logger + + // operationSuffix is appended to the operation name. + // + // We use this suffix to distinguish the output from dialing + // with the output from dialing an IP address when we are + // using a dialer without resolver, where otherwise both lines + // would read something like `dial 8.8.8.8:443...` + operationSuffix string } var _ Dialer = &dialerLogger{} // DialContext implements Dialer.DialContext func (d *dialerLogger) DialContext(ctx context.Context, network, address string) (net.Conn, error) { - d.Logger.Debugf("dial %s/%s...", address, network) + d.Logger.Debugf("dial%s %s/%s...", d.operationSuffix, address, network) start := time.Now() conn, err := d.Dialer.DialContext(ctx, network, address) elapsed := time.Since(start) if err != nil { - d.Logger.Debugf("dial %s/%s... %s in %s", address, network, err, elapsed) + d.Logger.Debugf("dial%s %s/%s... %s in %s", d.operationSuffix, + address, network, err, elapsed) return nil, err } - d.Logger.Debugf("dial %s/%s... ok in %s", address, network, elapsed) + d.Logger.Debugf("dial%s %s/%s... ok in %s", d.operationSuffix, + address, network, elapsed) return conn, nil } diff --git a/internal/netxlite/quic.go b/internal/netxlite/quic.go index 439a7f7..abfec10 100644 --- a/internal/netxlite/quic.go +++ b/internal/netxlite/quic.go @@ -11,6 +11,28 @@ import ( "github.com/ooni/probe-cli/v3/internal/netxlite/quicx" ) +// QUICListener listens for QUIC connections. +type QUICListener interface { + // Listen creates a new listening UDPConn. + Listen(addr *net.UDPAddr) (quicx.UDPLikeConn, error) +} + +// NewQUICListener creates a new QUICListener using the standard +// library to create listening UDP sockets. +func NewQUICListener() QUICListener { + return &quicListenerStdlib{} +} + +// quicListenerStdlib is a QUICListener using the standard library. +type quicListenerStdlib struct{} + +var _ QUICListener = &quicListenerStdlib{} + +// Listen implements QUICListener.Listen. +func (qls *quicListenerStdlib) Listen(addr *net.UDPAddr) (quicx.UDPLikeConn, error) { + return net.ListenUDP("udp", addr) +} + // QUICDialer dials QUIC sessions. type QUICDialer interface { // DialContext establishes a new QUIC session using the given @@ -23,20 +45,32 @@ type QUICDialer interface { CloseIdleConnections() } -// QUICListener listens for QUIC connections. -type QUICListener interface { - // Listen creates a new listening UDPConn. - Listen(addr *net.UDPAddr) (quicx.UDPLikeConn, error) +// NewQUICDialerWithResolver returns a QUICDialer using the given +// QUICListener to create listening connections and the given Resolver +// to resolve domain names (if needed). +func NewQUICDialerWithResolver(listener QUICListener, + logger Logger, resolver Resolver) QUICDialer { + return &quicDialerLogger{ + Dialer: &quicDialerResolver{ + Dialer: &quicDialerLogger{ + Dialer: &quicDialerQUICGo{ + QUICListener: listener, + }, + Logger: logger, + operationSuffix: "_address", + }, + Resolver: resolver, + }, + Logger: logger, + } } -// quicListenerStdlib is a QUICListener using the standard library. -type quicListenerStdlib struct{} - -var _ QUICListener = &quicListenerStdlib{} - -// Listen implements QUICListener.Listen. -func (qls *quicListenerStdlib) Listen(addr *net.UDPAddr) (quicx.UDPLikeConn, error) { - return net.ListenUDP("udp", addr) +// NewQUICDialerWithoutResolver is like NewQUICDialerWithResolver +// except that there is no configured resolver. So, if you pass in +// an address containing a domain name, the dial will fail with +// the ErrNoResolver failure. +func NewQUICDialerWithoutResolver(listener QUICListener, logger Logger) QUICDialer { + return NewQUICDialerWithResolver(listener, logger, &nullResolver{}) } // quicDialerQUICGo dials using the lucas-clemente/quic-go library. @@ -223,6 +257,14 @@ type quicDialerLogger struct { // Logger is the underlying logger. Logger Logger + + // operationSuffix is appended to the operation name. + // + // We use this suffix to distinguish the output from dialing + // with the output from dialing an IP address when we are + // using a dialer without resolver, where otherwise both lines + // would read something like `dial 8.8.8.8:443...` + operationSuffix string } var _ QUICDialer = &quicDialerLogger{} @@ -231,13 +273,14 @@ var _ QUICDialer = &quicDialerLogger{} 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) + d.Logger.Debugf("quic_dial%s %s/%s...", d.operationSuffix, 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) + d.Logger.Debugf("quic_dial%s %s/%s... %s", d.operationSuffix, + address, network, err) return nil, err } - d.Logger.Debugf("quic %s/%s... ok", address, network) + d.Logger.Debugf("quic_dial%s %s/%s... ok", d.operationSuffix, address, network) return sess, nil } diff --git a/internal/netxlite/quic_test.go b/internal/netxlite/quic_test.go index 844371a..470ac2f 100644 --- a/internal/netxlite/quic_test.go +++ b/internal/netxlite/quic_test.go @@ -427,3 +427,36 @@ func TestQUICDialerLoggerFailure(t *testing.T) { t.Fatal("expected nil session") } } + +func TestNewQUICDialerWithoutResolverChain(t *testing.T) { + ql := NewQUICListener() + dlr := NewQUICDialerWithoutResolver(ql, log.Log) + dlog, okay := dlr.(*quicDialerLogger) + if !okay { + t.Fatal("invalid type") + } + if dlog.Logger != log.Log { + t.Fatal("invalid logger") + } + dr, okay := dlog.Dialer.(*quicDialerResolver) + if !okay { + t.Fatal("invalid type") + } + if _, okay := dr.Resolver.(*nullResolver); !okay { + t.Fatal("invalid resolver type") + } + dlog, okay = dr.Dialer.(*quicDialerLogger) + if !okay { + t.Fatal("invalid type") + } + if dlog.Logger != log.Log { + t.Fatal("invalid logger") + } + dgo, okay := dlog.Dialer.(*quicDialerQUICGo) + if !okay { + t.Fatal("invalid type") + } + if dgo.QUICListener != ql { + t.Fatal("invalid quic listener") + } +}