netem

package module
v0.0.0-...-608dcbc Latest Latest
Warning

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

Go to latest
Published: Feb 8, 2024 License: BSD-3-Clause Imports: 38 Imported by: 6

README

Netem

alltests GoDoc Coverage Status Slack

Netem allows writing integration tests in Go where networking code uses Gvisor-based networking. Netem also includes primitives to emulate link latency, losses, and internet censorship (null routing, SNI-based blocking, throttling). Using netem, one can easily simulate complex integration testing scenarios involving difficult or adversarial networks.

Install instructions

We currently support go1.20.

To add netem as a dependency, run:

go get -u -v -d github.com/ooni/netem

This command will download netem and update your go.mod and go.sum.

You probably also want to manually force using the Gvisor version we're using in this library with:

go get -u -v -d gvisor.dev/gvisor@COMMIT_HASH

because Gvisor's default branch is not ready to be used with Go tools and go get would misbehave.

When updating Gvisor in this library, make sure you pin to a commit from the go branch, which is the Gvisor branch supporting go tools.

Running tests

go test .

To enable the race detector, run:

go test -race .

Note: we notice that the race detector would be very slow under macOS and many tests will fail; it still seems to be fine under Linux.

Usage

TODO(bassosimone): this section needs to be updated because we have recently removed the stdlib.go file and functionality, since we have much better functionality inside of ooni/probe-cli.

Existing Go code needs to be adjusted to support netem.

Suppose you have this Go code:

func yourCode(ctx context.Context) error {
	addrs, err := net.DefaultResolver.LookupHost(ctx, "www.example.com")
	// ...
}

You need to convert this code to use netem:

func yourCode(ctx context.Context, nn *netem.Net) error {
	addrs, err := nn.LookupHost(ctx, "www.example.com")
	// ...
}

Normally, you would create a netem.Net like this:

nn := &netem.Net{
	Stack: &netem.Stdlib{},
}

Your code will still work as intended. But, now you have the option to replace the Net underlying stack with an userspace TCP/IP network stack, for writing integration tests.

Let us do that. We start by creating a StarTopology:

topology, err := netem.NewStarTopology(&netem.NullLogger{})
if err != nil { /* ... */ }

defer topology.Close()

Then, we use AddHost to add two userspace network stacks to such a topology:

clientStack, err := netem.AddHost(
	"1.2.3.4",            // stack IPv4 address
	"5.4.3.2",            // resolver IPv4 address
	&netem.LinkConfig{},  // link with no delay, losses, or DPI
)
if err != nil { /* ... */ }

serverStack, err := netem.AddHost(
	"5.4.3.2",            // stack IPv4 address
	"5.4.3.2",            // resolver IPv4 address
	&netem.LinkConfig{},  // link with no delay, losses, or DPI
)
if err != nil { /* ... */ }

We now have the following topology:

graph TD
 client[clientStack<br>1.2.3.4]---router{Router}
 server[serverStack<br>5.4.3.2]---router

Now, we can create a DNSServer on 5.4.3.2 as follows:

dnsCfg := netem.NewDNSConfig()
dnsCfg.AddRecord(
	"www.example.com",
	"",                 // empty CNAME
	"5.6.7.8",
)

dnsServer, err := netem.NewDNSServer(
	&netem.NullLogger{},
	serverStack,
	"5.4.3.2",
	dnsCfg,
)
if err != nil { /* ... */ }

Finally, we create a netem.Net as follows:

nn2 := &netem.Net{
	Stack: clientStack,
}

and we can test yourCode as follows:

func TestYourCode(t *testing.T) {
	// ... create nn2 ...
	err := yourCode(context.Background(), nn2)
	if err != nil {
		t.Fatal(err)
	}
}

This test will test your code using the above network stacks and topology.

Documentation

Overview

Package netem is a framework to write integration tests that use TCP/IP stacks implemented in userspace.

Assuming that your code depends on UnderlyingNetwork (or on its wrapper Net), you can write your tests to create and use Gvisor-based TCP/IP stacks in userspace. To this end, use the NewUNetStack constructor or create it in the context of a specific network topology (explained below). Because UNetStack implements the UnderlyingNetwork model, your code can now use the TCP/IP stack in userspace as opposed to the Go standard library.

For normal operations, instead, you can use the [Stdlib] struct. This struct implements UnderlyingNetwork using the Go standard library. So, when your code uses a [Stdlib] as its UnderlyingNetwork, it will use your host network as usual.

A UNetStack alone is not attached to any link and so it cannot communicate with any other host. To connect two UNetStack, use NewLink. This factory will create a Link, which implements the following:

- delivery of TCP/IP packets between the two UNetStack;

- optionally, emulation of packet loss rate, addition of extra round trip time, and presence of DPI rules.

You configure the link properties using LinkConfig, which is the structure you pass to NewLink.

Rather than using a Link to connect two UNetStack in point-to-point fashion, you can alternatively create a Router. A new Router is not attached to any other host and so is not routing. To attach a UNetStack to a router, create a RouterPort and a Link between such a UNetStack and the RouterPort. Using the Router you can create complex topologies.

Because creating topologies manually is error prone, we have two predefined topologies:

- the PPPTopology automates creating a Link between two UNetStack instances;

- the StarTopology automates creating a Router and links between UNetStack and RouterPort.

The UnderlyingNetwork model includes a method to obtain the root X.509 certificate pool to use. When you are using a UNetStack, the root X.509 is constructed such that we are able to automatically produce a certificate for any SNI on the server side as long as the client code uses such a root.

Example (DpiCloseConnectionForString)

This example shows how to use DPI to provoke an EOF when you see an offending string.

package main

import (
	"fmt"
	"log"
	"net"
	"net/http"
	"time"

	"github.com/ooni/netem"
)

func main() {
	// Create a star topology for our hosts.
	topology := netem.MustNewStarTopology(&netem.NullLogger{})
	defer topology.Close()

	// Create DPI engine in the client link
	dpi := netem.NewDPIEngine(&netem.NullLogger{})

	// Add client stack to topology. Note that we don't need to
	// close the clientStack: the topology will do that.
	//
	// Note that we need to add delay because several DPI rules
	// rely on race conditions and delay helps.
	clientStack, err := topology.AddHost(
		"10.0.0.1", // host IP address
		"8.8.8.8",  // host DNS resolver IP address
		&netem.LinkConfig{
			LeftToRightDelay: time.Millisecond,
			RightToLeftDelay: time.Millisecond,
			DPIEngine:        dpi,
		},
	)
	if err != nil {
		log.Fatal(err)
	}

	// Add DNS server stack to topology.
	dnsServerStack, err := topology.AddHost(
		"8.8.8.8",
		"8.8.8.8", // this host is its own DNS resolver
		&netem.LinkConfig{
			LeftToRightDelay: time.Millisecond,
			RightToLeftDelay: time.Millisecond,
		},
	)
	if err != nil {
		log.Fatal(err)
	}

	// Add HTTPS server stack to topology.
	httpsServerStack, err := topology.AddHost(
		"5.4.3.21",
		"8.8.8.8",
		&netem.LinkConfig{
			LeftToRightDelay: time.Millisecond,
			RightToLeftDelay: time.Millisecond,
		},
	)
	if err != nil {
		log.Fatal(err)
	}

	// spawn a DNS server with the required configuration.
	dnsConfig := netem.NewDNSConfig()
	dnsConfig.AddRecord("www.example.com", "", "5.4.3.21")
	dnsConfig.AddRecord("example.com", "", "5.4.3.21")
	dnsServer, err := netem.NewDNSServer(
		&netem.NullLogger{},
		dnsServerStack,
		"8.8.8.8",
		dnsConfig,
	)
	if err != nil {
		log.Fatal(err)
	}
	defer dnsServer.Close()

	// spawn an HTTP server with the required configuration
	mux := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Bonsoir, Elliot!"))
	})
	httpsAddr := &net.TCPAddr{
		IP:   net.ParseIP("5.4.3.21"),
		Port: 80,
	}
	httpsListener, err := httpsServerStack.ListenTCP("tcp", httpsAddr)
	if err != nil {
		log.Fatal(err)
	}
	httpsServer := &http.Server{
		Handler: mux,
	}
	go httpsServer.Serve(httpsListener)
	defer httpsServer.Close()

	// create an HTTP transport using the clientStack
	txp := netem.NewHTTPTransport(clientStack)

	// add DPI rule that closes the connection for the www.example.com string
	dpi.AddRule(&netem.DPICloseConnectionForString{
		Logger:          &netem.NullLogger{},
		ServerIPAddress: "5.4.3.21",
		ServerPort:      80,
		String:          "www.example.com",
	})

	// Note that all the code that follows is standard Go code that
	// would work for any implementation of http.RoundTripper.

	{
		// create HTTP request
		req, err := http.NewRequest("GET", "http://www.example.com/", nil)
		if err != nil {
			log.Fatal(err)
		}

		// perform HTTP round trip
		resp, err := txp.RoundTrip(req)
		fmt.Printf("%s %v\n", err.Error(), resp == nil)
	}

	{
		// create HTTP request
		req, err := http.NewRequest("GET", "http://example.com/", nil)
		if err != nil {
			log.Fatal(err)
		}

		// perform HTTP round trip
		resp, err := txp.RoundTrip(req)
		fmt.Printf("%v %v\n", err != nil, resp == nil)
		defer resp.Body.Close()
	}

}
Output:

EOF true
false false
Example (DpiDoesNotAffectLoopbackTraffic)

This is a scenario where a www server modeling www.example.com communicates with itself, therefore, the DPI does not have any effect.

package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"time"

	apexlog "github.com/apex/log"
	"github.com/ooni/netem"
)

