feat(netxlite): write factories for quic code (#470)

Part of https://github.com/ooni/probe/issues/1591
This commit is contained in:
Simone Basso 2021-09-06 21:34:14 +02:00 committed by GitHub
parent 3ba5626b95
commit bdad392b61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 107 additions and 20 deletions

View File

@ -22,8 +22,9 @@ func NewDialerWithResolver(logger Logger, resolver Resolver) Dialer {
return &dialerLogger{ return &dialerLogger{
Dialer: &dialerResolver{ Dialer: &dialerResolver{
Dialer: &dialerLogger{ Dialer: &dialerLogger{
Dialer: &dialerSystem{}, Dialer: &dialerSystem{},
Logger: logger, Logger: logger,
operationSuffix: "_address",
}, },
Resolver: resolver, Resolver: resolver,
}, },
@ -120,21 +121,31 @@ type dialerLogger struct {
// Logger is the underlying logger. // Logger is the underlying logger.
Logger 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{} var _ Dialer = &dialerLogger{}
// DialContext implements Dialer.DialContext // DialContext implements Dialer.DialContext
func (d *dialerLogger) DialContext(ctx context.Context, network, address string) (net.Conn, error) { 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() start := time.Now()
conn, err := d.Dialer.DialContext(ctx, network, address) conn, err := d.Dialer.DialContext(ctx, network, address)
elapsed := time.Since(start) elapsed := time.Since(start)
if err != nil { 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 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 return conn, nil
} }

View File

@ -11,6 +11,28 @@ import (
"github.com/ooni/probe-cli/v3/internal/netxlite/quicx" "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. // QUICDialer dials QUIC sessions.
type QUICDialer interface { type QUICDialer interface {
// DialContext establishes a new QUIC session using the given // DialContext establishes a new QUIC session using the given
@ -23,20 +45,32 @@ type QUICDialer interface {
CloseIdleConnections() CloseIdleConnections()
} }
// QUICListener listens for QUIC connections. // NewQUICDialerWithResolver returns a QUICDialer using the given
type QUICListener interface { // QUICListener to create listening connections and the given Resolver
// Listen creates a new listening UDPConn. // to resolve domain names (if needed).
Listen(addr *net.UDPAddr) (quicx.UDPLikeConn, error) 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. // NewQUICDialerWithoutResolver is like NewQUICDialerWithResolver
type quicListenerStdlib struct{} // except that there is no configured resolver. So, if you pass in
// an address containing a domain name, the dial will fail with
var _ QUICListener = &quicListenerStdlib{} // the ErrNoResolver failure.
func NewQUICDialerWithoutResolver(listener QUICListener, logger Logger) QUICDialer {
// Listen implements QUICListener.Listen. return NewQUICDialerWithResolver(listener, logger, &nullResolver{})
func (qls *quicListenerStdlib) Listen(addr *net.UDPAddr) (quicx.UDPLikeConn, error) {
return net.ListenUDP("udp", addr)
} }
// quicDialerQUICGo dials using the lucas-clemente/quic-go library. // quicDialerQUICGo dials using the lucas-clemente/quic-go library.
@ -223,6 +257,14 @@ type quicDialerLogger struct {
// Logger is the underlying logger. // Logger is the underlying logger.
Logger 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{} var _ QUICDialer = &quicDialerLogger{}
@ -231,13 +273,14 @@ var _ QUICDialer = &quicDialerLogger{}
func (d *quicDialerLogger) DialContext( func (d *quicDialerLogger) DialContext(
ctx context.Context, network, address string, ctx context.Context, network, address string,
tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlySession, error) { 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) sess, err := d.Dialer.DialContext(ctx, network, address, tlsConfig, quicConfig)
if err != nil { 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 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 return sess, nil
} }

View File

@ -427,3 +427,36 @@ func TestQUICDialerLoggerFailure(t *testing.T) {
t.Fatal("expected nil session") 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")
}
}