refactor(netxlite): allow easy QUIC dialer chain customization (#771)

Like the previous diff, but for creating QUIC dialers.

See https://github.com/ooni/probe/issues/2121.
This commit is contained in:
Simone Basso 2022-05-31 20:28:25 +02:00 committed by GitHub
parent 69fd0c5119
commit dd5655eaee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 60 additions and 52 deletions

View File

@ -131,28 +131,26 @@ func NewQUICDialer(config Config) model.QUICDialer {
if config.FullResolver == nil { if config.FullResolver == nil {
config.FullResolver = NewResolver(config) config.FullResolver = NewResolver(config)
} }
var ql model.QUICListener = &netxlite.QUICListenerStdlib{} ql := netxlite.NewQUICListener()
ql = &netxlite.ErrorWrapperQUICListener{QUICListener: ql}
if config.ReadWriteSaver != nil { if config.ReadWriteSaver != nil {
ql = &quicdialer.QUICListenerSaver{ ql = &quicdialer.QUICListenerSaver{
QUICListener: ql, QUICListener: ql,
Saver: config.ReadWriteSaver, Saver: config.ReadWriteSaver,
} }
} }
var d model.QUICDialer = &netxlite.QUICDialerQUICGo{ var logger model.DebugLogger = model.DiscardLogger
QUICListener: ql, if config.Logger != nil {
logger = config.Logger
} }
d = &netxlite.ErrorWrapperQUICDialer{ extensions := []netxlite.QUICDialerWrapper{
QUICDialer: d, func(dialer model.QUICDialer) model.QUICDialer {
if config.TLSSaver != nil {
dialer = quicdialer.HandshakeSaver{Saver: config.TLSSaver, QUICDialer: dialer}
}
return dialer
},
} }
if config.TLSSaver != nil { return netxlite.NewQUICDialerWithResolver(ql, logger, config.FullResolver, extensions...)
d = quicdialer.HandshakeSaver{Saver: config.TLSSaver, QUICDialer: d}
}
d = &netxlite.QUICDialerResolver{
Resolver: config.FullResolver,
Dialer: d,
}
return d
} }
// NewTLSDialer creates a new TLSDialer from the specified config // NewTLSDialer creates a new TLSDialer from the specified config

View File

