Merge pull request #239 from safing/feature/resolver-improvements

Resolver improvements
This commit is contained in:
Daniel 2021-01-26 14:11:22 +01:00 committed by GitHub
commit 7970ab4f0b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 105 additions and 36 deletions

View file

@ -73,13 +73,6 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg)
return nil
}
// Return with server failure if offline.
if netenv.GetOnlineStatus() == netenv.StatusOffline &&
!netenv.IsConnectivityDomain(q.FQDN) {
tracer.Debugf("nameserver: not resolving %s, device is offline", q.FQDN)
return reply(nsutil.ServerFailure("resolving disabled, device is offline"))
}
// Check the Query Class.
if originalQuestion.Qclass != dns.ClassINET {
// we only serve IN records, return nxdomain
@ -186,6 +179,14 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg)
case errors.Is(err, resolver.ErrLocalhost):
tracer.Tracef("nameserver: returning localhost records")
return reply(nsutil.Localhost())
case errors.Is(err, resolver.ErrOffline):
if rrCache == nil {
log.Tracer(ctx).Debugf("nameserver: not resolving %s, device is offline", q.ID())
return reply(nsutil.ServerFailure(err.Error()))
}
// If an rrCache was returned, it's usable a backup.
rrCache.IsBackup = true
log.Tracer(ctx).Debugf("nameserver: device is offline, using backup cache for %s", q.ID())
default:
tracer.Warningf("nameserver: failed to resolve %s: %s", q.ID(), err)
return reply(nsutil.ServerFailure("internal error: " + err.Error()))

View file

