diff --git a/internal/netxlite/dialer.go b/internal/netxlite/dialer.go index d4e3672..791a605 100644 --- a/internal/netxlite/dialer.go +++ b/internal/netxlite/dialer.go @@ -85,12 +85,12 @@ var _ Dialer = &dialerSystem{} const dialerDefaultTimeout = 15 * time.Second -func (d *dialerSystem) newUnderlyingDialer() *net.Dialer { +func (d *dialerSystem) newUnderlyingDialer() TProxyDialer { t := d.timeout if t <= 0 { t = dialerDefaultTimeout } - return &net.Dialer{Timeout: t} + return TProxy.NewTProxyDialer(t) } func (d *dialerSystem) DialContext(ctx context.Context, network, address string) (net.Conn, error) { diff --git a/internal/netxlite/dialer_test.go b/internal/netxlite/dialer_test.go index f95eb1f..cdd275f 100644 --- a/internal/netxlite/dialer_test.go +++ b/internal/netxlite/dialer_test.go @@ -38,7 +38,7 @@ func TestDialerSystem(t *testing.T) { t.Run("has a default timeout", func(t *testing.T) { d := &dialerSystem{} ud := d.newUnderlyingDialer() - if ud.Timeout != dialerDefaultTimeout { + if ud.(*net.Dialer).Timeout != dialerDefaultTimeout { t.Fatal("unexpected default timeout") } }) @@ -47,7 +47,7 @@ func TestDialerSystem(t *testing.T) { const smaller = 1 * time.Second d := &dialerSystem{timeout: smaller} ud := d.newUnderlyingDialer() - if ud.Timeout != smaller { + if ud.(*net.Dialer).Timeout != smaller { t.Fatal("unexpected timeout") } }) diff --git a/internal/netxlite/quic.go b/internal/netxlite/quic.go index d0c1f66..2d9d4a0 100644 --- a/internal/netxlite/quic.go +++ b/internal/netxlite/quic.go @@ -34,7 +34,7 @@ var _ QUICListener = &quicListenerStdlib{} // Listen implements QUICListener.Listen. func (qls *quicListenerStdlib) Listen(addr *net.UDPAddr) (UDPLikeConn, error) { - return net.ListenUDP("udp", addr) + return TProxy.ListenUDP("udp", addr) } // QUICDialer dials QUIC sessions. diff --git a/internal/netxlite/resolver.go b/internal/netxlite/resolver.go index ed93b7f..4a0f674 100644 --- a/internal/netxlite/resolver.go +++ b/internal/netxlite/resolver.go @@ -133,7 +133,7 @@ func (r *resolverSystem) lookupHost() func(ctx context.Context, domain string) ( if r.testableLookupHost != nil { return r.testableLookupHost } - return net.DefaultResolver.LookupHost + return TProxy.LookupHost } func (r *resolverSystem) Network() string { diff --git a/internal/netxlite/tproxy.go b/internal/netxlite/tproxy.go new file mode 100644 index 0000000..c7df553 --- /dev/null +++ b/internal/netxlite/tproxy.go @@ -0,0 +1,59 @@ +package netxlite + +import ( + "context" + "net" + "time" + + "github.com/ooni/probe-cli/v3/internal/netxlite/quicx" +) + +// TProxable is the fundamental type used by the netxlite package to perform +// low-level network operations for which, by default, we use the stdlib. +// +// The t stands for transparent. By using this type as the fundamental type, +// we can transparently intercept connections and implement censorship +// policies. The implementation of this functionality is not part of netxlite: +// here we only have the basic mechanism to make this possible. +type TProxable interface { + // ListenUDP creates a new quicx.UDPLikeConn conn. + ListenUDP(network string, laddr *net.UDPAddr) (quicx.UDPLikeConn, error) + + // LookupHost lookups a domain using the stdlib resolver. + LookupHost(ctx context.Context, domain string) ([]string, error) + + // NewTProxyDialer returns a new TProxyDialer. + NewTProxyDialer(timeout time.Duration) TProxyDialer +} + +// TProxyDialer is the dialer type returned by TProxable.NewDialer. +type TProxyDialer interface { + // DialContext behaves like net.Dialer.DialContext. + DialContext(ctx context.Context, network, address string) (net.Conn, error) +} + +// TProxy is the fundamental variable controlling how netxlite creates +// net.Conn and quicx.UDPLikeConn, as well as how it uses the stdlib +// resolver. By modifying this variable, you can effectively transparently +// proxy netxlite (and hence OONI) activities to other services. This is +// quite convenient when performing quality assurance tests. +var TProxy TProxable = &TProxyStdlib{} + +// TProxyStdlib is the default TProxable implementation that uses +// the stdlib in the most obvious way for every functionality. +type TProxyStdlib struct{} + +// ListenUDP calls net.ListenUDP. +func (*TProxyStdlib) ListenUDP(network string, laddr *net.UDPAddr) (quicx.UDPLikeConn, error) { + return net.ListenUDP(network, laddr) +} + +// LookupHost calls net.DefaultResolver.LookupHost. +func (*TProxyStdlib) LookupHost(ctx context.Context, domain string) ([]string, error) { + return net.DefaultResolver.LookupHost(ctx, domain) +} + +// NewTProxyDialer returns a &net.Dialer{Timeout: timeout} instance. +func (*TProxyStdlib) NewTProxyDialer(timeout time.Duration) TProxyDialer { + return &net.Dialer{Timeout: timeout} +}