func main() {
	// Create a star topology for our hosts.
	topology := netem.MustNewStarTopology(&netem.NullLogger{})
	defer topology.Close()

	// Create DPI engine in the wwwStack link
	dpi := netem.NewDPIEngine(&netem.NullLogger{})

	// IP addresses used by this scenario
	const (
		wwwAddress      = "93.184.216.34"
		resolverAddress = "8.8.8.8"
	)

	// Add the WWW stack to topology. Note that we don't need to
	// close the stack: the topology will do that.
	//
	// Note that we need to add delay because several DPI rules
	// rely on race conditions and delay helps.
	wwwStack, err := topology.AddHost(
		wwwAddress,      // host IP address
		resolverAddress, // host DNS resolver IP address
		&netem.LinkConfig{
			DPIEngine:        dpi,
			LeftToRightDelay: time.Millisecond,
			RightToLeftDelay: time.Millisecond,
		},
	)
	if err != nil {
		log.Fatal(err)
	}

	// Add DNS server stack to topology.
	dnsServerStack, err := topology.AddHost(
		resolverAddress,
		resolverAddress, // this host is its own DNS resolver
		&netem.LinkConfig{
			LeftToRightDelay: time.Millisecond,
			RightToLeftDelay: time.Millisecond,
		},
	)
	if err != nil {
		log.Fatal(err)
	}

	// spawn a DNS server with the required configuration.
	dnsConfig := netem.NewDNSConfig()
	dnsConfig.AddRecord("www.example.com", "", wwwAddress)
	dnsServer, err := netem.NewDNSServer(
		&netem.NullLogger{},
		dnsServerStack,
		resolverAddress,
		dnsConfig,
	)
	if err != nil {
		log.Fatal(err)
	}
	defer dnsServer.Close()

	// spawn an HTTP server with the required configuration
	mux := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Bonsoir, Elliot!"))
	})
	httpsAddr := &net.TCPAddr{
		IP:   net.ParseIP(wwwAddress),
		Port: 80,
	}
	httpsListener, err := wwwStack.ListenTCP("tcp", httpsAddr)
	if err != nil {
		log.Fatal(err)
	}
	httpsServer := &http.Server{
		Handler: mux,
	}
	go httpsServer.Serve(httpsListener)
	defer httpsServer.Close()

	// create an HTTP transport using the wwwStack
	//
	// This is crucial: it means the traffic is not going to exit the
	// loopback interface of stack, so DPI wouldn't see it
	txp := netem.NewHTTPTransport(wwwStack)

	blockpage := []byte(`<html><head><title>451 Unavailable For Legal Reasons</title></head><body><center><h1>451 Unavailable For Legal Reasons</h1></center><p>This content is not available in your jurisdiction.</p></body></html>`)

	// add DPI rule that drops traffic for the www.example.com string
	dpi.AddRule(&netem.DPISpoofBlockpageForString{
		HTTPResponse:    netem.DPIFormatHTTPResponse(blockpage),
		Logger:          apexlog.Log,
		ServerIPAddress: wwwAddress,
		ServerPort:      80,
		String:          "www.example.com",
	})

	// Note that all the code that follows is standard Go code that
	// would work for any implementation of http.RoundTripper.

	{
		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
		defer cancel()

		// create HTTP request
		req, err := http.NewRequestWithContext(ctx, "GET", "http://www.example.com/", nil)
		if err != nil {
			log.Fatal(err)
		}

		// perform HTTP round trip
		resp, err := txp.RoundTrip(req)
		if err != nil {
			log.Fatal(err)
		}
		defer resp.Body.Close()
		respBody, err := io.ReadAll(resp.Body)
		if err != nil {
			log.Fatal(err)
		}
		fmt.Printf("%s\n", string(respBody))
	}

}
Output:

Bonsoir, Elliot!
Example (DpiDropTrafficForString)

This example shows how to use DPI to drop traffic after you see a given string,

package main

import (
	"context"
	"fmt"
	"log"
	"net"
	"net/http"
	"time"

	"github.com/ooni/netem"
)

func main() {
	// Create a star topology for our hosts.
	topology := netem.MustNewStarTopology(&netem.NullLogger{})
	defer topology.Close()

	// Create DPI engine in the client link
	dpi := netem.NewDPIEngine(&netem.NullLogger{})

	// Add client stack to topology. Note that we don't need to
	// close the clientStack: the topology will do that.
	//
	// Note that we need to add delay because several DPI rules
	// rely on race conditions and delay helps.
	clientStack, err := topology.AddHost(
		"10.0.0.1", // host IP address
		"8.8.8.8",  // host DNS resolver IP address
		&netem.LinkConfig{
			LeftToRightDelay: time.Millisecond,
			RightToLeftDelay: time.Millisecond,
			DPIEngine:        dpi,
		},
	)
	if err != nil {
		log.Fatal(err)
	}

	// Add DNS server stack to topology.
	dnsServerStack, err := topology.AddHost(
		"8.8.8.8",
		"8.8.8.8", // this host is its own DNS resolver
		&netem.LinkConfig{
			LeftToRightDelay: time.Millisecond,
			RightToLeftDelay: time.Millisecond,
		},
	)
	if err != nil {
		log.Fatal(err)
	}

	// Add HTTPS server stack to topology.
	httpsServerStack, err := topology.AddHost(
		"5.4.3.21",
		"8.8.8.8",
		&netem.LinkConfig{
			LeftToRightDelay: time.Millisecond,
			RightToLeftDelay: time.Millisecond,
		},
	)
	if err != nil {
		log.Fatal(err)
	}

	// spawn a DNS server with the required configuration.
	dnsConfig := netem.NewDNSConfig()
	dnsConfig.AddRecord("www.example.com", "", "5.4.3.21")
	dnsConfig.AddRecord("example.com", "", "5.4.3.21")
	dnsServer, err := netem.NewDNSServer(
		&netem.NullLogger{},
		dnsServerStack,
		"8.8.8.8",
		dnsConfig,
	)
	if err != nil {
		log.Fatal(err)
	}
	defer dnsServer.Close()

	// spawn an HTTP server with the required configuration
	mux := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Bonsoir, Elliot!"))
	})
	httpsAddr := &net.TCPAddr{
		IP:   net.ParseIP("5.4.3.21"),
		Port: 80,
	}
	httpsListener, err := httpsServerStack.ListenTCP("tcp", httpsAddr)
	if err != nil {
		log.Fatal(err)
	}
	httpsServer := &http.Server{
		Handler: mux,
	}
	go httpsServer.Serve(httpsListener)
	defer httpsServer.Close()

	// create an HTTP transport using the clientStack
	txp := netem.NewHTTPTransport(clientStack)

	// add DPI rule that drops traffic for the www.example.com string
	dpi.AddRule(&netem.DPIDropTrafficForString{
		Logger:          &netem.NullLogger{},
		ServerIPAddress: "5.4.3.21",
		ServerPort:      80,
		String:          "www.example.com",
	})

	// Note that all the code that follows is standard Go code that
	// would work for any implementation of http.RoundTripper.

	{
		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
		defer cancel()

		// create HTTP request
		req, err := http.NewRequestWithContext(ctx, "GET", "http://www.example.com/", nil)
		if err != nil {
			log.Fatal(err)
		}

		// perform HTTP round trip
		resp, err := txp.RoundTrip(req)
		fmt.Printf("%s - %v\n", err.Error(), resp == nil)
	}

	{
		// create HTTP request
		req, err := http.NewRequest("GET", "http://example.com/", nil)
		if err != nil {
			log.Fatal(err)
		}

		// perform HTTP round trip
		resp, err := txp.RoundTrip(req)
		fmt.Printf("%v - %v\n", err != nil, resp == nil)
		defer resp.Body.Close()
	}

}
Output:

context deadline exceeded - true
false - false
Example (DpiSpoofBlockpageForString)

This example shows how to use DPI to spoof a blockpage for a string

package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"time"

	apexlog "github.com/apex/log"
	"github.com/ooni/netem"
)

func main() {
	// Create a star topology for our hosts.
	topology := netem.MustNewStarTopology(&netem.NullLogger{})
	defer topology.Close()

	// Create DPI engine in the client link
	dpi := netem.NewDPIEngine(&netem.NullLogger{})

	// Add client stack to topology. Note that we don't need to
	// close the clientStack: the topology will do that.
	//
	// Note that we need to add delay because several DPI rules
	// rely on race conditions and delay helps.
	clientStack, err := topology.AddHost(
		"10.0.0.1", // host IP address
		"8.8.8.8",  // host DNS resolver IP address
		&netem.LinkConfig{
			DPIEngine:        dpi,
			LeftToRightDelay: time.Millisecond,
			RightToLeftDelay: time.Millisecond,
		},
	)
	if err != nil {
		log.Fatal(err)
	}

	// Add DNS server stack to topology.
	dnsServerStack, err := topology.AddHost(
		"8.8.8.8",
		"8.8.8.8", // this host is its own DNS resolver
		&netem.LinkConfig{
			LeftToRightDelay: time.Millisecond,
			RightToLeftDelay: time.Millisecond,
		},
	)
	if err != nil {
		log.Fatal(err)
	}

	// Add HTTPS server stack to topology.
	httpsServerStack, err := topology.AddHost(
		"5.4.3.21",
		"8.8.8.8",
		&netem.LinkConfig{
			LeftToRightDelay: time.Millisecond,
			RightToLeftDelay: time.Millisecond,
		},
	)
	if err != nil {
		log.Fatal(err)
	}

	// spawn a DNS server with the required configuration.
	dnsConfig := netem.NewDNSConfig()
	dnsConfig.AddRecord("www.example.com", "", "5.4.3.21")
	dnsConfig.AddRecord("example.com", "", "5.4.3.21")
	dnsServer, err := netem.NewDNSServer(
		&netem.NullLogger{},
		dnsServerStack,
		"8.8.8.8",
		dnsConfig,
	)
	if err != nil {
		log.Fatal(err)
	}
	defer dnsServer.Close()

	// spawn an HTTP server with the required configuration
	mux := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Bonsoir, Elliot!"))
	})
	httpsAddr := &net.TCPAddr{
		IP:   net.ParseIP("5.4.3.21"),
		Port: 80,
	}
	httpsListener, err := httpsServerStack.ListenTCP("tcp", httpsAddr)
	if err != nil {
		log.Fatal(err)
	}
	httpsServer := &http.Server{
		Handler: mux,
	}
	go httpsServer.Serve(httpsListener)
	defer httpsServer.Close()

	// create an HTTP transport using the clientStack
	txp := netem.NewHTTPTransport(clientStack)

	blockpage := []byte(`<html><head><title>451 Unavailable For Legal Reasons</title></head><body><center><h1>451 Unavailable For Legal Reasons</h1></center><p>This content is not available in your jurisdiction.</p></body></html>`)

	// add DPI rule that drops traffic for the www.example.com string
	dpi.AddRule(&netem.DPISpoofBlockpageForString{
		HTTPResponse:    netem.DPIFormatHTTPResponse(blockpage),
		Logger:          apexlog.Log,
		ServerIPAddress: "5.4.3.21",
		ServerPort:      80,
		String:          "www.example.com",
	})

	// Note that all the code that follows is standard Go code that
	// would work for any implementation of http.RoundTripper.

	{
		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
		defer cancel()

		// create HTTP request
		req, err := http.NewRequestWithContext(ctx, "GET", "http://www.example.com/", nil)
		if err != nil {
			log.Fatal(err)
		}

		// perform HTTP round trip
		resp, err := txp.RoundTrip(req)
		if err != nil {
			log.Fatal(err)
		}
		defer resp.Body.Close()
		respBody, err := io.ReadAll(resp.Body)
		if err != nil {
			log.Fatal(err)
		}
		fmt.Printf("%s\n", string(respBody))
	}

	{
		// create HTTP request
		req, err := http.NewRequest("GET", "http://example.com/", nil)
		if err != nil {
			log.Fatal(err)
		}

		// perform HTTP round trip
		resp, err := txp.RoundTrip(req)
		if err != nil {
			log.Fatal(err)
		}
		defer resp.Body.Close()
		respBody, err := io.ReadAll(resp.Body)
		if err != nil {
			log.Fatal(err)
		}
		fmt.Printf("%s\n", string(respBody))
	}

}
Output:

