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

feat(netxlite): construct types with custom UnderlyingNetwork #1197

Merged
merged 28 commits into from
Aug 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
252dc80
(chore): export webconnectivitylte fields to use them in test package
kelmenhorst Jun 29, 2023
c8c91f5
move cmd/oohelperd lib code to internal/oohelperd
kelmenhorst Jun 29, 2023
6cbe601
filtering: expose DNSComposeResponse
kelmenhorst Jun 29, 2023
65dc454
feat: webconnectivitylte success test using netemx
kelmenhorst Jun 29, 2023
daeea0d
renamed webconnectivitylte test
kelmenhorst Jun 29, 2023
4e53b8f
split webconnectivitylte tests
kelmenhorst Jun 29, 2023
7f46267
webconnectivitylte: adapt measurer_test to new netemx QAEnv
kelmenhorst Jul 7, 2023
8c608e2
webconnectivity: add (failing) DPITarget test
kelmenhorst Jul 7, 2023
24401f0
feat: add optional underlying network to netxlite stdlib structs
kelmenhorst Jul 15, 2023
457117e
webconnectivitylte: analysiscore: comment for legacy analysisFlagTLSB…
kelmenhorst Jul 15, 2023
0f26164
feat(netxlite): Net: constructors for operations on custom underlying…
kelmenhorst Jul 15, 2023
a65d6a5
feat(qaenv): make the environment's server stacks accessible, option …
kelmenhorst Jul 15, 2023
24439d6
feat(adapter): wrap given netem.UnderlyingNetwork
kelmenhorst Jul 15, 2023
c5e6740
test(webconnectivitylte): use testhelper on concurrent server stack
kelmenhorst Jul 15, 2023
3fe3dd0
Merge remote-tracking branch 'origin/master' into test-webconnectivity
kelmenhorst Jul 21, 2023
ca42b77
fix: add QAEnv.serverStacks
kelmenhorst Jul 21, 2023
66cff41
make underlying network mandatory for system dialers/resolver/handshaker
kelmenhorst Jul 21, 2023
617a529
remove package webconnectivitylte: use mocks.Session, revert exposing…
kelmenhorst Jul 21, 2023
a4f9e92
netemx geoip: change mock geoip service's response to public server
kelmenhorst Jul 21, 2023
16afba7
QAEnvHTTPHandlerFactory: change construction pattern for http.Handlers
kelmenhorst Jul 21, 2023
c383749
Merge remote-tracking branch 'origin/master' into test-webconnectivity
bassosimone Aug 23, 2023
05648fe
sync
bassosimone Aug 23, 2023
281850b
Merge branch 'master' into test-webconnectivity
bassosimone Aug 23, 2023
b02ecc3
x
bassosimone Aug 23, 2023
d350efd
x
bassosimone Aug 23, 2023
e0fa7c6
start to add some basic tests
bassosimone Aug 23, 2023
702548d
x
bassosimone Aug 23, 2023
6830215
x
bassosimone Aug 23, 2023
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
43 changes: 1 addition & 42 deletions internal/netemx/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,53 +5,12 @@ package netemx
//

import (
"context"
"crypto/x509"
"net"
"time"

"github.com/ooni/netem"
"github.com/ooni/probe-cli/v3/internal/model"
"github.com/ooni/probe-cli/v3/internal/netxlite"
"github.com/ooni/probe-cli/v3/internal/runtimex"
)

// WithCustomTProxy executes the given function using the given [netem.UnderlyingNetwork]
// as the [model.UnderlyingNetwork] used by the [netxlite] package.
func WithCustomTProxy(tproxy netem.UnderlyingNetwork, function func()) {
netxlite.WithCustomTProxy(&adapter{tproxy}, function)
}

// adapter adapts [netem.UnderlyingNetwork] to [model.UnderlyingNetwork].
type adapter struct {
tp netem.UnderlyingNetwork
}

var _ model.UnderlyingNetwork = &adapter{}

// DefaultCertPool implements model.UnderlyingNetwork
func (a *adapter) DefaultCertPool() *x509.CertPool {
return runtimex.Try1(a.tp.DefaultCertPool())
}

// DialContext implements model.UnderlyingNetwork
func (a *adapter) DialContext(ctx context.Context, timeout time.Duration, network string, address string) (net.Conn, error) {
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
return a.tp.DialContext(ctx, network, address)
}

// GetaddrinfoLookupANY implements model.UnderlyingNetwork
func (a *adapter) GetaddrinfoLookupANY(ctx context.Context, domain string) ([]string, string, error) {
return a.tp.GetaddrinfoLookupANY(ctx, domain)
}

// GetaddrinfoResolverNetwork implements model.UnderlyingNetwork
func (a *adapter) GetaddrinfoResolverNetwork() string {
return a.tp.GetaddrinfoResolverNetwork()
}

// ListenUDP implements model.UnderlyingNetwork
func (a *adapter) ListenUDP(network string, addr *net.UDPAddr) (model.UDPLikeConn, error) {
return a.tp.ListenUDP(network, addr)
netxlite.WithCustomTProxy(&netxlite.NetemUnderlyingNetworkAdapter{UNet: tproxy}, function)
}
46 changes: 46 additions & 0 deletions internal/netxlite/netem.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package netxlite

