netemx

package
v0.27.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 13, 2023 License: GPL-3.0 Imports: 21 Imported by: 1

Documentation

Overview

Package netemx contains code to run integration tests using github.com/ooni/netem.

Example (CustomNetStackHandler)

This example shows how to create a TCP listener attached to an arbitrary netstack handler.

package main

import (
	"bytes"
	"context"
	"fmt"

	"github.com/apex/log"
	"github.com/ooni/probe-engine/pkg/netemx"
	"github.com/ooni/probe-engine/pkg/netxlite"
)

func main() {
	// e1WhatsappNet is e1.whatsapp.net IP address as of 2023-07-11
	const e1WhatsappNet = "3.33.252.61"

	// create the QA environment
	env := netemx.MustNewQAEnv(
		netemx.QAEnvOptionNetStack(e1WhatsappNet, netemx.NewTCPEchoServerFactory(log.Log, 5222)),
		netemx.QAEnvOptionLogger(log.Log),
	)

	// make sure we close all the resources when we're done
	defer env.Close()

	// create common DNS configuration for clients and servers
	env.AddRecordToAllResolvers("e1.whatsapp.net", "", e1WhatsappNet)

	// run netxlite code inside the netemx environment
	env.Do(func() {
		// create a system resolver instance
		reso := netxlite.NewStdlibResolver(log.Log)

		// create a dialer
		dialer := netxlite.NewDialerWithResolver(log.Log, reso)

		// attempt to establish a TCP connection
		conn, err := dialer.DialContext(context.Background(), "tcp", "e1.whatsapp.net:5222")

		// make sure no error occurred
		if err != nil {
			log.Fatalf("dialer.DialContext failed: %s", err.Error())
		}

		// send data to the echo server
		input := []byte("0xdeadbeef")
		if _, err := conn.Write(input); err != nil {
			log.Fatalf("conn.Write failed: %s", err.Error())
		}

		// receive data from the echo server
		buffer := make([]byte, 1<<17)
		count, err := conn.Read(buffer)
		if err != nil {
			log.Fatalf("conn.Read failed: %s", err.Error())
		}
		output := buffer[:count]

		// print whether input and output are equal
		fmt.Println(bytes.Equal(input, output))

		// close the connection
		conn.Close()
	})

}
Output:

true
Example (DnsOverUDPWithInternetScenario)

This example shows how the InternetScenario defines DNS-over-UDP servers.

package main

import (
	"context"
	"fmt"
	"net"

	"github.com/apex/log"
	"github.com/ooni/probe-engine/pkg/netemx"
	"github.com/ooni/probe-engine/pkg/netxlite"
)

func main() {
	env := netemx.MustNewScenario(netemx.InternetScenario)
	defer env.Close()

	env.Do(func() {
		resolvers := []string{
			net.JoinHostPort(netemx.ISPResolverAddress, "53"),
			net.JoinHostPort(netemx.RootResolverAddress, "53"),
			net.JoinHostPort(netemx.AddressDNSGoogle8844, "53"),
			net.JoinHostPort(netemx.AddressDNSGoogle8888, "53"),
			net.JoinHostPort(netemx.AddressDNSQuad9Net, "53"),
			net.JoinHostPort(netemx.AddressMozillaCloudflareDNSCom, "53"),
		}

		for _, endpoint := range resolvers {
			dialer := netxlite.NewDialerWithoutResolver(log.Log)
			reso := netxlite.NewParallelUDPResolver(log.Log, dialer, endpoint)
			defer reso.CloseIdleConnections()

			addrs, err := reso.LookupHost(context.Background(), "www.example.com")
			if err != nil {
				log.Fatalf("reso.LookupHost failed: %s", err.Error())
			}

			fmt.Printf("%+v\n", addrs)
		}
	})

}
Output:

[93.184.216.34]
[93.184.216.34]
[93.184.216.34]
[93.184.216.34]
[93.184.216.34]
[93.184.216.34]
Example (DohWithInternetScenario)

This example shows how the InternetScenario defines DNS-over-HTTPS and DNS-over-UDP servers.

package main

import (
	"context"
	"fmt"
	"net"
	"net/url"

	"github.com/apex/log"
	"github.com/ooni/probe-engine/pkg/netemx"
	"github.com/ooni/probe-engine/pkg/netxlite"
)

func main() {
	env := netemx.MustNewScenario(netemx.InternetScenario)
	defer env.Close()

	env.Do(func() {
		for _, domain := range []string{"mozilla.cloudflare-dns.com", "dns.google", "dns.quad9.net"} {
			// DNS-over-UDP
			{
				dialer := netxlite.NewDialerWithResolver(log.Log, netxlite.NewStdlibResolver(log.Log))
				reso := netxlite.NewParallelUDPResolver(log.Log, dialer, net.JoinHostPort(domain, "53"))
				defer reso.CloseIdleConnections()

				addrs, err := reso.LookupHost(context.Background(), "www.example.com")
				if err != nil {
					log.Fatalf("reso.LookupHost failed: %s", err.Error())
				}

				fmt.Printf("%+v\n", addrs)
			}

			// DNS-over-HTTPS
			{
				URL := &url.URL{Scheme: "https", Host: domain, Path: "/dns-query"}
				reso := netxlite.NewParallelDNSOverHTTPSResolver(log.Log, URL.String())
				defer reso.CloseIdleConnections()

				addrs, err := reso.LookupHost(context.Background(), "www.example.com")
				if err != nil {
					log.Fatalf("reso.LookupHost failed: %s", err.Error())
				}

				fmt.Printf("%+v\n", addrs)
			}
		}
	})

}
Output:

[93.184.216.34]
[93.184.216.34]
[93.184.216.34]
[93.184.216.34]
[93.184.216.34]
[93.184.216.34]
Example (DpiRule)

This example shows how to configure a DPI rule for a QA environment.

package main