<html><head><title>451 Unavailable For Legal Reasons</title></head><body><center><h1>451 Unavailable For Legal Reasons</h1></center><p>This content is not available in your jurisdiction.</p></body></html>
Bonsoir, Elliot!
Example (StarTopologyDNSWhoami)

This example shows how DNS servers implement whoami.v4.powerdns.org

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/ooni/netem"
)

func main() {
	// Create a star topology for our hosts.
	topology := netem.MustNewStarTopology(&netem.NullLogger{})
	defer topology.Close()

	// Add client stack to topology. Note that we don't need to
	// close the clientStack: the topology will do that.
	clientStack, err := topology.AddHost(
		"130.192.91.211",    // host IP address
		"8.8.8.8",           // host DNS resolver IP address
		&netem.LinkConfig{}, // link with no PLR, RTT, DPI
	)
	if err != nil {
		log.Fatal(err)
	}

	// Add DNS server stack to topology.
	dnsServerStack, err := topology.AddHost(
		"8.8.8.8",
		"8.8.8.8", // this host is its own DNS resolver
		&netem.LinkConfig{},
	)
	if err != nil {
		log.Fatal(err)
	}

	// spawn a DNS server with empty configuration.
	dnsConfig := netem.NewDNSConfig()
	dnsServer, err := netem.NewDNSServer(
		&netem.NullLogger{},
		dnsServerStack,
		"8.8.8.8",
		dnsConfig,
	)
	if err != nil {
		log.Fatal(err)
	}
	defer dnsServer.Close()

	// create the DNS query to use
	query := netem.NewDNSRequestA("whoami.v4.powerdns.org")

	// issue a DNS request to the server
	response, err := netem.DNSRoundTrip(context.Background(), clientStack, "8.8.8.8", query)
	if err != nil {
		log.Fatal(err)
	}

	// parse the DNS response
	addrs, _, err := netem.DNSParseResponse(query, response)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", addrs)
}
Output:

[130.192.91.211]
Example (StarTopologyHTTPSAndDNS)

This example shows how to create a star topology, a DNS server, and an HTTPS server. Then we create an HTTPS client and we use such a client to fetch a very important message from the server.

package main

import (
	"fmt"
	"io"
	"log"
	"net"
	"net/http"

	"github.com/ooni/netem"
)

func main() {
	// Create a star topology for our hosts.
	topology := netem.MustNewStarTopology(&netem.NullLogger{})
	defer topology.Close()

	// Add client stack to topology. Note that we don't need to
	// close the clientStack: the topology will do that.
	clientStack, err := topology.AddHost(
		"10.0.0.1",          // host IP address
		"8.8.8.8",           // host DNS resolver IP address
		&netem.LinkConfig{}, // link with no PLR, RTT, DPI
	)
	if err != nil {
		log.Fatal(err)
	}

	// Add DNS server stack to topology.
	dnsServerStack, err := topology.AddHost(
		"8.8.8.8",
		"8.8.8.8", // this host is its own DNS resolver
		&netem.LinkConfig{},
	)
	if err != nil {
		log.Fatal(err)
	}

	// Add HTTPS server stack to topology.
	httpsServerStack, err := topology.AddHost(
		"5.4.3.21",
		"8.8.8.8",
		&netem.LinkConfig{},
	)
	if err != nil {
		log.Fatal(err)
	}

	// spawn a DNS server with the required configuration.
	dnsConfig := netem.NewDNSConfig()
	dnsConfig.AddRecord("tyrell.wellick.name", "tw01.fsociety.com.", "5.4.3.21")
	dnsServer, err := netem.NewDNSServer(
		&netem.NullLogger{},
		dnsServerStack,
		"8.8.8.8",
		dnsConfig,
	)
	if err != nil {
		log.Fatal(err)
	}
	defer dnsServer.Close()

	// spawn an HTTP server with the required configuration
	mux := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Bonsoir, Elliot!"))
	})
	httpsAddr := &net.TCPAddr{
		IP:   net.ParseIP("5.4.3.21"),
		Port: 443,
	}
	httpsListener, err := httpsServerStack.ListenTCP("tcp", httpsAddr)
	if err != nil {
		log.Fatal(err)
	}
	httpsServer := &http.Server{
		Handler:   mux,
		TLSConfig: httpsServerStack.MustNewServerTLSConfig("tyrell.wellick.name"),
	}
	go httpsServer.ServeTLS(httpsListener, "", "") // empty string: use .TLSConfig
	defer httpsServer.Close()

	// create an HTTP transport using the clientStack
	txp := netem.NewHTTPTransport(clientStack)

	// Note that all the code that follows is standard Go code that
	// would work for any implementation of http.RoundTripper.

	// create HTTP request
	req, err := http.NewRequest("GET", "https://tyrell.wellick.name/", nil)
	if err != nil {
		log.Fatal(err)
	}

	// perform HTTP round trip
	resp, err := txp.RoundTrip(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	// read the response body
	data, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%d\n", resp.StatusCode)
	fmt.Printf("%s\n", string(data))
}
Output:

200
Bonsoir, Elliot!

Index

Examples

Constants

View Source
const (
	// FrameFlagSpoof tells the router it should send the
	// spoofed frames inside the [Frame] Spoofed field.
	FrameFlagSpoof = 1 << iota

	// FrameFlagDrop tells the link that the frame should be
	// dropped rather than forwarded, to emulate a loss occurring
	// on the link while the frame was in flight.
	FrameFlagDrop
)
View Source
const DPIDirectionClientToServer = DPIDirection(0)

DPIDirectionClientToServer is the direction from the client to the server. The client is the endpoint that sends the first packet in a flow.

View Source
const DPIDirectionServerToClient = DPIDirection(1)

DPIDirectionServerToClient is the direction from the server to the client. The client is the endpoint that sends the first packet in a flow.

View Source
const NDT0CSVHeader = "filename,rtt(s),plr,final,elapsed (s),total (byte),current (byte),avg speed (Mbit/s),cur speed (Mbit/s)"

NDT0CSVHeader is the header for the CSV records returned by the NDT0PerformanceSample.CSVRecord function.

Variables

View Source
var ErrDNSNoAnswer = errors.New("netem: dns: no answer from DNS server")

ErrDNSNoAnswer is returned when the server response does not contain any answer for the original query (i.e., no IPv4 addresses).

View Source
var ErrDNSNoSuchHost = errors.New("netem: dns: no such host")

ErrDNSNoSuchHost is returned in case of NXDOMAIN.

View Source
var ErrDNSServerMisbehaving = errors.New("netem: dns: server misbehaving")

ErrDNSServerMisbehaving is the error we return for cases different from NXDOMAIN.

View Source
var ErrDissectNetwork = errors.New("netem: dissect: unsupported network protocol")

ErrDissectNetwork indicates that we do not support the packet's network protocol.

View Source
var ErrDissectShortPacket = errors.New("netem: dissect: packet too short")

ErrDissectShortPacket indicates the packet is too short.

View Source
var ErrDissectTransport = errors.New("netem: dissect: unsupported transport protocol")

ErrDissectTransport indicates that we do not support the packet's transport protocol.

View Source
var ErrDuplicateAddr = errors.New("netem: address has already been added")

ErrDuplicateAddr indicates that an address has already been added to a topology.

View Source
var ErrNoPacket = errors.New("netem: no packet in buffer")

ErrNoPacket indicates there's no packet ready.

View Source
var ErrNotIPAddress = errors.New("netem: not a valid IP address")

ErrNotIPAddress indicates that a string is not a serialized IP address.

View Source
var ErrPacketDropped = errors.New("netem: packet was dropped")

ErrPacketDropped indicates that a packet was dropped.

View Source
var ErrStackClosed = errors.New("netem: network stack closed")

ErrStackClosed indicates the network stack has been closed.

View Source
var ErrTLSParse = errors.New("tlsparse: parse error")

ErrTLSParse is the error returned in case there is a TLS parse error.

Functions

func DNSParseResponse

func DNSParseResponse(query, resp *dns.Msg) ([]string, string, error)

DNSParseResponse parses a dns.Msg into a getaddrinfo response

func DNSRoundTrip

func DNSRoundTrip(
	ctx context.Context,
	stack UnderlyingNetwork,
	ipAddress string,
	query *dns.Msg,
) (*dns.Msg, error)

DNSRoundTrip performs a DNS round trip using a given UnderlyingNetwork.

func DNSServerRoundTrip

func DNSServerRoundTrip(config *DNSConfig, rawQuery []byte) ([]byte, error)

DNSServerRoundTrip responds to a raw DNS query with a raw DNS response.

func DPIFormatHTTPResponse

func DPIFormatHTTPResponse(blockpage []byte) (output []byte)

DPIFormatHTTPResponse formats an HTTP response for a blockpage.

func ExtractTLSServerName

func ExtractTLSServerName(rawInput []byte) (string, error)

ExtractTLSServerName takes in input bytes read from the network, attempts to determine whether this is a TLS Handshale message, and if it is a ClientHello, and, if affirmative, attempts to extract the server name.

func LinkFwdFast

func LinkFwdFast(cfg *LinkFwdConfig)

LinkFwdFast is the fast implementation of frames forwarding. We select this implementation when there are no configured losses, delay, or DPI.

func LinkFwdFull