@ -321,11 +321,11 @@ func monitorOnlineStatus(ctx context.Context) error {
func getDynamicStatusTrigger() <-chan time.Time {
switch GetOnlineStatus() {
case StatusOffline:
return time.After(5 * time.Second)
return time.After(1 * time.Second)
case StatusLimited, StatusPortal:
return time.After(10 * time.Second)
return time.After(5 * time.Second)
case StatusSemiOnline:
return time.After(1 * time.Minute)
return time.After(20 * time.Second)
case StatusOnline:
return nil
case StatusUnknown:

49
resolver/api.go Normal file
View file

@ -0,0 +1,49 @@
package resolver
import (
"github.com/safing/portbase/api"
)
func registerAPI() error {
if err := api.RegisterEndpoint(api.Endpoint{
Path: "dns/clear",
Read: api.PermitUser,
ActionFunc: clearNameCache,
Name: "Clear cached DNS records",
Description: "Deletes all saved DNS records from the database.",
}); err != nil {
return err
}
if err := api.RegisterEndpoint(api.Endpoint{
Path: "dns/resolvers",
Read: api.PermitAnyone,
StructFunc: exportDNSResolvers,
Name: "List DNS Resolvers",
Description: "List currently configured DNS resolvers and their status.",
}); err != nil {
return err
}
return nil
}
type resolverExport struct {
*Resolver
Failing bool
}
func exportDNSResolvers(*api.Request) (interface{}, error) {
resolversLock.RLock()
defer resolversLock.RUnlock()
export := make([]*resolverExport, 0, len(globalResolvers))
for _, r := range globalResolvers {
export = append(export, &resolverExport{
Resolver: r,
Failing: r.Conn.IsFailing(),
})
}
return export, nil
}

View file

@ -6,8 +6,6 @@ import (
"strings"
"time"
"github.com/safing/portbase/api"
"github.com/safing/portbase/log"
"github.com/safing/portbase/modules"
"github.com/safing/portmaster/intel"
@ -31,6 +29,10 @@ func init() {
func prep() error {
intel.SetReverseResolver(ResolveIPAndValidate)
if err := registerAPI(); err != nil {
return err
}
if err := prepEnvResolver(); err != nil {
return err
}
@ -78,15 +80,6 @@ func start() error {
return err
}
// Register api endpoint to clear DNS cache.
if err := api.RegisterEndpoint(api.Endpoint{
Path: "dns/clear/namecache",
Read: api.PermitUser,
ActionFunc: clearNameCache,
}); err != nil {
return err
}
// DEPRECATED: remove in v0.7
// cache clearing
err = module.RegisterEventHook(

View file

@ -88,6 +88,10 @@ func GetNameRecord(domain, question string) (*NameRecord, error) {
// DeleteNameRecord deletes a NameRecord from the database.
func DeleteNameRecord(domain, question string) error {
// In order to properly delete an entry, we must also clear the caches.
recordDatabase.FlushCache()
recordDatabase.ClearCache()
key := makeNameRecordKey(domain, question)
return recordDatabase.Delete(key)
}
@ -107,24 +111,30 @@ func (rec *NameRecord) Save() error {
// clearNameCache clears all dns caches from the database.
func clearNameCache(ar *api.Request) (msg string, err error) {
log.Warning("resolver: user requested dns cache clearing via action")
log.Info("resolver: user requested dns cache clearing via action")
recordDatabase.FlushCache()
recordDatabase.ClearCache()
n, err := recordDatabase.Purge(ar.Context(), query.New(nameRecordsKeyPrefix))
if err != nil {
return "", err
}
log.Debugf("resolver: cleared %d entries from dns cache", n)
return fmt.Sprintf("cleared %d dns cache entries", n), nil
}
// DEPRECATED: remove in v0.7
func clearNameCacheEventHandler(ctx context.Context, _ interface{}) error {
log.Debugf("resolver: dns cache clearing started...")
recordDatabase.FlushCache()
recordDatabase.ClearCache()
n, err := recordDatabase.Purge(ctx, query.New(nameRecordsKeyPrefix))
if err != nil {
return err
}
log.Debugf("resolver: cleared %d entries in dns cache", n)
log.Debugf("resolver: cleared %d entries from dns cache", n)
return nil
}

View file

@ -317,9 +317,8 @@ func resolveAndCache(ctx context.Context, q *Query, oldCache *RRCache) (rrCache
// check if we are online
if netenv.GetOnlineStatus() == netenv.StatusOffline {
if !netenv.IsConnectivityDomain(q.FQDN) {
log.Tracer(ctx).Debugf("resolver: not resolving %s, device is offline", q.FQDN)
// we are offline and this is not an online check query
return nil, ErrOffline
return oldCache, ErrOffline
}
log.Tracer(ctx).Debugf("resolver: permitting online status test domain %s to resolve even though offline", q.FQDN)
}
@ -356,9 +355,8 @@ resolveLoop:
return nil, err
case netenv.GetOnlineStatus() == netenv.StatusOffline &&
!netenv.IsConnectivityDomain(q.FQDN):
log.Tracer(ctx).Debugf("resolver: not resolving %s, device is offline", q.FQDN)
// we are offline and this is not an online check query
return nil, ErrOffline
return oldCache, ErrOffline
case errors.Is(err, ErrContinue):
continue
case errors.Is(err, ErrTimeout):

View file

@ -41,6 +41,9 @@ type Resolver struct {
// - `zeroip`: Answer only contains zeroip
Server string
// Source describes from where the resolver configuration originated.
Source string
// Name is the name of the resolver as passed via
// ?name=.
Name string
@ -65,12 +68,9 @@ type Resolver struct {
// Special Options
VerifyDomain string
Search []string
SkipFQDN string
Source string
// logic interface
Conn ResolverConn
Conn ResolverConn `json:"-"`
}
// IsBlockedUpstream returns true if the request has been blocked

View file

@ -324,7 +324,7 @@ func (rrCache *RRCache) GetExtraRRs(ctx context.Context, query *dns.Msg) (extra
extra = addExtra(ctx, extra, "async request to refresh the cache has been started")
}
if rrCache.IsBackup {
extra = addExtra(ctx, extra, "this record is served because a fresh request failed")
extra = addExtra(ctx, extra, "this record is served because a fresh request was unsuccessful")
}
// Add information about filtered entries.

View file

@ -208,10 +208,6 @@ func (q *Query) checkCompliance() error {
}
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:

View file

@ -0,0 +1,22 @@
#!/bin/bash
DOMAINS="twitter.com news.ycombinator.com"
while true; do
for domain in $DOMAINS; do
# query domain
Q=$(dig $domain | tr '\n' '§')
# check result
if [[ $(echo $Q | grep NOERROR | wc -l) -gt 0 ]]; then
echo "$(date "+%y%m%d %H:%M:%S") [OK] $domain ($(echo $Q | grep -aoE 'valid for [a-z0-9]+'))"
else
echo ""
echo "$(date "+%y%m%d %H:%M:%S") [FAILED] $domain"
echo $Q | tr '§' '\n'
echo "#####"
echo ""
fi
# wait
sleep 5
done
done