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-cli/v3/internal/netemx" "github.com/ooni/probe-cli/v3/internal/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-cli/v3/internal/netemx" "github.com/ooni/probe-cli/v3/internal/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-cli/v3/internal/netemx" "github.com/ooni/probe-cli/v3/internal/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-cli/v3/internal/model" "github.com/ooni/probe-cli/v3/internal/netemx" "github.com/ooni/probe-cli/v3/internal/netxlite" "github.com/ooni/probe-cli/v3/internal/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-cli/v3/internal/netemx" "github.com/ooni/probe-cli/v3/internal/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-cli/v3/internal/netemx" "github.com/ooni/probe-cli/v3/internal/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-cli/v3/internal/netemx" "github.com/ooni/probe-cli/v3/internal/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-cli/v3/internal/netemx" "github.com/ooni/probe-cli/v3/internal/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-cli/v3/internal/netemx" "github.com/ooni/probe-cli/v3/internal/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-cli/v3/internal/netemx" "github.com/ooni/probe-cli/v3/internal/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-cli/v3/internal/netemx" "github.com/ooni/probe-cli/v3/internal/netxlite" "github.com/ooni/probe-cli/v3/internal/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-cli/v3/internal/netemx" "github.com/ooni/probe-cli/v3/internal/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 ¶
- Constants
- Variables
- func ExampleWebPageHandler() http.Handler
- func WithCustomTProxy(tproxy netem.UnderlyingNetwork, function func())
- type BadSSLServerFactory
- type DNSOverHTTPSHandlerFactory
- type DNSOverUDPServerFactory
- type GeoIPHandlerFactoryUbuntu
- type HTTP3ServerFactory
- type HTTPCleartextServerFactory
- type HTTPHandlerFactory
- type HTTPHandlerFactoryFunc
- type HTTPSecureServerFactory
- type NetStackServer
- type NetStackServerFactory
- type NetStackServerFactoryEnv
- type OOAPIHandler
- type OOAPIHandlerFactory
- type OOHelperDFactory
- type QAEnv
- func (env *QAEnv) AddRecordToAllResolvers(domain string, cname string, addrs ...string)
- func (env *QAEnv) Close() error
- func (env *QAEnv) DPIEngine() *netem.DPIEngine
- func (env *QAEnv) Do(function func())
- func (env *QAEnv) EmulateAndroidGetaddrinfo(value bool)
- func (env *QAEnv) ISPResolverConfig() *netem.DNSConfig
- func (env *QAEnv) Logger() model.Logger
- func (env *QAEnv) OtherResolversConfig() *netem.DNSConfig
- type QAEnvOption
- type ScenarioDomainAddresses
Examples ¶
- Package (CustomNetStackHandler)
- Package (DnsOverUDPWithInternetScenario)
- Package (DohWithInternetScenario)
- Package (DpiRule)
- Package (ExamplePublicBlockpage)
- Package (ExampleURLShortener)
- Package (ExampleWebServerWithInternetScenario)
- Package (GetaddrinfoWithInternetScenario)
- Package (OohelperdWithInternetScenario)
- Package (OoniAPIWithInternetScenario)
- Package (ResolverConfig)
- Package (UbuntuGeoIPWithInternetScenario)
Constants ¶
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 )
const AddressApiOONIIo = "162.55.247.208"
AddressApiOONIIo is the IP address for api.ooni.io.
const AddressBadSSLCom = "104.154.89.105"
AddressBadSSLCom is the IP address of badssl.com.
const AddressBitly = "67.199.248.11"
AddressBitly is the IP address of bitly.com.
const AddressDNSGoogle8844 = "8.8.4.4"
AddressDNSGoogle8844 is the 8.8.4.4 address for dns.google.
const AddressDNSGoogle8888 = "8.8.8.8"
AddressDNSGoogle8888 is the 8.8.8.8 address for dns.google.
const AddressDNSQuad9Net = "9.9.9.9"
AddressDNSQuad9Net is the IP address for dns.quad9.net.
const AddressGeoIPUbuntuCom = "185.125.188.132"
AddressGeoIPUbuntuCom is the IP address for geoip.ubuntu.com.
const AddressMozillaCloudflareDNSCom = "172.64.41.4"
AddressMozillaCloudflareDNSCom is the IP address for mozilla.cloudflare-dns.com.
const AddressOneThOONIOrg = "137.184.235.44"
AddressOneThOONIOrg is the IP address for 1.th.ooni.org.
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.
const AddressTHCloudfront = "52.85.15.84"
AddressTHCloudfront is the IP address for d33d1gs9kpq1c5.cloudfront.net.
const AddressThreeThOONIOrg = "209.97.183.73"
AddressThreeThOONIOrg is the IP address for 3.th.ooni.org.
const AddressTwoThOONIOrg = "178.62.195.24"
AddressTwoThOONIOrg is the IP address for 2.th.ooni.org.
const AddressWwwExampleCom = "93.184.216.34"
AddressWwwExampleCom is the IP address for www.example.com.
const AddressZeroThOONIOrg = "68.183.70.80"
AddressZeroThOONIOrg is the IP address for 0.th.ooni.org.
const Blockpage = `` /* 188-byte string literal not displayed */
Blockpage is the webpage returned by BlockpageHandlerFactory.
const DefaultClientAddress = "130.192.91.211"
DefaultClientAddress is the default client IP address.
const ExampleWebPage = `` /* 1533-byte string literal not displayed */
ExampleWebPage is the webpage returned by ExampleWebPageHandlerFactory.
const ISPProxyAddress = "130.192.182.17"
ISPProxyAddress is the IP address of the ISP's HTTP transparent proxy.
const ISPResolverAddress = "130.192.3.21"
ISPResolverAddress is the IP address of the client ISP resolver.
const RootResolverAddress = "193.0.14.129"
RootResolverAddress is the root resolver resolver IP address.
Variables ¶
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.
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 v3.19.0
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 v3.19.0
type BadSSLServerFactory struct{}
BadSSLServerFactory is a NetStackServerFactory that instantiates a NetStackServer used for testing common TLS issues.
func (*BadSSLServerFactory) MustNewServer ¶ added in v3.19.0
func (*BadSSLServerFactory) MustNewServer(env NetStackServerFactoryEnv, stack *netem.UNetStack) NetStackServer
MustNewServer implements NetStackServerFactory.
type DNSOverHTTPSHandlerFactory ¶ added in v3.19.0
type DNSOverHTTPSHandlerFactory struct{}
DNSOverHTTPSHandlerFactory is a [QAEnvHTTPHandlerFactory] for testingx.GeoIPHandlerUbuntu.
func (*DNSOverHTTPSHandlerFactory) NewHandler ¶ added in v3.19.0
func (f *DNSOverHTTPSHandlerFactory) NewHandler(env NetStackServerFactoryEnv, stack *netem.UNetStack) http.Handler
NewHandler implements QAEnvHTTPHandlerFactory.
type DNSOverUDPServerFactory ¶ added in v3.19.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 v3.19.0
func (f *DNSOverUDPServerFactory) MustNewServer(env NetStackServerFactoryEnv, stack *netem.UNetStack) NetStackServer
MustNewServer implements NetStackServerFactory.
type GeoIPHandlerFactoryUbuntu ¶ added in v3.19.0
type GeoIPHandlerFactoryUbuntu struct {
ProbeIP string
}
GeoIPHandlerFactoryUbuntu is a [QAEnvHTTPHandlerFactory] for testingx.GeoIPHandlerUbuntu.
func (*GeoIPHandlerFactoryUbuntu) NewHandler ¶ added in v3.19.0
func (f *GeoIPHandlerFactoryUbuntu) NewHandler(env NetStackServerFactoryEnv, stack *netem.UNetStack) http.Handler
NewHandler implements QAEnvHTTPHandlerFactory.
type HTTP3ServerFactory ¶ added in v3.19.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 v3.19.0
func (f *HTTP3ServerFactory) MustNewServer(env NetStackServerFactoryEnv, stack *netem.UNetStack) NetStackServer
MustNewServer implements NetStackServerFactory.
type HTTPCleartextServerFactory ¶ added in v3.19.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 v3.19.0
func (f *HTTPCleartextServerFactory) MustNewServer(env NetStackServerFactoryEnv, stack *netem.UNetStack) NetStackServer
MustNewServer implements NetStackServerFactory.
type HTTPHandlerFactory ¶ added in v3.19.0
type HTTPHandlerFactory interface {
NewHandler(env NetStackServerFactoryEnv, stack *netem.UNetStack) http.Handler
}
HTTPHandlerFactory constructs an http.Handler.
func BlockpageHandlerFactory ¶ added in v3.19.0
func BlockpageHandlerFactory() HTTPHandlerFactory
BlockpageHandlerFactory returns a blockpage regardless of the incoming domain.
func ExampleWebPageHandlerFactory ¶ added in v3.19.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 v3.19.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 v3.19.0
type HTTPHandlerFactoryFunc func(env NetStackServerFactoryEnv, stack *netem.UNetStack) http.Handler
HTTPHandlerFactoryFunc allows a func to become an HTTPHandlerFactory.
func (HTTPHandlerFactoryFunc) NewHandler ¶ added in v3.19.0
func (fx HTTPHandlerFactoryFunc) NewHandler(env NetStackServerFactoryEnv, stack *netem.UNetStack) http.Handler
NewHandler implements HTTPHandlerFactory.
type HTTPSecureServerFactory ¶ added in v3.19.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 v3.19.0
func (f *HTTPSecureServerFactory) MustNewServer(env NetStackServerFactoryEnv, stack *netem.UNetStack) NetStackServer
MustNewServer implements NetStackServerFactory.
type NetStackServer ¶ added in v3.19.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 v3.19.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 v3.19.0
func NewTCPEchoServerFactory(logger model.Logger, ports ...uint16) NetStackServerFactory
NewTCPEchoServerFactory is a NetStackServerFactory for the TCP echo service.
func NewTLSProxyServerFactory ¶ added in v3.19.0
func NewTLSProxyServerFactory(logger model.Logger, ports ...uint16) NetStackServerFactory
NewTLSProxyServerFactory is a NetStackServerFactory for the TCP echo service.
type NetStackServerFactoryEnv ¶ added in v3.19.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 v3.19.0
type OOAPIHandler struct{}
OOAPIHandler is an http.Handler implementing the OONI API.
func (*OOAPIHandler) ServeHTTP ¶ added in v3.19.0
func (p *OOAPIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP implements http.Handler.
type OOAPIHandlerFactory ¶ added in v3.19.0
type OOAPIHandlerFactory struct{}
OOAPIHandlerFactory is a [QAEnvHTTPHandlerFactory] that creates OOAPIHandler instances.
func (*OOAPIHandlerFactory) NewHandler ¶ added in v3.19.0
func (*OOAPIHandlerFactory) NewHandler(env NetStackServerFactoryEnv, stack *netem.UNetStack) http.Handler
NewHandler implements QAEnvHTTPHandlerFactory.
type OOHelperDFactory ¶ added in v3.19.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 v3.19.0
func (f *OOHelperDFactory) NewHandler(env NetStackServerFactoryEnv, unet *netem.UNetStack) http.Handler
NewHandler implements QAEnvHTTPHandlerFactory.NewHandler.
type QAEnv ¶ added in v3.19.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 v3.19.0
func MustNewQAEnv(options ...QAEnvOption) *QAEnv
MustNewQAEnv creates a new QAEnv. This function PANICs on failure.
func MustNewScenario ¶ added in v3.19.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 v3.19.0
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) DPIEngine ¶ added in v3.19.0
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 v3.19.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 v3.19.0
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 v3.19.0
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 v3.19.0
Logger is the model.Logger configured for this *QAEnv,
func (*QAEnv) OtherResolversConfig ¶ added in v3.19.0
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 v3.19.0
type QAEnvOption func(config *qaEnvConfig)
QAEnvOption is an option to modify [NewQAEnv] default behavior.
func QAEnvOptionClientAddress ¶ added in v3.19.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 v3.19.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 v3.19.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 v3.19.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:
- [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 v3.19.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.