func LinkFwdFull(cfg *LinkFwdConfig)

LinkFwdFull is a full implementation of link forwarding that deals with delays, packet losses, and DPI.

The kind of half-duplex link modeled by this function will look much more like a shared geographical link than an ethernet link. For example, this link allows out-of-order delivery of packets.

func LinkFwdWithDelay

func LinkFwdWithDelay(cfg *LinkFwdConfig)

LinkFwdWithDelay is an implementation of link forwarding that only delays packets without losses and deep packet inspection.

func Must0

func Must0(err error)

Must0 panics in case of error

func Must1

func Must1[Type any](value Type, err error) Type

Must1 panics in case of error otherwise returns the first value

func Must2

func Must2[A, B any](a A, b B, err error) (A, B)

Must2 panics in case of error otherwise returns the two values

func NewDNSRequestA

func NewDNSRequestA(domain string) *dns.Msg

NewDNSRequestA creates a new A request.

func NewHTTPTransport

func NewHTTPTransport(stack HTTPUnderlyingNetwork) *http.Transport

NewHTTPTransport creates a new http.Transport using an UnderlyingNetwork.

We fill the following fields of the transport:

- DialContext to call a dialing function that will eventually use stack.DialContext;

- DialTLSContext to use the stack's [MITMConfig];

- ForceAttemptHTTP2 to force enabling the HTTP/2 protocol.

func RunNDT0Client

func RunNDT0Client(
	ctx context.Context,
	stack UnderlyingNetwork,
	serverAddr string,
	logger Logger,
	TLS bool,
	errch chan<- error,
	perfch chan<- *NDT0PerformanceSample,
)

RunNDT0Client runs the NDT0 client nettest using the given server endpoint address and UnderlyingNetwork.

NDT0 is a stripped down NDT (network diagnostic tool) implementation where a client downloads from a server using a single stream.

The version number is zero because we use the network like ndt7 but we have much less implementation overhead.

This function prints on the standard output download speed information every 250 milliseconds using the CSV data format.

Arguments:

- ctx limits the overall measurement runtime;

- stack is the network stack to use;

- serverAddr is the server endpoint address (e.g., 10.0.0.1:443);

- logger is the logger to use;

- TLS controls whether we should use TLS;

- errch is the channel where we emit the overall error;

- perfch is the channel where we emit performance samples, which we close when we're done running.

func RunNDT0Server

func RunNDT0Server(
	ctx context.Context,
	stack UnderlyingNetwork,
	serverIPAddr net.IP,
	serverPort int,
	logger Logger,
	ready chan<- net.Listener,
	errorch chan<- error,
	TLS bool,
	serverNames ...string,
)

RunNDT0Server runs the NDT0 server. The server will listen for a single client connection and run until the client closes the connection.

You should run this function in a background goroutine.

Arguments:

- ctx limits the overall measurement runtime;

- stack is the network stack to use;

- serverIPAddr is the IP address where we should listen;

- serverPort is the TCP port where we should listen;

- logger is the logger to use;

- ready is the channel where we will post the listener once we have started listening: the caller OWNS the listener and is expected to close it when done or when the listener is stuck inside Accept and there is a need to interrupt it;

- errorch is where we post the overall result of this function (we will post a nil value in case of success);

- TLS controls whether we should use TLS;

- serverNames contains the SNIs to add to the certificate (TLS only).

func UnmarshalTLSServerNameExtension

func UnmarshalTLSServerNameExtension(cursor cryptobyte.String) (string, error)

UnmarshalTLSServerNameExtension unmarshals the server name from the bytes that consist of the extension value.

Types

type CA

type CA struct {
	// contains filtered or unexported fields
}

CA is a certification authority.

The zero value is invalid, please use [NewCA] to construct.

This code is derived from github.com/google/martian/v3.

SPDX-License-Identifier: Apache-2.0.

func MustNewCA

func MustNewCA() *CA

NewCA creates a new certification authority.

func MustNewCAWithTimeNow

func MustNewCAWithTimeNow(timeNow func() time.Time) *CA

MustNewCA is like [NewCA] but uses a custom time.Now func.

This code is derived from github.com/google/martian/v3.

SPDX-License-Identifier: Apache-2.0.

func (*CA) CACert

func (ca *CA) CACert() *x509.Certificate

CACert implements CertificationAuthority.

func (*CA) DefaultCertPool

func (ca *CA) DefaultCertPool() *x509.CertPool

DefaultCertPool implements CertificationAuthority.

func (*CA) MustNewServerTLSConfig

func (ca *CA) MustNewServerTLSConfig(commonName string, extraNames ...string) *tls.Config

MustNewServerTLSConfig implements CertificationAuthority.

func (*CA) MustNewTLSCertificate

func (ca *CA) MustNewTLSCertificate(commonName string, extraNames ...string) *tls.Certificate

MustNewTLSCertificate implements CertificationAuthority.

func (*CA) MustNewTLSCertificateWithTimeNow

func (ca *CA) MustNewTLSCertificateWithTimeNow(timeNow func() time.Time,
	commonName string, extraNames ...string) *tls.Certificate

MustNewCertWithTimeNow implements CertificationAuthority.

This code is derived from github.com/google/martian/v3.

SPDX-License-Identifier: Apache-2.0.

type CertificationAuthority

type CertificationAuthority interface {
	// CACert returns the CA certificate used by the server, which
	// allows you to add to an existing [*x509.CertPool].
	CACert() *x509.Certificate

	// DefaultCertPool returns the default cert pool to use.
	DefaultCertPool() *x509.CertPool

	// MustNewServerTLSConfig constructs a server certificate for
	// the given common name and extra names, all of which could be
	// either IPv4/IPv6 addresses or domain names.
	MustNewServerTLSConfig(commonName string, extraNames ...string) *tls.Config

	// MustNewTLSCertificate constructs a TLS certificate for
	// the given common name and extra names, all of which could be
	// either IPv4/IPv6 addresses or domain names.
	MustNewTLSCertificate(commonName string, extraNames ...string) *tls.Certificate

	// MustNewTLSCertificateWithTimeNow is like MustNewTLSCertificate
	// but takes as input an explicit [time.Now] like func.
	MustNewTLSCertificateWithTimeNow(timeNow func() time.Time,
		commonName string, extraNames ...string) *tls.Certificate
}

CertificationAuthority is a TLS certification authority.

type DNSConfig

type DNSConfig struct {
	// contains filtered or unexported fields
}

DNSConfig is the DNS configuration to use. The zero value is invalid; please use NewDNSConfig.

func NewDNSConfig

func NewDNSConfig() *DNSConfig

NewDNSConfig constructs a DNSConfig instance.

func (*DNSConfig) AddRecord

func (dc *DNSConfig) AddRecord(domain string, cname string, addrs ...string) error

AddRecord adds a record to the DNS server's database or returns an error.

func (*DNSConfig) Clone

func (dc *DNSConfig) Clone() *DNSConfig

Clone clones a DNSConfig.

func (*DNSConfig) Lookup

func (dc *DNSConfig) Lookup(name string) (*DNSRecord, bool)

Lookup searches a name inside the DNSConfig.

func (*DNSConfig) RemoveRecord

func (dc *DNSConfig) RemoveRecord(domain string)

RemoveRecord removes a record from the DNS server's database. If the record does not exist, this method does nothing.

type DNSRecord

type DNSRecord struct {
	// A is the A resource record.
	A []net.IP

	// CNAME is the CNAME.
	CNAME string
}

DNSRecord is a DNS record in the DNSConfig.

type DNSServer

type DNSServer struct {
	// contains filtered or unexported fields
}

DNSServer is a DNS server. The zero value is invalid, please construct using NewDNSServer.

func NewDNSServer

func NewDNSServer(
	logger Logger,
	stack UnderlyingNetwork,
	ipAddress string,
	config *DNSConfig,
) (*DNSServer, error)

NewDNSServer creates a new DNSServer instance. Remember to call DNSServer.Close when you are done using this server.

The ipAddress argument is the IPv4 DNS server address.

func (*DNSServer) Close

func (ds *DNSServer) Close() error

Close shuts down the DNS server

type DPICloseConnectionForServerEndpoint

type DPICloseConnectionForServerEndpoint struct {
	// Logger is the MANDATORY logger.
	Logger Logger

	// ServerIPAddress is the MANDATORY server endpoint IP address.
	ServerIPAddress string

	// ServerPort is the MANDATORY server endpoint port.
	ServerPort uint16
}

DPICloseConnectionForServerEndpoint is a DPIRule that spoofs a FIN|ACK TCP segment after it sees a given TCP connect attempt. The zero value is invalid; please, fill all the fields marked as MANDATORY.

Note: this rule assumes that there is a router in the path that can generate a spoofed RST segment. If there is no router in the path, no RST segment will ever be generated.

Note: this rule relies on a race condition. For consistent results you MUST set some delay in the router<->server link.

func (*DPICloseConnectionForServerEndpoint) Filter

Filter implements DPIRule

type DPICloseConnectionForString

type DPICloseConnectionForString struct {
	// Logger is the MANDATORY logger.
	Logger Logger

	// ServerIPAddress is the MANDATORY server endpoint IP address.
	ServerIPAddress string

	// ServerPort is the MANDATORY server endpoint port.
	ServerPort uint16

	// SNI is the MANDATORY offending string.
	String string
}

DPICloseConnectionForString is a DPIRule that spoofs a FIN|ACK TCP segment after it sees a given string in the payload. The zero value is invalid; please, fill all the fields marked as MANDATORY.

Note: this rule assumes that there is a router in the path that can generate a spoofed RST segment. If there is no router in the path, no RST segment will ever be generated.

Note: this rule relies on a race condition. For consistent results you MUST set some delay in the router<->server link.

func (*DPICloseConnectionForString) Filter

func (r *DPICloseConnectionForString) Filter(
	direction DPIDirection, packet *DissectedPacket) (*DPIPolicy, bool)

Filter implements DPIRule

type DPICloseConnectionForTLSSNI

type DPICloseConnectionForTLSSNI struct {
	// Logger is the MANDATORY logger.
	Logger Logger

	// SNI is the MANDATORY offending SNI.
	SNI string
}

DPICloseConnectionForTLSSNI is a DPIRule that spoofs a FIN|ACK TCP segment after it sees a given TLS SNI. The zero value is invalid; please, fill all the fields marked as MANDATORY.

