Skip to content

Commit

Permalink
refactor(netxlite): expose useful HTTPTransport/DNSTransport factories (
Browse files Browse the repository at this point in the history
#813)

These factories will soon be useful to finish with
ooni/probe#2135.
  • Loading branch information
bassosimone authored Jun 8, 2022
1 parent 1a706e4 commit 1685ef7
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 30 deletions.
3 changes: 1 addition & 2 deletions internal/cmd/oohelper/oohelper.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ func init() {
// puzzling https://github.com/ooni/probe/issues/1409 issue.
const resolverURL = "https://8.8.8.8/dns-query"
resolver = netxlite.NewParallelDNSOverHTTPSResolver(log.Log, resolverURL)
txp := netxlite.NewHTTPTransportWithResolver(log.Log, resolver)
httpClient = netxlite.NewHTTPClient(txp)
httpClient = netxlite.NewHTTPClientWithResolver(log.Log, resolver)
}

func main() {
Expand Down
3 changes: 1 addition & 2 deletions internal/cmd/oohelperd/oohelperd.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ func init() {
// default resolver configured by the box. Also, use an encrypted transport thus
// we're less vulnerable to any policy implemented by the box's provider.
resolver = netxlite.NewParallelDNSOverHTTPSResolver(log.Log, "https://8.8.8.8/dns-query")
txp := netxlite.NewHTTPTransportWithResolver(log.Log, resolver)
httpClient = netxlite.NewHTTPClient(txp)
httpClient = netxlite.NewHTTPClientWithResolver(log.Log, resolver)
}

func shutdown(srv *http.Server) {
Expand Down
8 changes: 3 additions & 5 deletions internal/engine/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,11 +197,9 @@ func NewSession(ctx context.Context, config SessionConfig) (*Session, error) {
Logger: sess.logger,
ProxyURL: proxyURL,
}
dialer := netxlite.NewDialerWithResolver(sess.logger, sess.resolver)
dialer = netxlite.MaybeWrapWithProxyDialer(dialer, proxyURL)
handshaker := netxlite.NewTLSHandshakerStdlib(sess.logger)
tlsDialer := netxlite.NewTLSDialer(dialer, handshaker)
txp := netxlite.NewHTTPTransport(sess.logger, dialer, tlsDialer)
txp := netxlite.NewHTTPTransportWithLoggerResolverAndOptionalProxyURL(
sess.logger, sess.resolver, sess.proxyURL,
)
txp = bytecounter.WrapHTTPTransport(txp, sess.byteCounter)
sess.httpDefaultTransport = txp
return sess, nil
Expand Down
12 changes: 12 additions & 0 deletions internal/netxlite/dnsoverhttps.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ func NewUnwrappedDNSOverHTTPSTransport(client model.HTTPClient, URL string) *DNS
return NewUnwrappedDNSOverHTTPSTransportWithHostOverride(client, URL, "")
}

// NewDNSOverHTTPSTransport is like NewUnwrappedDNSOverHTTPSTransport but
// returns an already wrapped DNSTransport.
func NewDNSOverHTTPSTransport(client model.HTTPClient, URL string) model.DNSTransport {
return WrapDNSTransport(NewUnwrappedDNSOverHTTPSTransport(client, URL))
}

// NewDNSOverHTTPSTransportWithHTTPTransport is like NewDNSOverHTTPSTransport
// but takes in input an HTTPTransport rather than an HTTPClient.
func NewDNSOverHTTPSTransportWithHTTPTransport(txp model.HTTPTransport, URL string) model.DNSTransport {
return WrapDNSTransport(NewUnwrappedDNSOverHTTPSTransport(NewHTTPClient(txp), URL))
}

// NewUnwrappedDNSOverHTTPSTransportWithHostOverride creates a new DNSOverHTTPSTransport
// with the given Host header override. This instance has not been wrapped yet.
func NewUnwrappedDNSOverHTTPSTransportWithHostOverride(
Expand Down
30 changes: 30 additions & 0 deletions internal/netxlite/dnsoverhttps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,36 @@ import (
"github.com/ooni/probe-cli/v3/internal/model/mocks"
)

func TestNewDNSOverHTTPSTransport(t *testing.T) {
const URL = "https://1.1.1.1/dns-query"
clnt := NewHTTPClientStdlib(model.DiscardLogger)
txp := NewDNSOverHTTPSTransport(clnt, URL)
ew := txp.(*dnsTransportErrWrapper)
https := ew.DNSTransport.(*DNSOverHTTPSTransport)
if https.Client != clnt {
t.Fatal("invalid client")
}
if https.URL != URL {
t.Fatal("invalid URL")
}
}

func TestNewDNSOverHTTPSTransportWithHTTPTransport(t *testing.T) {
const URL = "https://1.1.1.1/dns-query"
httpTxp := NewHTTPTransportStdlib(model.DiscardLogger)
txp := NewDNSOverHTTPSTransportWithHTTPTransport(httpTxp, URL)
ew := txp.(*dnsTransportErrWrapper)
https := ew.DNSTransport.(*DNSOverHTTPSTransport)
ewClient := https.Client.(*httpClientErrWrapper)
clnt := ewClient.HTTPClient.(*http.Client)
if clnt.Transport != httpTxp {
t.Fatal("invalid transport")
}
if https.URL != URL {
t.Fatal("invalid URL")
}
}

func TestDNSOverHTTPSTransport(t *testing.T) {
t.Run("RoundTrip", func(t *testing.T) {
t.Run("query serialization failure", func(t *testing.T) {
Expand Down
26 changes: 26 additions & 0 deletions internal/netxlite/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"errors"
"net"
"net/http"
"net/url"
"time"

oohttp "github.com/ooni/oohttp"
Expand Down Expand Up @@ -105,6 +106,25 @@ func (txp *httpTransportConnectionsCloser) CloseIdleConnections() {
txp.TLSDialer.CloseIdleConnections()
}

// NewHTTPTransportWithLoggerResolverAndOptionalProxyURL creates an HTTPTransport using
// the given logger and resolver and an optional proxy URL.
//
// Arguments:
//
// - logger is the MANDATORY logger;
//
// - resolver is the MANDATORY resolver;
//
// - purl is the OPTIONAL proxy URL.
func NewHTTPTransportWithLoggerResolverAndOptionalProxyURL(
logger model.DebugLogger, resolver model.Resolver, purl *url.URL) model.HTTPTransport {
dialer := NewDialerWithResolver(logger, resolver)
dialer = MaybeWrapWithProxyDialer(dialer, purl)
handshaker := NewTLSHandshakerStdlib(logger)
tlsDialer := NewTLSDialer(dialer, handshaker)
return NewHTTPTransport(logger, dialer, tlsDialer)
}

// NewHTTPTransportWithResolver creates a new HTTP transport using
// the stdlib for everything but the given resolver.
func NewHTTPTransportWithResolver(logger model.DebugLogger, reso model.Resolver) model.HTTPTransport {
Expand Down Expand Up @@ -335,6 +355,12 @@ func NewHTTPClientStdlib(logger model.DebugLogger) model.HTTPClient {
return NewHTTPClient(txp)
}

// NewHTTPClientWithResolver creates a new HTTPTransport using the
// given resolver and then from that builds an HTTPClient.
func NewHTTPClientWithResolver(logger model.Logger, reso model.Resolver) model.HTTPClient {
return NewHTTPClient(NewHTTPTransportWithResolver(logger, reso))
}

// NewHTTPClient creates a new, wrapped HTTPClient using the given transport.
func NewHTTPClient(txp model.HTTPTransport) model.HTTPClient {
return WrapHTTPClient(&http.Client{Transport: txp})
Expand Down
16 changes: 16 additions & 0 deletions internal/netxlite/http3.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,19 @@ func NewHTTP3Transport(
dialer: dialer,
})
}

// NewHTTP3TransportStdlib creates a new HTTPTransport using http3 that
// uses standard functionality for everything but the logger.
func NewHTTP3TransportStdlib(logger model.DebugLogger) model.HTTPTransport {
ql := NewQUICListener()
reso := NewStdlibResolver(logger)
qd := NewQUICDialerWithResolver(ql, logger, reso)
return NewHTTP3Transport(logger, qd, nil)
}

// NewHTTPTransportWithResolver creates a new HTTPTransport using http3
// that uses the given logger and the given resolver.
func NewHTTP3TransportWithResolver(logger model.Logger, reso model.Resolver) model.HTTPTransport {
qd := NewQUICDialerWithResolver(NewQUICListener(), logger, reso)
return NewHTTP3Transport(logger, qd, nil)
}
91 changes: 70 additions & 21 deletions internal/netxlite/http3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import (
"net/http"
"testing"

"github.com/apex/log"
"github.com/lucas-clemente/quic-go/http3"
"github.com/ooni/probe-cli/v3/internal/model"
"github.com/ooni/probe-cli/v3/internal/model/mocks"
nlmocks "github.com/ooni/probe-cli/v3/internal/netxlite/mocks"
)
Expand Down Expand Up @@ -63,29 +63,78 @@ func TestHTTP3Transport(t *testing.T) {
})
}

// verifyTypeChainForHTTP3 helps to verify type chains for HTTP3.
//
// Arguments:
//
// - t is the MANDATORY testing ref;
//
// - txp is the MANDATORY HTTP transport to verify;
//
// - underlyingLogger is the MANDATORY logger we expect to find;
//
// - qd is the OPTIONAL QUIC dialer: if not nil, we expect to
// see this value as the QUIC dialer, otherwise we will check the
// type chain of the real dialer;
//
// - config is the MANDATORY TLS config: we'll always check
// whether the TLSClientConfig is equal to this value: passing
// nil here means we expect to see nil in the object;
//
// - reso is the OPTIONAL resolver: if present and the qd is
// nil, we'll unwrap the QUIC dialer and check whether we have
// this resolver as the underlying resolver.
func verifyTypeChainForHTTP3(t *testing.T, txp model.HTTPTransport,
underlyingLogger model.DebugLogger, qd model.QUICDialer,
config *tls.Config, reso model.Resolver) {
logger := txp.(*httpTransportLogger)
if logger.Logger != underlyingLogger {
t.Fatal("invalid logger")
}
ew := logger.HTTPTransport.(*httpTransportErrWrapper)
h3txp := ew.HTTPTransport.(*http3Transport)
if qd != nil && h3txp.dialer != qd {
t.Fatal("invalid dialer")
}
if qd == nil {
qdlog := h3txp.dialer.(*quicDialerLogger)
qdr := qdlog.Dialer.(*quicDialerResolver)
if reso != nil && qdr.Resolver != reso {
t.Fatal("invalid resolver")
}
}
h3 := h3txp.child.(*http3.RoundTripper)
if h3.Dial == nil {
t.Fatal("invalid Dial")
}
if !h3.DisableCompression {
t.Fatal("invalid DisableCompression")
}
if h3.TLSClientConfig != config {
t.Fatal("invalid TLSClientConfig")
}
}

func TestNewHTTP3Transport(t *testing.T) {
t.Run("creates the correct type chain", func(t *testing.T) {
qd := &mocks.QUICDialer{}
config := &tls.Config{}
txp := NewHTTP3Transport(log.Log, qd, config)
logger := txp.(*httpTransportLogger)
if logger.Logger != log.Log {
t.Fatal("invalid logger")
}
ew := logger.HTTPTransport.(*httpTransportErrWrapper)
h3txp := ew.HTTPTransport.(*http3Transport)
if h3txp.dialer != qd {
t.Fatal("invalid dialer")
}
h3 := h3txp.child.(*http3.RoundTripper)
if h3.Dial == nil {
t.Fatal("invalid Dial")
}
if !h3.DisableCompression {
t.Fatal("invalid DisableCompression")
}
if h3.TLSClientConfig != config {
t.Fatal("invalid TLSClientConfig")
}
txp := NewHTTP3Transport(model.DiscardLogger, qd, config)
verifyTypeChainForHTTP3(t, txp, model.DiscardLogger, qd, config, nil)
})
}

func TestNewHTTP3TransportStdlib(t *testing.T) {
t.Run("creates the correct type chain", func(t *testing.T) {
txp := NewHTTP3TransportStdlib(model.DiscardLogger)
verifyTypeChainForHTTP3(t, txp, model.DiscardLogger, nil, nil, nil)
})
}

func TestNewHTTP3TransportWithResolver(t *testing.T) {
t.Run("creates the correct type chain", func(t *testing.T) {
reso := &mocks.Resolver{}
txp := NewHTTP3TransportWithResolver(model.DiscardLogger, reso)
verifyTypeChainForHTTP3(t, txp, model.DiscardLogger, nil, nil, reso)
})
}
65 changes: 65 additions & 0 deletions internal/netxlite/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io"
"net"
"net/http"
"net/url"
"strings"
"testing"
"time"
Expand All @@ -16,6 +17,48 @@ import (
"github.com/ooni/probe-cli/v3/internal/model/mocks"
)

func TestNewHTTPTransportWithLoggerResolverAndOptionalProxyURL(t *testing.T) {
t.Run("without proxy URL", func(t *testing.T) {
logger := &mocks.Logger{}
resolver := &mocks.Resolver{}
txp := NewHTTPTransportWithLoggerResolverAndOptionalProxyURL(logger, resolver, nil)
txpLogger := txp.(*httpTransportLogger)
if txpLogger.Logger != logger {
t.Fatal("unexpected logger")
}
txpErrWrapper := txpLogger.HTTPTransport.(*httpTransportErrWrapper)
txpCc := txpErrWrapper.HTTPTransport.(*httpTransportConnectionsCloser)
dialer := txpCc.Dialer
dialerWithReadTimeout := dialer.(*httpDialerWithReadTimeout)
dialerLog := dialerWithReadTimeout.Dialer.(*dialerLogger)
dialerReso := dialerLog.Dialer.(*dialerResolver)
if dialerReso.Resolver != resolver {
t.Fatal("invalid resolver")
}
})

t.Run("with proxy URL", func(t *testing.T) {
URL := &url.URL{}
logger := &mocks.Logger{}
resolver := &mocks.Resolver{}
txp := NewHTTPTransportWithLoggerResolverAndOptionalProxyURL(logger, resolver, URL)
txpLogger := txp.(*httpTransportLogger)
if txpLogger.Logger != logger {
t.Fatal("unexpected logger")
}
txpErrWrapper := txpLogger.HTTPTransport.(*httpTransportErrWrapper)
txpCc := txpErrWrapper.HTTPTransport.(*httpTransportConnectionsCloser)
dialer := txpCc.Dialer
dialerWithReadTimeout := dialer.(*httpDialerWithReadTimeout)
dialerProxy := dialerWithReadTimeout.Dialer.(*proxyDialer)
dialerLog := dialerProxy.Dialer.(*dialerLogger)
dialerReso := dialerLog.Dialer.(*dialerResolver)
if dialerReso.Resolver != resolver {
t.Fatal("invalid resolver")
}
})
}

func TestNewHTTPTransportWithResolver(t *testing.T) {
expected := errors.New("mocked error")
reso := &mocks.Resolver{
Expand Down Expand Up @@ -553,6 +596,28 @@ func TestNewHTTPClientStdlib(t *testing.T) {
}
}

func TestNewHTTPClientWithResolver(t *testing.T) {
reso := &mocks.Resolver{}
clnt := NewHTTPClientWithResolver(model.DiscardLogger, reso)
ewc, ok := clnt.(*httpClientErrWrapper)
if !ok {
t.Fatal("expected *httpClientErrWrapper")
}
httpClnt, ok := ewc.HTTPClient.(*http.Client)
if !ok {
t.Fatal("expected *http.Client")
}
txp := httpClnt.Transport.(*httpTransportLogger)
txpEwrap := txp.HTTPTransport.(*httpTransportErrWrapper)
txpCc := txpEwrap.HTTPTransport.(*httpTransportConnectionsCloser)
dialer := txpCc.Dialer.(*httpDialerWithReadTimeout)
dialerLogger := dialer.Dialer.(*dialerLogger)
dialerReso := dialerLogger.Dialer.(*dialerResolver)
if dialerReso.Resolver != reso {
t.Fatal("invalid resolver")
}
}

func TestWrapHTTPClient(t *testing.T) {
origClient := &http.Client{}
wrapped := WrapHTTPClient(origClient)
Expand Down

0 comments on commit 1685ef7

Please sign in to comment.