import (
	"fmt"
	"net/http"

	"github.com/apex/log"
	"github.com/ooni/netem"
	"github.com/ooni/probe-engine/pkg/model"
	"github.com/ooni/probe-engine/pkg/netemx"
	"github.com/ooni/probe-engine/pkg/netxlite"
	"github.com/ooni/probe-engine/pkg/runtimex"
)

// exampleNewEnvironment creates a QA environment setting all possible options. We're going
// to use this QA environment in all the examples for this package.
func exampleNewEnvironment() *netemx.QAEnv {
	return netemx.MustNewQAEnv(
		netemx.QAEnvOptionNetStack("8.8.4.4", &netemx.DNSOverUDPServerFactory{}),
		netemx.QAEnvOptionNetStack("9.9.9.9", &netemx.DNSOverUDPServerFactory{}),
		netemx.QAEnvOptionClientAddress(netemx.DefaultClientAddress),
		netemx.QAEnvOptionNetStack(
			netemx.AddressWwwExampleCom,
			&netemx.HTTPCleartextServerFactory{
				Factory: netemx.ExampleWebPageHandlerFactory(),
				Ports:   []int{80},
			},
			&netemx.HTTPSecureServerFactory{
				Factory:          netemx.ExampleWebPageHandlerFactory(),
				Ports:            []int{443},
				ServerNameMain:   "www.example.com",
				ServerNameExtras: []string{},
			},
			&netemx.HTTP3ServerFactory{
				Factory:          netemx.ExampleWebPageHandlerFactory(),
				Ports:            []int{443},
				ServerNameMain:   "www.example.com",
				ServerNameExtras: []string{},
			},
		),
		netemx.QAEnvOptionLogger(log.Log),
	)
}

// exampleAddRecordToAllResolvers shows how to add a DNS record to all the resolvers (i.e., including
// both the custom created resolvers and the ISP specific resolver).
func exampleAddRecordToAllResolvers(env *netemx.QAEnv) {
	env.AddRecordToAllResolvers(
		"example.com",
		"",
		netemx.AddressWwwExampleCom,
	)
}

func main() {
	// create the QA environment
	env := exampleNewEnvironment()

	// make sure we close all the resources when we're done
	defer env.Close()

	// create common DNS configuration for clients and servers
	exampleAddRecordToAllResolvers(env)

	// install a DPI rule in the environment
	dpi := env.DPIEngine()
	dpi.AddRule(&netem.DPIResetTrafficForTLSSNI{
		Logger: model.DiscardLogger,
		SNI:    "example.com",
	})

	// collect the overall error
	var err error

	// run netxlite code inside the netemx environment
	env.Do(func() {
		// create a system resolver instance
		reso := netxlite.NewStdlibResolver(model.DiscardLogger)

		// create the HTTP client
		// TODO(https://github.com/ooni/probe/issues/2534): the NewHTTPClientWithResolver func has QUIRKS but we don't care.
		client := netxlite.NewHTTPClientWithResolver(model.DiscardLogger, reso)

		// create the HTTP request
		req := runtimex.Try1(http.NewRequest("GET", "https://example.com", nil))

		// obtain the HTTP response or error
		_, err = client.Do(req)
	})

	// print the error that we received
	fmt.Println(err)

}
Output:

connection_reset
Example (ExamplePublicBlockpage)

This example shows how the InternetScenario defines a public blockpage server.

package main

import (
	"fmt"
	"net/http"

	"github.com/apex/log"
	"github.com/ooni/probe-engine/pkg/netemx"
	"github.com/ooni/probe-engine/pkg/netxlite"
)

func main() {
	env := netemx.MustNewScenario(netemx.InternetScenario)
	defer env.Close()

	env.Do(func() {
		// TODO(https://github.com/ooni/probe/issues/2534): NewHTTPClientStdlib has QUIRKS but they're not needed here
		client := netxlite.NewHTTPClientStdlib(log.Log)

		req, err := http.NewRequest("GET", "http://"+netemx.AddressPublicBlockpage+"/", nil)
		if err != nil {
			log.Fatalf("http.NewRequest failed: %s", err.Error())
		}

		resp, err := client.Do(req)
		if err != nil {
			log.Fatalf("client.Do failed: %s", err.Error())
		}
		defer resp.Body.Close()
		body, err := netxlite.ReadAllContext(req.Context(), resp.Body)
		if err != nil {
			log.Fatalf("netxlite.ReadAllContext failed: %s", err.Error())
		}

		fmt.Printf("%+v\n", string(body))
	})

}
Output:

<!doctype html>
<html>
<head>
	<title>Access Denied</title>
</head>
<body>
<div>
	<h1>Access Denied</h1>
	<p>This request cannot be served in your jurisdiction.</p>
</div>
</body>
</html>
Example (ExampleURLShortener)

This example shows how the InternetScenario includes an URL shortener.

package main

import (
	"fmt"
	"net/http"

	"github.com/apex/log"
	"github.com/ooni/probe-engine/pkg/netemx"
	"github.com/ooni/probe-engine/pkg/netxlite"
)