Note: this rule assumes that there is a router in the path that can generate a spoofed RST segment. If there is no router in the path, no RST segment will ever be generated.

Note: this rule relies on a race condition. For consistent results you MUST set some delay in the router<->server link.

func (*DPICloseConnectionForTLSSNI) Filter

func (r *DPICloseConnectionForTLSSNI) Filter(
	direction DPIDirection, packet *DissectedPacket) (*DPIPolicy, bool)

Filter implements DPIRule

type DPIDirection

type DPIDirection int

DPIDirection is the direction of packets within a flow according to the DPIEngine.

type DPIDropTrafficForServerEndpoint

type DPIDropTrafficForServerEndpoint struct {
	// Logger is the MANDATORY logger
	Logger Logger

	// ServerIPAddress is the MANDATORY server endpoint IP address.
	ServerIPAddress string

	// ServerPort is the MANDATORY server endpoint port.
	ServerPort uint16

	// ServerProtocol is the MANDATORY server endpoint protocol.
	ServerProtocol layers.IPProtocol
}

DPIDropTrafficForServerEndpoint is a DPIRule that drops all the traffic towards a given server endpoint. The zero value is invalid; please fill all the fields marked as MANDATORY.

func (*DPIDropTrafficForServerEndpoint) Filter

func (r *DPIDropTrafficForServerEndpoint) Filter(
	direction DPIDirection, packet *DissectedPacket) (*DPIPolicy, bool)

Filter implements DPIRule

type DPIDropTrafficForString

type DPIDropTrafficForString struct {
	// Logger is the MANDATORY logger
	Logger Logger

	// ServerIPAddress is the MANDATORY server endpoint IP address.
	ServerIPAddress string

	// ServerPort is the MANDATORY server endpoint port.
	ServerPort uint16

	// SNI is the MANDATORY string
	String string
}

DPIDropTrafficForString is a DPIRule that drops all the traffic after it sees a given string. The zero value is invalid; please fill all the fields marked as MANDATORY.

func (*DPIDropTrafficForString) Filter

func (r *DPIDropTrafficForString) Filter(
	direction DPIDirection, packet *DissectedPacket) (*DPIPolicy, bool)

Filter implements DPIRule

type DPIDropTrafficForTLSSNI

type DPIDropTrafficForTLSSNI struct {
	// Logger is the MANDATORY logger
	Logger Logger

	// SNI is the MANDATORY SNI
	SNI string
}

DPIDropTrafficForTLSSNI is a DPIRule that drops all the traffic after it sees a given TLS SNI. The zero value is invalid; please fill all the fields marked as MANDATORY.

func (*DPIDropTrafficForTLSSNI) Filter

func (r *DPIDropTrafficForTLSSNI) Filter(
	direction DPIDirection, packet *DissectedPacket) (*DPIPolicy, bool)

Filter implements DPIRule

type DPIEngine

type DPIEngine struct {
	// contains filtered or unexported fields
}

DPIEngine is a deep packet inspection engine. The zero value is invalid; construct using NewDPIEngine.

func NewDPIEngine

func NewDPIEngine(logger Logger) *DPIEngine

NewDPIEngine creates a new DPIEngine instance.

func (*DPIEngine) AddRule

func (de *DPIEngine) AddRule(rule DPIRule)

AddRule adds a DPIRule to the DPIEngine.

type DPIPolicy

type DPIPolicy struct {
	// Delay is the extra delay to add to the packet.
	Delay time.Duration

	// Flags contains the flags to apply to the packet [Frame].
	Flags int64

	// PLR is the extra PLR to add to the packet.
	PLR float64

	// Spoofed contains the spoofed frames to attach to
	// the [Frame] so that we emit spoofed packets in the
	// router when the frame is being processed.
	Spoofed [][]byte
}

DPIPolicy tells the DPIEngine which policy to apply to a packet.

type DPIResetTrafficForString

type DPIResetTrafficForString struct {
	// Logger is the MANDATORY logger.
	Logger Logger

	// ServerIPAddress is the MANDATORY server endpoint IP address.
	ServerIPAddress string

	// ServerPort is the MANDATORY server endpoint port.
	ServerPort uint16

	// String is the MANDATORY offending string.
	String string
}

DPIResetTrafficForString is a DPIRule that spoofs a RST TCP segment after it sees a given string in the payload for a given offending server endpoint. The zero value is invalid; please, fill all the fields marked as MANDATORY.

Note: this rule assumes that there is a router in the path that can generate a spoofed RST segment. If there is no router in the path, no RST segment will ever be generated.

Note: this rule relies on a race condition. For consistent results you MUST set some delay in the router<->server link.

func (*DPIResetTrafficForString) Filter

func (r *DPIResetTrafficForString) Filter(
	direction DPIDirection, packet *DissectedPacket) (*DPIPolicy, bool)

Filter implements DPIRule

type DPIResetTrafficForTLSSNI

type DPIResetTrafficForTLSSNI struct {
	// Logger is the MANDATORY logger.
	Logger Logger

	// SNI is the MANDATORY offending SNI.
	SNI string
}

DPIResetTrafficForTLSSNI is a DPIRule that spoofs a RST TCP segment after it sees a given TLS SNI. The zero value is invalid; please, fill all the fields marked as MANDATORY.

Note: this rule assumes that there is a router in the path that can generate a spoofed RST segment. If there is no router in the path, no RST segment will ever be generated.

Note: this rule relies on a race condition. For consistent results you MUST set some delay in the router<->server link.

func (*DPIResetTrafficForTLSSNI) Filter

func (r *DPIResetTrafficForTLSSNI) Filter(
	direction DPIDirection, packet *DissectedPacket) (*DPIPolicy, bool)

Filter implements DPIRule

type DPIRule

type DPIRule interface {
	Filter(direction DPIDirection, packet *DissectedPacket) (*DPIPolicy, bool)
}

DPIRule is a deep packet inspection rule.

type DPISpoofBlockpageForString

type DPISpoofBlockpageForString struct {
	// HTTPResponse is the MANDATORY blockpage content prefix with HTTP
	// headers (use DPIFormatHTTPResponse to produce this field).
	HTTPResponse []byte

	// Logger is the MANDATORY logger.
	Logger Logger

	// ServerIPAddress is the MANDATORY server endpoint IP address.
	ServerIPAddress string

	// ServerPort is the MANDATORY server endpoint port.
	ServerPort uint16

	// SNI is the MANDATORY offending string.
	String string
}

DPISpoofBlockpageForString is a DPIRule that spoofs a blockpage after it sees a given string in the payload. The zero value is invalid; please, fill all the fields marked as MANDATORY.

Note: this rule assumes that there is a router in the path that can generate a spoofed RST segment. If there is no router in the path, no RST segment will ever be generated.

Note: this rule relies on a race condition. For consistent results you MUST set some delay in the router<->server link.

Note: this rule requires the blockpage to be very small.

func (*DPISpoofBlockpageForString) Filter

func (r *DPISpoofBlockpageForString) Filter(
	direction DPIDirection, packet *DissectedPacket) (*DPIPolicy, bool)

Filter implements DPIRule

type DPISpoofDNSResponse

type DPISpoofDNSResponse struct {
	// Addresses contains the OPTIONAL addresses to include
	// in the spoofed response. If this field is empty, we
	// will return a NXDOMAIN response to the user.
	Addresses []string

	// Logger is the MANDATORY logger.
	Logger Logger

	// Domain is the MANDATORY offending SNI.
	Domain string
}

DPISpoofDNSResponse is a DPIRule that spoofs a DNS response after it sees a given DNS request. The zero value is invalid; please, fill all the fields marked as MANDATORY.

Note: this rule assumes that there is a router in the path that can generate a spoofed RST segment. If there is no router in the path, no RST segment will ever be generated.

Note: this rule relies on a race condition. For consistent results you MUST set some delay in the router<->server link.

func (*DPISpoofDNSResponse) Filter

func (r *DPISpoofDNSResponse) Filter(
	direction DPIDirection, packet *DissectedPacket) (*DPIPolicy, bool)

Filter implements DPIRule

type DPIThrottleTrafficForTCPEndpoint

type DPIThrottleTrafficForTCPEndpoint struct {
	// Delay is the OPTIONAL extra delay to add to the flow.
	Delay time.Duration

	// Logger is the MANDATORY logger to use.
	Logger Logger

	// PLR is the OPTIONAL extra packet loss rate to apply to the packet.
	PLR float64

	// ServerIPAddress is the MANDATORY server endpoint IP address.
	ServerIPAddress string

	// ServerPort is the MANDATORY server endpoint port.
	ServerPort uint16
}

DPIThrottleTrafficForTCPEndpoint is a DPIRule that throttles traffic for a given TCP endpoint. The zero value is not valid. Make sure you initialize all fields marked as MANDATORY.

func (*DPIThrottleTrafficForTCPEndpoint) Filter

func (r *DPIThrottleTrafficForTCPEndpoint) Filter(
	direction DPIDirection, packet *DissectedPacket) (*DPIPolicy, bool)

Filter implements DPIRule

type DPIThrottleTrafficForTLSSNI

type DPIThrottleTrafficForTLSSNI struct {
	// Delay is the OPTIONAL extra delay to add to the flow.
	Delay time.Duration

	// Logger is the MANDATORY logger to use.
	Logger Logger

	// PLR is the OPTIONAL extra packet loss rate to apply to the packet.
	PLR float64

	// SNI is the OPTIONAL offending SNI
	SNI string
}

DPIThrottleTrafficForTLSSNI is a DPIRule that throttles traffic after it sees a given TLS SNI. The zero value is not valid. Make sure you initialize all fields marked as MANDATORY.

func (*DPIThrottleTrafficForTLSSNI) Filter

func (r *DPIThrottleTrafficForTLSSNI) Filter(
	direction DPIDirection, packet *DissectedPacket) (*DPIPolicy, bool)

Filter implements DPIRule

type DissectedPacket

type DissectedPacket struct {
	// Packet is the underlying packet.
	Packet gopacket.Packet

	// IP is the network layer (either IPv4 or IPv6).
	IP gopacket.NetworkLayer

	// TCP is the POSSIBLY NIL tcp layer.
	TCP *layers.TCP

	// UDP is the POSSIBLY NIL UDP layer.
	UDP *layers.UDP
}

DissectedPacket is a dissected IP packet. The zero-value is invalid; you MUST use the [dissectPacket] factory to create a new instance.

func DissectPacket

