From 099ad990719e875306f184cacec9a5a586081c9e Mon Sep 17 00:00:00 2001 From: Simone Basso Date: Fri, 1 Nov 2019 00:01:59 +0100 Subject: [PATCH] porcelain: Implement automatic DNS fallback Xref: - https://github.com/ooni/probe-engine/issues/87 - https://github.com/ooni/probe-engine/issues/88 --- x/porcelain/porcelain.go | 72 +++++++++++++++++++++++++++++++++-- x/porcelain/porcelain_test.go | 18 +++++++++ 2 files changed, 86 insertions(+), 4 deletions(-) diff --git a/x/porcelain/porcelain.go b/x/porcelain/porcelain.go index ba5b42b..8e08ea8 100644 --- a/x/porcelain/porcelain.go +++ b/x/porcelain/porcelain.go @@ -9,11 +9,13 @@ package porcelain import ( "context" "io/ioutil" + "math/rand" "net/http" "sync" "sync/atomic" "time" + "github.com/m-lab/go/rtx" "github.com/ooni/netx" "github.com/ooni/netx/handlers" "github.com/ooni/netx/httpx" @@ -88,6 +90,62 @@ func (r *Results) collect( } } +type dnsFallback struct { + network, address string +} + +func configureDNS(seed int64, network, address string) (model.DNSResolver, error) { + resolver, err := netx.NewResolver(handlers.NoHandler, network, address) + if err != nil { + return nil, err + } + fallbacks := []dnsFallback{ + dnsFallback{ + network: "doh", + address: "https://cloudflare-dns.com/dns-query", + }, + dnsFallback{ + network: "doh", + address: "https://dns.google/dns-query", + }, + dnsFallback{ + network: "dot", + address: "8.8.8.8:853", + }, + dnsFallback{ + network: "dot", + address: "8.8.4.4:853", + }, + dnsFallback{ + network: "dot", + address: "1.1.1.1:853", + }, + dnsFallback{ + network: "dot", + address: "9.9.9.9:853", + }, + } + random := rand.New(rand.NewSource(seed)) + random.Shuffle(len(fallbacks), func(i, j int) { + fallbacks[i], fallbacks[j] = fallbacks[j], fallbacks[i] + }) + var configured int + for i := 0; configured < 2 && i < len(fallbacks); i++ { + if fallbacks[i].network == network { + continue + } + var fallback model.DNSResolver + fallback, err = netx.NewResolver( + handlers.NoHandler, fallbacks[i].network, + fallbacks[i].address, + ) + rtx.PanicOnError(err, "porcelain: invalid fallbacks table") + resolver = netx.ChainResolvers(resolver, fallback) + configured++ + } + return resolver, nil +} + // DNSLookupConfig contains DNSLookup settings. type DNSLookupConfig struct { Handler model.Handler @@ -173,12 +231,15 @@ func HTTPDo( } ctx = model.WithMeasurementRoot(ctx, root) client := httpx.NewClient(handlers.NoHandler) - err := client.ConfigureDNS( - config.DNSServerNetwork, config.DNSServerAddress, + resolver, err := configureDNS( + time.Now().UnixNano(), + config.DNSServerNetwork, + config.DNSServerAddress, ) if err != nil { return nil, err } + client.SetResolver(resolver) // TODO(bassosimone): implement sending body req, err := http.NewRequest(config.Method, config.URL, nil) if err != nil { @@ -246,12 +307,15 @@ func TLSConnect( ctx = model.WithMeasurementRoot(ctx, root) dialer := netx.NewDialer(handlers.NoHandler) // TODO(bassosimone): tell dialer to use specific CA bundle? - err := dialer.ConfigureDNS( - config.DNSServerNetwork, config.DNSServerAddress, + resolver, err := configureDNS( + time.Now().UnixNano(), + config.DNSServerNetwork, + config.DNSServerAddress, ) if err != nil { return nil, err } + dialer.SetResolver(resolver) // TODO(bassosimone): can this call really fail? dialer.ForceSpecificSNI(config.SNI) var ( diff --git a/x/porcelain/porcelain_test.go b/x/porcelain/porcelain_test.go index 1a4788b..26a4045 100644 --- a/x/porcelain/porcelain_test.go +++ b/x/porcelain/porcelain_test.go @@ -181,6 +181,24 @@ func TestIntegrationTLSConnectGood(t *testing.T) { } } +func TestIntegrationTLSConnectGoodWithDoT(t *testing.T) { + ctx := context.Background() + results, err := TLSConnect(ctx, TLSConnectConfig{ + Address: "ooni.io:443", + DNSServerNetwork: "dot", + DNSServerAddress: "9.9.9.9:853", + }) + if err != nil { + t.Fatal(err) + } + if results.Error != nil { + t.Fatal(results.Error) + } + if results.TestKeys.Scoreboard == nil { + t.Fatal("no scoreboard?!") + } +} + func TestIntegrationTLSConnectCancellation(t *testing.T) { ctx, cancel := context.WithTimeout( context.Background(), time.Microsecond,