mirror of
https://github.com/safing/portmaster
synced 2025-09-01 10:09:11 +00:00
Clean up domain resolving scopes
Switch to .17.home.arpa. for internal use
This commit is contained in:
parent
633bb34288
commit
1da8b7148e
8 changed files with 213 additions and 237 deletions
|
@ -41,7 +41,10 @@ var (
|
|||
DNSTestDomain = "one.one.one.one."
|
||||
DNSTestExpectedIP = net.IPv4(1, 1, 1, 1)
|
||||
|
||||
SpecialCaptivePortalDomain = "captiveportal.local."
|
||||
// SpecialCaptivePortalDomain is the domain name used to point to the detected captive portal IP
|
||||
// or the captive portal test IP. The default value should be overridden by the resolver package,
|
||||
// which defines the custom internal domain name to use.
|
||||
SpecialCaptivePortalDomain = "captiveportal.invalid."
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
@ -72,10 +72,6 @@ var (
|
|||
dontResolveSpecialDomains status.SecurityLevelOption
|
||||
cfgOptionDontResolveSpecialDomainsOrder = 16
|
||||
|
||||
CfgOptionDontResolveTestDomainsKey = "dns/dontResolveTestDomains"
|
||||
dontResolveTestDomains status.SecurityLevelOption
|
||||
cfgOptionDontResolveTestDomainsOrder = 17
|
||||
|
||||
CfgOptionNameserverRetryRateKey = "dns/nameserverRetryRate"
|
||||
nameserverRetryRate config.IntOption
|
||||
cfgOptionNameserverRetryRateOrder = 32
|
||||
|
@ -191,7 +187,7 @@ Parameters:
|
|||
err = config.Register(&config.Option{
|
||||
Name: "Do not resolve special domains",
|
||||
Key: CfgOptionDontResolveSpecialDomainsKey,
|
||||
Description: fmt.Sprintf("Do not resolve the special top level domains %s", formatScopeList(specialServiceScopes)),
|
||||
Description: fmt.Sprintf("Do not resolve the special top level domains %s", formatScopeList(specialServiceDomains)),
|
||||
Order: cfgOptionDontResolveSpecialDomainsOrder,
|
||||
OptType: config.OptTypeInt,
|
||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||
|
@ -205,23 +201,6 @@ Parameters:
|
|||
}
|
||||
dontResolveSpecialDomains = status.ConfigIsActiveConcurrent(CfgOptionDontResolveSpecialDomainsKey)
|
||||
|
||||
err = config.Register(&config.Option{
|
||||
Name: "Do not resolve test domains",
|
||||
Key: CfgOptionDontResolveTestDomainsKey,
|
||||
Description: fmt.Sprintf("Do not resolve the special testing top level domains %s", formatScopeList(localTestScopes)),
|
||||
Order: cfgOptionDontResolveTestDomainsOrder,
|
||||
OptType: config.OptTypeInt,
|
||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||
ReleaseLevel: config.ReleaseLevelStable,
|
||||
ExternalOptType: "security level",
|
||||
DefaultValue: status.SecurityLevelsHighAndExtreme,
|
||||
ValidationRegex: "^(4|6|7)$",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dontResolveTestDomains = status.ConfigIsActiveConcurrent(CfgOptionDontResolveTestDomainsKey)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -292,7 +292,7 @@ retry:
|
|||
|
||||
func resolveAndCache(ctx context.Context, q *Query) (rrCache *RRCache, err error) { //nolint:gocognit
|
||||
// get resolvers
|
||||
resolvers := GetResolversInScope(ctx, q)
|
||||
resolvers, tryAll := GetResolversInScope(ctx, q)
|
||||
if len(resolvers) == 0 {
|
||||
return nil, ErrNoCompliance
|
||||
}
|
||||
|
@ -330,6 +330,9 @@ resolveLoop:
|
|||
switch {
|
||||
case errors.Is(err, ErrNotFound):
|
||||
// NXDomain, or similar
|
||||
if tryAll {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
case errors.Is(err, ErrBlocked):
|
||||
// some resolvers might also block
|
||||
|
|
|
@ -2,6 +2,7 @@ package resolver
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
|
@ -10,6 +11,13 @@ import (
|
|||
"github.com/safing/portmaster/network/netutils"
|
||||
)
|
||||
|
||||
const (
|
||||
internalSpecialUseDomain = "17.home.arpa."
|
||||
|
||||
routerDomain = "router.local." + internalSpecialUseDomain
|
||||
captivePortalDomain = "captiveportal.local." + internalSpecialUseDomain
|
||||
)
|
||||
|
||||
var (
|
||||
envResolver = &Resolver{
|
||||
Server: ServerSourceEnv,
|
||||
|
@ -18,67 +26,92 @@ var (
|
|||
Source: ServerSourceEnv,
|
||||
Conn: &envResolverConn{},
|
||||
}
|
||||
envResolvers = []*Resolver{envResolver}
|
||||
|
||||
localSOA dns.RR
|
||||
internalSpecialUseSOA dns.RR
|
||||
internalSpecialUseComment dns.RR
|
||||
)
|
||||
|
||||
func prepEnvResolver() (err error) {
|
||||
localSOA, err = dns.NewRR("local. 17 IN SOA localhost. none.localhost. 17 17 17 17 17")
|
||||
netenv.SpecialCaptivePortalDomain = captivePortalDomain
|
||||
|
||||
internalSpecialUseSOA, err = dns.NewRR(fmt.Sprintf(
|
||||
"%s 17 IN SOA localhost. none.localhost. 0 0 0 0 0",
|
||||
internalSpecialUseDomain,
|
||||
))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
internalSpecialUseComment, err = dns.NewRR(fmt.Sprintf(
|
||||
`%s 17 IN TXT "This is a special use TLD of the Portmaster."`,
|
||||
internalSpecialUseDomain,
|
||||
))
|
||||
return err
|
||||
}
|
||||
|
||||
type envResolverConn struct{}
|
||||
|
||||
func (er *envResolverConn) Query(ctx context.Context, q *Query) (*RRCache, error) {
|
||||
// prepping
|
||||
portal := netenv.GetCaptivePortal()
|
||||
switch uint16(q.QType) {
|
||||
case dns.TypeA, dns.TypeAAAA: // We respond with all IPv4/6 addresses we can find.
|
||||
switch q.FQDN {
|
||||
case captivePortalDomain:
|
||||
// Get IP address of the captive portal.
|
||||
portal := netenv.GetCaptivePortal()
|
||||
portalIP := portal.IP
|
||||
if portalIP == nil {
|
||||
portalIP = netenv.PortalTestIP
|
||||
}
|
||||
// Convert IP to record and respond.
|
||||
records, err := netutils.IPsToRRs(q.FQDN, []net.IP{portalIP})
|
||||
if err != nil {
|
||||
log.Warningf("nameserver: failed to create captive portal response to %s: %s", q.FQDN, err)
|
||||
return er.nxDomain(q), nil
|
||||
}
|
||||
return er.makeRRCache(q, records), nil
|
||||
|
||||
// check for matching name
|
||||
switch q.FQDN {
|
||||
case "local.":
|
||||
// Firefox requests the SOA request for local. before resolving any local. domains.
|
||||
// Others might be doing this too. We guessed this behaviour, weren't able to find docs.
|
||||
if q.QType == dns.Type(dns.TypeSOA) {
|
||||
return er.makeRRCache(q, []dns.RR{localSOA}), nil
|
||||
}
|
||||
return nil, ErrNotFound
|
||||
case routerDomain:
|
||||
// Get gateways from netenv system.
|
||||
routers := netenv.Gateways()
|
||||
if len(routers) == 0 {
|
||||
return er.nxDomain(q), nil
|
||||
}
|
||||
// Convert IP to record and respond.
|
||||
records, err := netutils.IPsToRRs(q.FQDN, routers)
|
||||
if err != nil {
|
||||
log.Warningf("nameserver: failed to create gateway response to %s: %s", q.FQDN, err)
|
||||
return er.nxDomain(q), nil
|
||||
}
|
||||
return er.makeRRCache(q, records), nil
|
||||
|
||||
case netenv.SpecialCaptivePortalDomain:
|
||||
portalIP := portal.IP
|
||||
if portal.IP == nil {
|
||||
portalIP = netenv.PortalTestIP
|
||||
}
|
||||
|
||||
records, err := netutils.IPsToRRs(q.FQDN, []net.IP{portalIP})
|
||||
if err != nil {
|
||||
log.Warningf("nameserver: failed to create captive portal response to %s: %s", q.FQDN, err)
|
||||
return nil, ErrNotFound
|
||||
case dns.TypeSOA:
|
||||
// Direct query for the SOA record.
|
||||
if q.FQDN == internalSpecialUseDomain {
|
||||
return er.makeRRCache(q, []dns.RR{internalSpecialUseSOA}), nil
|
||||
}
|
||||
return er.makeRRCache(q, records), nil
|
||||
|
||||
case "router.local.":
|
||||
routers := netenv.Gateways()
|
||||
if len(routers) == 0 {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
records, err := netutils.IPsToRRs(q.FQDN, routers)
|
||||
if err != nil {
|
||||
log.Warningf("nameserver: failed to create gateway response to %s: %s", q.FQDN, err)
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return er.makeRRCache(q, records), nil
|
||||
}
|
||||
|
||||
// no match
|
||||
return nil, ErrContinue // continue with next resolver
|
||||
// No match, reply with NXDOMAIN and SOA record
|
||||
reply := er.nxDomain(q)
|
||||
reply.Ns = []dns.RR{internalSpecialUseSOA}
|
||||
return reply, nil
|
||||
}
|
||||
|
||||
func (er *envResolverConn) nxDomain(q *Query) *RRCache {
|
||||
return er.makeRRCache(q, nil)
|
||||
}
|
||||
|
||||
func (er *envResolverConn) makeRRCache(q *Query, answers []dns.RR) *RRCache {
|
||||
q.NoCaching = true // disable caching, as the env always has the data available and more up to date.
|
||||
// Disable caching, as the env always has the raw data available.
|
||||
q.NoCaching = true
|
||||
|
||||
return &RRCache{
|
||||
Domain: q.FQDN,
|
||||
Question: q.QType,
|
||||
Answer: answers,
|
||||
Extra: []dns.RR{internalSpecialUseComment}, // Always add comment about this TLD.
|
||||
Server: envResolver.Server,
|
||||
ServerScope: envResolver.ServerIPScope,
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ var (
|
|||
Source: ServerSourceMDNS,
|
||||
Conn: &mDNSResolverConn{},
|
||||
}
|
||||
mDNSResolvers = []*Resolver{mDNSResolver}
|
||||
)
|
||||
|
||||
type mDNSResolverConn struct{}
|
||||
|
@ -208,7 +209,7 @@ func handleMDNSMessages(ctx context.Context, messages chan *dns.Msg) error {
|
|||
|
||||
// add all entries to RRCache
|
||||
for _, entry := range message.Answer {
|
||||
if strings.HasSuffix(entry.Header().Name, ".local.") || domainInScope(entry.Header().Name, localReverseScopes) {
|
||||
if domainInScope(entry.Header().Name, multicastDomains) {
|
||||
if saveFullRequest {
|
||||
k := indexOfRR(entry.Header(), &rrCache.Answer)
|
||||
if k == -1 {
|
||||
|
@ -230,7 +231,7 @@ func handleMDNSMessages(ctx context.Context, messages chan *dns.Msg) error {
|
|||
}
|
||||
}
|
||||
for _, entry := range message.Ns {
|
||||
if strings.HasSuffix(entry.Header().Name, ".local.") || domainInScope(entry.Header().Name, localReverseScopes) {
|
||||
if domainInScope(entry.Header().Name, multicastDomains) {
|
||||
if saveFullRequest {
|
||||
k := indexOfRR(entry.Header(), &rrCache.Ns)
|
||||
if k == -1 {
|
||||
|
@ -252,7 +253,7 @@ func handleMDNSMessages(ctx context.Context, messages chan *dns.Msg) error {
|
|||
}
|
||||
}
|
||||
for _, entry := range message.Extra {
|
||||
if strings.HasSuffix(entry.Header().Name, ".local.") || domainInScope(entry.Header().Name, localReverseScopes) {
|
||||
if domainInScope(entry.Header().Name, multicastDomains) {
|
||||
if saveFullRequest {
|
||||
k := indexOfRR(entry.Header(), &rrCache.Extra)
|
||||
if k == -1 {
|
||||
|
|
|
@ -344,7 +344,7 @@ func checkSearchScope(searchDomain string) (ok bool) {
|
|||
}
|
||||
|
||||
// check if suffix is a special service domain (may be handled fully by local nameserver)
|
||||
if domainInScope("."+suffix+".", specialServiceScopes) {
|
||||
if domainInScope("."+suffix+".", specialServiceDomains) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ func TestCheckResolverSearchScope(t *testing.T) {
|
|||
test(t, "a.com", true)
|
||||
test(t, "b.a.com", true)
|
||||
test(t, "c.b.a.com", true)
|
||||
|
||||
test(t, "onion", true)
|
||||
test(t, "a.onion", true)
|
||||
test(t, "b.a.onion", true)
|
||||
|
|
|
@ -11,87 +11,74 @@ import (
|
|||
"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
|
||||
|
||||
// Domain Scopes
|
||||
var (
|
||||
// RFC6761 - respond with 127.0.0.1 and ::1 to A and AAAA queries respectively, else nxdomain
|
||||
localhost = ".localhost."
|
||||
// Localhost Domain
|
||||
// Handling: Respond with 127.0.0.1 and ::1 to A and AAAA queries, respectively.
|
||||
// RFC6761
|
||||
localhostDomain = ".localhost."
|
||||
|
||||
// RFC6761 - always respond with nxdomain
|
||||
invalid = ".invalid."
|
||||
// Invalid Domain
|
||||
// Handling: Always respond with NXDOMAIN.
|
||||
// RFC6761
|
||||
invalidDomain = ".invalid."
|
||||
|
||||
// RFC6762 - resolve locally
|
||||
local = ".local."
|
||||
// Internal Special-Use Domain
|
||||
// Used by Portmaster for special addressing.
|
||||
internalSpecialUseDomainScope = "." + internalSpecialUseDomain
|
||||
|
||||
// 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
|
||||
// Multicast DNS
|
||||
// Handling: Send to nameservers with matching search scope, then MDNS
|
||||
// RFC6762
|
||||
multicastDomains = []string{
|
||||
".local.",
|
||||
".254.169.in-addr.arpa.",
|
||||
".8.e.f.ip6.arpa.",
|
||||
".9.e.f.ip6.arpa.",
|
||||
".a.e.f.ip6.arpa.",
|
||||
".b.e.f.ip6.arpa.",
|
||||
}
|
||||
|
||||
// RFC6761 - only resolve locally
|
||||
localTestScopes = []string{
|
||||
// Special-Use Domain Names
|
||||
// Handling: Send to nameservers with matching search scope, then local and system assigned nameservers
|
||||
// IANA Ref: https://www.iana.org/assignments/special-use-domain-names
|
||||
specialUseDomains = []string{
|
||||
// RFC8375: Designated for non-unique use in residential home networks.
|
||||
".home.arpa.",
|
||||
|
||||
// RFC6762 (Appendix G): Non-official, but officially listed, private use domains.
|
||||
".intranet.",
|
||||
".internal.",
|
||||
".private.",
|
||||
".corp.",
|
||||
".home.",
|
||||
".lan.",
|
||||
|
||||
// RFC6761: IPv4 private-address reverse-mapping domains.
|
||||
".10.in-addr.arpa.",
|
||||
".16.172.in-addr.arpa.",
|
||||
".17.172.in-addr.arpa.",
|
||||
".18.172.in-addr.arpa.",
|
||||
".19.172.in-addr.arpa.",
|
||||
".20.172.in-addr.arpa.",
|
||||
".21.172.in-addr.arpa.",
|
||||
".22.172.in-addr.arpa.",
|
||||
".23.172.in-addr.arpa.",
|
||||
".24.172.in-addr.arpa.",
|
||||
".25.172.in-addr.arpa.",
|
||||
".26.172.in-addr.arpa.",
|
||||
".27.172.in-addr.arpa.",
|
||||
".28.172.in-addr.arpa.",
|
||||
".29.172.in-addr.arpa.",
|
||||
".30.172.in-addr.arpa.",
|
||||
".31.172.in-addr.arpa.",
|
||||
".168.192.in-addr.arpa.",
|
||||
|
||||
// RFC4193: IPv6 private-address reverse-mapping domains.
|
||||
".d.f.ip6.arpa",
|
||||
".c.f.ip6.arpa",
|
||||
|
||||
// RFC6761: Special use domains for documentation and testing.
|
||||
".example.",
|
||||
".example.com.",
|
||||
".example.net.",
|
||||
|
@ -99,10 +86,14 @@ var (
|
|||
".test.",
|
||||
}
|
||||
|
||||
// resolve globally - resolving these should be disabled by default
|
||||
specialServiceScopes = []string{
|
||||
".onion.", // Tor Hidden Services, RFC7686
|
||||
".bit.", // Namecoin, https://www.namecoin.org/
|
||||
// Special-Service Domain Names
|
||||
// Handling: Send to nameservers with matching search scope, then local and system assigned nameservers
|
||||
specialServiceDomains = []string{
|
||||
// RFC7686: Tor Hidden Services
|
||||
".onion.",
|
||||
|
||||
// Namecoin: Blockchain based nameservice, https://www.namecoin.org/
|
||||
".bit.",
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -116,99 +107,70 @@ func domainInScope(dotPrefixedFQDN string, scopeList []string) bool {
|
|||
}
|
||||
|
||||
// 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
|
||||
func GetResolversInScope(ctx context.Context, q *Query) (selected []*Resolver, tryAll bool) { //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
|
||||
|
||||
// special connectivity domains
|
||||
if netenv.IsConnectivityDomain(q.FQDN) && len(systemResolvers) > 0 {
|
||||
selected = append(selected, envResolver)
|
||||
selected = append(selected, systemResolvers...) // dhcp assigned resolvers
|
||||
return selected
|
||||
// Internal use domains
|
||||
if strings.HasSuffix(q.dotPrefixedFQDN, internalSpecialUseDomainScope) {
|
||||
return envResolvers, false
|
||||
}
|
||||
|
||||
// check local scopes
|
||||
// Special connectivity domains
|
||||
if netenv.IsConnectivityDomain(q.FQDN) && len(systemResolvers) > 0 {
|
||||
// Do not do compliance checks for connectivity domains.
|
||||
selected = append(selected, systemResolvers...) // dhcp assigned resolvers
|
||||
return selected, false
|
||||
}
|
||||
|
||||
// Prioritize search 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)
|
||||
}
|
||||
selected = addResolvers(ctx, q, selected, scope.Resolvers)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle multicast domains
|
||||
if domainInScope(q.dotPrefixedFQDN, multicastDomains) {
|
||||
selected = addResolvers(ctx, q, selected, mDNSResolvers)
|
||||
// Add local resolvers if no resolvers were selected.
|
||||
if len(selected) == 0 {
|
||||
selected = addResolvers(ctx, q, selected, localResolvers)
|
||||
}
|
||||
return selected, true
|
||||
}
|
||||
|
||||
// Special use domains
|
||||
if domainInScope(q.dotPrefixedFQDN, specialUseDomains) ||
|
||||
domainInScope(q.dotPrefixedFQDN, specialServiceDomains) {
|
||||
selected = addResolvers(ctx, q, selected, localResolvers)
|
||||
selected = addResolvers(ctx, q, selected, systemResolvers)
|
||||
return selected, true
|
||||
}
|
||||
|
||||
// Global domains
|
||||
selected = addResolvers(ctx, q, selected, globalResolvers)
|
||||
return selected, false
|
||||
}
|
||||
|
||||
func addResolvers(ctx context.Context, q *Query, selected []*Resolver, addResolvers []*Resolver) []*Resolver {
|
||||
addNextResolver:
|
||||
for _, resolver := range addResolvers {
|
||||
// check for compliance
|
||||
if err := resolver.checkCompliance(ctx, q); err != nil {
|
||||
log.Tracef("skipping non-compliant resolver %s: %s", resolver.GetName(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
// deduplicate
|
||||
for _, selectedResolver := range selected {
|
||||
if selectedResolver.Server == resolver.Server {
|
||||
continue addNextResolver
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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 env resolver
|
||||
selected = append(selected, envResolver)
|
||||
// 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)
|
||||
}
|
||||
// add compliant and unique resolvers to selected resolvers
|
||||
selected = append(selected, resolver)
|
||||
}
|
||||
return selected
|
||||
}
|
||||
|
@ -222,12 +184,12 @@ var (
|
|||
|
||||
func (q *Query) checkCompliance() error {
|
||||
// RFC6761 - always respond with nxdomain
|
||||
if strings.HasSuffix(q.dotPrefixedFQDN, invalid) {
|
||||
if strings.HasSuffix(q.dotPrefixedFQDN, invalidDomain) {
|
||||
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) {
|
||||
if strings.HasSuffix(q.dotPrefixedFQDN, localhostDomain) {
|
||||
switch uint16(q.QType) {
|
||||
case dns.TypeA, dns.TypeAAAA:
|
||||
return ErrLocalhost
|
||||
|
@ -238,16 +200,10 @@ func (q *Query) checkCompliance() error {
|
|||
|
||||
// special TLDs
|
||||
if dontResolveSpecialDomains(q.SecurityLevel) &&
|
||||
domainInScope(q.dotPrefixedFQDN, specialServiceScopes) {
|
||||
domainInScope(q.dotPrefixedFQDN, specialServiceDomains) {
|
||||
return ErrSpecialDomainsDisabled
|
||||
}
|
||||
|
||||
// testing TLDs
|
||||
if dontResolveTestDomains(q.SecurityLevel) &&
|
||||
domainInScope(q.dotPrefixedFQDN, localTestScopes) {
|
||||
return ErrTestDomainsDisabled
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue