Use pre-authorized ports for dns queries, improve logging

This commit is contained in:
Daniel 2019-05-10 11:56:50 +02:00
parent 9fcfd34f28
commit 55ef385dcb
8 changed files with 154 additions and 98 deletions

View file

@ -2,12 +2,24 @@ package intel
import ( import (
"crypto/tls" "crypto/tls"
"net"
"sync" "sync"
"time" "time"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
var (
localAddrFactory func(network string) net.Addr
)
// SetLocalAddrFactory supplied the intel package with a function to set local addresses for connections.
func SetLocalAddrFactory(laf func(network string) net.Addr) {
if localAddrFactory == nil {
localAddrFactory = laf
}
}
type clientManager struct { type clientManager struct {
dnsClient *dns.Client dnsClient *dns.Client
factory func() *dns.Client factory func() *dns.Client
@ -21,10 +33,13 @@ type clientManager struct {
func newDNSClientManager(resolver *Resolver) *clientManager { func newDNSClientManager(resolver *Resolver) *clientManager {
return &clientManager{ return &clientManager{
ttl: -1 * time.Minute, // ttl: 1 * time.Minute,
factory: func() *dns.Client { factory: func() *dns.Client {
return &dns.Client{ return &dns.Client{
Timeout: 5 * time.Second, Timeout: 5 * time.Second,
Dialer: &net.Dialer{
LocalAddr: localAddrFactory("udp"),
},
} }
}, },
} }
@ -32,11 +47,14 @@ func newDNSClientManager(resolver *Resolver) *clientManager {
func newTCPClientManager(resolver *Resolver) *clientManager { func newTCPClientManager(resolver *Resolver) *clientManager {
return &clientManager{ return &clientManager{
ttl: -15 * time.Minute, // ttl: 5 * time.Minute,
factory: func() *dns.Client { factory: func() *dns.Client {
return &dns.Client{ return &dns.Client{
Net: "tcp", Net: "tcp",
Timeout: 5 * time.Second, Timeout: 5 * time.Second,
Dialer: &net.Dialer{
LocalAddr: localAddrFactory("tcp"),
},
} }
}, },
} }
@ -44,7 +62,7 @@ func newTCPClientManager(resolver *Resolver) *clientManager {
func newTLSClientManager(resolver *Resolver) *clientManager { func newTLSClientManager(resolver *Resolver) *clientManager {
return &clientManager{ return &clientManager{
ttl: -15 * time.Minute, // ttl: 5 * time.Minute,
factory: func() *dns.Client { factory: func() *dns.Client {
return &dns.Client{ return &dns.Client{
Net: "tcp-tls", Net: "tcp-tls",
@ -55,6 +73,9 @@ func newTLSClientManager(resolver *Resolver) *clientManager {
// Rand: io.Reader, // Rand: io.Reader,
}, },
Timeout: 5 * time.Second, Timeout: 5 * time.Second,
Dialer: &net.Dialer{
LocalAddr: localAddrFactory("tcp"),
},
} }
}, },
} }
@ -62,7 +83,7 @@ func newTLSClientManager(resolver *Resolver) *clientManager {
func newHTTPSClientManager(resolver *Resolver) *clientManager { func newHTTPSClientManager(resolver *Resolver) *clientManager {
return &clientManager{ return &clientManager{
ttl: -15 * time.Minute, // ttl: 5 * time.Minute,
factory: func() *dns.Client { factory: func() *dns.Client {
new := &dns.Client{ new := &dns.Client{
Net: "https", Net: "https",
@ -72,6 +93,9 @@ func newHTTPSClientManager(resolver *Resolver) *clientManager {
// Rand: io.Reader, // Rand: io.Reader,
}, },
Timeout: 5 * time.Second, Timeout: 5 * time.Second,
Dialer: &net.Dialer{
LocalAddr: localAddrFactory("tcp"),
},
} }
if resolver.VerifyDomain != "" { if resolver.VerifyDomain != "" {
new.TLSConfig.ServerName = resolver.VerifyDomain new.TLSConfig.ServerName = resolver.VerifyDomain
@ -85,7 +109,7 @@ func (cm *clientManager) getDNSClient() *dns.Client {
cm.lock.Lock() cm.lock.Lock()
defer cm.lock.Unlock() defer cm.lock.Unlock()
if cm.dnsClient == nil || time.Now().After(cm.refreshAfter) { if cm.dnsClient == nil || cm.ttl == 0 || time.Now().After(cm.refreshAfter) {
cm.dnsClient = cm.factory() cm.dnsClient = cm.factory()
cm.refreshAfter = time.Now().Add(cm.ttl) cm.refreshAfter = time.Now().Add(cm.ttl)
} }

View file

@ -8,18 +8,19 @@ import (
var ( var (
configuredNameServers config.StringArrayOption configuredNameServers config.StringArrayOption
defaultNameServers = []string{ defaultNameServers = []string{
"dns|1.1.1.1:53", // Cloudflare
"dns|1.0.0.1:53", // Cloudflare
"dns|9.9.9.9:53", // Quad9
"tls|1.1.1.1:853|cloudflare-dns.com", // Cloudflare "tls|1.1.1.1:853|cloudflare-dns.com", // Cloudflare
"tls|1.0.0.1:853|cloudflare-dns.com", // Cloudflare "tls|1.0.0.1:853|cloudflare-dns.com", // Cloudflare
"tls|9.9.9.9:853|dns.quad9.net", // Quad9 "tls|9.9.9.9:853|dns.quad9.net", // Quad9
// "https|cloudflare-dns.com/dns-query", // HTTPS still experimental // "https|cloudflare-dns.com/dns-query", // HTTPS still experimental
"dns|1.1.1.1:53", // Cloudflare
"dns|1.0.0.1:53", // Cloudflare
"dns|9.9.9.9:53", // Quad9
} }
nameserverRetryRate config.IntOption nameserverRetryRate config.IntOption
doNotUseMulticastDNS status.SecurityLevelOption doNotUseMulticastDNS status.SecurityLevelOption
doNotUseAssignedNameservers status.SecurityLevelOption doNotUseAssignedNameservers status.SecurityLevelOption
doNotUseInsecureProtocols status.SecurityLevelOption
doNotResolveSpecialDomains status.SecurityLevelOption doNotResolveSpecialDomains status.SecurityLevelOption
) )
@ -73,7 +74,7 @@ func prep() error {
ExpertiseLevel: config.ExpertiseLevelExpert, ExpertiseLevel: config.ExpertiseLevelExpert,
OptType: config.OptTypeInt, OptType: config.OptTypeInt,
ExternalOptType: "security level", ExternalOptType: "security level",
DefaultValue: 6, DefaultValue: 4,
ValidationRegex: "^(7|6|4)$", ValidationRegex: "^(7|6|4)$",
}) })
if err != nil { if err != nil {
@ -81,6 +82,21 @@ func prep() error {
} }
doNotUseAssignedNameservers = status.ConfigIsActiveConcurrent("intel/doNotUseAssignedNameservers") doNotUseAssignedNameservers = status.ConfigIsActiveConcurrent("intel/doNotUseAssignedNameservers")
err = config.Register(&config.Option{
Name: "Do not resolve insecurely",
Key: "intel/doNotUseInsecureProtocols",
Description: "Do not resolve domains with insecure protocols, ie. plain DNS",
ExpertiseLevel: config.ExpertiseLevelExpert,
OptType: config.OptTypeInt,
ExternalOptType: "security level",
DefaultValue: 4,
ValidationRegex: "^(7|6|4)$",
})
if err != nil {
return err
}
doNotUseInsecureProtocols = status.ConfigIsActiveConcurrent("intel/doNotUseInsecureProtocols")
err = config.Register(&config.Option{ err = config.Register(&config.Option{
Name: "Do not resolve special domains", Name: "Do not resolve special domains",
Key: "intel/doNotResolveSpecialDomains", Key: "intel/doNotResolveSpecialDomains",

View file

@ -1,6 +1,8 @@
package intel package intel
import ( import (
"context"
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/Safing/portbase/log" "github.com/Safing/portbase/log"
@ -24,12 +26,16 @@ func start() error {
} }
// GetIntelAndRRs returns intel and DNS resource records for the given domain. // GetIntelAndRRs returns intel and DNS resource records for the given domain.
func GetIntelAndRRs(domain string, qtype dns.Type, securityLevel uint8) (intel *Intel, rrs *RRCache) { func GetIntelAndRRs(ctx context.Context, domain string, qtype dns.Type, securityLevel uint8) (intel *Intel, rrs *RRCache) {
log.Tracer(ctx).Trace("intel: getting intel")
intel, err := GetIntel(domain) intel, err := GetIntel(domain)
if err != nil { if err != nil {
log.Tracer(ctx).Warningf("intel: failed to get intel: %s", err)
log.Errorf("intel: failed to get intel: %s", err) log.Errorf("intel: failed to get intel: %s", err)
intel = nil intel = nil
} }
rrs = Resolve(domain, qtype, securityLevel)
log.Tracer(ctx).Tracef("intel: getting records")
rrs = Resolve(ctx, domain, qtype, securityLevel)
return return
} }

View file

@ -3,6 +3,7 @@
package intel package intel
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"net" "net"
@ -262,7 +263,9 @@ func listenForDNSPackets(conn *net.UDPConn, messages chan *dns.Msg) {
} }
} }
func queryMulticastDNS(fqdn string, qtype dns.Type) (*RRCache, error) { func queryMulticastDNS(ctx context.Context, fqdn string, qtype dns.Type) (*RRCache, error) {
log.Tracer(ctx).Trace("intel: resolving with mDNS")
q := new(dns.Msg) q := new(dns.Msg)
q.SetQuestion(fqdn, uint16(qtype)) q.SetQuestion(fqdn, uint16(qtype))
// request unicast response // request unicast response

View file

@ -3,6 +3,7 @@
package intel package intel
import ( import (
"context"
"fmt" "fmt"
"math/rand" "math/rand"
"net" "net"
@ -69,7 +70,7 @@ import (
// special -> local scopes, local // special -> local scopes, local
// Resolve resolves the given query for a domain and type and returns a RRCache object or nil, if the query failed. // Resolve resolves the given query for a domain and type and returns a RRCache object or nil, if the query failed.
func Resolve(fqdn string, qtype dns.Type, securityLevel uint8) *RRCache { func Resolve(ctx context.Context, fqdn string, qtype dns.Type, securityLevel uint8) *RRCache {
fqdn = dns.Fqdn(fqdn) fqdn = dns.Fqdn(fqdn)
// use this to time how long it takes resolve this domain // use this to time how long it takes resolve this domain
@ -82,15 +83,17 @@ func Resolve(fqdn string, qtype dns.Type, securityLevel uint8) *RRCache {
switch err { switch err {
case database.ErrNotFound: case database.ErrNotFound:
default: default:
log.Tracer(ctx).Warningf("intel: getting RRCache %s%s from database failed: %s", fqdn, qtype.String(), err)
log.Warningf("intel: getting RRCache %s%s from database failed: %s", fqdn, qtype.String(), err) log.Warningf("intel: getting RRCache %s%s from database failed: %s", fqdn, qtype.String(), err)
} }
return resolveAndCache(fqdn, qtype, securityLevel) return resolveAndCache(ctx, fqdn, qtype, securityLevel)
} }
if rrCache.TTL <= time.Now().Unix() { if rrCache.TTL <= time.Now().Unix() {
log.Tracef("intel: serving cache, requesting new. TTL=%d, now=%d", rrCache.TTL, time.Now().Unix()) log.Tracer(ctx).Tracef("intel: serving from cache, requesting new. TTL=%d, now=%d", rrCache.TTL, time.Now().Unix())
// log.Tracef("intel: serving cache, requesting new. TTL=%d, now=%d", rrCache.TTL, time.Now().Unix())
rrCache.requestingNew = true rrCache.requestingNew = true
go resolveAndCache(fqdn, qtype, securityLevel) go resolveAndCache(nil, fqdn, qtype, securityLevel)
} }
// randomize records to allow dumb clients (who only look at the first record) to reliably connect // randomize records to allow dumb clients (who only look at the first record) to reliably connect
@ -102,8 +105,8 @@ func Resolve(fqdn string, qtype dns.Type, securityLevel uint8) *RRCache {
return rrCache return rrCache
} }
func resolveAndCache(fqdn string, qtype dns.Type, securityLevel uint8) (rrCache *RRCache) { func resolveAndCache(ctx context.Context, fqdn string, qtype dns.Type, securityLevel uint8) (rrCache *RRCache) {
// log.Tracef("intel: resolving %s%s", fqdn, qtype.String()) log.Tracer(ctx).Tracef("intel: resolving %s%s", fqdn, qtype.String())
// dedup requests // dedup requests
dupKey := fmt.Sprintf("%s%s", fqdn, qtype.String()) dupKey := fmt.Sprintf("%s%s", fqdn, qtype.String())
@ -116,7 +119,8 @@ func resolveAndCache(fqdn string, qtype dns.Type, securityLevel uint8) (rrCache
dupReqLock.Unlock() dupReqLock.Unlock()
} else { } else {
dupReqLock.Unlock() dupReqLock.Unlock()
log.Tracef("intel: waiting for duplicate query for %s to complete", dupKey) log.Tracer(ctx).Tracef("intel: waiting for duplicate query for %s to complete", dupKey)
// log.Tracef("intel: waiting for duplicate query for %s to complete", dupKey)
mutex.Lock() mutex.Lock()
// wait until duplicate request is finished, then fetch current RRCache and return // wait until duplicate request is finished, then fetch current RRCache and return
mutex.Unlock() mutex.Unlock()
@ -136,7 +140,7 @@ func resolveAndCache(fqdn string, qtype dns.Type, securityLevel uint8) (rrCache
}() }()
// resolve // resolve
rrCache = intelligentResolve(fqdn, qtype, securityLevel) rrCache = intelligentResolve(ctx, fqdn, qtype, securityLevel)
if rrCache == nil { if rrCache == nil {
return nil return nil
} }
@ -148,7 +152,7 @@ func resolveAndCache(fqdn string, qtype dns.Type, securityLevel uint8) (rrCache
return rrCache return rrCache
} }
func intelligentResolve(fqdn string, qtype dns.Type, securityLevel uint8) *RRCache { func intelligentResolve(ctx context.Context, fqdn string, qtype dns.Type, securityLevel uint8) *RRCache {
// TODO: handle being offline // TODO: handle being offline
// TODO: handle multiple network connections // TODO: handle multiple network connections
@ -178,7 +182,7 @@ func intelligentResolve(fqdn string, qtype dns.Type, securityLevel uint8) *RRCac
if domainInScopes(preDottedFqdn, localReverseScopes) { if domainInScopes(preDottedFqdn, localReverseScopes) {
// try local resolvers // try local resolvers
for _, resolver := range localResolvers { for _, resolver := range localResolvers {
rrCache, ok := tryResolver(resolver, lastFailBoundary, fqdn, qtype, securityLevel) rrCache, ok := tryResolver(ctx, resolver, lastFailBoundary, fqdn, qtype, securityLevel)
if ok && rrCache != nil && !rrCache.IsNXDomain() { if ok && rrCache != nil && !rrCache.IsNXDomain() {
return rrCache return rrCache
} }
@ -188,8 +192,9 @@ func intelligentResolve(fqdn string, qtype dns.Type, securityLevel uint8) *RRCac
return nil return nil
} }
// try mdns // try mdns
rrCache, err := queryMulticastDNS(fqdn, qtype) rrCache, err := queryMulticastDNS(ctx, fqdn, qtype)
if err != nil { if err != nil {
log.Tracer(ctx).Warningf("intel: failed to query mdns: %s", err)
log.Errorf("intel: failed to query mdns: %s", err) log.Errorf("intel: failed to query mdns: %s", err)
} }
return rrCache return rrCache
@ -199,7 +204,7 @@ func intelligentResolve(fqdn string, qtype dns.Type, securityLevel uint8) *RRCac
for _, scope := range localScopes { for _, scope := range localScopes {
if strings.HasSuffix(preDottedFqdn, scope.Domain) { if strings.HasSuffix(preDottedFqdn, scope.Domain) {
for _, resolver := range scope.Resolvers { for _, resolver := range scope.Resolvers {
rrCache, ok := tryResolver(resolver, lastFailBoundary, fqdn, qtype, securityLevel) rrCache, ok := tryResolver(ctx, resolver, lastFailBoundary, fqdn, qtype, securityLevel)
if ok && rrCache != nil && !rrCache.IsNXDomain() { if ok && rrCache != nil && !rrCache.IsNXDomain() {
return rrCache return rrCache
} }
@ -214,8 +219,9 @@ func intelligentResolve(fqdn string, qtype dns.Type, securityLevel uint8) *RRCac
return nil return nil
} }
// try mdns // try mdns
rrCache, err := queryMulticastDNS(fqdn, qtype) rrCache, err := queryMulticastDNS(ctx, fqdn, qtype)
if err != nil { if err != nil {
log.Tracer(ctx).Warningf("intel: failed to query mdns: %s", err)
log.Errorf("intel: failed to query mdns: %s", err) log.Errorf("intel: failed to query mdns: %s", err)
} }
return rrCache return rrCache
@ -226,7 +232,7 @@ func intelligentResolve(fqdn string, qtype dns.Type, securityLevel uint8) *RRCac
} }
// try local resolvers // try local resolvers
for _, resolver := range localResolvers { for _, resolver := range localResolvers {
rrCache, ok := tryResolver(resolver, lastFailBoundary, fqdn, qtype, securityLevel) rrCache, ok := tryResolver(ctx, resolver, lastFailBoundary, fqdn, qtype, securityLevel)
if ok { if ok {
return rrCache return rrCache
} }
@ -234,13 +240,14 @@ func intelligentResolve(fqdn string, qtype dns.Type, securityLevel uint8) *RRCac
default: default:
// try global resolvers // try global resolvers
for _, resolver := range globalResolvers { for _, resolver := range globalResolvers {
rrCache, ok := tryResolver(resolver, lastFailBoundary, fqdn, qtype, securityLevel) rrCache, ok := tryResolver(ctx, resolver, lastFailBoundary, fqdn, qtype, securityLevel)
if ok { if ok {
return rrCache return rrCache
} }
} }
} }
log.Tracer(ctx).Warningf("intel: failed to resolve %s%s: all resolvers failed (or were skipped to fulfill the security level)", fqdn, qtype.String())
log.Criticalf("intel: failed to resolve %s%s: all resolvers failed (or were skipped to fulfill the security level)", fqdn, qtype.String()) log.Criticalf("intel: failed to resolve %s%s: all resolvers failed (or were skipped to fulfill the security level)", fqdn, qtype.String())
return nil return nil
@ -248,24 +255,29 @@ func intelligentResolve(fqdn string, qtype dns.Type, securityLevel uint8) *RRCac
} }
func tryResolver(resolver *Resolver, lastFailBoundary int64, fqdn string, qtype dns.Type, securityLevel uint8) (*RRCache, bool) { func tryResolver(ctx context.Context, resolver *Resolver, lastFailBoundary int64, fqdn string, qtype dns.Type, securityLevel uint8) (*RRCache, bool) {
// skip if not allowed in current security level log.Tracer(ctx).Tracef("intel: resolving with %s", resolver)
if resolver.AllowedSecurityLevel < status.ActiveSecurityLevel() || resolver.AllowedSecurityLevel < securityLevel {
log.Tracef("intel: skipping resolver %s, because it isn't allowed to operate on the current security level: %d|%d", resolver, status.ActiveSecurityLevel(), securityLevel) // skip if not security level denies insecure protocols
if doNotUseInsecureProtocols(securityLevel) && resolver.ServerType == "dns" {
log.Tracer(ctx).Tracef("intel: skipping resolver %s, because it isn't allowed to operate on the current security level: %d|%d", resolver, status.ActiveSecurityLevel(), securityLevel)
return nil, false return nil, false
} }
// skip if not security level denies assigned dns servers // skip if not security level denies assigned dns servers
if doNotUseAssignedNameservers(securityLevel) && resolver.Source == "dhcp" { if doNotUseAssignedNameservers(securityLevel) && resolver.Source == "dhcp" {
log.Tracef("intel: skipping resolver %s, because assigned nameservers are not allowed on the current security level: %d|%d", resolver, status.ActiveSecurityLevel(), securityLevel) log.Tracer(ctx).Tracef("intel: skipping resolver %s, because assigned nameservers are not allowed on the current security level: %d|%d", resolver, status.ActiveSecurityLevel(), securityLevel)
return nil, false return nil, false
} }
// check if failed recently // check if failed recently
if atomic.LoadInt64(resolver.LastFail) > lastFailBoundary { if atomic.LoadInt64(resolver.LastFail) > lastFailBoundary {
log.Tracer(ctx).Tracef("intel: skipping resolver %s, because it failed recently", resolver)
return nil, false return nil, false
} }
// TODO: put SkipFqdnBeforeInit back into !resolver.Initialized.IsSet() as soon as Go1.9 arrives and we can use a custom resolver // TODO: put SkipFqdnBeforeInit back into !resolver.Initialized.IsSet() as soon as Go1.9 arrives and we can use a custom resolver
// skip resolver if initializing and fqdn is set to skip // skip resolver if initializing and fqdn is set to skip
if fqdn == resolver.SkipFqdnBeforeInit { if fqdn == resolver.SkipFqdnBeforeInit {
log.Tracer(ctx).Tracef("intel: skipping resolver %s, because %s is set to be skipped before init", resolver, fqdn)
return nil, false return nil, false
} }
// check if resolver is already initialized // check if resolver is already initialized
@ -285,15 +297,16 @@ func tryResolver(resolver *Resolver, lastFailBoundary int64, fqdn string, qtype
} }
} }
// resolve // resolve
log.Tracef("intel: trying to resolve %s%s with %s", fqdn, qtype.String(), resolver.Server) rrCache, err := query(ctx, resolver, fqdn, qtype)
rrCache, err := query(resolver, fqdn, qtype)
if err != nil { if err != nil {
// check if failing is disabled // check if failing is disabled
if atomic.LoadInt64(resolver.LastFail) == -1 { if atomic.LoadInt64(resolver.LastFail) == -1 {
log.Tracef("intel: non-failing resolver %s failed (%s), moving to next", resolver, err) log.Tracer(ctx).Tracef("intel: non-failing resolver %s failed, moving to next: %s", resolver, err)
// log.Tracef("intel: non-failing resolver %s failed (%s), moving to next", resolver, err)
return nil, false return nil, false
} }
log.Warningf("intel: resolver %s failed (%s), moving to next", resolver, err) log.Tracer(ctx).Warningf("intel: resolver %s failed, moving to next: %s", resolver, err)
log.Warningf("intel: resolver %s failed, moving to next: %s", resolver, err)
resolver.LockReason.Lock() resolver.LockReason.Lock()
resolver.FailReason = err.Error() resolver.FailReason = err.Error()
resolver.LockReason.Unlock() resolver.LockReason.Unlock()
@ -306,7 +319,7 @@ func tryResolver(resolver *Resolver, lastFailBoundary int64, fqdn string, qtype
return rrCache, true return rrCache, true
} }
func query(resolver *Resolver, fqdn string, qtype dns.Type) (*RRCache, error) { func query(ctx context.Context, resolver *Resolver, fqdn string, qtype dns.Type) (*RRCache, error) {
q := new(dns.Msg) q := new(dns.Msg)
q.SetQuestion(fqdn, uint16(qtype)) q.SetQuestion(fqdn, uint16(qtype))
@ -322,7 +335,7 @@ func query(resolver *Resolver, fqdn string, qtype dns.Type) (*RRCache, error) {
// error handling // error handling
if err != nil { if err != nil {
log.Tracef("intel: query to %s encountered error: %s", resolver.Server, err) log.Tracer(ctx).Tracef("intel: query to %s encountered error: %s", resolver.Server, err)
// TODO: handle special cases // TODO: handle special cases
// 1. connect: network is unreachable // 1. connect: network is unreachable
@ -330,7 +343,7 @@ func query(resolver *Resolver, fqdn string, qtype dns.Type) (*RRCache, error) {
// temporary error // temporary error
if nerr, ok := err.(net.Error); ok && nerr.Timeout() { if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
log.Tracef("intel: retrying to resolve %s%s with %s, error was: %s", fqdn, qtype.String(), resolver.Server, err) log.Tracer(ctx).Tracef("intel: retrying to resolve %s%s with %s, error is temporary", fqdn, qtype, resolver.Server)
continue continue
} }
@ -343,8 +356,6 @@ func query(resolver *Resolver, fqdn string, qtype dns.Type) (*RRCache, error) {
} }
if err != nil { if err != nil {
err = fmt.Errorf("resolving %s%s failed: %s", fqdn, qtype.String(), err)
log.Warning(err.Error())
return nil, err return nil, err
} }

View file

@ -16,7 +16,6 @@ import (
"github.com/Safing/portmaster/network/environment" "github.com/Safing/portmaster/network/environment"
"github.com/Safing/portmaster/network/netutils" "github.com/Safing/portmaster/network/netutils"
"github.com/Safing/portmaster/status"
) )
// Resolver holds information about an active resolver. // Resolver holds information about an active resolver.
@ -33,7 +32,6 @@ type Resolver struct {
clientManager *clientManager clientManager *clientManager
Search *[]string Search *[]string
AllowedSecurityLevel uint8
SkipFqdnBeforeInit string SkipFqdnBeforeInit string
// atomic // atomic
@ -149,7 +147,7 @@ configuredServersLoop:
var lastFail int64 var lastFail int64
new := &Resolver{ new := &Resolver{
Server: server, Server: server,
ServerType: parts[0], ServerType: strings.ToLower(parts[0]),
ServerAddress: parts[1], ServerAddress: parts[1],
ServerIP: ip, ServerIP: ip,
ServerIPScope: netutils.ClassifyIP(ip), ServerIPScope: netutils.ClassifyIP(ip),
@ -159,13 +157,12 @@ configuredServersLoop:
Initialized: abool.NewBool(false), Initialized: abool.NewBool(false),
} }
switch strings.ToLower(parts[0]) { switch new.ServerType {
case "dns": case "dns":
new.clientManager = newDNSClientManager(new) new.clientManager = newDNSClientManager(new)
case "tcp": case "tcp":
new.clientManager = newTCPClientManager(new) new.clientManager = newTCPClientManager(new)
case "tls": case "tls":
new.AllowedSecurityLevel = status.SecurityLevelFortress
if len(parts) < 3 { if len(parts) < 3 {
log.Warningf("intel: nameserver missing verification domain as third parameter: %s", server) log.Warningf("intel: nameserver missing verification domain as third parameter: %s", server)
continue configuredServersLoop continue configuredServersLoop
@ -173,7 +170,6 @@ configuredServersLoop:
new.VerifyDomain = parts[2] new.VerifyDomain = parts[2]
new.clientManager = newTLSClientManager(new) new.clientManager = newTLSClientManager(new)
case "https": case "https":
new.AllowedSecurityLevel = status.SecurityLevelFortress
new.SkipFqdnBeforeInit = dns.Fqdn(strings.Split(parts[1], ":")[0]) new.SkipFqdnBeforeInit = dns.Fqdn(strings.Split(parts[1], ":")[0])
if len(parts) > 2 { if len(parts) > 2 {
new.VerifyDomain = parts[2] new.VerifyDomain = parts[2]
@ -212,7 +208,6 @@ assignedServersLoop:
LastFail: &lastFail, LastFail: &lastFail,
Source: "dhcp", Source: "dhcp",
Initialized: abool.NewBool(false), Initialized: abool.NewBool(false),
AllowedSecurityLevel: status.SecurityLevelSecure,
} }
new.clientManager = newDNSClientManager(new) new.clientManager = newDNSClientManager(new)

View file

@ -18,7 +18,7 @@ func ResolveIPAndValidate(ip string, securityLevel uint8) (domain string, err er
} }
// get PTR record // get PTR record
rrCache := Resolve(rQ, dns.Type(dns.TypePTR), securityLevel) rrCache := Resolve(nil, rQ, dns.Type(dns.TypePTR), securityLevel)
if rrCache == nil { if rrCache == nil {
return "", errors.New("querying for PTR record failed (may be NXDomain)") return "", errors.New("querying for PTR record failed (may be NXDomain)")
} }
@ -42,9 +42,9 @@ func ResolveIPAndValidate(ip string, securityLevel uint8) (domain string, err er
// get forward record // get forward record
if strings.Contains(ip, ":") { if strings.Contains(ip, ":") {
rrCache = Resolve(ptrName, dns.Type(dns.TypeAAAA), securityLevel) rrCache = Resolve(nil, ptrName, dns.Type(dns.TypeAAAA), securityLevel)
} else { } else {
rrCache = Resolve(ptrName, dns.Type(dns.TypeA), securityLevel) rrCache = Resolve(nil, ptrName, dns.Type(dns.TypeA), securityLevel)
} }
if rrCache == nil { if rrCache == nil {
return "", errors.New("querying for A/AAAA record failed (may be NXDomain)") return "", errors.New("querying for A/AAAA record failed (may be NXDomain)")

View file

@ -3,6 +3,7 @@
package nameserver package nameserver
import ( import (
"context"
"net" "net"
"time" "time"
@ -73,6 +74,21 @@ func handleRequest(w dns.ResponseWriter, query *dns.Msg) {
fqdn := dns.Fqdn(question.Name) fqdn := dns.Fqdn(question.Name)
qtype := dns.Type(question.Qtype) qtype := dns.Type(question.Qtype)
// check class
if question.Qclass != dns.ClassINET {
// we only serve IN records, return nxdomain
nxDomain(w, query)
return
}
// handle request for localhost
if fqdn == "localhost." {
m := new(dns.Msg)
m.SetReply(query)
m.Answer = localhostIPs
w.WriteMsg(m)
}
// get addresses // get addresses
remoteAddr, ok := w.RemoteAddr().(*net.UDPAddr) remoteAddr, ok := w.RemoteAddr().(*net.UDPAddr)
if !ok { if !ok {
@ -91,28 +107,25 @@ func handleRequest(w dns.ResponseWriter, query *dns.Msg) {
return return
} }
log.Tracef("nameserver: handling request for %s%s from %s:%d", fqdn, qtype, remoteAddr.IP, remoteAddr.Port) // check if valid domain name
if !netutils.IsValidFqdn(fqdn) {
// TODO: if there are 3 request for the same domain/type in a row, delete all caches of that domain log.Debugf("nameserver: domain name %s is invalid, returning nxdomain", fqdn)
// check class
if question.Qclass != dns.ClassINET {
// we only serve IN records, return nxdomain
nxDomain(w, query) nxDomain(w, query)
return return
} }
// handle request for localhost // start tracer
if fqdn == "localhost." { ctx := log.AddTracer(context.Background())
m := new(dns.Msg) log.Tracer(ctx).Tracef("nameserver: handling new request for %s%s from %s:%d", fqdn, qtype, remoteAddr.IP, remoteAddr.Port)
m.SetReply(query)
m.Answer = localhostIPs
w.WriteMsg(m)
}
// check if valid domain name // TODO: if there are 3 request for the same domain/type in a row, delete all caches of that domain
if !netutils.IsValidFqdn(fqdn) {
log.Tracef("nameserver: domain name %s is invalid, returning nxdomain", fqdn) // get connection
// start = time.Now()
comm, err := network.GetCommunicationByDNSRequest(ctx, remoteAddr.IP, uint16(remoteAddr.Port), fqdn)
// log.Tracef("nameserver: took %s to get comms (and maybe process)", time.Since(start))
if err != nil {
log.ErrorTracef(ctx, "nameserver: could not identify process of %s:%d, returning nxdomain: %s", remoteAddr.IP, remoteAddr.Port, err)
nxDomain(w, query) nxDomain(w, query)
return return
} }
@ -122,44 +135,29 @@ func handleRequest(w dns.ResponseWriter, query *dns.Msg) {
lms := algs.LmsScoreOfDomain(fqdn) lms := algs.LmsScoreOfDomain(fqdn)
// log.Tracef("nameserver: domain %s has lms score of %f", fqdn, lms) // log.Tracef("nameserver: domain %s has lms score of %f", fqdn, lms)
if lms < 10 { if lms < 10 {
log.Tracef("nameserver: possible data tunnel: %s has lms score of %f, returning nxdomain", fqdn, lms) log.WarningTracef(ctx, "nameserver: possible data tunnel by %s: %s has lms score of %f, returning nxdomain", comm.Process(), fqdn, lms)
nxDomain(w, query) nxDomain(w, query)
return return
} }
// [1/2] use this to time how long it takes to get process info
// timed := time.Now()
// get connection
// start = time.Now()
comm, err := network.GetCommunicationByDNSRequest(remoteAddr.IP, uint16(remoteAddr.Port), fqdn)
// log.Tracef("nameserver: took %s to get comms (and maybe process)", time.Since(start))
if err != nil {
log.Warningf("nameserver: someone is requesting %s, but could not identify process: %s, returning nxdomain", fqdn, err)
nxDomain(w, query)
return
}
// [2/2] use this to time how long it takes to get process info
// log.Tracef("nameserver: took %s to get connection/process of %s request", time.Now().Sub(timed).String(), fqdn)
// check profile before we even get intel and rr // check profile before we even get intel and rr
// start = time.Now() // start = time.Now()
firewall.DecideOnCommunicationBeforeIntel(comm, fqdn) firewall.DecideOnCommunicationBeforeIntel(comm, fqdn)
// log.Tracef("nameserver: took %s to make decision", time.Since(start)) // log.Tracef("nameserver: took %s to make decision", time.Since(start))
if comm.GetVerdict() == network.VerdictBlock || comm.GetVerdict() == network.VerdictDrop { if comm.GetVerdict() == network.VerdictBlock || comm.GetVerdict() == network.VerdictDrop {
log.InfoTracef(ctx, "nameserver: %s denied before intel, returning nxdomain", comm)
nxDomain(w, query) nxDomain(w, query)
return return
} }
// get intel and RRs // get intel and RRs
// start = time.Now() // start = time.Now()
domainIntel, rrCache := intel.GetIntelAndRRs(fqdn, qtype, comm.Process().ProfileSet().SecurityLevel()) domainIntel, rrCache := intel.GetIntelAndRRs(ctx, fqdn, qtype, comm.Process().ProfileSet().SecurityLevel())
// log.Tracef("nameserver: took %s to get intel and RRs", time.Since(start)) // log.Tracef("nameserver: took %s to get intel and RRs", time.Since(start))
if rrCache == nil { if rrCache == nil {
// TODO: analyze nxdomain requests, malware could be trying DGA-domains // TODO: analyze nxdomain requests, malware could be trying DGA-domains
log.Infof("nameserver: %s tried to query %s, but is nxdomain", comm.Process().String(), fqdn) log.WarningTracef(ctx, "nameserver: %s requested %s%s, is nxdomain", comm.Process(), fqdn, qtype)
nxDomain(w, query) nxDomain(w, query)
return return
} }
@ -174,6 +172,7 @@ func handleRequest(w dns.ResponseWriter, query *dns.Msg) {
firewall.DecideOnCommunicationAfterIntel(comm, fqdn, rrCache) firewall.DecideOnCommunicationAfterIntel(comm, fqdn, rrCache)
switch comm.GetVerdict() { switch comm.GetVerdict() {
case network.VerdictUndecided, network.VerdictBlock, network.VerdictDrop: case network.VerdictUndecided, network.VerdictBlock, network.VerdictDrop:
log.InfoTracef(ctx, "nameserver: %s denied after intel, returning nxdomain", comm)
nxDomain(w, query) nxDomain(w, query)
return return
} }
@ -181,6 +180,7 @@ func handleRequest(w dns.ResponseWriter, query *dns.Msg) {
// filter DNS response // filter DNS response
rrCache = firewall.FilterDNSResponse(comm, fqdn, rrCache) rrCache = firewall.FilterDNSResponse(comm, fqdn, rrCache)
if rrCache == nil { if rrCache == nil {
log.InfoTracef(ctx, "nameserver: %s implicitly denied by filtering the dns response, returning nxdomain", comm)
nxDomain(w, query) nxDomain(w, query)
return return
} }
@ -224,4 +224,5 @@ func handleRequest(w dns.ResponseWriter, query *dns.Msg) {
m.Ns = rrCache.Ns m.Ns = rrCache.Ns
m.Extra = rrCache.Extra m.Extra = rrCache.Extra
w.WriteMsg(m) w.WriteMsg(m)
log.DebugTracef(ctx, "nameserver: returning response %s%s to %s", fqdn, qtype, comm.Process())
} }