From 7109b14ad6e6a94ff61f0d772dd37959f79aa739 Mon Sep 17 00:00:00 2001 From: Simone Basso Date: Mon, 4 Sep 2023 15:05:12 +0200 Subject: [PATCH] fix: avoid using 130.192.91.x for most netemx addresses (#1219) As we learned in https://github.com/ooni/probe-cli/pull/1216, using the 130.192.91.x namespace for every IP address in netemx breaks mapping domain names to IP addresess in Web Connectivity. No IP address would ever, in fact, be inconsistent, because they all belong to AS137. Initially, I thought about overriding the code that maps IP addresses to ASNs, to provide a custom implementation. But then I realized it was a more thorough test to use the default implementation (relying on maxminddb files) and using the correct IP addresses in the correct address space. My original thought for using 130.192.91.x addresses was that they were not the right addresses for the domains we're testing, thus, in the event in which netem was not WAI, all tests would have failed. However, we have many tests checking that netem is WAI already, so probably I was being excessively paranoid. As a result, this patch modifies the code to use the correct addresses. We're still using some 130.192.91.x addresses where it makes sense to do so (user's IP address and default user's resolver). Part of https://github.com/ooni/probe/issues/1803. ## Checklist - [x] I have read the [contribution guidelines](https://github.com/ooni/probe-cli/blob/master/CONTRIBUTING.md) - [x] reference issue for this pull request: see above - [x] if you changed anything related to how experiments work and you need to reflect these changes in the ooni/spec repository, please link to the related ooni/spec pull request: N/A - [x] if you changed code inside an experiment, make sure you bump its version number: N/A --- .../experiment/webconnectivitylte/qa_test.go | 4 +- .../webconnectivityqa/tcpblocking.go | 2 +- .../webconnectivityqa/tcpblocking_test.go | 4 +- internal/netemx/android_test.go | 30 ++++ internal/netemx/example_test.go | 44 +++--- internal/netemx/oohelperd_test.go | 38 ++---- internal/netemx/qaenv.go | 18 +-- internal/netemx/scenario.go | 129 ++++++++++++------ 8 files changed, 158 insertions(+), 111 deletions(-) create mode 100644 internal/netemx/android_test.go diff --git a/internal/experiment/webconnectivitylte/qa_test.go b/internal/experiment/webconnectivitylte/qa_test.go index 83da0cc38a..35be2419a6 100644 --- a/internal/experiment/webconnectivitylte/qa_test.go +++ b/internal/experiment/webconnectivitylte/qa_test.go @@ -15,9 +15,7 @@ func TestQA(t *testing.T) { t.Skip("this nettest cannot run on Web Connectivity LTE") } measurer := NewExperimentMeasurer(&Config{ - // We override the resolver used by default because the QA environment uses - // only IP addresses in the 130.192.91.x namespace for extra robustness in case - // netem is not working as intended and we're using the real network. + // We override the resolver to use the one we should be using with netem DNSOverUDPResolver: net.JoinHostPort(netemx.QAEnvDefaultUncensoredResolverAddress, "53"), }) if err := webconnectivityqa.RunTestCase(measurer, tc); err != nil { diff --git a/internal/experiment/webconnectivityqa/tcpblocking.go b/internal/experiment/webconnectivityqa/tcpblocking.go index f2df02d96e..fd512ccd13 100644 --- a/internal/experiment/webconnectivityqa/tcpblocking.go +++ b/internal/experiment/webconnectivityqa/tcpblocking.go @@ -17,7 +17,7 @@ func tcpBlockingConnectTimeout() *TestCase { Configure: func(env *netemx.QAEnv) { env.DPIEngine().AddRule(&netem.DPIDropTrafficForServerEndpoint{ Logger: log.Log, - ServerIPAddress: "130.192.91.7", // www.example.com + ServerIPAddress: netemx.InternetScenarioAddressWwwExampleCom, ServerPort: 443, ServerProtocol: layers.IPProtocolTCP, }) diff --git a/internal/experiment/webconnectivityqa/tcpblocking_test.go b/internal/experiment/webconnectivityqa/tcpblocking_test.go index 55a713a940..33d8d61771 100644 --- a/internal/experiment/webconnectivityqa/tcpblocking_test.go +++ b/internal/experiment/webconnectivityqa/tcpblocking_test.go @@ -2,6 +2,7 @@ package webconnectivityqa import ( "context" + "net" "testing" "github.com/apex/log" @@ -16,7 +17,8 @@ func TestTCPBlockingConnectTimeout(t *testing.T) { env.Do(func() { dialer := netxlite.NewDialerWithoutResolver(log.Log) - conn, err := dialer.DialContext(context.Background(), "tcp", "130.192.91.7:443") + endpoint := net.JoinHostPort(netemx.InternetScenarioAddressWwwExampleCom, "443") + conn, err := dialer.DialContext(context.Background(), "tcp", endpoint) if err == nil || err.Error() != netxlite.FailureGenericTimeoutError { t.Fatal("unexpected error", err) } diff --git a/internal/netemx/android_test.go b/internal/netemx/android_test.go new file mode 100644 index 0000000000..1bab281463 --- /dev/null +++ b/internal/netemx/android_test.go @@ -0,0 +1,30 @@ +package netemx + +import ( + "context" + "errors" + "testing" + + "github.com/apex/log" + "github.com/ooni/probe-cli/v3/internal/netxlite" +) + +// Make sure we can emulate Android's getaddrinfo behavior. +func TestEmulateAndroidGetaddrinfo(t *testing.T) { + env := MustNewScenario(InternetScenario) + defer env.Close() + + env.EmulateAndroidGetaddrinfo(true) + defer env.EmulateAndroidGetaddrinfo(false) + + env.Do(func() { + reso := netxlite.NewStdlibResolver(log.Log) + addrs, err := reso.LookupHost(context.Background(), "www.nonexistent.xyz") + if !errors.Is(err, netxlite.ErrAndroidDNSCacheNoData) { + t.Fatal("unexpected error") + } + if len(addrs) != 0 { + t.Fatal("expected zero-length addresses") + } + }) +} diff --git a/internal/netemx/example_test.go b/internal/netemx/example_test.go index 036a006093..9b6e5f8a54 100644 --- a/internal/netemx/example_test.go +++ b/internal/netemx/example_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "net" "net/http" "github.com/apex/log" @@ -14,26 +15,15 @@ import ( "github.com/ooni/probe-cli/v3/internal/runtimex" ) -// exampleExampleComAddress is the address of example.com -const exampleExampleComAddress = "93.184.216.34" - -// exampleClientAddress is the address used by the client -const exampleClientAddress = "130.192.91.211" - -// exampleISPResolverAddress is the address used by the resolver. -const exampleISPResolverAddress = "130.192.3.24" - -// exampleCensoredAddress is a censored IP address. -const exampleCensoredAddress = "10.10.34.35" - // 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.QAEnvOptionDNSOverUDPResolvers("8.8.4.4", "9.9.9.9"), - netemx.QAEnvOptionClientAddress(exampleClientAddress), - netemx.QAEnvOptionISPResolverAddress(exampleISPResolverAddress), - netemx.QAEnvOptionHTTPServer(exampleExampleComAddress, netemx.ExampleWebPageHandlerFactory()), + netemx.QAEnvOptionClientAddress(netemx.QAEnvDefaultClientAddress), + netemx.QAEnvOptionISPResolverAddress(netemx.QAEnvDefaultISPResolverAddress), + netemx.QAEnvOptionHTTPServer( + netemx.InternetScenarioAddressWwwExampleCom, netemx.ExampleWebPageHandlerFactory()), netemx.QAEnvOptionLogger(log.Log), ) } @@ -44,7 +34,7 @@ func exampleAddRecordToAllResolvers(env *netemx.QAEnv) { env.AddRecordToAllResolvers( "example.com", "", // CNAME - exampleExampleComAddress, + netemx.InternetScenarioAddressWwwExampleCom, ) } @@ -103,14 +93,14 @@ func Example_resolverConfig() { env.OtherResolversConfig().AddRecord( "example.com", "", // CNAME - exampleExampleComAddress, + netemx.InternetScenarioAddressWwwExampleCom, ) // create a censored configuration for getaddrinfo env.ISPResolverConfig().AddRecord( "example.com", "", - exampleCensoredAddress, + "10.10.34.35", ) // collect the overall results @@ -226,7 +216,7 @@ func Example_dohWithInternetScenario() { }) // Output: - // [130.192.91.7] + // [93.184.216.34] } // This example shows how the [InternetScenario] defines DNS-over-UDP servers. @@ -236,8 +226,8 @@ func Example_dnsOverUDPWithInternetScenario() { env.Do(func() { resolvers := []string{ - "130.192.91.3:53", // possibly censored and used by the client's getaddrinfo - "130.192.91.4:53", // uncensored and used by the servers + net.JoinHostPort(netemx.QAEnvDefaultISPResolverAddress, "53"), + net.JoinHostPort(netemx.QAEnvDefaultUncensoredResolverAddress, "53"), } for _, endpoint := range resolvers { @@ -255,8 +245,8 @@ func Example_dnsOverUDPWithInternetScenario() { }) // Output: - // [130.192.91.7] - // [130.192.91.7] + // [93.184.216.34] + // [93.184.216.34] } // This example shows how the [InternetScenario] supports calling getaddrinfo. @@ -277,7 +267,7 @@ func Example_getaddrinfoWithInternetScenario() { }) // Output: - // [130.192.91.7] + // [93.184.216.34] } // This example shows how the [InternetScenario] defines an example.com-like webserver. @@ -358,7 +348,7 @@ func Example_oohelperdWithInternetScenario() { env.Do(func() { client := netxlite.NewHTTPClientStdlib(log.Log) - thRequest := []byte(`{"http_request": "https://www.example.com/","http_request_headers":{},"tcp_connect":["130.192.91.7"]}`) + 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 { @@ -381,7 +371,7 @@ func Example_oohelperdWithInternetScenario() { }) // Output: - // {"tcp_connect":{"130.192.91.7:443":{"status":true,"failure":null}},"tls_handshake":{"130.192.91.7:443":{"server_name":"www.example.com","status":true,"failure":null}},"quic_handshake":{},"http_request":{"body_length":194,"discovered_h3_endpoint":"www.example.com:443","failure":null,"title":"Default Web Page","headers":{"Alt-Svc":"h3=\":443\"","Content-Length":"194","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":["130.192.91.7"]},"ip_info":{"130.192.91.7":{"asn":137,"flags":10}}} + // {"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":194,"discovered_h3_endpoint":"www.example.com:443","failure":null,"title":"Default Web Page","headers":{"Alt-Svc":"h3=\":443\"","Content-Length":"194","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}}} } // This example shows how the [InternetScenario] defines a GeoIP service like Ubuntu's one. @@ -411,5 +401,5 @@ func Example_ubuntuGeoIPWithInternetScenario() { }) // Output: - // 130.192.91.2 + // 130.192.91.211 } diff --git a/internal/netemx/oohelperd_test.go b/internal/netemx/oohelperd_test.go index d7ab2b9339..ddde757abb 100644 --- a/internal/netemx/oohelperd_test.go +++ b/internal/netemx/oohelperd_test.go @@ -15,32 +15,18 @@ import ( ) func TestOOHelperDHandler(t *testing.T) { - // we use completely unrelated IP addresses such that, in the unlikely event in - // which we're not using netem, the test is poised to fail. - // - // (These were two IP addresses assigned to me when I was at polito.it.) - const ( - zeroThOONIOrgAddr = "130.192.91.211" - exampleComAddr = "130.192.91.231" - ) - - env := MustNewQAEnv( - QAEnvOptionHTTPServer(zeroThOONIOrgAddr, &OOHelperDFactory{}), - QAEnvOptionHTTPServer(exampleComAddr, ExampleWebPageHandlerFactory()), - ) - env.AddRecordToAllResolvers("example.com", "web01.example.com", exampleComAddr) - env.AddRecordToAllResolvers("0.th.ooni.org", "0-th.ooni.org", zeroThOONIOrgAddr) + env := MustNewScenario(InternetScenario) defer env.Close() env.Do(func() { thReq := &model.THRequest{ - HTTPRequest: "https://example.com/", + HTTPRequest: "https://www.example.com/", HTTPRequestHeaders: map[string][]string{ "accept": {model.HTTPHeaderAccept}, "accept-language": {model.HTTPHeaderAcceptLanguage}, "user-agent": {model.HTTPHeaderUserAgent}, }, - TCPConnect: []string{exampleComAddr}, + TCPConnect: []string{InternetScenarioAddressWwwExampleCom}, XQUICEnabled: true, } thReqRaw := runtimex.Try1(json.Marshal(thReq)) @@ -76,28 +62,28 @@ func TestOOHelperDHandler(t *testing.T) { expectedTHResp := &model.THResponse{ TCPConnect: map[string]model.THTCPConnectResult{ - "130.192.91.231:443": { + "93.184.216.34:443": { Status: true, Failure: nil, }, }, TLSHandshake: map[string]model.THTLSHandshakeResult{ - "130.192.91.231:443": { - ServerName: "example.com", + "93.184.216.34:443": { + ServerName: "www.example.com", Status: true, Failure: nil, }, }, QUICHandshake: map[string]model.THTLSHandshakeResult{ - "130.192.91.231:443": { - ServerName: "example.com", + "93.184.216.34:443": { + ServerName: "www.example.com", Status: true, Failure: nil, }, }, HTTPRequest: model.THHTTPRequestResult{ BodyLength: 194, - DiscoveredH3Endpoint: "example.com:443", + DiscoveredH3Endpoint: "www.example.com:443", Failure: nil, Title: "Default Web Page", Headers: map[string]string{ @@ -122,12 +108,12 @@ func TestOOHelperDHandler(t *testing.T) { }, DNS: model.THDNSResult{ Failure: nil, - Addrs: []string{"130.192.91.231"}, + Addrs: []string{"93.184.216.34"}, ASNs: nil, }, IPInfo: map[string]*model.THIPInfo{ - "130.192.91.231": { - ASN: 137, + "93.184.216.34": { + ASN: 15133, Flags: 10, }, }, diff --git a/internal/netemx/qaenv.go b/internal/netemx/qaenv.go index 2c6dce9bf7..103e7cfd29 100644 --- a/internal/netemx/qaenv.go +++ b/internal/netemx/qaenv.go @@ -21,25 +21,13 @@ import ( ) // QAEnvDefaultClientAddress is the default client IP address. -// -// The 130.192.91.x address space belongs to polito.it and is not used for hosting a DNS server, which -// gives us additional robustness in case netem is not working as intended. (We have several -// tests making sure of that, but additional robustness won't hurt.) -const QAEnvDefaultClientAddress = "130.192.91.2" +const QAEnvDefaultClientAddress = "130.192.91.211" // QAEnvDefaultISPResolverAddress is the default IP address of the client ISP resolver. -// -// The 130.192.91.x address space belongs to polito.it and is not used for hosting a DNS server, which -// gives us additional robustness in case netem is not working as intended. (We have several -// tests making sure of that, but additional robustness won't hurt.) -const QAEnvDefaultISPResolverAddress = "130.192.91.3" +const QAEnvDefaultISPResolverAddress = "130.192.3.21" // QAEnvDefaultUncensoredResolverAddress is the default uncensored resolver IP address. -// -// The 130.192.91.x address space belongs to polito.it and is not used for hosting a DNS server, which -// gives us additional robustness in case netem is not working as intended. (We have several -// tests making sure of that, but additional robustness won't hurt.) -const QAEnvDefaultUncensoredResolverAddress = "130.192.91.4" +const QAEnvDefaultUncensoredResolverAddress = "1.1.1.1" type qaEnvConfig struct { // clientAddress is the client IP address to use. diff --git a/internal/netemx/scenario.go b/internal/netemx/scenario.go index fa62c93b91..c2e8f98423 100644 --- a/internal/netemx/scenario.go +++ b/internal/netemx/scenario.go @@ -21,57 +21,110 @@ const ( // ScenarioDomainAddresses describes a domain and address used in a scenario. type ScenarioDomainAddresses struct { - Domain string + // Domain is the related domain name (e.g., api.ooni.io). + Domain string + + // Addresses contains the related IP addresses. Addresses []string - Role uint64 + + // Role is the role for this domain (e.g., ScenarioRoleOONIAPI). + Role uint64 } +const ( + // InternetScenarioAddressApiOONIIo is the IP address we use for api.ooni.io in the [InternetScenario]. + InternetScenarioAddressApiOONIIo = "162.55.247.208" + + // InternetScenarioAddressGeoIPUbuntuCom is the IP address we use for geoip.ubuntu.com in the [InternetScenario]. + InternetScenarioAddressGeoIPUbuntuCom = "185.125.188.132" + + // InternetScenarioAddressWwwExampleCom is the IP address we use for www.example.com in the [InternetScenario]. + InternetScenarioAddressWwwExampleCom = "93.184.216.34" + + // InternetScenarioAddressZeroThOONIOrg is the IP address we use for 0.th.ooni.org in the [InternetScenario]. + InternetScenarioAddressZeroThOONIOrg = "68.183.70.80" + + // InternetScenarioAddressOneThOONIOrg is the IP address we use for 1.th.ooni.org in the [InternetScenario]. + InternetScenarioAddressOneThOONIOrg = "137.184.235.44" + + // InternetScenarioAddressTwoThOONIOrg is the IP address we use for 2.th.ooni.org in the [InternetScenario]. + InternetScenarioAddressTwoThOONIOrg = "178.62.195.24" + + // InternetScenarioAddressThreeThOONIOrg is the IP address we use for 3.th.ooni.org in the [InternetScenario]. + InternetScenarioAddressThreeThOONIOrg = "209.97.183.73" + + // InternetScenarioAddressDNSQuad9Net is the IP address we use for dns.quad9.net in the [InternetScenario]. + InternetScenarioAddressDNSQuad9Net = "9.9.9.9" + + // InternetScenarioAddressMozillaCloudflareDNSCom is the IP address we use for mozilla.cloudflare-dns.com + // in the [InternetScenario]. + InternetScenarioAddressMozillaCloudflareDNSCom = "172.64.41.4" + + // InternetScenarioAddressDNSGoogle is the IP address we use for dns.google in the [InternetScenario]. + InternetScenarioAddressDNSGoogle = "8.8.4.4" +) + // InternetScenario contains the domains and addresses used by [NewInternetScenario]. -// -// Note that the 130.192.91.x address space belongs to polito.it and is not used for hosting -// servers, therefore we're more confident that tests using this scenario will break in bad -// way if for some reason netem is not working as intended. (We have several tests making sure -// of that, but some extra robustness won't hurt.) var InternetScenario = []*ScenarioDomainAddresses{{ - Domain: "api.ooni.io", - Addresses: []string{"130.192.91.5"}, - Role: ScenarioRoleOONIAPI, + Domain: "api.ooni.io", + Addresses: []string{ + InternetScenarioAddressApiOONIIo, + }, + Role: ScenarioRoleOONIAPI, }, { - Domain: "geoip.ubuntu.com", - Addresses: []string{"130.192.91.6"}, - Role: ScenarioRoleUbuntuGeoIP, + Domain: "geoip.ubuntu.com", + Addresses: []string{ + InternetScenarioAddressGeoIPUbuntuCom, + }, + Role: ScenarioRoleUbuntuGeoIP, }, { - Domain: "www.example.com", - Addresses: []string{"130.192.91.7"}, - Role: ScenarioRoleExampleLikeWebServer, + Domain: "www.example.com", + Addresses: []string{ + InternetScenarioAddressWwwExampleCom, + }, + Role: ScenarioRoleExampleLikeWebServer, }, { - Domain: "0.th.ooni.org", - Addresses: []string{"130.192.91.8"}, - Role: ScenarioRoleOONITestHelper, + Domain: "0.th.ooni.org", + Addresses: []string{ + InternetScenarioAddressZeroThOONIOrg, + }, + Role: ScenarioRoleOONITestHelper, }, { - Domain: "1.th.ooni.org", - Addresses: []string{"130.192.91.9"}, - Role: ScenarioRoleOONITestHelper, + Domain: "1.th.ooni.org", + Addresses: []string{ + InternetScenarioAddressOneThOONIOrg, + }, + Role: ScenarioRoleOONITestHelper, }, { - Domain: "2.th.ooni.org", - Addresses: []string{"130.192.91.10"}, - Role: ScenarioRoleOONITestHelper, + Domain: "2.th.ooni.org", + Addresses: []string{ + InternetScenarioAddressTwoThOONIOrg, + }, + Role: ScenarioRoleOONITestHelper, }, { - Domain: "3.th.ooni.org", - Addresses: []string{"130.192.91.11"}, - Role: ScenarioRoleOONITestHelper, + Domain: "3.th.ooni.org", + Addresses: []string{ + InternetScenarioAddressThreeThOONIOrg, + }, + Role: ScenarioRoleOONITestHelper, }, { - Domain: "dns.quad9.net", - Addresses: []string{"130.192.91.12"}, - Role: ScenarioRoleDNSOverHTTPS, + Domain: "dns.quad9.net", + Addresses: []string{ + InternetScenarioAddressDNSQuad9Net, + }, + Role: ScenarioRoleDNSOverHTTPS, }, { - Domain: "mozilla.cloudflare-dns.com", - Addresses: []string{"130.192.91.13"}, - Role: ScenarioRoleDNSOverHTTPS, + Domain: "mozilla.cloudflare-dns.com", + Addresses: []string{ + InternetScenarioAddressMozillaCloudflareDNSCom, + }, + Role: ScenarioRoleDNSOverHTTPS, }, { - Domain: "dns.google", - Addresses: []string{"130.192.91.14"}, - Role: ScenarioRoleDNSOverHTTPS, + Domain: "dns.google", + Addresses: []string{ + InternetScenarioAddressDNSGoogle, + }, + Role: ScenarioRoleDNSOverHTTPS, }} // MustNewScenario constructs a complete testing scenario using the domains and IP @@ -86,7 +139,7 @@ func MustNewScenario(cfg []*ScenarioDomainAddresses) *QAEnv { } // explicitly create the uncensored resolver - opts = append(opts, QAEnvOptionDNSOverUDPResolvers("130.192.91.4")) + opts = append(opts, QAEnvOptionDNSOverUDPResolvers(QAEnvDefaultUncensoredResolverAddress)) // fill options based on the scenario config for _, sad := range cfg {