func DissectPacket(rawPacket []byte) (*DissectedPacket, error)

DissectPacket parses a packet TCP/IP layers.

func (*DissectedPacket) DecrementTimeToLive

func (dp *DissectedPacket) DecrementTimeToLive()

DecrementTimeToLive decrements the IPv4 or IPv6 time to live.

func (*DissectedPacket) DestinationIPAddress

func (dp *DissectedPacket) DestinationIPAddress() string

DestinationIPAddress returns the packet's destination IP address.

func (*DissectedPacket) DestinationPort

func (dp *DissectedPacket) DestinationPort() uint16

DestinationPort returns the packet's destination port.

func (*DissectedPacket) FlowHash

func (dp *DissectedPacket) FlowHash() uint64

FlowHash returns the hash uniquely identifying the transport flow. Both directions of a flow will have the same hash.

func (*DissectedPacket) MatchesDestination

func (dp *DissectedPacket) MatchesDestination(proto layers.IPProtocol, address string, port uint16) bool

MatchesDestination returns true when the given IPv4 packet has the expected protocol, destination address, and port.

func (*DissectedPacket) MatchesSource

func (dp *DissectedPacket) MatchesSource(proto layers.IPProtocol, address string, port uint16) bool

MatchesSource returns true when the given IPv4 packet has the expected protocol, source address, and port.

func (*DissectedPacket) Serialize

func (dp *DissectedPacket) Serialize() ([]byte, error)

Serialize serializes a previously dissected and modified packet.

func (*DissectedPacket) SourceIPAddress

func (dp *DissectedPacket) SourceIPAddress() string

SourceIPAddress returns the packet's source IP address.

func (*DissectedPacket) SourcePort

func (dp *DissectedPacket) SourcePort() uint16

SourcePort returns the packet's source port.

func (*DissectedPacket) TimeToLive

func (dp *DissectedPacket) TimeToLive() int64

TimeToLive returns the packet's IPv4 or IPv6 time to live.

func (*DissectedPacket) TransportProtocol

func (dp *DissectedPacket) TransportProtocol() layers.IPProtocol

TransportProtocol returns the packet's transport protocol.

type ErrDial

type ErrDial struct {
	// Errors contains the list of errors.
	Errors []error
}

ErrDial contains all the errors occurred during a [DialContext] operation.

func (*ErrDial) Error

func (e *ErrDial) Error() string

Error implements error

func (*ErrDial) Is

func (e *ErrDial) Is(target error) bool

Is allows errors.Is predicates to match child errors.

type Frame

type Frame struct {
	// Deadline is the time when this frame should be delivered.
	Deadline time.Time

	// Flags contains additional flags that [Router] will interpret
	// when processing this [Frame].
	Flags int64

	// Payload contains the packet payload.
	Payload []byte

	// Spoofed contains zero or more packets that the router should
	// spoof when processing this packet. We honor this field iff the
	// FrameFlagSpoof flag is set in the Flags field.
	Spoofed [][]byte
}

Frame contains an IPv4 or IPv6 packet.

func NewFrame

func NewFrame(payload []byte) *Frame

NewFrame constructs a Frame for the given [Payload].

func (*Frame) ShallowCopy

func (f *Frame) ShallowCopy() *Frame

ShallowCopy creates a shallow copy of the Frame allowing us to modify its [Deadline] without data races risks.

type FrameReader

type FrameReader interface {
	// FrameAvailable returns a channel that becomes readable
	// when a new frame has arrived.
	FrameAvailable() <-chan any

	// ReadFrameNonblocking reads an incoming frame. You should only call
	// this function after FrameAvailable has been readable. This function
	// returns one of the following errors:
	//
	// - ErrStackClosed if the underlying stack has been closed;
	//
	// - ErrNoPacket if no packet is available.
	//
	// Callers should ignore ErrNoPacket and try reading again later.
	ReadFrameNonblocking() (*Frame, error)

	// StackClosed returns a channel that becomes readable when the
	// userspace network stack has been closed.
	StackClosed() <-chan any
}

FrameReader allows one to read incoming frames.

type HTTPUnderlyingNetwork

type HTTPUnderlyingNetwork interface {
	UnderlyingNetwork
	IPAddress() string
	Logger() Logger
}

HTTPUnderlyingNetwork is the UnderlyingNetwork used by HTTP code.

type Link struct {
	// contains filtered or unexported fields
}

Link models a link between a "left" and a "right" NIC. The zero value is invalid; please, use a constructor to create a new instance.

A link is characterized by left-to-right and right-to-left delays, which are configured by the Link constructors. A link is also characterized by a left-to-right and right-to-left packet loss rate (PLR).

Once you created a link, it will immediately start to forward traffic until you call Link.Close to shut it down.

func NewLink(logger Logger, left, right NIC, config *LinkConfig) *Link

NewLink creates a new Link instance and spawns goroutines for forwarding traffic between the left and the right [LinkNIC]. You MUST call Link.Close to stop these goroutines when you are done with the Link.

NewLink selects the fastest link implementation that satisfies the provided config. Emulating PLR, RTT, and DPI has a cost, and it doesn't make sense to pay such a cost if you don't need them.

The returned Link TAKES OWNERSHIP of the left and right network stacks and ensures that their [Close] method is called when you call Link.Close.

func (*Link) Close

func (lnk *Link) Close() error

Close closes the Link.

type LinkConfig

type LinkConfig struct {
	// DPIEngine is the OPTIONAL [DPIEngine].
	DPIEngine *DPIEngine

	// LeftNICWrapper is the OPTIONAL [LinkNICWrapper] for the left NIC.
	LeftNICWrapper LinkNICWrapper

	// LeftToRightDelay is the OPTIONAL delay in the left->right direction.
	LeftToRightDelay time.Duration

	// LeftToRightPLR is the OPTIONAL packet-loss rate in the left->right direction.
	LeftToRightPLR float64

	// RightNICWrapper is the OPTIONAL [LinkNICWrapper] for the right NIC.
	RightNICWrapper LinkNICWrapper

	// RightToLeftDelay is the OPTIONAL delay in the right->left direction.
	RightToLeftDelay time.Duration

	// RightToLeftPLR is the OPTIONAL packet-loss rate in the right->left direction.
	RightToLeftPLR float64
}

LinkConfig contains config for creating a Link.

type LinkFwdConfig

type LinkFwdConfig struct {
	// DPIEngine is the OPTIONAL DPI engine.
	DPIEngine *DPIEngine

	// Logger is the MANDATORY logger.
	Logger Logger

	// NewLinkFwdRNG is an OPTIONAL factory that creates a new
	// random number generator, used for writing tests.
	NewLinkFwdRNG func() LinkFwdRNG

	// OneWayDelay is the OPTIONAL link one-way delay.
	OneWayDelay time.Duration

	// PLR is the OPTIONAL link packet-loss rate.
	PLR float64

	// Reader is the MANDATORY [NIC] from which to read frames.
	Reader ReadableNIC

	// Writer is the MANDATORY [NIC] where to write frames.
	Writer WriteableNIC

	// Wg is MANDATORY the wait group that the frame forwarding goroutine
	// will notify when it is shutting down.
	Wg *sync.WaitGroup
}

LinkFwdConfig contains config for frame forwarding algorithms. Make sure you initialize all the fields marked as MANDATORY.

type LinkFwdFunc

type LinkFwdFunc func(cfg *LinkFwdConfig)

LinkFwdFunc is type type of a link forwarding function.

type LinkFwdRNG

type LinkFwdRNG interface {
	// Float64 is like [rand.Rand.Float64].
	Float64() float64

	// Int63n is like [rand.Rand.Int63n].
	Int63n(n int64) int64
}

LinkFwdRNG is a LinkFwdFunc view of a [rand.Rand] abstracted for festability.

type LinkNICWrapper

type LinkNICWrapper interface {
	WrapNIC(NIC) NIC
}

LinkNICWrapper allows wrapping [NIC]s used by a Link to log packets, collect PCAPs and implement DPI.

type Logger

type Logger interface {
	// Debugf formats and emits a debug message.
	Debugf(format string, v ...any)

	// Debug emits a debug message.
	Debug(message string)

	// Infof formats and emits an informational message.
	Infof(format string, v ...any)

	// Info emits an informational message.
	Info(message string)

	// Warnf formats and emits a warning message.
	Warnf(format string, v ...any)

	// Warn emits a warning message.
	Warn(message string)
}

Logger is the logger we're using.

type MockableNIC

type MockableNIC struct {
	// MockFrameAvailable allows mocking [NIC.FrameAvailable].
	MockFrameAvailable func() <-chan any

	// MockReadFrameNonblocking allows mocking [NIC.ReadFrameNonblocking].
	MockReadFrameNonblocking func() (*Frame, error)

	// MockStackClosed allows mocking [NIC.StackClosed].
	MockStackClosed func() <-chan any

	// MockClose allows mocking [NIC.Close].
	MockClose func() error

	// MockIPAddress allows mocking [NIC.IPAddress].
	MockIPAddress func() string

	// MockInterfaceName allows mocking [NIC.InterfaceName].
	MockInterfaceName func() string

	// MockWriteFrame allows mocking [NIC.WriteFrame].
	MockWriteFrame func(frame *Frame) error
}

MocakbleNIC is a mockable NIC implementation.

func (*MockableNIC) Close

func (n *MockableNIC) Close() error

Close implements NIC

func (*MockableNIC) FrameAvailable

func (n *MockableNIC) FrameAvailable() <-chan any

FrameAvailable implements NIC

func (*MockableNIC) IPAddress

func (n *MockableNIC) IPAddress() string

IPAddress implements NIC

func (*MockableNIC) InterfaceName

func (n *MockableNIC) InterfaceName() string

InterfaceName implements NIC

func (*MockableNIC) ReadFrameNonblocking

func (n *MockableNIC) ReadFrameNonblocking() (*Frame, error)

ReadFrameNonblocking implements NIC

func (*MockableNIC) StackClosed

func (n *MockableNIC) StackClosed() <-chan any

StackClosed implements NIC

func (*MockableNIC) WriteFrame

func (n *MockableNIC) WriteFrame(frame *Frame) error

WriteFrame implements NIC

type NDT0PerformanceSample