import (
"context"
"crypto/x509"
"net"
"time"

"github.com/ooni/netem"
"github.com/ooni/probe-cli/v3/internal/model"
"github.com/ooni/probe-cli/v3/internal/runtimex"
)

// NetemUnderlyingNetworkAdapter adapts [netem.UnderlyingNetwork] to [model.UnderlyingNetwork].
type NetemUnderlyingNetworkAdapter struct {
UNet netem.UnderlyingNetwork
}

var _ model.UnderlyingNetwork = &NetemUnderlyingNetworkAdapter{}

// DefaultCertPool implements model.UnderlyingNetwork
func (a *NetemUnderlyingNetworkAdapter) DefaultCertPool() *x509.CertPool {
return runtimex.Try1(a.UNet.DefaultCertPool())
}

// DialContext implements model.UnderlyingNetwork
func (a *NetemUnderlyingNetworkAdapter) DialContext(ctx context.Context, timeout time.Duration, network string, address string) (net.Conn, error) {
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
return a.UNet.DialContext(ctx, network, address)
}

// GetaddrinfoLookupANY implements model.UnderlyingNetwork
func (a *NetemUnderlyingNetworkAdapter) GetaddrinfoLookupANY(ctx context.Context, domain string) ([]string, string, error) {
return a.UNet.GetaddrinfoLookupANY(ctx, domain)
}

// GetaddrinfoResolverNetwork implements model.UnderlyingNetwork
func (a *NetemUnderlyingNetworkAdapter) GetaddrinfoResolverNetwork() string {
return a.UNet.GetaddrinfoResolverNetwork()
}

// ListenUDP implements model.UnderlyingNetwork
func (a *NetemUnderlyingNetworkAdapter) ListenUDP(network string, addr *net.UDPAddr) (model.UDPLikeConn, error) {
return a.UNet.ListenUDP(network, addr)
}
78 changes: 78 additions & 0 deletions internal/netxlite/netx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package netxlite

//
// Netx is a high-level structure that provides constructors for basic netxlite
// network operations using a custom model.UnderlyingNetwork.
//

import "github.com/ooni/probe-cli/v3/internal/model"

// TODO(bassosimone,kelmenhorst): we should gradually refactor the top-level netxlite
// functions to operate on a [Net] struct using a nil-initialized Underlying field.

// Netx allows constructing netxlite data types using a specific [model.UnderlyingNetwork].
type Netx struct {
// Underlying is the OPTIONAL [model.UnderlyingNetwork] to use. Leaving this field
// nil makes this implementation functionally equivalent to netxlite top-level functions.
Underlying model.UnderlyingNetwork
}

// tproxyNilSafeProvider wraps the [model.UnderlyingNetwork] using a [tproxyNilSafeProvider].
func (n *Netx) tproxyNilSafeProvider() *tproxyNilSafeProvider {
return &tproxyNilSafeProvider{n.Underlying}
}

// NewStdlibResolver is like [netxlite.NewStdlibResolver] but the constructed [model.Resolver]
// uses the [UnderlyingNetwork] configured inside the [Net] structure.
func (n *Netx) NewStdlibResolver(logger model.DebugLogger, wrappers ...model.DNSTransportWrapper) model.Resolver {
unwrapped := &resolverSystem{
t: WrapDNSTransport(&dnsOverGetaddrinfoTransport{provider: n.tproxyNilSafeProvider()}, wrappers...),
}
return WrapResolver(logger, unwrapped)
}

// NewDialerWithResolver is like [netxlite.NewDialerWithResolver] but the constructed [model.Dialer]
// uses the [UnderlyingNetwork] configured inside the [Net] structure.
func (n *Netx) NewDialerWithResolver(dl model.DebugLogger, r model.Resolver, w ...model.DialerWrapper) model.Dialer {
return WrapDialer(dl, r, &DialerSystem{provider: n.tproxyNilSafeProvider()}, w...)
}

// NewQUICListener is like [netxlite.NewQUICListener] but the constructed [model.QUICListener]
// uses the [UnderlyingNetwork] configured inside the [Net] structure.
func (n *Netx) NewQUICListener() model.QUICListener {
return &quicListenerErrWrapper{&quicListenerStdlib{provider: n.tproxyNilSafeProvider()}}
}

// NewQUICDialerWithResolver is like [netxlite.NewQUICDialerWithResolver] but the constructed
// [model.QUICDialer] uses the [UnderlyingNetwork] configured inside the [Net] structure.
func (n *Netx) NewQUICDialerWithResolver(listener model.QUICListener, logger model.DebugLogger,
resolver model.Resolver, wrappers ...model.QUICDialerWrapper) (outDialer model.QUICDialer) {
baseDialer := &quicDialerQUICGo{
QUICListener: listener,
provider: n.tproxyNilSafeProvider(),
}
return WrapQUICDialer(logger, resolver, baseDialer, wrappers...)
}

