refactor(netxlite): better integration with tracex (#774)
Rather than passing functions to construct complex objects such as Dialer and QUICDialer, pass interface implementations. Ensure that a nil implementation does not cause harm. Make Saver implement the correct interface either directly or indirectly. We need to implement the correct interface indirectly for TCP conns (or connected UDP sockets) because we have two distinct use cases inside netx: observing just the connect event and observing just the I/O events. With this change, the construction of composed Dialers and QUICDialers is greatly simplified and more obvious. Part of https://github.com/ooni/probe/issues/2121
This commit is contained in:
parent
f4f3ed7c42
commit
7e0b47311d
|
@ -54,21 +54,10 @@ func New(config *Config, resolver model.Resolver) model.Dialer {
|
||||||
if config.Logger != nil {
|
if config.Logger != nil {
|
||||||
logger = config.Logger
|
logger = config.Logger
|
||||||
}
|
}
|
||||||
modifiers := []netxlite.DialerWrapper{
|
d := netxlite.NewDialerWithResolver(
|
||||||
func(dialer model.Dialer) model.Dialer {
|
logger, resolver, config.DialSaver.NewConnectObserver(),
|
||||||
if config.DialSaver != nil {
|
config.ReadWriteSaver.NewReadWriteObserver(),
|
||||||
dialer = &tracex.SaverDialer{Dialer: dialer, Saver: config.DialSaver}
|
)
|
||||||
}
|
|
||||||
return dialer
|
|
||||||
},
|
|
||||||
func(dialer model.Dialer) model.Dialer {
|
|
||||||
if config.ReadWriteSaver != nil {
|
|
||||||
dialer = &tracex.SaverConnDialer{Dialer: dialer, Saver: config.ReadWriteSaver}
|
|
||||||
}
|
|
||||||
return dialer
|
|
||||||
},
|
|
||||||
}
|
|
||||||
d := netxlite.NewDialerWithResolver(logger, resolver, modifiers...)
|
|
||||||
d = &netxlite.MaybeProxyDialer{ProxyURL: config.ProxyURL, Dialer: d}
|
d = &netxlite.MaybeProxyDialer{ProxyURL: config.ProxyURL, Dialer: d}
|
||||||
if config.ContextByteCounting {
|
if config.ContextByteCounting {
|
||||||
d = &bytecounter.ContextAwareDialer{Dialer: d}
|
d = &bytecounter.ContextAwareDialer{Dialer: d}
|
||||||
|
|
|
@ -132,12 +132,7 @@ func NewQUICDialer(config Config) model.QUICDialer {
|
||||||
if config.Logger != nil {
|
if config.Logger != nil {
|
||||||
logger = config.Logger
|
logger = config.Logger
|
||||||
}
|
}
|
||||||
extensions := []netxlite.QUICDialerWrapper{
|
return netxlite.NewQUICDialerWithResolver(ql, logger, config.FullResolver, config.TLSSaver)
|
||||||
func(dialer model.QUICDialer) model.QUICDialer {
|
|
||||||
return config.TLSSaver.WrapQUICDialer(dialer) // robust to nil TLSSaver
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return netxlite.NewQUICDialerWithResolver(ql, logger, config.FullResolver, extensions...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTLSDialer creates a new TLSDialer from the specified config
|
// NewTLSDialer creates a new TLSDialer from the specified config
|
||||||
|
|
|
@ -22,6 +22,31 @@ type SaverDialer struct {
|
||||||
Saver *Saver
|
Saver *Saver
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewConnectObserver returns a DialerWrapper that observes the
|
||||||
|
// connect event. This function will return nil, which is a valid
|
||||||
|
// DialerWrapper for netxlite.WrapDialer, if Saver is nil.
|
||||||
|
func (s *Saver) NewConnectObserver() model.DialerWrapper {
|
||||||
|
if s == nil {
|
||||||
|
return nil // valid DialerWrapper according to netxlite's docs
|
||||||
|
}
|
||||||
|
return &saverDialerWrapper{
|
||||||
|
saver: s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type saverDialerWrapper struct {
|
||||||
|
saver *Saver
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ model.DialerWrapper = &saverDialerWrapper{}
|
||||||
|
|
||||||
|
func (w *saverDialerWrapper) WrapDialer(d model.Dialer) model.Dialer {
|
||||||
|
return &SaverDialer{
|
||||||
|
Dialer: d,
|
||||||
|
Saver: w.saver,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// DialContext implements Dialer.DialContext
|
// DialContext implements Dialer.DialContext
|
||||||
func (d *SaverDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
func (d *SaverDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
@ -52,6 +77,31 @@ type SaverConnDialer struct {
|
||||||
Saver *Saver
|
Saver *Saver
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewReadWriteObserver returns a DialerWrapper that observes the
|
||||||
|
// I/O events. This function will return nil, which is a valid
|
||||||
|
// DialerWrapper for netxlite.WrapDialer, if Saver is nil.
|
||||||
|
func (s *Saver) NewReadWriteObserver() model.DialerWrapper {
|
||||||
|
if s == nil {
|
||||||
|
return nil // valid DialerWrapper according to netxlite's docs
|
||||||
|
}
|
||||||
|
return &saverReadWriteWrapper{
|
||||||
|
saver: s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type saverReadWriteWrapper struct {
|
||||||
|
saver *Saver
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ model.DialerWrapper = &saverReadWriteWrapper{}
|
||||||
|
|
||||||
|
func (w *saverReadWriteWrapper) WrapDialer(d model.Dialer) model.Dialer {
|
||||||
|
return &SaverConnDialer{
|
||||||
|
Dialer: d,
|
||||||
|
Saver: w.saver,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// DialContext implements Dialer.DialContext
|
// DialContext implements Dialer.DialContext
|
||||||
func (d *SaverConnDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
func (d *SaverConnDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
conn, err := d.Dialer.DialContext(ctx, network, address)
|
conn, err := d.Dialer.DialContext(ctx, network, address)
|
||||||
|
|
|
@ -7,7 +7,7 @@ package tracex
|
||||||
import "sync"
|
import "sync"
|
||||||
|
|
||||||
// The Saver saves a trace. The zero value of this type
|
// The Saver saves a trace. The zero value of this type
|
||||||
// is valid and can be used without initializtion.
|
// is valid and can be used without initialization.
|
||||||
type Saver struct {
|
type Saver struct {
|
||||||
// ops contains the saved events.
|
// ops contains the saved events.
|
||||||
ops []Event
|
ops []Event
|
||||||
|
|
|
@ -23,12 +23,7 @@ func TestSaverTLSHandshakerSuccessWithReadWrite(t *testing.T) {
|
||||||
Dialer: netxlite.NewDialerWithResolver(
|
Dialer: netxlite.NewDialerWithResolver(
|
||||||
model.DiscardLogger,
|
model.DiscardLogger,
|
||||||
netxlite.NewResolverStdlib(model.DiscardLogger),
|
netxlite.NewResolverStdlib(model.DiscardLogger),
|
||||||
func(dialer model.Dialer) model.Dialer {
|
saver.NewReadWriteObserver(),
|
||||||
return &SaverConnDialer{
|
|
||||||
Dialer: dialer,
|
|
||||||
Saver: saver,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
TLSHandshaker: saver.WrapTLSHandshaker(&netxlite.TLSHandshakerConfigurable{}),
|
TLSHandshaker: saver.WrapTLSHandshaker(&netxlite.TLSHandshakerConfigurable{}),
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,6 +119,12 @@ type DNSTransport interface {
|
||||||
CloseIdleConnections()
|
CloseIdleConnections()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialerWrapper is a type that takes in input a Dialer
|
||||||
|
// and returns in output a wrapped Dialer.
|
||||||
|
type DialerWrapper interface {
|
||||||
|
WrapDialer(d Dialer) Dialer
|
||||||
|
}
|
||||||
|
|
||||||
// SimpleDialer establishes network connections.
|
// SimpleDialer establishes network connections.
|
||||||
type SimpleDialer interface {
|
type SimpleDialer interface {
|
||||||
// DialContext behaves like net.Dialer.DialContext.
|
// DialContext behaves like net.Dialer.DialContext.
|
||||||
|
@ -171,6 +177,12 @@ type QUICListener interface {
|
||||||
Listen(addr *net.UDPAddr) (UDPLikeConn, error)
|
Listen(addr *net.UDPAddr) (UDPLikeConn, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QUICDialerWrapper is a type that takes in input a QUICDialer
|
||||||
|
// and returns in output a wrapped QUICDialer.
|
||||||
|
type QUICDialerWrapper interface {
|
||||||
|
WrapQUICDialer(qd QUICDialer) QUICDialer
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
|
|
@ -14,13 +14,9 @@ import (
|
||||||
"github.com/ooni/probe-cli/v3/internal/model"
|
"github.com/ooni/probe-cli/v3/internal/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DialerWrapper is a function that allows you to customize the kind of Dialer returned
|
|
||||||
// by WrapDialer, NewDialerWithResolver, and NewDialerWithoutResolver.
|
|
||||||
type DialerWrapper func(dialer model.Dialer) model.Dialer
|
|
||||||
|
|
||||||
// NewDialerWithResolver is equivalent to calling WrapDialer with
|
// NewDialerWithResolver is equivalent to calling WrapDialer with
|
||||||
// the dialer argument being equal to &DialerSystem{}.
|
// the dialer argument being equal to &DialerSystem{}.
|
||||||
func NewDialerWithResolver(dl model.DebugLogger, r model.Resolver, w ...DialerWrapper) model.Dialer {
|
func NewDialerWithResolver(dl model.DebugLogger, r model.Resolver, w ...model.DialerWrapper) model.Dialer {
|
||||||
return WrapDialer(dl, r, &DialerSystem{}, w...)
|
return WrapDialer(dl, r, &DialerSystem{}, w...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +36,8 @@ func NewDialerWithResolver(dl model.DebugLogger, r model.Resolver, w ...DialerWr
|
||||||
// 3. baseDialer is the dialer to wrap (MUST NOT be nil);
|
// 3. baseDialer is the dialer to wrap (MUST NOT be nil);
|
||||||
//
|
//
|
||||||
// 4. wrappers is a list of zero or more functions allowing you to
|
// 4. wrappers is a list of zero or more functions allowing you to
|
||||||
// modify the behavior of the returned dialer (see below).
|
// modify the behavior of the returned dialer (see below). Please note
|
||||||
|
// that this function will just ignore any nil wrapper.
|
||||||
//
|
//
|
||||||
// Return value
|
// Return value
|
||||||
//
|
//
|
||||||
|
@ -109,12 +106,15 @@ func NewDialerWithResolver(dl model.DebugLogger, r model.Resolver, w ...DialerWr
|
||||||
// of a single connect operation. You may want to use the context to reduce
|
// of a single connect operation. You may want to use the context to reduce
|
||||||
// the overall time spent trying all addresses and timing out.
|
// the overall time spent trying all addresses and timing out.
|
||||||
func WrapDialer(logger model.DebugLogger, resolver model.Resolver,
|
func WrapDialer(logger model.DebugLogger, resolver model.Resolver,
|
||||||
baseDialer model.Dialer, wrappers ...DialerWrapper) (outDialer model.Dialer) {
|
baseDialer model.Dialer, wrappers ...model.DialerWrapper) (outDialer model.Dialer) {
|
||||||
outDialer = &dialerErrWrapper{
|
outDialer = &dialerErrWrapper{
|
||||||
Dialer: baseDialer,
|
Dialer: baseDialer,
|
||||||
}
|
}
|
||||||
for _, wrapper := range wrappers {
|
for _, wrapper := range wrappers {
|
||||||
outDialer = wrapper(outDialer) // extend with user-supplied constructors
|
if wrapper == nil {
|
||||||
|
continue // ignore as documented
|
||||||
|
}
|
||||||
|
outDialer = wrapper.WrapDialer(outDialer) // extend with user-supplied constructors
|
||||||
}
|
}
|
||||||
return &dialerLogger{
|
return &dialerLogger{
|
||||||
Dialer: &dialerResolver{
|
Dialer: &dialerResolver{
|
||||||
|
@ -131,7 +131,7 @@ func WrapDialer(logger model.DebugLogger, resolver model.Resolver,
|
||||||
|
|
||||||
// NewDialerWithoutResolver is equivalent to calling NewDialerWithResolver
|
// NewDialerWithoutResolver is equivalent to calling NewDialerWithResolver
|
||||||
// with the resolver argument being &NullResolver{}.
|
// with the resolver argument being &NullResolver{}.
|
||||||
func NewDialerWithoutResolver(dl model.DebugLogger, w ...DialerWrapper) model.Dialer {
|
func NewDialerWithoutResolver(dl model.DebugLogger, w ...model.DialerWrapper) model.Dialer {
|
||||||
return NewDialerWithResolver(dl, &NullResolver{}, w...)
|
return NewDialerWithResolver(dl, &NullResolver{}, w...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,19 +19,27 @@ type extensionDialerFirst struct {
|
||||||
model.Dialer
|
model.Dialer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dialerWrapperFirst struct{}
|
||||||
|
|
||||||
|
func (*dialerWrapperFirst) WrapDialer(d model.Dialer) model.Dialer {
|
||||||
|
return &extensionDialerFirst{d}
|
||||||
|
}
|
||||||
|
|
||||||
type extensionDialerSecond struct {
|
type extensionDialerSecond struct {
|
||||||
model.Dialer
|
model.Dialer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dialerWrapperSecond struct{}
|
||||||
|
|
||||||
|
func (*dialerWrapperSecond) WrapDialer(d model.Dialer) model.Dialer {
|
||||||
|
return &extensionDialerSecond{d}
|
||||||
|
}
|
||||||
func TestNewDialer(t *testing.T) {
|
func TestNewDialer(t *testing.T) {
|
||||||
t.Run("produces a chain with the expected types", func(t *testing.T) {
|
t.Run("produces a chain with the expected types", func(t *testing.T) {
|
||||||
modifiers := []DialerWrapper{
|
modifiers := []model.DialerWrapper{
|
||||||
func(dialer model.Dialer) model.Dialer {
|
&dialerWrapperFirst{},
|
||||||
return &extensionDialerFirst{dialer}
|
nil, // explicitly test for this documented case
|
||||||
},
|
&dialerWrapperSecond{},
|
||||||
func(dialer model.Dialer) model.Dialer {
|
|
||||||
return &extensionDialerSecond{dialer}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
d := NewDialerWithoutResolver(log.Log, modifiers...)
|
d := NewDialerWithoutResolver(log.Log, modifiers...)
|
||||||
logger := d.(*dialerLogger)
|
logger := d.(*dialerLogger)
|
||||||
|
|
|
@ -32,18 +32,16 @@ func (qls *quicListenerStdlib) Listen(addr *net.UDPAddr) (model.UDPLikeConn, err
|
||||||
return TProxy.ListenUDP("udp", addr)
|
return TProxy.ListenUDP("udp", addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// QUICDialerWrapper is a function that allows you to customize the kind of QUICDialer
|
|
||||||
// returned by NewQUICDialerWithResolver and NewQUICDialerWithoutResolver.
|
|
||||||
type QUICDialerWrapper func(dialer model.QUICDialer) model.QUICDialer
|
|
||||||
|
|
||||||
// NewQUICDialerWithResolver is the WrapDialer equivalent for QUIC where
|
// NewQUICDialerWithResolver is the WrapDialer equivalent for QUIC where
|
||||||
// we return a composed QUICDialer modified by optional wrappers.
|
// we return a composed QUICDialer modified by optional wrappers.
|
||||||
//
|
//
|
||||||
|
// Please, note that this fuunction will just ignore any nil wrapper.
|
||||||
|
//
|
||||||
// Unlike the dialer returned by WrapDialer, this dialer MAY attempt
|
// Unlike the dialer returned by WrapDialer, this dialer MAY attempt
|
||||||
// happy eyeballs, perform parallel dial attempts, and return an error
|
// happy eyeballs, perform parallel dial attempts, and return an error
|
||||||
// that aggregates all the errors that occurred.
|
// that aggregates all the errors that occurred.
|
||||||
func NewQUICDialerWithResolver(listener model.QUICListener, logger model.DebugLogger,
|
func NewQUICDialerWithResolver(listener model.QUICListener, logger model.DebugLogger,
|
||||||
resolver model.Resolver, wrappers ...QUICDialerWrapper) (outDialer model.QUICDialer) {
|
resolver model.Resolver, wrappers ...model.QUICDialerWrapper) (outDialer model.QUICDialer) {
|
||||||
outDialer = &quicDialerErrWrapper{
|
outDialer = &quicDialerErrWrapper{
|
||||||
QUICDialer: &quicDialerHandshakeCompleter{
|
QUICDialer: &quicDialerHandshakeCompleter{
|
||||||
Dialer: &quicDialerQUICGo{
|
Dialer: &quicDialerQUICGo{
|
||||||
|
@ -52,7 +50,10 @@ func NewQUICDialerWithResolver(listener model.QUICListener, logger model.DebugLo
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, wrapper := range wrappers {
|
for _, wrapper := range wrappers {
|
||||||
outDialer = wrapper(outDialer) // extend with user-supplied constructors
|
if wrapper == nil {
|
||||||
|
continue // ignore as documented
|
||||||
|
}
|
||||||
|
outDialer = wrapper.WrapQUICDialer(outDialer) // extend with user-supplied constructors
|
||||||
}
|
}
|
||||||
return &quicDialerLogger{
|
return &quicDialerLogger{
|
||||||
Dialer: &quicDialerResolver{
|
Dialer: &quicDialerResolver{
|
||||||
|
@ -70,7 +71,7 @@ func NewQUICDialerWithResolver(listener model.QUICListener, logger model.DebugLo
|
||||||
// NewQUICDialerWithoutResolver is equivalent to calling NewQUICDialerWithResolver
|
// NewQUICDialerWithoutResolver is equivalent to calling NewQUICDialerWithResolver
|
||||||
// with the resolver argument set to &NullResolver{}.
|
// with the resolver argument set to &NullResolver{}.
|
||||||
func NewQUICDialerWithoutResolver(listener model.QUICListener,
|
func NewQUICDialerWithoutResolver(listener model.QUICListener,
|
||||||
logger model.DebugLogger, wrappers ...QUICDialerWrapper) model.QUICDialer {
|
logger model.DebugLogger, wrappers ...model.QUICDialerWrapper) model.QUICDialer {
|
||||||
return NewQUICDialerWithResolver(listener, logger, &NullResolver{}, wrappers...)
|
return NewQUICDialerWithResolver(listener, logger, &NullResolver{}, wrappers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,19 +26,30 @@ type extensionQUICDialerFirst struct {
|
||||||
model.QUICDialer
|
model.QUICDialer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type quicDialerWrapperFirst struct{}
|
||||||
|
|
||||||
|
func (*quicDialerWrapperFirst) WrapQUICDialer(qd model.QUICDialer) model.QUICDialer {
|
||||||
|
return &extensionQUICDialerFirst{qd}
|
||||||
|
}
|
||||||
|
|
||||||
type extensionQUICDialerSecond struct {
|
type extensionQUICDialerSecond struct {
|
||||||
model.QUICDialer
|
model.QUICDialer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type quicDialerWrapperSecond struct {
|
||||||
|
model.QUICDialer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*quicDialerWrapperSecond) WrapQUICDialer(qd model.QUICDialer) model.QUICDialer {
|
||||||
|
return &extensionQUICDialerSecond{qd}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewQUICDialer(t *testing.T) {
|
func TestNewQUICDialer(t *testing.T) {
|
||||||
ql := NewQUICListener()
|
ql := NewQUICListener()
|
||||||
extensions := []QUICDialerWrapper{
|
extensions := []model.QUICDialerWrapper{
|
||||||
func(dialer model.QUICDialer) model.QUICDialer {
|
&quicDialerWrapperFirst{},
|
||||||
return &extensionQUICDialerFirst{dialer}
|
nil, // explicitly test for this documented case
|
||||||
},
|
&quicDialerWrapperSecond{},
|
||||||
func(dialer model.QUICDialer) model.QUICDialer {
|
|
||||||
return &extensionQUICDialerSecond{dialer}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
dlr := NewQUICDialerWithoutResolver(ql, log.Log, extensions...)
|
dlr := NewQUICDialerWithoutResolver(ql, log.Log, extensions...)
|
||||||
logger := dlr.(*quicDialerLogger)
|
logger := dlr.(*quicDialerLogger)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user