diff --git a/go.mod b/go.mod index 4303b0a88..24d4cf9e6 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/miekg/dns v1.1.57 github.com/mitchellh/go-wordwrap v1.0.1 github.com/montanaflynn/stats v0.7.1 - github.com/ooni/netem v0.0.0-20240205182847-14e4ce92d41e + github.com/ooni/netem v0.0.0-20240208095707-608dcbcd82b8 github.com/ooni/oocrypto v0.5.7 github.com/ooni/oohttp v0.6.7 github.com/ooni/probe-assets v0.21.0 diff --git a/go.sum b/go.sum index 499386518..7bba329fe 100644 --- a/go.sum +++ b/go.sum @@ -356,8 +356,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= -github.com/ooni/netem v0.0.0-20240205182847-14e4ce92d41e h1:OwDVOPs8NBLb1yEZXnn7rbfbBQJVOpIgEZsqq6QpqG8= -github.com/ooni/netem v0.0.0-20240205182847-14e4ce92d41e/go.mod h1:b/wAvTR5n92Vk2b0SBmuMU0xO4ZGVrsXtU7zjTby7vw= +github.com/ooni/netem v0.0.0-20240208095707-608dcbcd82b8 h1:kJ2wn19lIP/y9ng85BbFRdWKHK6Er116Bbt5uhqHVD4= +github.com/ooni/netem v0.0.0-20240208095707-608dcbcd82b8/go.mod h1:b/wAvTR5n92Vk2b0SBmuMU0xO4ZGVrsXtU7zjTby7vw= github.com/ooni/oocrypto v0.5.7 h1:QEb1KTh5gZ9s1IQjk7rNF076YVwit+2sDKNbo39IEa8= github.com/ooni/oocrypto v0.5.7/go.mod h1:HjEQ5pQBl6btcWgAsKKq1tFo8CfBrZu63C/vPAUGIDk= github.com/ooni/oohttp v0.6.7 h1:wmCjx9+gzx7p1xc/kMAmgXSgXKu7G8CAmil4Zii3g10= diff --git a/internal/experiment/webconnectivitylte/dnswhoami.go b/internal/experiment/webconnectivitylte/dnswhoami.go index f348c9c49..e03039e27 100644 --- a/internal/experiment/webconnectivitylte/dnswhoami.go +++ b/internal/experiment/webconnectivitylte/dnswhoami.go @@ -1,8 +1,9 @@ package webconnectivitylte import ( + "github.com/ooni/probe-cli/v3/internal/model" "github.com/ooni/probe-cli/v3/internal/webconnectivityalgo" ) // DNSWhoamiSingleton is the DNSWhoamiService singleton. -var DNSWhoamiSingleton = webconnectivityalgo.NewDNSWhoamiService() +var DNSWhoamiSingleton = webconnectivityalgo.NewDNSWhoamiService(model.DiscardLogger) diff --git a/internal/webconnectivityalgo/dnswhoami.go b/internal/webconnectivityalgo/dnswhoami.go index 9490bb76c..084eadc5c 100644 --- a/internal/webconnectivityalgo/dnswhoami.go +++ b/internal/webconnectivityalgo/dnswhoami.go @@ -30,27 +30,41 @@ type DNSWhoamiInfoEntry struct { // TODO(bassosimone): consider factoring this code and keeping state // on disk rather than on memory. +// TODO(bassosimone): we should periodically invalidate the whoami lookup results. + // DNSWhoamiService is a service that performs DNS whoami lookups. // // The zero value of this struct is invalid. Please, construct using // the [NewDNSWhoamiService] factory function. type DNSWhoamiService struct { + // logger is the logger + logger model.Logger + // mu provides mutual exclusion mu *sync.Mutex + // netx is the underlying network we're using + netx *netxlite.Netx + // systemv4 contains systemv4 results systemv4 []DNSWhoamiInfoEntry // udpv4 contains udpv4 results udpv4 map[string][]DNSWhoamiInfoEntry + + // whoamiDomain is the whoamiDomain to query for. + whoamiDomain string } // NewDNSWhoamiService constructs a new [*DNSWhoamiService]. -func NewDNSWhoamiService() *DNSWhoamiService { +func NewDNSWhoamiService(logger model.Logger) *DNSWhoamiService { return &DNSWhoamiService{ - mu: &sync.Mutex{}, - systemv4: []DNSWhoamiInfoEntry{}, - udpv4: map[string][]DNSWhoamiInfoEntry{}, + logger: logger, + mu: &sync.Mutex{}, + netx: &netxlite.Netx{Underlying: nil}, + systemv4: []DNSWhoamiInfoEntry{}, + udpv4: map[string][]DNSWhoamiInfoEntry{}, + whoamiDomain: "whoami.v4.powerdns.org", } } @@ -61,9 +75,8 @@ func (svc *DNSWhoamiService) SystemV4(ctx context.Context) ([]DNSWhoamiInfoEntry if len(svc.systemv4) <= 0 { ctx, cancel := context.WithTimeout(ctx, 4*time.Second) defer cancel() - netx := &netxlite.Netx{} - reso := netx.NewStdlibResolver(model.DiscardLogger) - addrs, err := reso.LookupHost(ctx, "whoami.v4.powerdns.org") + reso := svc.netx.NewStdlibResolver(svc.logger) + addrs, err := reso.LookupHost(ctx, svc.whoamiDomain) if err != nil || len(addrs) < 1 { return nil, false } @@ -81,12 +94,11 @@ func (svc *DNSWhoamiService) UDPv4(ctx context.Context, address string) ([]DNSWh if len(svc.udpv4[address]) <= 0 { ctx, cancel := context.WithTimeout(ctx, 4*time.Second) defer cancel() - netx := &netxlite.Netx{} - dialer := netxlite.NewDialerWithStdlibResolver(model.DiscardLogger) - reso := netx.NewParallelUDPResolver(model.DiscardLogger, dialer, address) + dialer := svc.netx.NewDialerWithResolver(svc.logger, svc.netx.NewStdlibResolver(svc.logger)) + reso := svc.netx.NewParallelUDPResolver(svc.logger, dialer, address) // TODO(bassosimone): this should actually only send an A query. Sending an AAAA // query is _way_ unnecessary since we know that only A is going to work. - addrs, err := reso.LookupHost(ctx, "whoami.v4.powerdns.org") + addrs, err := reso.LookupHost(ctx, svc.whoamiDomain) if err != nil || len(addrs) < 1 { return nil, false } diff --git a/internal/webconnectivityalgo/dnswhoami_test.go b/internal/webconnectivityalgo/dnswhoami_test.go new file mode 100644 index 000000000..0de0c8b01 --- /dev/null +++ b/internal/webconnectivityalgo/dnswhoami_test.go @@ -0,0 +1,97 @@ +package webconnectivityalgo + +import ( + "context" + "testing" + + "github.com/apex/log" + "github.com/google/go-cmp/cmp" + "github.com/ooni/probe-cli/v3/internal/netemx" + "github.com/ooni/probe-cli/v3/internal/netxlite" +) + +func TestDNSWhoamiService(t *testing.T) { + // expectation describes expectations + type expectation struct { + Entries []DNSWhoamiInfoEntry + Good bool + } + + // testcase is a test case defined by this function + type testcase struct { + // name is the test case name + name string + + // domain is the domain to query for + domain string + + // expectations contains the expecations + expectations []expectation + } + + cases := []testcase{{ + name: "common case using the default domain", + domain: "", // forces using default + expectations: []expectation{{ + Entries: []DNSWhoamiInfoEntry{{ + Address: netemx.DefaultClientAddress, + }}, + Good: true, + }, { + Entries: []DNSWhoamiInfoEntry{{ + Address: netemx.DefaultClientAddress, + }}, + Good: true, + }}, + }, { + name: "error case using another domain", + domain: "example.xyz", + expectations: []expectation{{ + Entries: nil, + Good: false, + }, { + Entries: nil, + Good: false, + }}, + }} + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + // create testing scenario + env := netemx.MustNewScenario(netemx.InternetScenario) + defer env.Close() + + // create the service + svc := NewDNSWhoamiService(log.Log) + + // override fields + svc.netx = &netxlite.Netx{Underlying: &netxlite.NetemUnderlyingNetworkAdapter{UNet: env.ClientStack}} + if tc.domain != "" { + svc.whoamiDomain = tc.domain + } + + // prepare collecting results + var results []expectation + + // run with the system resolver + sysEntries, sysGood := svc.SystemV4(context.Background()) + results = append(results, expectation{ + Entries: sysEntries, + Good: sysGood, + }) + + // run with an UDP resolver + udpEntries, udpGood := svc.UDPv4(context.Background(), "8.8.8.8:53") + results = append(results, expectation{ + Entries: udpEntries, + Good: udpGood, + }) + + // check whether we've got what we expected + if diff := cmp.Diff(tc.expectations, results); diff != "" { + t.Fatal(diff) + } + }) + } + +}