func main() {
	env := netemx.MustNewScenario(netemx.InternetScenario)
	defer env.Close()

	env.Do(func() {
		// TODO(https://github.com/ooni/probe/issues/2534): NewHTTPTransportStdlib has QUIRKS but we
		// don't actually care about those QUIRKS in this context
		client := netxlite.NewHTTPTransportStdlib(log.Log)

		req, err := http.NewRequest("GET", "https://bit.ly/21645", nil)
		if err != nil {
			log.Fatalf("http.NewRequest failed: %s", err.Error())
		}

		resp, err := client.RoundTrip(req)
		if err != nil {
			log.Fatalf("client.Do failed: %s", err.Error())
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusPermanentRedirect {
			log.Fatalf("got unexpected status code: %d", resp.StatusCode)
		}

		fmt.Printf("%+v\n", resp.Header.Get("Location"))
	})

}
Output:

https://www.example.com/
Example (ExampleWebServerWithInternetScenario)

This example shows how the InternetScenario defines an example.com-like webserver.

package main

import (
	"bytes"
	"fmt"
	"net/http"

	"github.com/apex/log"
	"github.com/ooni/probe-engine/pkg/netemx"
	"github.com/ooni/probe-engine/pkg/netxlite"
)

func main() {
	env := netemx.MustNewScenario(netemx.InternetScenario)
	defer env.Close()

	env.Do(func() {
		// TODO(https://github.com/ooni/probe/issues/2534): NewHTTPClientStdlib has QUIRKS but they're not needed here
		client := netxlite.NewHTTPClientStdlib(log.Log)

		req, err := http.NewRequest("GET", "https://www.example.com/", nil)
		if err != nil {
			log.Fatalf("http.NewRequest failed: %s", err.Error())
		}

		resp, err := client.Do(req)
		if err != nil {
			log.Fatalf("client.Do failed: %s", err.Error())
		}
		defer resp.Body.Close()
		body, err := netxlite.ReadAllContext(req.Context(), resp.Body)
		if err != nil {
			log.Fatalf("netxlite.ReadAllContext failed: %s", err.Error())
		}

		// simplify comparison by stripping all the leading whitespaces
		simplifyBody := func(body []byte) (output []byte) {
			lines := bytes.Split(body, []byte("\n"))
			for _, line := range lines {
				line = bytes.TrimSpace(line)
				line = append(line, '\n')
				output = append(output, line...)
			}
			return output
		}

		fmt.Printf("%+v\n", string(simplifyBody(body)))
	})

}
Output:

<!doctype html>
<html>
<head>
<title>Default Web Page</title>
</head>
<body>
<div>
<h1>Default Web Page</h1>

<p>This is the default web page of the default domain.</p>

<p>We detect webpage blocking by checking for the status code first. If the status
code is different, we consider the measurement http-diff. On the contrary when
the status code matches, we say it's all good if one of the following check succeeds:</p>

<p><ol>
<li>the body length does not match (we say they match is the smaller of the two
webpages is 70% or more of the size of the larger webpage);</li>

<li>the uncommon headers match;</li>

<li>the webpage title contains mostly the same words.</li>
</ol></p>

<p>If the three above checks fail, then we also say that there is http-diff. Because
we need QA checks to work as intended, the size of THIS webpage you are reading
has been increased, by adding this description, such that the body length check fails. The
original webpage size was too close to the blockpage in size, and therefore we did see
that there was no http-diff, as it ought to be.</p>

<p>To make sure we're not going to have this issue in the future, there is now a runtime
check that causes our code to crash if this web page size is too similar to the one of
the default blockpage. We chose to add this text for additional clarity.</p>

<p>Also, note that the blockpage MUST be very small, because in some cases we need
to spoof it into a single TCP segment using ooni/netem's DPI.</p>
</div>
</body>
</html>
Example (GetaddrinfoWithInternetScenario)

This example shows how the InternetScenario supports calling getaddrinfo.

package main

import (
	"context"
	"fmt"

	"github.com/apex/log"
	"github.com/ooni/probe-engine/pkg/netemx"
	"github.com/ooni/probe-engine/pkg/netxlite"
)

func main() {
	env := netemx.MustNewScenario(netemx.InternetScenario)
	defer env.Close()

	env.Do(func() {
		reso := netxlite.NewStdlibResolver(log.Log)
		defer reso.CloseIdleConnections()

		addrs, err := reso.LookupHost(context.Background(), "www.example.com")
		if err != nil {
			log.Fatalf("reso.LookupHost failed: %s", err.Error())
		}

		fmt.Printf("%+v\n", addrs)
	})

}
Output:

[93.184.216.34]
Example (OohelperdWithInternetScenario)

This example shows how the InternetScenario defines an oohelperd instance.

package main

import (
	"bytes"
	"fmt"
	"net/http"

	"github.com/apex/log"
	"github.com/ooni/probe-engine/pkg/netemx"
	"github.com/ooni/probe-engine/pkg/netxlite"
)

func main() {
	env := netemx.MustNewScenario(netemx.InternetScenario)
	defer env.Close()

	env.Do(func() {
		// TODO(https://github.com/ooni/probe/issues/2534): NewHTTPClientStdlib has QUIRKS but they're not needed here
		client := netxlite.NewHTTPClientStdlib(log.Log)
		thRequest := []byte(`{"http_request": "https://www.example.com/","http_request_headers":{},"tcp_connect":["93.184.216.34:443"]}`)

		req, err := http.NewRequest("POST", "https://0.th.ooni.org/", bytes.NewReader(thRequest))
		if err != nil {
			log.Fatalf("http.NewRequest failed: %s", err.Error())
		}

		log.SetLevel(log.DebugLevel)

		resp, err := client.Do(req)
		if err != nil {
			log.Fatalf("client.Do failed: %s", err.Error())
		}
		defer resp.Body.Close()
		body, err := netxlite.ReadAllContext(req.Context(), resp.Body)
		if err != nil {
			log.Fatalf("netxlite.ReadAllContext failed: %s", err.Error())
		}

		fmt.Printf("%+v\n", string(body))
	})

}
Output:

{"tcp_connect":{"93.184.216.34:443":{"status":true,"failure":null}},"tls_handshake":{"93.184.216.34:443":{"server_name":"www.example.com","status":true,"failure":null}},"quic_handshake":{},"http_request":{"body_length":1533,"discovered_h3_endpoint":"www.example.com:443","failure":null,"title":"Default Web Page","headers":{"Alt-Svc":"h3=\":443\"","Content-Length":"1533","Content-Type":"text/html; charset=utf-8","Date":"Thu, 24 Aug 2023 14:35:29 GMT"},"status_code":200},"http3_request":null,"dns":{"failure":null,"addrs":["93.184.216.34"]},"ip_info":{"93.184.216.34":{"asn":15133,"flags":11}}}
Example (OoniAPIWithInternetScenario)

This example shows how the InternetScenario defines an OONI-API-like service.

package main

import (
	"fmt"
	"net/http"

	"github.com/apex/log"
	"github.com/ooni/probe-engine/pkg/netemx"
	"github.com/ooni/probe-engine/pkg/netxlite"
)

func main() {
	env := netemx.MustNewScenario(netemx.InternetScenario)
	defer env.Close()

	env.Do(func() {
		// TODO(https://github.com/ooni/probe/issues/2534): NewHTTPClientStdlib has QUIRKS but they're not needed here
		client := netxlite.NewHTTPClientStdlib(log.Log)

		req, err := http.NewRequest("GET", "https://api.ooni.io/api/v1/test-helpers", nil)
		if err != nil {
			log.Fatalf("http.NewRequest failed: %s", err.Error())
		}

		resp, err := client.Do(req)
		if err != nil {
			log.Fatalf("client.Do failed: %s", err.Error())
		}
		defer resp.Body.Close()
		body, err := netxlite.ReadAllContext(req.Context(), resp.Body)
		if err != nil {
			log.Fatalf("netxlite.ReadAllContext failed: %s", err.Error())
		}

		fmt.Printf("%+v\n", string(body))
	})

}
Output:

{"web-connectivity":[{"address":"https://2.th.ooni.org","type":"https"},{"address":"https://3.th.ooni.org","type":"https"},{"address":"https://0.th.ooni.org","type":"https"},{"address":"https://1.th.ooni.org","type":"https"}]}
Example (ResolverConfig)

This example shows how to configure different resolvers to reply differently

package main

import (
	"context"
	"fmt"

	"github.com/apex/log"
	"github.com/ooni/probe-engine/pkg/netemx"
	"github.com/ooni/probe-engine/pkg/netxlite"
	"github.com/ooni/probe-engine/pkg/runtimex"
)

// exampleNewEnvironment creates a QA environment setting all possible options. We're going
// to use this QA environment in all the examples for this package.
func exampleNewEnvironment() *netemx.QAEnv {
	return netemx.MustNewQAEnv(
		netemx.QAEnvOptionNetStack("8.8.4.4", &netemx.DNSOverUDPServerFactory{}),
		netemx.QAEnvOptionNetStack("9.9.9.9", &netemx.DNSOverUDPServerFactory{}),
		netemx.QAEnvOptionClientAddress(netemx.DefaultClientAddress),
		netemx.QAEnvOptionNetStack(
			netemx.AddressWwwExampleCom,
			&netemx.HTTPCleartextServerFactory{
				Factory: netemx.ExampleWebPageHandlerFactory(),
				Ports:   []int{80},
			},
			&netemx.HTTPSecureServerFactory{
				Factory:          netemx.ExampleWebPageHandlerFactory(),
				Ports:            []int{443},
				ServerNameMain:   "www.example.com",
				ServerNameExtras: []string{},
			},
			&netemx.HTTP3ServerFactory{
				Factory:          netemx.ExampleWebPageHandlerFactory(),
				Ports:            []int{443},
				ServerNameMain:   "www.example.com",
				ServerNameExtras: []string{},
			},
		),
		netemx.QAEnvOptionLogger(log.Log),
	)
}

func main() {
	// create the QA environment
	env := exampleNewEnvironment()

	// make sure we close all the resources when we're done
	defer env.Close()

	// create a configuration for the uncensored resolvers in the network
	env.OtherResolversConfig().AddRecord(
		"example.com",
		"", // CNAME
		netemx.AddressWwwExampleCom,
	)

	// create a censored configuration for getaddrinfo
	env.ISPResolverConfig().AddRecord(
		"example.com",
		"",
		"10.10.34.35",
	)

	// collect the overall results
	var (
		googleResults []string
		quad9Results  []string
		ispResults    []string
	)

	// run netxlite code inside the netemx environment
	env.Do(func() {
		// use a system resolver instance
		{
			reso := netxlite.NewStdlibResolver(log.Log)
			ispResults = runtimex.Try1(reso.LookupHost(context.Background(), "example.com"))
		}

		// use 8.8.4.4
		{
			dialer := netxlite.NewDialerWithoutResolver(log.Log)
			reso := netxlite.NewParallelUDPResolver(log.Log, dialer, "8.8.4.4:53")
			googleResults = runtimex.Try1(reso.LookupHost(context.Background(), "example.com"))
		}

		// use 9.9.9.9
		{
			dialer := netxlite.NewDialerWithoutResolver(log.Log)
			reso := netxlite.NewParallelUDPResolver(log.Log, dialer, "9.9.9.9:53")
			quad9Results = runtimex.Try1(reso.LookupHost(context.Background(), "example.com"))
		}
	})

	// print the results that we received
	fmt.Println(googleResults, quad9Results, ispResults)

}
Output:

[93.184.216.34] [93.184.216.34] [10.10.34.35]
Example (UbuntuGeoIPWithInternetScenario)

This example shows how the InternetScenario defines a GeoIP service like Ubuntu's one.

package main

import (
	"fmt"
	"net/http"

	"github.com/apex/log"
	"github.com/ooni/probe-engine/pkg/netemx"
	"github.com/ooni/probe-engine/pkg/netxlite"
)

func main() {
	env := netemx.MustNewScenario(netemx.InternetScenario)
	defer env.Close()

	env.Do(func() {
		// TODO(https://github.com/ooni/probe/issues/2534): NewHTTPClientStdlib has QUIRKS but they're not needed here
		client := netxlite.NewHTTPClientStdlib(log.Log)

		req, err := http.NewRequest("GET", "https://geoip.ubuntu.com/lookup", nil)
		if err != nil {
			log.Fatalf("http.NewRequest failed: %s", err.Error())
		}

		resp, err := client.Do(req)
		if err != nil {
			log.Fatalf("client.Do failed: %s", err.Error())
		}
		defer resp.Body.Close()
		body, err := netxlite.ReadAllContext(req.Context(), resp.Body)
		if err != nil {
			log.Fatalf("netxlite.ReadAllContext failed: %s", err.Error())
		}

		fmt.Printf("%+v\n", string(body))
	})

}
Output:

<?xml version="1.0" encoding="UTF-8"?><Response><Ip>130.192.91.211</Ip></Response>

Index

Examples

Constants

View Source
const (
	// ScenarioRolePublicDNS means we should create DNS-over-HTTPS and DNS-over-UDP servers.
	ScenarioRolePublicDNS = iota

	// ScenarioRoleWebServer means we should instantiate a webserver using a specific factory.
	ScenarioRoleWebServer

	// ScenarioRoleOONIAPI means we should instantiate the OONI API.
	ScenarioRoleOONIAPI

	// ScenarioRoleUbuntuGeoIP means we should instantiate the Ubuntu geoip service.
	ScenarioRoleUbuntuGeoIP

	// ScenarioRoleOONITestHelper means we should instantiate the oohelperd.
	ScenarioRoleOONITestHelper

	// ScenarioRoleBlockpageServer means we should serve a blockpage using HTTP.
	ScenarioRoleBlockpageServer

	// ScenarioRoleProxy means the host is a transparent proxy.
	ScenarioRoleProxy

	// ScenarioRoleURLShortener means that the host is an URL shortener.
	ScenarioRoleURLShortener

	// ScenarioRoleBadSSL means that the host hosts services to
	// measure against common TLS issues.
	ScenarioRoleBadSSL
)
View Source
const AddressApiOONIIo = "162.55.247.208"

AddressApiOONIIo is the IP address for api.ooni.io.

View Source
const AddressBadSSLCom = "104.154.89.105"

AddressBadSSLCom is the IP address of badssl.com.

View Source
const AddressBitly = "67.199.248.11"

AddressBitly is the IP address of bitly.com.

View Source
const AddressDNSGoogle8844 = "8.8.4.4"

AddressDNSGoogle8844 is the 8.8.4.4 address for dns.google.

View Source
const AddressDNSGoogle8888 = "8.8.8.8"

AddressDNSGoogle8888 is the 8.8.8.8 address for dns.google.

View Source
const AddressDNSQuad9Net = "9.9.9.9"

AddressDNSQuad9Net is the IP address for dns.quad9.net.

View Source
const AddressGeoIPUbuntuCom = "185.125.188.132"

AddressGeoIPUbuntuCom is the IP address for geoip.ubuntu.com.

View Source
const AddressMozillaCloudflareDNSCom = "172.64.41.4"

AddressMozillaCloudflareDNSCom is the IP address for mozilla.cloudflare-dns.com.

View Source
const AddressOneThOONIOrg = "137.184.235.44"

AddressOneThOONIOrg is the IP address for 1.th.ooni.org.

View Source
const AddressPublicBlockpage = "83.224.65.41"

AddressPublicBlockpage is the IP address we use for modeling a public IP address that is serving blockpages to its users. As of 2023-09-04, this is the IP address resolving for thepiratebay.com when you're attempting to access this website from Italy.

View Source
const AddressTHCloudfront = "52.85.15.84"

AddressTHCloudfront is the IP address for d33d1gs9kpq1c5.cloudfront.net.

View Source
const AddressThreeThOONIOrg = "209.97.183.73"

AddressThreeThOONIOrg is the IP address for 3.th.ooni.org.

View Source
const AddressTwoThOONIOrg = "178.62.195.24"

AddressTwoThOONIOrg is the IP address for 2.th.ooni.org.

View Source
const AddressWwwExampleCom = "93.184.216.34"

AddressWwwExampleCom is the IP address for www.example.com.

View Source
const AddressZeroThOONIOrg = "68.183.70.80"

AddressZeroThOONIOrg is the IP address for 0.th.ooni.org.

View Source
const Blockpage = `` /* 188-byte string literal not displayed */

Blockpage is the webpage returned by BlockpageHandlerFactory.

View Source
const DefaultClientAddress = "130.192.91.211"

DefaultClientAddress is the default client IP address.

View Source
const ExampleWebPage = `` /* 1533-byte string literal not displayed */

ExampleWebPage is the webpage returned by ExampleWebPageHandlerFactory.

View Source
const ISPProxyAddress = "130.192.182.17"

ISPProxyAddress is the IP address of the ISP's HTTP transparent proxy.

View Source
const ISPResolverAddress = "130.192.3.21"

ISPResolverAddress is the IP address of the client ISP resolver.

View Source
const RootResolverAddress = "193.0.14.129"

RootResolverAddress is the root resolver resolver IP address.

Variables

View Source
var DefaultURLShortenerMapping = map[string]string{
	"/21645": "https://www.example.com/",
	"/32447": "http://www.example.com/",
	"/24561": "https://example.com/",
	"/21309": "http://example.com/",
	"/30744": "https://www.example.org/",
	"/23894": "http://www.example.org/",
	"/30179": "https://example.org/",
	"/11372": "http://example.org/",
}

DefaultURLShortenerMapping is the default URL shortener mapping we use.

View Source
var InternetScenario = []*ScenarioDomainAddresses{{
	Domains: []string{"api.ooni.io"},
	Addresses: []string{
		AddressApiOONIIo,
	},
	Role:             ScenarioRoleOONIAPI,
	ServerNameMain:   "api.ooni.io",
	ServerNameExtras: []string{},
}, {
	Domains: []string{"geoip.ubuntu.com"},
	Addresses: []string{
		AddressGeoIPUbuntuCom,
	},
	Role:             ScenarioRoleUbuntuGeoIP,
	ServerNameMain:   "geoip.ubuntu.com",
	ServerNameExtras: []string{},
}, {
	Domains: []string{"www.example.com", "example.com", "www.example.org", "example.org"},
	Addresses: []string{
		AddressWwwExampleCom,
	},
	Role:             ScenarioRoleWebServer,
	WebServerFactory: ExampleWebPageHandlerFactory(),
	ServerNameMain:   "www.example.com",
	ServerNameExtras: []string{"example.com", "www.example.org", "example.org"},
}, {
	Domains: []string{"0.th.ooni.org"},
	Addresses: []string{
		AddressZeroThOONIOrg,
	},
	Role:             ScenarioRoleOONITestHelper,
	ServerNameMain:   "0.th.ooni.org",
	ServerNameExtras: []string{},
}, {
	Domains: []string{"1.th.ooni.org"},
	Addresses: []string{
		AddressOneThOONIOrg,
	},
	Role:             ScenarioRoleOONITestHelper,
	ServerNameMain:   "1.th.ooni.org",
	ServerNameExtras: []string{},
}, {
	Domains: []string{"2.th.ooni.org"},
	Addresses: []string{
		AddressTwoThOONIOrg,
	},
	Role:             ScenarioRoleOONITestHelper,
	ServerNameMain:   "2.th.ooni.org",
	ServerNameExtras: []string{},
}, {
	Domains: []string{"3.th.ooni.org"},
	Addresses: []string{
		AddressThreeThOONIOrg,
	},
	Role:             ScenarioRoleOONITestHelper,
	ServerNameMain:   "3.th.ooni.org",
	ServerNameExtras: []string{},
}, {
	Domains: []string{"d33d1gs9kpq1c5.cloudfront.net"},
	Addresses: []string{
		AddressTHCloudfront,
	},
	Role:             ScenarioRoleOONITestHelper,
	ServerNameMain:   "d33d1gs9kpq1c5.cloudfront.net",
	ServerNameExtras: []string{},
}, {
	Domains: []string{"dns.quad9.net"},
	Addresses: []string{
		AddressDNSQuad9Net,
	},
	Role:             ScenarioRolePublicDNS,
	ServerNameMain:   "dns.quad9.net",
	ServerNameExtras: []string{},
}, {
	Domains: []string{"mozilla.cloudflare-dns.com"},
	Addresses: []string{
		AddressMozillaCloudflareDNSCom,
	},
	Role:             ScenarioRolePublicDNS,
	ServerNameMain:   "mozilla.cloudflare-dns.com",
	ServerNameExtras: []string{},
}, {
	Domains: []string{"dns.google", "dns.google.com"},
	Addresses: []string{
		AddressDNSGoogle8844,
		AddressDNSGoogle8888,
	},
	Role:             ScenarioRolePublicDNS,
	ServerNameMain:   "dns.google",
	ServerNameExtras: []string{"dns.google.com"},
}, {
	Domains: []string{},
	Addresses: []string{
		AddressPublicBlockpage,
	},
	Role:             ScenarioRoleBlockpageServer,
	WebServerFactory: BlockpageHandlerFactory(),
	ServerNameMain:   "blockpage.local",
	ServerNameExtras: []string{},
}, {
	Domains: []string{},
	Addresses: []string{
		ISPProxyAddress,
	},
	Role:             ScenarioRoleProxy,
	ServerNameMain:   "proxy.local",
	ServerNameExtras: []string{},
}, {
	Domains: []string{"bit.ly", "bitly.com"},
	Addresses: []string{
		AddressBitly,
	},
	Role:             ScenarioRoleURLShortener,
	ServerNameMain:   "bit.ly",
	ServerNameExtras: []string{"bitly.com"},
}, {
	Domains: []string{
		"wrong.host.badssl.com",
		"untrusted-root.badssl.com",
		"expired.badssl.com",
	},
	Addresses: []string{
		AddressBadSSLCom,
	},
	Role:             ScenarioRoleBadSSL,
	ServerNameMain:   "badssl.com",
	ServerNameExtras: []string{},
}}

InternetScenario contains the domains and addresses used by [NewInternetScenario].

Functions

func ExampleWebPageHandler added in v0.26.0

func ExampleWebPageHandler() http.Handler

ExampleWebPageHandler returns a handler returning a webpage similar to example.org's one when the domain is www.example.{com,org} and redirecting to www. when the domain is example.{com,org}.

func WithCustomTProxy

func WithCustomTProxy(tproxy netem.UnderlyingNetwork, function func())

WithCustomTProxy executes the given function using the given netem.UnderlyingNetwork as the model.UnderlyingNetwork used by the netxlite package.

Types

type BadSSLServerFactory added in v0.26.0

type BadSSLServerFactory struct{}

BadSSLServerFactory is a NetStackServerFactory that instantiates a NetStackServer used for testing common TLS issues.

func (*BadSSLServerFactory) MustNewServer added in v0.26.0

MustNewServer implements NetStackServerFactory.

type DNSOverHTTPSHandlerFactory added in v0.26.0

type DNSOverHTTPSHandlerFactory struct{}

DNSOverHTTPSHandlerFactory is a [QAEnvHTTPHandlerFactory] for testingx.GeoIPHandlerUbuntu.

func (*DNSOverHTTPSHandlerFactory) NewHandler added in v0.26.0

NewHandler implements QAEnvHTTPHandlerFactory.

type DNSOverUDPServerFactory added in v0.26.0

type DNSOverUDPServerFactory struct{}

DNSOverUDPServerFactory implements NetStackServerFactory for DNS-over-UDP servers.

When this factory constructs a NetStackServer, it will use:

1. the [NetStackServerFactoryEnv.OtherResolversConfig] as DNS configuration;

2. the [NetStackServerFactoryEnv.Logger] as logger.

Use this factory along with QAEnvOptionNetStack to create DNS-over-UDP servers.

func (*DNSOverUDPServerFactory) MustNewServer added in v0.26.0

MustNewServer implements NetStackServerFactory.

type GeoIPHandlerFactoryUbuntu added in v0.26.0

type GeoIPHandlerFactoryUbuntu struct {
	ProbeIP string
}

GeoIPHandlerFactoryUbuntu is a [QAEnvHTTPHandlerFactory] for testingx.GeoIPHandlerUbuntu.

func (*GeoIPHandlerFactoryUbuntu) NewHandler added in v0.26.0

NewHandler implements QAEnvHTTPHandlerFactory.

type HTTP3ServerFactory added in v0.26.0

type HTTP3ServerFactory struct {
	// Factory is the MANDATORY factory for creating the [http.Handler].
	Factory HTTPHandlerFactory

	// Ports is the MANDATORY list of ports where to listen.
	Ports []int

	// ServerNameMain is the MANDATORY server name we should configure.
	ServerNameMain string

	// ServerNameExtras contains OPTIONAL extra server names we should configure.
	ServerNameExtras []string
}

HTTP3ServerFactory implements NetStackServerFactory for HTTP-over-TLS (i.e., HTTPS).

Use this factory along with QAEnvOptionNetStack to create HTTP3 servers.

func (*HTTP3ServerFactory) MustNewServer added in v0.26.0

MustNewServer implements NetStackServerFactory.

type HTTPCleartextServerFactory added in v0.26.0

type HTTPCleartextServerFactory struct {
	// Factory is the MANDATORY factory for creating the [http.Handler].
	Factory HTTPHandlerFactory

	// Ports is the MANDATORY list of ports where to listen.
	Ports []int
}

HTTPCleartextServerFactory implements NetStackServerFactory for cleartext HTTP.

Use this factory along with QAEnvOptionNetStack to create cleartext HTTP servers.

func (*HTTPCleartextServerFactory) MustNewServer added in v0.26.0

MustNewServer implements NetStackServerFactory.

type HTTPHandlerFactory added in v0.26.0

type HTTPHandlerFactory interface {
	NewHandler(env NetStackServerFactoryEnv, stack *netem.UNetStack) http.Handler
}

HTTPHandlerFactory constructs an http.Handler.

func BlockpageHandlerFactory added in v0.26.0

func BlockpageHandlerFactory() HTTPHandlerFactory

BlockpageHandlerFactory returns a blockpage regardless of the incoming domain.

func ExampleWebPageHandlerFactory added in v0.26.0

func ExampleWebPageHandlerFactory() HTTPHandlerFactory

ExampleWebPageHandlerFactory returns a webpage similar to example.org's one when the domain is www.example.{com,org} and redirects to www.example.{com,org} when it is example.{com,org}.

func URLShortenerFactory added in v0.26.0

func URLShortenerFactory(mapping map[string]string) HTTPHandlerFactory

URLShortenerFactory returns an HTTPHandlerFactory that eventually redirects requests using the map provided as argument or returns 404.

type HTTPHandlerFactoryFunc added in v0.26.0

type HTTPHandlerFactoryFunc func(env NetStackServerFactoryEnv, stack *netem.UNetStack) http.Handler

HTTPHandlerFactoryFunc allows a func to become an HTTPHandlerFactory.

func (HTTPHandlerFactoryFunc) NewHandler added in v0.26.0

NewHandler implements HTTPHandlerFactory.

type HTTPSecureServerFactory added in v0.26.0

type HTTPSecureServerFactory struct {
	// Factory is the MANDATORY factory for creating the [http.Handler].
	Factory HTTPHandlerFactory

	// Ports is the MANDATORY list of ports where to listen.
	Ports []int

	// ServerNameMain is the MANDATORY server name we should configure.
	ServerNameMain string

	// ServerNameExtras contains OPTIONAL extra server names we should configure.
	ServerNameExtras []string
}

HTTPSecureServerFactory implements NetStackServerFactory for HTTP-over-TLS (i.e., HTTPS).

Use this factory along with QAEnvOptionNetStack to create HTTPS servers.

func (*HTTPSecureServerFactory) MustNewServer added in v0.26.0

MustNewServer implements NetStackServerFactory.

type NetStackServer added in v0.26.0

type NetStackServer interface {
	// MustStart uses the underlying stack to create all the listening TCP and UDP sockets
	// required by the specific test case, as well as to start the required background
	// goroutines servicing incoming requests for the created listeners. This method
	// MUST BE CONCURRENCY SAFE and it MUST NOT arrange for the Close method to close
	// the stack because it is managed by the [QAEnv]. This method MUST call PANIC
	// in case there is any error in listening or starting the required servers.
	MustStart()

	// Close should close the listening TCP and UDP sockets and the background
	// goroutines created by Listen. This method MUST BE CONCURRENCY SAFE and IDEMPOTENT.
	Close() error
}

NetStackServer handles the lifecycle of a server using a TCP/IP stack in userspace.

type NetStackServerFactory added in v0.26.0

type NetStackServerFactory interface {
	// MustNewServer constructs a [NetStackServer] BORROWING a reference to an
	// underlying network attached to an userspace TCP/IP stack. This method MAY
	// call PANIC in case of failure.
	MustNewServer(env NetStackServerFactoryEnv, stack *netem.UNetStack) NetStackServer
}

NetStackServerFactory constructs a new NetStackServer.

func NewTCPEchoServerFactory added in v0.26.0

func NewTCPEchoServerFactory(logger model.Logger, ports ...uint16) NetStackServerFactory

NewTCPEchoServerFactory is a NetStackServerFactory for the TCP echo service.

func NewTLSProxyServerFactory added in v0.26.0

func NewTLSProxyServerFactory(logger model.Logger, ports ...uint16) NetStackServerFactory

NewTLSProxyServerFactory is a NetStackServerFactory for the TCP echo service.

type NetStackServerFactoryEnv added in v0.26.0

type NetStackServerFactoryEnv interface {
	// ISPResolverConfig returns the configuration used by the ISP's resolver.
	ISPResolverConfig() *netem.DNSConfig

	// Logger returns the base logger configured for the [*QAEnv].
	Logger() model.Logger

	// OtherResolversConfig returns the configuration used by all the
	// DNS resolvers except the ISP's DNS resolver.
	OtherResolversConfig() *netem.DNSConfig
}

NetStackServerFactoryEnv is NetStackServerFactory view of *QAEnv.

type OOAPIHandler added in v0.26.0

type OOAPIHandler struct{}

OOAPIHandler is an http.Handler implementing the OONI API.

func (*OOAPIHandler) ServeHTTP added in v0.26.0

func (p *OOAPIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler.

type OOAPIHandlerFactory added in v0.26.0

type OOAPIHandlerFactory struct{}

OOAPIHandlerFactory is a [QAEnvHTTPHandlerFactory] that creates OOAPIHandler instances.

func (*OOAPIHandlerFactory) NewHandler added in v0.26.0

NewHandler implements QAEnvHTTPHandlerFactory.

type OOHelperDFactory added in v0.26.0

type OOHelperDFactory struct{}

OOHelperDFactory is the factory to create an http.Handler implementing the OONI Web Connectivity test helper using a specific netem.UnderlyingNetwork.

func (*OOHelperDFactory) NewHandler added in v0.26.0

NewHandler implements QAEnvHTTPHandlerFactory.NewHandler.

type QAEnv added in v0.26.0

type QAEnv struct {

	// ClientStack is the client stack to use.
	ClientStack *netem.UNetStack
	// contains filtered or unexported fields
}

QAEnv is the environment for running QA tests using github.com/ooni/netem. The zero value of this struct is invalid; please, use [NewQAEnv].

func MustNewQAEnv added in v0.26.0

func MustNewQAEnv(options ...QAEnvOption) *QAEnv

MustNewQAEnv creates a new QAEnv. This function PANICs on failure.

func MustNewScenario added in v0.26.0

func MustNewScenario(config []*ScenarioDomainAddresses) *QAEnv

MustNewScenario constructs a complete testing scenario using the domains and IP addresses contained by the given ScenarioDomainAddresses array.

func (*QAEnv) AddRecordToAllResolvers added in v0.26.0

func (env *QAEnv) AddRecordToAllResolvers(domain string, cname string, addrs ...string)

AddRecordToAllResolvers adds the given DNS record to all DNS resolvers. You can safely add new DNS records from concurrent goroutines at any time.

func (*QAEnv) Close added in v0.26.0

func (env *QAEnv) Close() error

Close closes all the resources used by QAEnv.

func (*QAEnv) DPIEngine added in v0.26.0

func (env *QAEnv) DPIEngine() *netem.DPIEngine

DPIEngine returns the *netem.DPIEngine we're using on the link between the client stack and the router. You can safely add new DPI rules from concurrent goroutines at any time.

func (*QAEnv) Do added in v0.26.0

func (env *QAEnv) Do(function func())

Do executes the given function such that netxlite code uses the underlying clientStack rather than ordinary networking code.

func (*QAEnv) EmulateAndroidGetaddrinfo added in v0.26.0

func (env *QAEnv) EmulateAndroidGetaddrinfo(value bool)

EmulateAndroidGetaddrinfo configures QAEnv such that the Do method wraps the underlying client stack to return android_dns_cache_no_data on any error that occurs. This method can be safely called by multiple goroutines.

func (*QAEnv) ISPResolverConfig added in v0.26.0

func (env *QAEnv) ISPResolverConfig() *netem.DNSConfig

ISPResolverConfig returns the *netem.DNSConfig of the ISP resolver. Note that can safely add new DNS records from concurrent goroutines at any time.

func (*QAEnv) Logger added in v0.26.0

func (env *QAEnv) Logger() model.Logger

Logger is the model.Logger configured for this *QAEnv,

func (*QAEnv) OtherResolversConfig added in v0.26.0

func (env *QAEnv) OtherResolversConfig() *netem.DNSConfig

OtherResolversConfig returns the *netem.DNSConfig of the non-ISP resolvers. Note that can safely add new DNS records from concurrent goroutines at any time.

type QAEnvOption added in v0.26.0

type QAEnvOption func(config *qaEnvConfig)

QAEnvOption is an option to modify [NewQAEnv] default behavior.

func QAEnvOptionClientAddress added in v0.26.0

func QAEnvOptionClientAddress(ipAddr string) QAEnvOption

QAEnvOptionClientAddress sets the client IP address. If you do not set this option we will use DefaultClientAddress.

func QAEnvOptionClientNICWrapper added in v0.26.0

func QAEnvOptionClientNICWrapper(wrapper netem.LinkNICWrapper) QAEnvOption

QAEnvOptionClientNICWrapper sets the NIC wrapper for the client. The most common use case for this functionality is capturing packets using netem.NewPCAPDumper.

func QAEnvOptionLogger added in v0.26.0

func QAEnvOptionLogger(logger model.Logger) QAEnvOption

QAEnvOptionLogger sets the logger to use. If you do not set this option we will use model.DiscardLogger as the logger.

func QAEnvOptionNetStack added in v0.26.0

func QAEnvOptionNetStack(ipAddr string, factories ...NetStackServerFactory) QAEnvOption

QAEnvOptionNetStack creates an userspace network stack with the given IP address and binds it to the given factory, which will be responsible to create listening sockets and closing them when we're done running. Examples of factories you can use with this method are:

- NewTCPEchoServerFactory;

- HTTPCleartextServerFactory;

- HTTPSecureServerFactory;

- HTTP3ServerFactory;

- [UDPResolverFactory].

Calling this method multiple times is equivalent to calling this method once with several factories. This would work as long as you do not specify the same port multiple times, otherwise the second bind attempt for an already bound port would fail.

This function PANICS if you try to configure ISPResolverAddress or RootResolverAddress because these two addresses are already configured by MustNewQAEnv.

type ScenarioDomainAddresses added in v0.26.0

type ScenarioDomainAddresses struct {
	// Addresses contains the MANDATORY list of addresses belonging to the domain.
	Addresses []string

	// Domains contains a related set of domains domains (MANDATORY field).
	Domains []string

	// Role is the MANDATORY role of this domain (e.g., ScenarioRoleOONIAPI).
	Role uint64

	// ServerNameMain is the MANDATORY server name to use as common name for X.509 certs.
	ServerNameMain string

	// ServerNameExtras contains OPTIONAL extra names to also configure into the cert.
	ServerNameExtras []string

	// WebServerFactory is the factory to use when Role is ScenarioRoleWebServer.
	WebServerFactory HTTPHandlerFactory
}

ScenarioDomainAddresses describes a domain and address used in a scenario.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL