package resolver import ( "context" "errors" "strings" "github.com/miekg/dns" "github.com/safing/portbase/log" ) // special scopes: // localhost. [RFC6761] - respond with 127.0.0.1 and ::1 to A and AAAA queries, else nxdomain // local. [RFC6762] - resolve if search, else resolve with mdns // 10.in-addr.arpa. [RFC6761] // 16.172.in-addr.arpa. [RFC6761] // 17.172.in-addr.arpa. [RFC6761] // 18.172.in-addr.arpa. [RFC6761] // 19.172.in-addr.arpa. [RFC6761] // 20.172.in-addr.arpa. [RFC6761] // 21.172.in-addr.arpa. [RFC6761] // 22.172.in-addr.arpa. [RFC6761] // 23.172.in-addr.arpa. [RFC6761] // 24.172.in-addr.arpa. [RFC6761] // 25.172.in-addr.arpa. [RFC6761] // 26.172.in-addr.arpa. [RFC6761] // 27.172.in-addr.arpa. [RFC6761] // 28.172.in-addr.arpa. [RFC6761] // 29.172.in-addr.arpa. [RFC6761] // 30.172.in-addr.arpa. [RFC6761] // 31.172.in-addr.arpa. [RFC6761] // 168.192.in-addr.arpa. [RFC6761] // 254.169.in-addr.arpa. [RFC6762] // 8.e.f.ip6.arpa. [RFC6762] // 9.e.f.ip6.arpa. [RFC6762] // a.e.f.ip6.arpa. [RFC6762] // b.e.f.ip6.arpa. [RFC6762] // example. [RFC6761] - resolve if search, else return nxdomain // example.com. [RFC6761] - resolve if search, else return nxdomain // example.net. [RFC6761] - resolve if search, else return nxdomain // example.org. [RFC6761] - resolve if search, else return nxdomain // invalid. [RFC6761] - resolve if search, else return nxdomain // test. [RFC6761] - resolve if search, else return nxdomain // onion. [RFC7686] - resolve if search, else return nxdomain // resolvers: // local // global // mdns var ( // RFC6761 - respond with 127.0.0.1 and ::1 to A and AAAA queries respectively, else nxdomain localhost = ".localhost." // RFC6761 - always respond with nxdomain invalid = ".invalid." // RFC6762 - resolve locally local = ".local." // local reverse dns localReverseScopes = []string{ ".10.in-addr.arpa.", // RFC6761 ".16.172.in-addr.arpa.", // RFC6761 ".17.172.in-addr.arpa.", // RFC6761 ".18.172.in-addr.arpa.", // RFC6761 ".19.172.in-addr.arpa.", // RFC6761 ".20.172.in-addr.arpa.", // RFC6761 ".21.172.in-addr.arpa.", // RFC6761 ".22.172.in-addr.arpa.", // RFC6761 ".23.172.in-addr.arpa.", // RFC6761 ".24.172.in-addr.arpa.", // RFC6761 ".25.172.in-addr.arpa.", // RFC6761 ".26.172.in-addr.arpa.", // RFC6761 ".27.172.in-addr.arpa.", // RFC6761 ".28.172.in-addr.arpa.", // RFC6761 ".29.172.in-addr.arpa.", // RFC6761 ".30.172.in-addr.arpa.", // RFC6761 ".31.172.in-addr.arpa.", // RFC6761 ".168.192.in-addr.arpa.", // RFC6761 ".254.169.in-addr.arpa.", // RFC6762 ".8.e.f.ip6.arpa.", // RFC6762 ".9.e.f.ip6.arpa.", // RFC6762 ".a.e.f.ip6.arpa.", // RFC6762 ".b.e.f.ip6.arpa.", // RFC6762 } // RFC6761 - only resolve locally localTestScopes = []string{ ".example.", ".example.com.", ".example.net.", ".example.org.", ".test.", } // resolve globally - resolving these should be disabled by default specialServiceScopes = []string{ ".onion.", // Tor Hidden Services, RFC7686 ".bit.", // Namecoin, https://www.namecoin.org/ } ) func domainInScope(dotPrefixedFQDN string, scopeList []string) bool { for _, scope := range scopeList { if strings.HasSuffix(dotPrefixedFQDN, scope) { return true } } return false } // GetResolversInScope returns all resolvers that are in scope the resolve the given query and options. func GetResolversInScope(ctx context.Context, q *Query) (selected []*Resolver) { //nolint:gocognit // TODO resolversLock.RLock() defer resolversLock.RUnlock() // resolver selection: // local -> local scopes, mdns // local-inaddr -> local, mdns // global -> local scopes, global // special -> local scopes, local // check local scopes for _, scope := range localScopes { if strings.HasSuffix(q.dotPrefixedFQDN, scope.Domain) { // scoped resolvers for _, resolver := range scope.Resolvers { if err := resolver.checkCompliance(ctx, q); err == nil { selected = append(selected, resolver) } else { log.Tracef("skipping non-compliant resolver: %s", resolver.Server) } } } } // if there was a match with a local scope, stop here if len(selected) > 0 { // add mdns if err := mDNSResolver.checkCompliance(ctx, q); err == nil { selected = append(selected, mDNSResolver) } else { log.Tracef("skipping non-compliant resolver: %s", mDNSResolver.Server) } return selected } // check local reverse scope if domainInScope(q.dotPrefixedFQDN, localReverseScopes) { // local resolvers for _, resolver := range localResolvers { if err := resolver.checkCompliance(ctx, q); err == nil { selected = append(selected, resolver) } else { log.Tracef("skipping non-compliant resolver: %s", resolver.Server) } } // mdns resolver if err := mDNSResolver.checkCompliance(ctx, q); err == nil { selected = append(selected, mDNSResolver) } else { log.Tracef("skipping non-compliant resolver: %s", mDNSResolver.Server) } return selected } // check for .local mdns if strings.HasSuffix(q.dotPrefixedFQDN, local) { // add mdns if err := mDNSResolver.checkCompliance(ctx, q); err == nil { selected = append(selected, mDNSResolver) } else { log.Tracef("skipping non-compliant resolver: %s", mDNSResolver.Server) } return selected } // check for test scopes if domainInScope(q.dotPrefixedFQDN, localTestScopes) { // local resolvers for _, resolver := range localResolvers { if err := resolver.checkCompliance(ctx, q); err == nil { selected = append(selected, resolver) } else { log.Tracef("skipping non-compliant resolver: %s", resolver.Server) } } return selected } // finally, query globally for _, resolver := range globalResolvers { if err := resolver.checkCompliance(ctx, q); err == nil { selected = append(selected, resolver) } else { log.Tracef("skipping non-compliant resolver: %s", resolver.Server) } } return selected } var ( errInsecureProtocol = errors.New("insecure protocols disabled") errAssignedServer = errors.New("assigned (dhcp) nameservers disabled") errMulticastDNS = errors.New("multicast DNS disabled") errSkip = errors.New("this fqdn cannot resolved by this resolver") ) func (q *Query) checkCompliance() error { // RFC6761 - always respond with nxdomain if strings.HasSuffix(q.dotPrefixedFQDN, invalid) { return ErrNotFound } // RFC6761 - respond with 127.0.0.1 and ::1 to A and AAAA queries respectively, else nxdomain if strings.HasSuffix(q.dotPrefixedFQDN, localhost) { switch uint16(q.QType) { case dns.TypeA, dns.TypeAAAA: return ErrLocalhost default: return ErrNotFound } } // special TLDs if dontResolveSpecialDomains(q.SecurityLevel) && domainInScope(q.dotPrefixedFQDN, specialServiceScopes) { return ErrSpecialDomainsDisabled } // testing TLDs if dontResolveTestDomains(q.SecurityLevel) && domainInScope(q.dotPrefixedFQDN, localTestScopes) { return ErrTestDomainsDisabled } return nil } func (resolver *Resolver) checkCompliance(_ context.Context, q *Query) error { if q.FQDN == resolver.SkipFQDN { return errSkip } if noInsecureProtocols(q.SecurityLevel) { switch resolver.ServerType { case ServerTypeDNS: return errInsecureProtocol case ServerTypeTCP: return errInsecureProtocol case ServerTypeDoT: // compliant case ServerTypeDoH: // compliant default: return errInsecureProtocol } } if noAssignedNameservers(q.SecurityLevel) { if resolver.Source == ServerSourceAssigned { return errAssignedServer } } if noMulticastDNS(q.SecurityLevel) { if resolver.Source == ServerSourceMDNS { return errMulticastDNS } } return nil }