mirror of
https://github.com/safing/portmaster
synced 2025-09-02 10:39:22 +00:00
Merge pull request #136 from safing/feature/cleanup-dns-scopes
Clean up domain resolving scopes
This commit is contained in:
commit
1e44f44bf5
8 changed files with 213 additions and 237 deletions
|
@ -41,7 +41,10 @@ var (
|
||||||
DNSTestDomain = "one.one.one.one."
|
DNSTestDomain = "one.one.one.one."
|
||||||
DNSTestExpectedIP = net.IPv4(1, 1, 1, 1)
|
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 (
|
var (
|
||||||
|
|
|
@ -72,10 +72,6 @@ var (
|
||||||
dontResolveSpecialDomains status.SecurityLevelOption
|
dontResolveSpecialDomains status.SecurityLevelOption
|
||||||
cfgOptionDontResolveSpecialDomainsOrder = 16
|
cfgOptionDontResolveSpecialDomainsOrder = 16
|
||||||
|
|
||||||
CfgOptionDontResolveTestDomainsKey = "dns/dontResolveTestDomains"
|
|
||||||
dontResolveTestDomains status.SecurityLevelOption
|
|
||||||
cfgOptionDontResolveTestDomainsOrder = 17
|
|
||||||
|
|
||||||
CfgOptionNameserverRetryRateKey = "dns/nameserverRetryRate"
|
CfgOptionNameserverRetryRateKey = "dns/nameserverRetryRate"
|
||||||
nameserverRetryRate config.IntOption
|
nameserverRetryRate config.IntOption
|
||||||
cfgOptionNameserverRetryRateOrder = 32
|
cfgOptionNameserverRetryRateOrder = 32
|
||||||
|
@ -191,7 +187,7 @@ Parameters:
|
||||||
err = config.Register(&config.Option{
|
err = config.Register(&config.Option{
|
||||||
Name: "Do not resolve special domains",
|
Name: "Do not resolve special domains",
|
||||||
Key: CfgOptionDontResolveSpecialDomainsKey,
|
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,
|
Order: cfgOptionDontResolveSpecialDomainsOrder,
|
||||||
OptType: config.OptTypeInt,
|
OptType: config.OptTypeInt,
|
||||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||||
|
@ -205,23 +201,6 @@ Parameters:
|
||||||
}
|
}
|
||||||
dontResolveSpecialDomains = status.ConfigIsActiveConcurrent(CfgOptionDontResolveSpecialDomainsKey)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -292,7 +292,7 @@ retry:
|
||||||
|
|
||||||
func resolveAndCache(ctx context.Context, q *Query) (rrCache *RRCache, err error) { //nolint:gocognit
|
func resolveAndCache(ctx context.Context, q *Query) (rrCache *RRCache, err error) { //nolint:gocognit
|
||||||
// get resolvers
|
// get resolvers
|
||||||
resolvers := GetResolversInScope(ctx, q)
|
resolvers, tryAll := GetResolversInScope(ctx, q)
|
||||||
if len(resolvers) == 0 {
|
if len(resolvers) == 0 {
|
||||||
return nil, ErrNoCompliance
|
return nil, ErrNoCompliance
|
||||||
}
|
}
|
||||||
|
@ -330,6 +330,9 @@ resolveLoop:
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, ErrNotFound):
|
case errors.Is(err, ErrNotFound):
|
||||||
// NXDomain, or similar
|
// NXDomain, or similar
|
||||||
|
if tryAll {
|
||||||
|
continue
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
case errors.Is(err, ErrBlocked):
|
case errors.Is(err, ErrBlocked):
|
||||||
// some resolvers might also block
|
// some resolvers might also block
|
||||||
|
|
|
@ -2,6 +2,7 @@ package resolver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
@ -10,6 +11,13 @@ import (
|
||||||
"github.com/safing/portmaster/network/netutils"
|
"github.com/safing/portmaster/network/netutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
internalSpecialUseDomain = "17.home.arpa."
|
||||||
|
|
||||||
|
routerDomain = "router.local." + internalSpecialUseDomain
|
||||||
|
captivePortalDomain = "captiveportal.local." + internalSpecialUseDomain
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
envResolver = &Resolver{
|
envResolver = &Resolver{
|
||||||
Server: ServerSourceEnv,
|
Server: ServerSourceEnv,
|
||||||
|
@ -18,67 +26,92 @@ var (
|
||||||
Source: ServerSourceEnv,
|
Source: ServerSourceEnv,
|
||||||
Conn: &envResolverConn{},
|
Conn: &envResolverConn{},
|
||||||
}
|
}
|
||||||
|
envResolvers = []*Resolver{envResolver}
|
||||||
|
|
||||||
localSOA dns.RR
|
internalSpecialUseSOA dns.RR
|
||||||
|
internalSpecialUseComment dns.RR
|
||||||
)
|
)
|
||||||
|
|
||||||
func prepEnvResolver() (err error) {
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
type envResolverConn struct{}
|
type envResolverConn struct{}
|
||||||
|
|
||||||
func (er *envResolverConn) Query(ctx context.Context, q *Query) (*RRCache, error) {
|
func (er *envResolverConn) Query(ctx context.Context, q *Query) (*RRCache, error) {
|
||||||
// prepping
|
switch uint16(q.QType) {
|
||||||
portal := netenv.GetCaptivePortal()
|
case dns.TypeA, dns.TypeAAAA: // We respond with all IPv4/6 addresses we can find.
|
||||||
|
|
||||||
// check for matching name
|
|
||||||
switch q.FQDN {
|
switch q.FQDN {
|
||||||
case "local.":
|
case captivePortalDomain:
|
||||||
// Firefox requests the SOA request for local. before resolving any local. domains.
|
// Get IP address of the captive portal.
|
||||||
// Others might be doing this too. We guessed this behaviour, weren't able to find docs.
|
portal := netenv.GetCaptivePortal()
|
||||||
if q.QType == dns.Type(dns.TypeSOA) {
|
|
||||||
return er.makeRRCache(q, []dns.RR{localSOA}), nil
|
|
||||||
}
|
|
||||||
return nil, ErrNotFound
|
|
||||||
|
|
||||||
case netenv.SpecialCaptivePortalDomain:
|
|
||||||
portalIP := portal.IP
|
portalIP := portal.IP
|
||||||
if portal.IP == nil {
|
if portalIP == nil {
|
||||||
portalIP = netenv.PortalTestIP
|
portalIP = netenv.PortalTestIP
|
||||||
}
|
}
|
||||||
|
// Convert IP to record and respond.
|
||||||
records, err := netutils.IPsToRRs(q.FQDN, []net.IP{portalIP})
|
records, err := netutils.IPsToRRs(q.FQDN, []net.IP{portalIP})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warningf("nameserver: failed to create captive portal response to %s: %s", q.FQDN, err)
|
log.Warningf("nameserver: failed to create captive portal response to %s: %s", q.FQDN, err)
|
||||||
return nil, ErrNotFound
|
return er.nxDomain(q), nil
|
||||||
}
|
}
|
||||||
return er.makeRRCache(q, records), nil
|
return er.makeRRCache(q, records), nil
|
||||||
|
|
||||||
case "router.local.":
|
case routerDomain:
|
||||||
|
// Get gateways from netenv system.
|
||||||
routers := netenv.Gateways()
|
routers := netenv.Gateways()
|
||||||
if len(routers) == 0 {
|
if len(routers) == 0 {
|
||||||
return nil, ErrNotFound
|
return er.nxDomain(q), nil
|
||||||
}
|
}
|
||||||
|
// Convert IP to record and respond.
|
||||||
records, err := netutils.IPsToRRs(q.FQDN, routers)
|
records, err := netutils.IPsToRRs(q.FQDN, routers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warningf("nameserver: failed to create gateway response to %s: %s", q.FQDN, err)
|
log.Warningf("nameserver: failed to create gateway response to %s: %s", q.FQDN, err)
|
||||||
return nil, ErrNotFound
|
return er.nxDomain(q), nil
|
||||||
}
|
}
|
||||||
return er.makeRRCache(q, records), nil
|
return er.makeRRCache(q, records), nil
|
||||||
|
|
||||||
|
}
|
||||||
|
case dns.TypeSOA:
|
||||||
|
// Direct query for the SOA record.
|
||||||
|
if q.FQDN == internalSpecialUseDomain {
|
||||||
|
return er.makeRRCache(q, []dns.RR{internalSpecialUseSOA}), nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// no match
|
// No match, reply with NXDOMAIN and SOA record
|
||||||
return nil, ErrContinue // continue with next resolver
|
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 {
|
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{
|
return &RRCache{
|
||||||
Domain: q.FQDN,
|
Domain: q.FQDN,
|
||||||
Question: q.QType,
|
Question: q.QType,
|
||||||
Answer: answers,
|
Answer: answers,
|
||||||
|
Extra: []dns.RR{internalSpecialUseComment}, // Always add comment about this TLD.
|
||||||
Server: envResolver.Server,
|
Server: envResolver.Server,
|
||||||
ServerScope: envResolver.ServerIPScope,
|
ServerScope: envResolver.ServerIPScope,
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ var (
|
||||||
Source: ServerSourceMDNS,
|
Source: ServerSourceMDNS,
|
||||||
Conn: &mDNSResolverConn{},
|
Conn: &mDNSResolverConn{},
|
||||||
}
|
}
|
||||||
|
mDNSResolvers = []*Resolver{mDNSResolver}
|
||||||
)
|
)
|
||||||
|
|
||||||
type mDNSResolverConn struct{}
|
type mDNSResolverConn struct{}
|
||||||
|
@ -208,7 +209,7 @@ func handleMDNSMessages(ctx context.Context, messages chan *dns.Msg) error {
|
||||||
|
|
||||||
// add all entries to RRCache
|
// add all entries to RRCache
|
||||||
for _, entry := range message.Answer {
|
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 {
|
if saveFullRequest {
|
||||||
k := indexOfRR(entry.Header(), &rrCache.Answer)
|
k := indexOfRR(entry.Header(), &rrCache.Answer)
|
||||||
if k == -1 {
|
if k == -1 {
|
||||||
|
@ -230,7 +231,7 @@ func handleMDNSMessages(ctx context.Context, messages chan *dns.Msg) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, entry := range message.Ns {
|
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 {
|
if saveFullRequest {
|
||||||
k := indexOfRR(entry.Header(), &rrCache.Ns)
|
k := indexOfRR(entry.Header(), &rrCache.Ns)
|
||||||
if k == -1 {
|
if k == -1 {
|
||||||
|
@ -252,7 +253,7 @@ func handleMDNSMessages(ctx context.Context, messages chan *dns.Msg) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, entry := range message.Extra {
|
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 {
|
if saveFullRequest {
|
||||||
k := indexOfRR(entry.Header(), &rrCache.Extra)
|
k := indexOfRR(entry.Header(), &rrCache.Extra)
|
||||||
if k == -1 {
|
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)
|
// 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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ func TestCheckResolverSearchScope(t *testing.T) {
|
||||||
test(t, "a.com", true)
|
test(t, "a.com", true)
|
||||||
test(t, "b.a.com", true)
|
test(t, "b.a.com", true)
|
||||||
test(t, "c.b.a.com", true)
|
test(t, "c.b.a.com", true)
|
||||||
|
|
||||||
test(t, "onion", true)
|
test(t, "onion", true)
|
||||||
test(t, "a.onion", true)
|
test(t, "a.onion", true)
|
||||||
test(t, "b.a.onion", true)
|
test(t, "b.a.onion", true)
|
||||||
|
|
|
@ -11,87 +11,74 @@ import (
|
||||||
"github.com/safing/portbase/log"
|
"github.com/safing/portbase/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// special scopes:
|
// Domain 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 (
|
var (
|
||||||
// RFC6761 - respond with 127.0.0.1 and ::1 to A and AAAA queries respectively, else nxdomain
|
// Localhost Domain
|
||||||
localhost = ".localhost."
|
// Handling: Respond with 127.0.0.1 and ::1 to A and AAAA queries, respectively.
|
||||||
|
// RFC6761
|
||||||
|
localhostDomain = ".localhost."
|
||||||
|
|
||||||
// RFC6761 - always respond with nxdomain
|
// Invalid Domain
|
||||||
invalid = ".invalid."
|
// Handling: Always respond with NXDOMAIN.
|
||||||
|
// RFC6761
|
||||||
|
invalidDomain = ".invalid."
|
||||||
|
|
||||||
// RFC6762 - resolve locally
|
// Internal Special-Use Domain
|
||||||
local = ".local."
|
// Used by Portmaster for special addressing.
|
||||||
|
internalSpecialUseDomainScope = "." + internalSpecialUseDomain
|
||||||
|
|
||||||
// local reverse dns
|
// Multicast DNS
|
||||||
localReverseScopes = []string{
|
// Handling: Send to nameservers with matching search scope, then MDNS
|
||||||
".10.in-addr.arpa.", // RFC6761
|
// RFC6762
|
||||||
".16.172.in-addr.arpa.", // RFC6761
|
multicastDomains = []string{
|
||||||
".17.172.in-addr.arpa.", // RFC6761
|
".local.",
|
||||||
".18.172.in-addr.arpa.", // RFC6761
|
".254.169.in-addr.arpa.",
|
||||||
".19.172.in-addr.arpa.", // RFC6761
|
".8.e.f.ip6.arpa.",
|
||||||
".20.172.in-addr.arpa.", // RFC6761
|
".9.e.f.ip6.arpa.",
|
||||||
".21.172.in-addr.arpa.", // RFC6761
|
".a.e.f.ip6.arpa.",
|
||||||
".22.172.in-addr.arpa.", // RFC6761
|
".b.e.f.ip6.arpa.",
|
||||||
".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
|
// Special-Use Domain Names
|
||||||
localTestScopes = []string{
|
// 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.",
|
||||||
".example.com.",
|
".example.com.",
|
||||||
".example.net.",
|
".example.net.",
|
||||||
|
@ -99,10 +86,14 @@ var (
|
||||||
".test.",
|
".test.",
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolve globally - resolving these should be disabled by default
|
// Special-Service Domain Names
|
||||||
specialServiceScopes = []string{
|
// Handling: Send to nameservers with matching search scope, then local and system assigned nameservers
|
||||||
".onion.", // Tor Hidden Services, RFC7686
|
specialServiceDomains = []string{
|
||||||
".bit.", // Namecoin, https://www.namecoin.org/
|
// 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.
|
// 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()
|
resolversLock.RLock()
|
||||||
defer resolversLock.RUnlock()
|
defer resolversLock.RUnlock()
|
||||||
|
|
||||||
// resolver selection:
|
// Internal use domains
|
||||||
// local -> local scopes, mdns
|
if strings.HasSuffix(q.dotPrefixedFQDN, internalSpecialUseDomainScope) {
|
||||||
// local-inaddr -> local, mdns
|
return envResolvers, false
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
for _, scope := range localScopes {
|
||||||
if strings.HasSuffix(q.dotPrefixedFQDN, scope.Domain) {
|
if strings.HasSuffix(q.dotPrefixedFQDN, scope.Domain) {
|
||||||
// scoped resolvers
|
selected = addResolvers(ctx, q, selected, scope.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
|
// Handle multicast domains
|
||||||
if domainInScope(q.dotPrefixedFQDN, localReverseScopes) {
|
if domainInScope(q.dotPrefixedFQDN, multicastDomains) {
|
||||||
// local resolvers
|
selected = addResolvers(ctx, q, selected, mDNSResolvers)
|
||||||
for _, resolver := range localResolvers {
|
// Add local resolvers if no resolvers were selected.
|
||||||
if err := resolver.checkCompliance(ctx, q); err == nil {
|
if len(selected) == 0 {
|
||||||
selected = append(selected, resolver)
|
selected = addResolvers(ctx, q, selected, localResolvers)
|
||||||
} else {
|
|
||||||
log.Tracef("skipping non-compliant resolver: %s", resolver.Server)
|
|
||||||
}
|
}
|
||||||
}
|
return selected, true
|
||||||
// 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
|
// Special use domains
|
||||||
if strings.HasSuffix(q.dotPrefixedFQDN, local) {
|
if domainInScope(q.dotPrefixedFQDN, specialUseDomains) ||
|
||||||
// add env resolver
|
domainInScope(q.dotPrefixedFQDN, specialServiceDomains) {
|
||||||
selected = append(selected, envResolver)
|
selected = addResolvers(ctx, q, selected, localResolvers)
|
||||||
// add mdns
|
selected = addResolvers(ctx, q, selected, systemResolvers)
|
||||||
if err := mDNSResolver.checkCompliance(ctx, q); err == nil {
|
return selected, true
|
||||||
selected = append(selected, mDNSResolver)
|
|
||||||
} else {
|
|
||||||
log.Tracef("skipping non-compliant resolver: %s", mDNSResolver.Server)
|
|
||||||
}
|
|
||||||
return selected
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for test scopes
|
// Global domains
|
||||||
if domainInScope(q.dotPrefixedFQDN, localTestScopes) {
|
selected = addResolvers(ctx, q, selected, globalResolvers)
|
||||||
// local resolvers
|
return selected, false
|
||||||
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
|
func addResolvers(ctx context.Context, q *Query, selected []*Resolver, addResolvers []*Resolver) []*Resolver {
|
||||||
for _, resolver := range globalResolvers {
|
addNextResolver:
|
||||||
if err := resolver.checkCompliance(ctx, q); err == nil {
|
for _, resolver := range addResolvers {
|
||||||
selected = append(selected, resolver)
|
// check for compliance
|
||||||
} else {
|
if err := resolver.checkCompliance(ctx, q); err != nil {
|
||||||
log.Tracef("skipping non-compliant resolver: %s", resolver.Server)
|
log.Tracef("skipping non-compliant resolver %s: %s", resolver.GetName(), err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deduplicate
|
||||||
|
for _, selectedResolver := range selected {
|
||||||
|
if selectedResolver.Server == resolver.Server {
|
||||||
|
continue addNextResolver
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add compliant and unique resolvers to selected resolvers
|
||||||
|
selected = append(selected, resolver)
|
||||||
}
|
}
|
||||||
return selected
|
return selected
|
||||||
}
|
}
|
||||||
|
@ -222,12 +184,12 @@ var (
|
||||||
|
|
||||||
func (q *Query) checkCompliance() error {
|
func (q *Query) checkCompliance() error {
|
||||||
// RFC6761 - always respond with nxdomain
|
// RFC6761 - always respond with nxdomain
|
||||||
if strings.HasSuffix(q.dotPrefixedFQDN, invalid) {
|
if strings.HasSuffix(q.dotPrefixedFQDN, invalidDomain) {
|
||||||
return ErrNotFound
|
return ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
// RFC6761 - respond with 127.0.0.1 and ::1 to A and AAAA queries respectively, else nxdomain
|
// 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) {
|
switch uint16(q.QType) {
|
||||||
case dns.TypeA, dns.TypeAAAA:
|
case dns.TypeA, dns.TypeAAAA:
|
||||||
return ErrLocalhost
|
return ErrLocalhost
|
||||||
|
@ -238,16 +200,10 @@ func (q *Query) checkCompliance() error {
|
||||||
|
|
||||||
// special TLDs
|
// special TLDs
|
||||||
if dontResolveSpecialDomains(q.SecurityLevel) &&
|
if dontResolveSpecialDomains(q.SecurityLevel) &&
|
||||||
domainInScope(q.dotPrefixedFQDN, specialServiceScopes) {
|
domainInScope(q.dotPrefixedFQDN, specialServiceDomains) {
|
||||||
return ErrSpecialDomainsDisabled
|
return ErrSpecialDomainsDisabled
|
||||||
}
|
}
|
||||||
|
|
||||||
// testing TLDs
|
|
||||||
if dontResolveTestDomains(q.SecurityLevel) &&
|
|
||||||
domainInScope(q.dotPrefixedFQDN, localTestScopes) {
|
|
||||||
return ErrTestDomainsDisabled
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue