package measurex // // Resolver // // Wrappers for Resolver to store events into a WritableDB. // import ( "context" "strings" "time" "github.com/ooni/probe-cli/v3/internal/model" "github.com/ooni/probe-cli/v3/internal/netxlite" "github.com/ooni/probe-cli/v3/internal/tracex" ) // WrapResolver creates a new Resolver that saves events into the WritableDB. func (mx *Measurer) WrapResolver(db WritableDB, r model.Resolver) model.Resolver { return WrapResolver(mx.Begin, db, r) } // WrapResolver wraps a resolver. func WrapResolver(begin time.Time, db WritableDB, r model.Resolver) model.Resolver { return &resolverDB{Resolver: r, db: db, begin: begin} } // NewResolverSystem creates a system resolver and then wraps // it using the WrapResolver function. func (mx *Measurer) NewResolverSystem(db WritableDB, logger model.Logger) model.Resolver { return mx.WrapResolver(db, netxlite.NewStdlibResolver(logger)) } // NewResolverUDP is a convenience factory for creating a Resolver // using UDP that saves measurements into the DB. // // Arguments: // // - db is where to save events; // // - logger is the logger; // // - address is the resolver address (e.g., "1.1.1.1:53"). func (mx *Measurer) NewResolverUDP(db WritableDB, logger model.Logger, address string) model.Resolver { return mx.WrapResolver(db, netxlite.WrapResolver( logger, netxlite.NewUnwrappedSerialResolver( mx.WrapDNSXRoundTripper(db, netxlite.NewUnwrappedDNSOverUDPTransport( mx.NewDialerWithSystemResolver(db, logger), address, )))), ) } type resolverDB struct { model.Resolver begin time.Time db WritableDB } // DNSLookupEvent contains the results of a DNS lookup. type DNSLookupEvent struct { Network string Failure *string Domain string QueryType string Address string Finished float64 Started float64 Oddity Oddity A []string AAAA []string ALPN []string } // SupportsHTTP3 returns true if this query is for HTTPS and // the answer contains an ALPN for "h3" func (ev *DNSLookupEvent) SupportsHTTP3() bool { if ev.QueryType != "HTTPS" { return false } for _, alpn := range ev.ALPN { if alpn == "h3" { return true } } return false } // Addrs returns all the IPv4/IPv6 addresses func (ev *DNSLookupEvent) Addrs() (out []string) { out = append(out, ev.A...) out = append(out, ev.AAAA...) return } func (r *resolverDB) LookupHost(ctx context.Context, domain string) ([]string, error) { started := time.Since(r.begin).Seconds() addrs, err := r.Resolver.LookupHost(ctx, domain) finished := time.Since(r.begin).Seconds() r.saveLookupResults(domain, started, finished, err, addrs, "A") r.saveLookupResults(domain, started, finished, err, addrs, "AAAA") return addrs, err } func (r *resolverDB) saveLookupResults(domain string, started, finished float64, err error, addrs []string, qtype string) { ev := &DNSLookupEvent{ Network: tracex.ResolverNetworkAdaptNames(r.Resolver.Network()), Address: r.Resolver.Address(), Failure: NewFailure(err), Domain: domain, QueryType: qtype, Finished: finished, Started: started, } for _, addr := range addrs { if qtype == "A" && !strings.Contains(addr, ":") { ev.A = append(ev.A, addr) continue } if qtype == "AAAA" && strings.Contains(addr, ":") { ev.AAAA = append(ev.AAAA, addr) continue } } switch qtype { case "A": ev.Oddity = r.computeOddityLookupHost(ev.A, err) case "AAAA": ev.Oddity = r.computeOddityLookupHost(ev.AAAA, err) } r.db.InsertIntoLookupHost(ev) } func (r *resolverDB) computeOddityLookupHost(addrs []string, err error) Oddity { if err != nil { switch err.Error() { case netxlite.FailureGenericTimeoutError: return OddityDNSLookupTimeout case netxlite.FailureDNSNXDOMAINError: return OddityDNSLookupNXDOMAIN case netxlite.FailureDNSRefusedError: return OddityDNSLookupRefused default: return OddityDNSLookupOther } } for _, addr := range addrs { if netxlite.IsBogon(addr) { return OddityDNSLookupBogon } } return "" } func (r *resolverDB) LookupHTTPS(ctx context.Context, domain string) (*model.HTTPSSvc, error) { started := time.Since(r.begin).Seconds() https, err := r.Resolver.LookupHTTPS(ctx, domain) finished := time.Since(r.begin).Seconds() ev := &DNSLookupEvent{ Network: tracex.ResolverNetworkAdaptNames(r.Resolver.Network()), Address: r.Resolver.Address(), Domain: domain, QueryType: "HTTPS", Started: started, Finished: finished, Failure: NewFailure(err), Oddity: Oddity(r.computeOddityHTTPSSvc(https, err)), } if err == nil { ev.A = append(ev.A, https.IPv4...) ev.AAAA = append(ev.AAAA, https.IPv6...) ev.ALPN = append(ev.ALPN, https.ALPN...) } r.db.InsertIntoLookupHTTPSSvc(ev) return https, err } func (r *resolverDB) computeOddityHTTPSSvc(https *model.HTTPSSvc, err error) Oddity { if err != nil { return r.computeOddityLookupHost(nil, err) } var addrs []string addrs = append(addrs, https.IPv4...) addrs = append(addrs, https.IPv6...) return r.computeOddityLookupHost(addrs, nil) }