// NewTLSHandshakerStdlib is like [netxlite.NewTLSHandshakerStdlib] but the constructed [model.TLSHandshaker]
// uses the [UnderlyingNetwork] configured inside the [Net] structure.
func (n *Netx) NewTLSHandshakerStdlib(logger model.DebugLogger) model.TLSHandshaker {
return newTLSHandshakerLogger(&tlsHandshakerConfigurable{provider: n.tproxyNilSafeProvider()}, logger)
}

// NewHTTPTransportStdlib is like [netxlite.NewHTTPTransportStdlib] but the constructed [model.HTTPTransport]
// uses the [UnderlyingNetwork] configured inside the [Net] structure.
func (n *Netx) NewHTTPTransportStdlib(logger model.DebugLogger) model.HTTPTransport {
dialer := n.NewDialerWithResolver(logger, n.NewStdlibResolver(logger))
tlsDialer := NewTLSDialer(dialer, n.NewTLSHandshakerStdlib(logger))
return NewHTTPTransport(logger, dialer, tlsDialer)
}

// NewHTTP3TransportStdlib is like [netxlite.NewHTTP3TransportStdlib] but the constructed [model.HTTPTransport]
// uses the [UnderlyingNetwork] configured inside the [Net] structure.
func (n *Netx) NewHTTP3TransportStdlib(logger model.DebugLogger) model.HTTPTransport {
ql := n.NewQUICListener()
reso := n.NewStdlibResolver(logger)
qd := n.NewQUICDialerWithResolver(ql, logger, reso)
return NewHTTP3Transport(logger, qd, nil)
}
116 changes: 116 additions & 0 deletions internal/netxlite/netx_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package netxlite

import (
"context"
"net"
"net/http"
"testing"

"github.com/apex/log"
"github.com/google/go-cmp/cmp"
"github.com/ooni/netem"
"github.com/ooni/probe-cli/v3/internal/runtimex"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3"
)

func TestNetx(t *testing.T) {
// create a star network topology
topology := runtimex.Try1(netem.NewStarTopology(log.Log))
defer topology.Close()

// constants for the IP address we're using
const (
clientAddress = "130.192.91.211"
exampleComAddress = "93.184.216.34"
quad8Address = "8.8.8.8"
)

// create and configure the name server
nameServerStack := runtimex.Try1(topology.AddHost(quad8Address, quad8Address, &netem.LinkConfig{}))
nameServerConfig := netem.NewDNSConfig()
nameServerConfig.AddRecord("www.example.com", "web01.example.com", exampleComAddress)
nameServer := runtimex.Try1(netem.NewDNSServer(log.Log, nameServerStack, quad8Address, nameServerConfig))
defer nameServer.Close()

// create the web server handler
bonsoirElliot := []byte("Bonsoir, Elliot!\r\n")
webServerStack := runtimex.Try1(topology.AddHost(exampleComAddress, quad8Address, &netem.LinkConfig{}))
webServerHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write(bonsoirElliot)
})

// listen for HTTPS requests using the above handler
webServerTCPAddress := &net.TCPAddr{
IP: net.ParseIP(exampleComAddress),
Port: 443,
Zone: "",
}
webServerTCPListener := runtimex.Try1(webServerStack.ListenTCP("tcp", webServerTCPAddress))
webServerTCPServer := &http.Server{
Handler: webServerHandler,
TLSConfig: webServerStack.ServerTLSConfig(),
}
go webServerTCPServer.ServeTLS(webServerTCPListener, "", "")
defer webServerTCPServer.Close()

// listen for HTTP/3 requests using the above handler
webServerUDPAddress := &net.UDPAddr{
IP: net.ParseIP(exampleComAddress),
Port: 443,
Zone: "",
}
webServerUDPListener := runtimex.Try1(webServerStack.ListenUDP("udp", webServerUDPAddress))
webServerUDPServer := &http3.Server{
TLSConfig: webServerStack.ServerTLSConfig(),
QuicConfig: &quic.Config{},
Handler: webServerHandler,
}
go webServerUDPServer.Serve(webServerUDPListener)
defer webServerUDPServer.Close()

// create the client userspace TCP/IP stack and the corresponding netx
clientStack := runtimex.Try1(topology.AddHost(clientAddress, quad8Address, &netem.LinkConfig{}))
underlyingNetwork := &NetemUnderlyingNetworkAdapter{clientStack}
netx := &Netx{underlyingNetwork}

t.Run("HTTPS fetch", func(t *testing.T) {
txp := netx.NewHTTPTransportStdlib(log.Log)
client := &http.Client{Transport: txp}
resp, err := client.Get("https://www.example.com/")
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
t.Fatal("unexpected status code")
}
body, err := ReadAllContext(context.Background(), resp.Body)
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(bonsoirElliot, body); diff != "" {
t.Fatal(diff)
}
})

t.Run("HTTP/3 fetch", func(t *testing.T) {
txp := netx.NewHTTP3TransportStdlib(log.Log)
client := &http.Client{Transport: txp}
resp, err := client.Get("https://www.example.com/")
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
t.Fatal("unexpected status code")
}
body, err := ReadAllContext(context.Background(), resp.Body)
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(bonsoirElliot, body); diff != "" {
t.Fatal(diff)
}
})
}