@ -135,7 +135,7 @@ func NewDialerWithoutResolver(dl model.DebugLogger, w ...DialerWrapper) model.Di
return NewDialerWithResolver(dl, &NullResolver{}, w...) return NewDialerWithResolver(dl, &NullResolver{}, w...)
} }
// DialerSystem is a model.Dialer that users TProxy.NewSimplerDialer // DialerSystem is a model.Dialer that uses TProxy.NewSimplerDialer
// to construct the new SimpleDialer used for dialing. This dialer has // to construct the new SimpleDialer used for dialing. This dialer has
// a fixed timeout for each connect operation equal to 15 seconds. // a fixed timeout for each connect operation equal to 15 seconds.
type DialerSystem struct { type DialerSystem struct {

View File

@ -32,39 +32,32 @@ func (qls *quicListenerStdlib) Listen(addr *net.UDPAddr) (model.UDPLikeConn, err
return TProxy.ListenUDP("udp", addr) return TProxy.ListenUDP("udp", addr)
} }
// NewQUICDialerWithResolver returns a QUICDialer using the given // QUICDialerWrapper is a function that allows you to customize the kind of QUICDialer
// QUICListener to create listening connections and the given Resolver // returned by NewQUICDialerWithResolver and NewQUICDialerWithoutResolver.
// to resolve domain names (if needed). type QUICDialerWrapper func(dialer model.QUICDialer) model.QUICDialer
// NewQUICDialerWithResolver is the WrapDialer equivalent for QUIC where
// we return a composed QUICDialer modified by optional wrappers.
// //
// Properties of the dialer: // Unlike the dialer returned by WrapDialer, this dialer MAY attempt
// // happy eyeballs, perform parallel dial attempts, and return an error
// 1. logs events using the given logger; // that aggregates all the errors that occurred.
// func NewQUICDialerWithResolver(listener model.QUICListener, logger model.DebugLogger,
// 2. resolves domain names using the givern resolver; resolver model.Resolver, wrappers ...QUICDialerWrapper) (outDialer model.QUICDialer) {
// outDialer = &quicDialerErrWrapper{
// 3. when using a resolver, _may_ attempt multiple dials QUICDialer: &quicDialerHandshakeCompleter{
// in parallel (happy eyeballs) and _may_ return an aggregate Dialer: &quicDialerQUICGo{
// error to the caller; QUICListener: listener,
// },
// 4. wraps errors; },
// }
// 5. has a configured connect timeout; for _, wrapper := range wrappers {
// outDialer = wrapper(outDialer) // extend with user-supplied constructors
// 6. if a dialer wraps a resolver, the dialer will forward }
// the CloseIdleConnection call to its resolver (which is
// instrumental to manage a DoH resolver connections properly).
func NewQUICDialerWithResolver(listener model.QUICListener,
logger model.DebugLogger, resolver model.Resolver) model.QUICDialer {
return &quicDialerLogger{ return &quicDialerLogger{
Dialer: &quicDialerResolver{ Dialer: &quicDialerResolver{
Dialer: &quicDialerLogger{ Dialer: &quicDialerLogger{
Dialer: &quicDialerErrWrapper{ Dialer: outDialer,
QUICDialer: &quicDialerHandshakeCompleter{
Dialer: &quicDialerQUICGo{
QUICListener: listener,
},
},
},
Logger: logger, Logger: logger,
operationSuffix: "_address", operationSuffix: "_address",
}, },
@ -74,12 +67,11 @@ func NewQUICDialerWithResolver(listener model.QUICListener,
} }
} }
// NewQUICDialerWithoutResolver is like NewQUICDialerWithResolver // NewQUICDialerWithoutResolver is equivalent to calling NewQUICDialerWithResolver
// except that there is no configured resolver. So, if you pass in // with the resolver argument set to &NullResolver{}.
// an address containing a domain name, the dial will fail with func NewQUICDialerWithoutResolver(listener model.QUICListener,
// the ErrNoResolver failure. logger model.DebugLogger, wrappers ...QUICDialerWrapper) model.QUICDialer {
func NewQUICDialerWithoutResolver(listener model.QUICListener, logger model.DebugLogger) model.QUICDialer { return NewQUICDialerWithResolver(listener, logger, &NullResolver{}, wrappers...)
return NewQUICDialerWithResolver(listener, logger, &NullResolver{})
} }
// quicDialerQUICGo dials using the lucas-clemente/quic-go library. // quicDialerQUICGo dials using the lucas-clemente/quic-go library.

View File

@ -22,9 +22,25 @@ func TestNewQUICListener(t *testing.T) {
_ = qew.QUICListener.(*quicListenerStdlib) _ = qew.QUICListener.(*quicListenerStdlib)
} }
type extensionQUICDialerFirst struct {
model.QUICDialer
}
type extensionQUICDialerSecond struct {
model.QUICDialer
}
func TestNewQUICDialer(t *testing.T) { func TestNewQUICDialer(t *testing.T) {
ql := NewQUICListener() ql := NewQUICListener()
dlr := NewQUICDialerWithoutResolver(ql, log.Log) extensions := []QUICDialerWrapper{
func(dialer model.QUICDialer) model.QUICDialer {
return &extensionQUICDialerFirst{dialer}
},
func(dialer model.QUICDialer) model.QUICDialer {
return &extensionQUICDialerSecond{dialer}
},
}
dlr := NewQUICDialerWithoutResolver(ql, log.Log, extensions...)
logger := dlr.(*quicDialerLogger) logger := dlr.(*quicDialerLogger)
if logger.Logger != log.Log { if logger.Logger != log.Log {
t.Fatal("invalid logger") t.Fatal("invalid logger")
@ -37,7 +53,9 @@ func TestNewQUICDialer(t *testing.T) {
if logger.Logger != log.Log { if logger.Logger != log.Log {
t.Fatal("invalid logger") t.Fatal("invalid logger")
} }
errWrapper := logger.Dialer.(*quicDialerErrWrapper) ext2 := logger.Dialer.(*extensionQUICDialerSecond)
ext1 := ext2.QUICDialer.(*extensionQUICDialerFirst)
errWrapper := ext1.QUICDialer.(*quicDialerErrWrapper)
handshakeCompleter := errWrapper.QUICDialer.(*quicDialerHandshakeCompleter) handshakeCompleter := errWrapper.QUICDialer.(*quicDialerHandshakeCompleter)
base := handshakeCompleter.Dialer.(*quicDialerQUICGo) base := handshakeCompleter.Dialer.(*quicDialerQUICGo)
if base.QUICListener != ql { if base.QUICListener != ql {