type NDT0PerformanceSample struct {
	// Final indicates whether this is the final sample.
	Final bool

	// ReceivedTotal is the total number of bytes received.
	ReceivedTotal int64

	// ReceivedLast is the total number of bytes received since
	// we collected the last sample.
	ReceivedLast int64

	// TimeLast is the last time we collected a sample.
	TimeLast time.Time

	// TimeNow is the time when we collected this sample.
	TimeNow time.Time

	// TimeZero is when the measurement started.
	TimeZero time.Time
}

NDT0PerformanceSample is a performance sample returned by RunNDT0Client.

func (*NDT0PerformanceSample) AvgSpeedMbps

func (ps *NDT0PerformanceSample) AvgSpeedMbps() float64

AvgSpeedMbps returns the average speed since the beginning of the measurement expressed in Mbit/s.

func (*NDT0PerformanceSample) CSVRecord

func (ps *NDT0PerformanceSample) CSVRecord(pcapfile string, rtt time.Duration, plr float64) string

CSVRecord returns a CSV representation of the sample.

func (*NDT0PerformanceSample) ElapsedSeconds

func (ps *NDT0PerformanceSample) ElapsedSeconds() float64

ElapsedSeconds returns the elapsed time since the beginning of the measurement expressed in seconds.

type NIC

type NIC interface {
	// A NIC implements FrameReader
	FrameReader

	// Close closes this network interface.
	Close() error

	// IPAddress returns the IP address assigned to the NIC.
	IPAddress() string

	// InterfaceName returns the name of the NIC.
	InterfaceName() string

	// WriteFrame writes a frame or returns an error. This function
	// returns ErrStackClosed when the underlying stack has been closed.
	WriteFrame(frame *Frame) error
}

NIC is a network interface card with which you can send and receive [Frame]s.

type Net

type Net struct {
	// Stack is the MANDATORY underlying stack.
	Stack UnderlyingNetwork
}

Net is a drop-in replacement for the net package. The zero value is invalid; please init all the MANDATORY fields.

func (*Net) DialContext

func (n *Net) DialContext(ctx context.Context, network, address string) (net.Conn, error)

DialContext is a drop-in replacement for net.Dialer.DialContext.

func (*Net) DialTLSContext

func (n *Net) DialTLSContext(ctx context.Context, network, address string) (net.Conn, error)

DialTLSContext is like Net.DialContext but also performs a TLS handshake.

func (*Net) ListenTCP

func (n *Net) ListenTCP(network string, addr *net.TCPAddr) (net.Listener, error)

ListenTCP is a drop-in replacement for net.ListenTCP.

func (*Net) ListenTLS

func (n *Net) ListenTLS(network string, laddr *net.TCPAddr, config *tls.Config) (net.Listener, error)

ListenTLS is a replacement for tls.Listen that uses the underlying stack's TLS MITM capabilities during the TLS handshake.

func (*Net) ListenUDP

func (n *Net) ListenUDP(network string, addr *net.UDPAddr) (UDPLikeConn, error)

ListenUDP is a drop-in replacement for net.ListenUDP.

func (*Net) LookupCNAME

func (n *Net) LookupCNAME(ctx context.Context, domain string) (string, error)

LookupCNAME is a drop-in replacement for net.Resolver.LookupCNAME.

func (*Net) LookupHost

func (n *Net) LookupHost(ctx context.Context, domain string) ([]string, error)

LookupHost is a drop-in replacement for net.Resolver.LookupHost.

type NullLogger

type NullLogger struct{}

NullLogger is a netem.Logger that does not emit logs.

func (*NullLogger) Debug

func (nl *NullLogger) Debug(message string)

Debug implements netem.Logger

func (*NullLogger) Debugf

func (nl *NullLogger) Debugf(format string, v ...any)

Debugf implements netem.Logger

func (*NullLogger) Info

func (nl *NullLogger) Info(message string)

Info implements netem.Logger

func (*NullLogger) Infof

func (nl *NullLogger) Infof(format string, v ...any)

Infof implements netem.Logger

func (*NullLogger) Warn

func (nl *NullLogger) Warn(message string)

Warn implements netem.Logger

func (*NullLogger) Warnf

func (nl *NullLogger) Warnf(format string, v ...any)

Warnf implements netem.Logger

type PCAPDumper

type PCAPDumper struct {
	// contains filtered or unexported fields
}

PCAPDumper collects a PCAP trace. The zero value is invalid and you should use NewPCAPDumper to instantiate. Once you have a valid instance, you should register the PCAPDumper as a LinkNICWrapper inside the LinkConfig.

func NewPCAPDumper

func NewPCAPDumper(filename string, logger Logger) *PCAPDumper

NewPCAPDumper creates a new PCAPDumper.

func (*PCAPDumper) WrapNIC

func (pd *PCAPDumper) WrapNIC(nic NIC) NIC

WrapNIC implements the LinkNICWrapper interface.

type PPPTopology

type PPPTopology struct {
	// Client is the client network stack in the PPP topology.
	Client *UNetStack

	// Server is the server network stack in the PPP topology.
	Server *UNetStack
	// contains filtered or unexported fields
}

PPPTopology is a point-to-point topology with two network stacks and a Link in the middle. By convention, the left stack is the client and the right one is the server. The zero value of this struct is invalid; use [NewPPPTopology] to create a new instance.

func MustNewPPPTopology

func MustNewPPPTopology(
	clientAddress string,
	serverAddress string,
	logger Logger,
	lc *LinkConfig,
) *PPPTopology

MustNewPPPTopology creates a PPPTopology. Use the Close method to shutdown the link created by this topology.

Arguments:

- clientAddress is the client IP address;

- serverAddress is the server IP address;

- logger is the logger to use;

- MTU is the MTU to use (1500 is a good MTU value);

- lc describes the link characteristics.

func (*PPPTopology) Close

func (t *PPPTopology) Close() error

Close closes all the hosts and links allocated by the topology

type ReadableNIC

type ReadableNIC interface {
	FrameReader
	InterfaceName() string
}

ReadableNIC is the read-only NIC used by frame forwarding algorithms.

type Router

type Router struct {
	// contains filtered or unexported fields
}

Router routes traffic between [RouterPort]s. The zero value of this structure isn't invalid; construct using NewRouter.

func NewRouter

func NewRouter(logger Logger) *Router

NewRouter creates a new Router instance.

func (*Router) AddRoute

func (r *Router) AddRoute(destIP string, destPort *RouterPort)

AddRoute adds a route to the routing table.

type RouterPort

type RouterPort struct {
	// contains filtered or unexported fields
}

RouterPort is a port of a Router. The zero value is invalid, use the NewRouterPort constructor to instantiate.

func NewRouterPort

func NewRouterPort(router *Router) *RouterPort

NewRouterPort creates a new RouterPort for a given Router.

func (*RouterPort) Close

func (sp *RouterPort) Close() error

Close implements NIC

func (*RouterPort) FrameAvailable

func (sp *RouterPort) FrameAvailable() <-chan any

FrameAvailable implements NIC

func (*RouterPort) IPAddress

func (sp *RouterPort) IPAddress() string

IPAddress implements NIC

func (*RouterPort) InterfaceName

func (sp *RouterPort) InterfaceName() string

InterfaceName implements NIC

func (*RouterPort) ReadFrameNonblocking

func (sp *RouterPort) ReadFrameNonblocking() (*Frame, error)

ReadFrameNonblocking implements NIC

func (*RouterPort) StackClosed

func (sp *RouterPort) StackClosed() <-chan any

StackClosed implements NIC

func (*RouterPort) WriteFrame

func (sp *RouterPort) WriteFrame(frame *Frame) error

WriteFrame implements NIC

type StarTopology

type StarTopology struct {
	// contains filtered or unexported fields
}

StarTopology is the star network topology: there is a router in the middle and all hosts connect to it. The zero value is invalid; please, construct using the [NewStarTopology].

func MustNewStarTopology

func MustNewStarTopology(logger Logger) *StarTopology

MustNewStarTopology constructs a new, empty StarTopology consisting of a Router sitting in the middle. Once you have the StarTopology you can now add hosts using [AddHost], [AddHTTPServer], etc.

func (*StarTopology) AddHost

func (t *StarTopology) AddHost(
	hostAddress string,
	resolverAddress string,
	lc *LinkConfig,
) (*UNetStack, error)

AddHost creates a new UNetStack and a RouterPort, creates a Link to connect them, attaches the port to the topology's Router, and returns the UNetStack to the caller. You do not need to call [Close] for the returned UNetStack because calling the [StartTopology]'s Close method will also close the UNetStack.

Arguments:

- hostAddress is the IPv4 address to assign to the UNetStack;

- resolverAddress is the IPv4 address of the resolver the UNetStack should use; use 0.0.0.0 if you don't need DNS resolution;

- lc contains config for the Link connecting the UNetStack to the Router of the StarTopology.

func (*StarTopology) CA

func (t *StarTopology) CA() *CA

CA exposes the *CA.

func (*StarTopology) Close

func (t *StarTopology) Close() error

Close closes (a) the router and (b) all the links and the hosts created using this StarTopology.

type StaticReadableNIC

type StaticReadableNIC struct {
	// contains filtered or unexported fields
}

StaticReadableNIC is a ReadableNIC that will return a fixed amount of frames. The zero value is invalid; use NewStaticReadableNIC factory to construct an instance. Remember to call CloseNetworkStack when you have read all the frames emitted by a channel to unblock the stack.

func NewStaticReadableNIC

func NewStaticReadableNIC(name string, frames ...*Frame) *StaticReadableNIC

NewStaticReadableNIC constructs a new StaticReadableNIC instance.

func (*StaticReadableNIC) CloseNetworkStack

func (n *StaticReadableNIC) CloseNetworkStack()

CloseNetworkStack closes the network stack used by this NIC, which in turn causes StackClosed() to become readable.

func (*StaticReadableNIC) FrameAvailable

func (n *StaticReadableNIC) FrameAvailable() <-chan any

FrameAvailable implements ReadableNIC

func (*StaticReadableNIC) InterfaceName

func (n *StaticReadableNIC) InterfaceName() string

InterfaceName implements ReadableNIC

func (*StaticReadableNIC) ReadFrameNonblocking

func (n *StaticReadableNIC) ReadFrameNonblocking() (*Frame, error)

ReadFrameNonblocking implements ReadableNIC

func (*StaticReadableNIC) StackClosed

func (n *StaticReadableNIC) StackClosed() <-chan any

StackClosed implements ReadableNIC

type StaticWriteableNIC

type StaticWriteableNIC struct {
	// contains filtered or unexported fields
}

