Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 1 commit into from
May 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 12 additions & 14 deletions internal/engine/netx/netx.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,28 +131,26 @@ func NewQUICDialer(config Config) model.QUICDialer {
if config.FullResolver == nil {
config.FullResolver = NewResolver(config)
}
var ql model.QUICListener = &netxlite.QUICListenerStdlib{}
ql = &netxlite.ErrorWrapperQUICListener{QUICListener: ql}
ql := netxlite.NewQUICListener()
if config.ReadWriteSaver != nil {
ql = &quicdialer.QUICListenerSaver{
QUICListener: ql,
Saver: config.ReadWriteSaver,
}
}
var d model.QUICDialer = &netxlite.QUICDialerQUICGo{
QUICListener: ql,
}
d = &netxlite.ErrorWrapperQUICDialer{
QUICDialer: d,
}
if config.TLSSaver != nil {
d = quicdialer.HandshakeSaver{Saver: config.TLSSaver, QUICDialer: d}
var logger model.DebugLogger = model.DiscardLogger
if config.Logger != nil {
logger = config.Logger
}
d = &netxlite.QUICDialerResolver{
Resolver: config.FullResolver,
Dialer: d,
extensions := []netxlite.QUICDialerWrapper{
func(dialer model.QUICDialer) model.QUICDialer {
if config.TLSSaver != nil {
dialer = quicdialer.HandshakeSaver{Saver: config.TLSSaver, QUICDialer: dialer}
}
return dialer
},
}
return d
return netxlite.NewQUICDialerWithResolver(ql, logger, config.FullResolver, extensions...)
}

// NewTLSDialer creates a new TLSDialer from the specified config
Expand Down
2 changes: 1 addition & 1 deletion internal/netxlite/dialer.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ func NewDialerWithoutResolver(dl model.DebugLogger, w ...DialerWrapper) model.Di
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
// a fixed timeout for each connect operation equal to 15 seconds.
type DialerSystem struct {
Expand Down
62 changes: 27 additions & 35 deletions internal/netxlite/quic.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,39 +32,32 @@ func (qls *quicListenerStdlib) Listen(addr *net.UDPAddr) (model.UDPLikeConn, err
return TProxy.ListenUDP("udp", addr)
}

// NewQUICDialerWithResolver returns a QUICDialer using the given
// QUICListener to create listening connections and the given Resolver
// to resolve domain names (if needed).
//
// Properties of the dialer:
//
// 1. logs events using the given logger;
//
// 2. resolves domain names using the givern resolver;
//
// 3. when using a resolver, _may_ attempt multiple dials
// in parallel (happy eyeballs) and _may_ return an aggregate
// error to the caller;
//
// 4. wraps errors;
//
// 5. has a configured connect timeout;
// 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
// we return a composed QUICDialer modified by optional wrappers.
//
// 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 {
// Unlike the dialer returned by WrapDialer, this dialer MAY attempt
// happy eyeballs, perform parallel dial attempts, and return an error
// that aggregates all the errors that occurred.
func NewQUICDialerWithResolver(listener model.QUICListener, logger model.DebugLogger,
resolver model.Resolver, wrappers ...QUICDialerWrapper) (outDialer model.QUICDialer) {
outDialer = &quicDialerErrWrapper{
QUICDialer: &quicDialerHandshakeCompleter{
Dialer: &quicDialerQUICGo{
QUICListener: listener,
},
},
}
for _, wrapper := range wrappers {
outDialer = wrapper(outDialer) // extend with user-supplied constructors
}
return &quicDialerLogger{
Dialer: &quicDialerResolver{
Dialer: &quicDialerLogger{
Dialer: &quicDialerErrWrapper{
QUICDialer: &quicDialerHandshakeCompleter{
Dialer: &quicDialerQUICGo{
QUICListener: listener,
},
},
},
Dialer: outDialer,
Logger: logger,
operationSuffix: "_address",
},
Expand All @@ -74,12 +67,11 @@ func NewQUICDialerWithResolver(listener model.QUICListener,
}
}

// 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 model.QUICListener, logger model.DebugLogger) model.QUICDialer {
return NewQUICDialerWithResolver(listener, logger, &NullResolver{})
// NewQUICDialerWithoutResolver is equivalent to calling NewQUICDialerWithResolver
// with the resolver argument set to &NullResolver{}.
func NewQUICDialerWithoutResolver(listener model.QUICListener,
logger model.DebugLogger, wrappers ...QUICDialerWrapper) model.QUICDialer {
return NewQUICDialerWithResolver(listener, logger, &NullResolver{}, wrappers...)
}

// quicDialerQUICGo dials using the lucas-clemente/quic-go library.
Expand Down
22 changes: 20 additions & 2 deletions internal/netxlite/quic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,25 @@ func TestNewQUICListener(t *testing.T) {
_ = qew.QUICListener.(*quicListenerStdlib)
}

type extensionQUICDialerFirst struct {
model.QUICDialer
}

type extensionQUICDialerSecond struct {
model.QUICDialer
}

func TestNewQUICDialer(t *testing.T) {
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)
if logger.Logger != log.Log {
t.Fatal("invalid logger")
Expand All @@ -37,7 +53,9 @@ func TestNewQUICDialer(t *testing.T) {
if logger.Logger != log.Log {
t.Fatal("invalid logger")
}
errWrapper := logger.Dialer.(*quicDialerErrWrapper)
ext2 := logger.Dialer.(*extensionQUICDialerSecond)
ext1 := ext2.QUICDialer.(*extensionQUICDialerFirst)
errWrapper := ext1.QUICDialer.(*quicDialerErrWrapper)
handshakeCompleter := errWrapper.QUICDialer.(*quicDialerHandshakeCompleter)
base := handshakeCompleter.Dialer.(*quicDialerQUICGo)
if base.QUICListener != ql {
Expand Down