StaticWriteableNIC is a [WritableNIC] that collects all the frames it received for you to inspect later. The zero value is invalid; construct using [NewStaticWritableNIC].

func NewStaticWriteableNIC

func NewStaticWriteableNIC(name string) *StaticWriteableNIC

NewStaticWriteableNIC constructs a new StaticWriteableNIC instance.

func (*StaticWriteableNIC) Frames

func (n *StaticWriteableNIC) Frames() <-chan *Frame

Frames returns the channel where we post frames

func (*StaticWriteableNIC) InterfaceName

func (n *StaticWriteableNIC) InterfaceName() string

InterfaceName implements WriteableNIC

func (*StaticWriteableNIC) WriteFrame

func (n *StaticWriteableNIC) WriteFrame(frame *Frame) error

WriteFrame implements WriteableNIC

type TLSClientHello

type TLSClientHello struct {
	// ProtocolVersion is the protocol version.
	ProtocolVersion uint16

	// Random contains exacty 32 bytes of random data.
	Random []byte

	// LegacySessionID is the legacy session ID.
	LegacySessionID cryptobyte.String

	// CipherSuites contains the client cipher suites.
	CipherSuites cryptobyte.String

	// LegacyCompressionMethods contains the legacy compression methods.
	LegacyCompressionMethods cryptobyte.String

	// Extensions contains the extensions.
	Extensions cryptobyte.String
}

TLSClientHello is the TLSClientHello message.

type TLSExtension

type TLSExtension struct {
	// Type is the extension type.
	Type uint16

	// Data contains the extension data.
	Data cryptobyte.String
}

TLSExtension is a TLS extension.

func FindTLSServerNameExtension

func FindTLSServerNameExtension(exts []*TLSExtension) (*TLSExtension, bool)

FindTLSServerNameExtension returns the first ServerName extension in case of success or false in case of failure.

func UnmarshalTLSExtensions

func UnmarshalTLSExtensions(cursor cryptobyte.String) ([]*TLSExtension, error)

UnmarshalTLSExtensions unmarshals the extensions.

Return value:

1. the parsed []*Extensions (on success);

2. an error (nil on success).

type TLSHandshakeMsg

type TLSHandshakeMsg struct {
	// HandshakeType is the type of handshake message.
	HandshakeType uint8

	// ClientHello is either nil or the parsed ClientHello.
	ClientHello *TLSClientHello
}

TLSHandshakeMsg is the TLSHandshakeMsg message.

func UnmarshalTLSHandshakeMsg

func UnmarshalTLSHandshakeMsg(cursor cryptobyte.String) (*TLSHandshakeMsg, error)

UnmarshalTLSHandshakeMsg unmarshals an Handshake message.

Return value:

1. the parsed Handshake (on success);

2. an error (nil on success).

type TLSRecordHeader

type TLSRecordHeader struct {
	// ContentType is the type of the content.
	ContentType uint8

	// ProtocolVersion is the version of the TLS protocol.
	ProtocolVersion uint16

	// Rest contains the rest of the message.
	Rest cryptobyte.String
}

TLSRecordHeader is a TLS TLSRecordHeader.

func UnmarshalTLSRecordHeader

func UnmarshalTLSRecordHeader(cursor cryptobyte.String) (*TLSRecordHeader, cryptobyte.String, error)

UnmarshalTLSRecordHeader unmarshals a RecordHeader.

Return value:

1. the parsed RecordHeader (on success);

2. the unparsed bytes (on success), which may be empty if the input only contained a whole RecordHeader;

3. an error (nil on success).

type UDPLikeConn

type UDPLikeConn interface {
	// An UDPLikeConn is a net.PacketConn conn.
	net.PacketConn

	// SetReadBuffer allows setting the read buffer.
	SetReadBuffer(bytes int) error

	// SyscallConn returns a conn suitable for calling syscalls,
	// which is also instrumental to setting the read buffer.
	SyscallConn() (syscall.RawConn, error)
}

UDPLikeConn is a net.PacketConn with some extra functions required to convince the QUIC library (lucas-clemente/quic-go) to inflate the receive buffer of the connection.

The QUIC library will treat this connection as a "dumb" net.PacketConn, calling its ReadFrom and WriteTo methods as opposed to more efficient methods that are available under Linux and (maybe?) FreeBSD.

It seems fine to avoid performance optimizations, because they would complicate the implementation on our side and our use cases (blocking and heavy throttling) do not seem to require such optimizations.

See https://github.com/ooni/probe/issues/1754 for a more comprehensive discussion of UDPLikeConn.

type UNetStack

type UNetStack struct {
	// contains filtered or unexported fields
}

UNetStack is a network stack in user space. The zero value is invalid; please, use NewUNetStack to construct.

Because UNetStack implements UnderlyingNetwork, you can use it to perform the following operations:

- connect TCP/UDP sockets using UNetStack.DialContext;

- create listening UDP sockets using UNetStack.ListenUDP;

- create listening TCP sockets using UNetStack.ListenTCP;

- perform getaddrinfo like DNS lookups using UNetStack.GetaddrinfoLookupANY;

Use [UNetStack.NIC] to obtain a NIC to read and write the [Frames] produced by using the network stack as the UnderlyingNetwork.

func NewUNetStack

func NewUNetStack(
	logger Logger,
	MTU uint32,
	stackAddress string,
	ca *CA,
	resolverAddress string,
) (*UNetStack, error)

NewUNetStack constructs a new UNetStack instance.

Arguments:

- logger is the logger to use;

- MTU is the MTU to use (you MUST use at least 1252 bytes if you want to use github.com/lucas-clemente/quic-go);

- stackAddress is the IPv4 address to assign to the stack;

- cfg contains TLS MITM configuration;

- resolverAddress is the IPv4 address of the resolver.

func (*UNetStack) CACert

func (gs *UNetStack) CACert() *x509.Certificate

CACert implements CertificationAuthority.

func (*UNetStack) Close

func (gs *UNetStack) Close() error

Close shuts down the virtual network stack.

func (*UNetStack) DefaultCertPool

func (gs *UNetStack) DefaultCertPool() *x509.CertPool

DefaultCertPool implements CertificationAuthority.

func (*UNetStack) DialContext

func (gs *UNetStack) DialContext(
	ctx context.Context, network string, address string) (net.Conn, error)

DialContext implements UnderlyingNetwork.

func (*UNetStack) FrameAvailable

func (gs *UNetStack) FrameAvailable() <-chan any

FrameAvailable implements NIC

func (*UNetStack) GetaddrinfoLookupANY

func (gs *UNetStack) GetaddrinfoLookupANY(ctx context.Context, domain string) ([]string, string, error)

GetaddrinfoLookupANY implements UnderlyingNetwork.

func (*UNetStack) GetaddrinfoResolverNetwork

func (gs *UNetStack) GetaddrinfoResolverNetwork() string

GetaddrinfoResolverNetwork implements UnderlyingNetwork

func (*UNetStack) IPAddress

func (gs *UNetStack) IPAddress() string

IPAddress implements NIC

func (*UNetStack) InterfaceName

func (gs *UNetStack) InterfaceName() string

InterfaceName implements NIC

func (*UNetStack) ListenTCP

func (gs *UNetStack) ListenTCP(network string, addr *net.TCPAddr) (net.Listener, error)

ListenTCP implements UnderlyingNetwork

func (*UNetStack) ListenUDP

func (gs *UNetStack) ListenUDP(network string, addr *net.UDPAddr) (UDPLikeConn, error)

ListenUDP implements UnderlyingNetwork.

func (*UNetStack) Logger

func (gs *UNetStack) Logger() Logger

Logger implements HTTPUnderlyingNetwork.

func (*UNetStack) MustNewServerTLSConfig

func (gs *UNetStack) MustNewServerTLSConfig(commonName string, extraNames ...string) *tls.Config

MustNewServerTLSConfig implements CertificationAuthority.

func (*UNetStack) MustNewTLSCertificate

func (gs *UNetStack) MustNewTLSCertificate(commonName string, extraNames ...string) *tls.Certificate

MustNewTLSCertificate implements implements CertificationAuthority.

func (*UNetStack) MustNewTLSCertificateWithTimeNow

func (gs *UNetStack) MustNewTLSCertificateWithTimeNow(timeNow func() time.Time,
	commonName string, extraNames ...string) *tls.Certificate

MustNewTLSCertificateWithTimeNow implements CertificationAuthority.

func (*UNetStack) ReadFrameNonblocking

func (gs *UNetStack) ReadFrameNonblocking() (*Frame, error)

ReadFrameNonblocking implements NIC

func (*UNetStack) StackClosed

func (gs *UNetStack) StackClosed() <-chan any

StackClosed implements NIC

func (*UNetStack) WriteFrame

func (gs *UNetStack) WriteFrame(frame *Frame) error

WriteFrame implements NIC

type UnderlyingNetwork

type UnderlyingNetwork interface {
	// CertificationAuthority allows accessing the certification authority
	// associated with this host or set of hosts.
	CertificationAuthority

	// DialContext dials a TCP or UDP connection. Unlike [net.DialContext], this
	// function does not implement dialing when address contains a domain.
	DialContext(ctx context.Context, network, address string) (net.Conn, error)

	// GetaddrinfoLookupANY is like [net.Resolver.LookupHost] except that it
	// also returns to the caller the CNAME when it is available.
	GetaddrinfoLookupANY(ctx context.Context, domain string) ([]string, string, error)

	// GetaddrinfoResolverNetwork returns the resolver network.
	GetaddrinfoResolverNetwork() string

	// ListenTCP creates a new listening TCP socket.
	ListenTCP(network string, addr *net.TCPAddr) (net.Listener, error)

	// ListenUDP creates a new listening UDP socket. The [UDPLikeConn] returned
	// by this function is a best effort attempt to emulate a [net.UDPConn] that
	// works with the github.com/lucas-clemente/quic-go library.
	ListenUDP(network string, addr *net.UDPAddr) (UDPLikeConn, error)
}

UnderlyingNetwork replaces for functions in the net package.

type WriteableNIC

type WriteableNIC interface {
	InterfaceName() string
	WriteFrame(frame *Frame) error
}

WriteableNIC is the write-only NIC used by frame forwarding algorithms.

Directories

Path Synopsis
cmd
calibrate
Command calibrate helps calibrating the implementation of [Link].
Command calibrate helps calibrating the implementation of [Link].

Jump to

Keyboard shortcuts

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