mirror of
https://github.com/safing/portmaster
synced 2025-09-02 02:29:12 +00:00
Merge pull request #239 from safing/feature/resolver-improvements
Resolver improvements
This commit is contained in:
commit
7970ab4f0b
10 changed files with 105 additions and 36 deletions
|
@ -73,13 +73,6 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg)
|
||||||
return nil
|
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.
|
// Check the Query Class.
|
||||||
if originalQuestion.Qclass != dns.ClassINET {
|
if originalQuestion.Qclass != dns.ClassINET {
|
||||||
// we only serve IN records, return nxdomain
|
// 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):
|
case errors.Is(err, resolver.ErrLocalhost):
|
||||||
tracer.Tracef("nameserver: returning localhost records")
|
tracer.Tracef("nameserver: returning localhost records")
|
||||||
return reply(nsutil.Localhost())
|
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:
|
default:
|
||||||
tracer.Warningf("nameserver: failed to resolve %s: %s", q.ID(), err)
|
tracer.Warningf("nameserver: failed to resolve %s: %s", q.ID(), err)
|
||||||
return reply(nsutil.ServerFailure("internal error: " + err.Error()))
|
return reply(nsutil.ServerFailure("internal error: " + err.Error()))
|
||||||
|
|
|
@ -321,11 +321,11 @@ func monitorOnlineStatus(ctx context.Context) error {
|
||||||
func getDynamicStatusTrigger() <-chan time.Time {
|
func getDynamicStatusTrigger() <-chan time.Time {
|
||||||
switch GetOnlineStatus() {
|
switch GetOnlineStatus() {
|
||||||
case StatusOffline:
|
case StatusOffline:
|
||||||
return time.After(5 * time.Second)
|
return time.After(1 * time.Second)
|
||||||
case StatusLimited, StatusPortal:
|
case StatusLimited, StatusPortal:
|
||||||
return time.After(10 * time.Second)
|
return time.After(5 * time.Second)
|
||||||
case StatusSemiOnline:
|
case StatusSemiOnline:
|
||||||
return time.After(1 * time.Minute)
|
return time.After(20 * time.Second)
|
||||||
case StatusOnline:
|
case StatusOnline:
|
||||||
return nil
|
return nil
|
||||||
case StatusUnknown:
|
case StatusUnknown:
|
||||||
|
|
49
resolver/api.go
Normal file
49
resolver/api.go
Normal 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
|
||||||
|
}
|
|
@ -6,8 +6,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/safing/portbase/api"
|
|
||||||
|
|
||||||
"github.com/safing/portbase/log"
|
"github.com/safing/portbase/log"
|
||||||
"github.com/safing/portbase/modules"
|
"github.com/safing/portbase/modules"
|
||||||
"github.com/safing/portmaster/intel"
|
"github.com/safing/portmaster/intel"
|
||||||
|
@ -31,6 +29,10 @@ func init() {
|
||||||
func prep() error {
|
func prep() error {
|
||||||
intel.SetReverseResolver(ResolveIPAndValidate)
|
intel.SetReverseResolver(ResolveIPAndValidate)
|
||||||
|
|
||||||
|
if err := registerAPI(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err := prepEnvResolver(); err != nil {
|
if err := prepEnvResolver(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -78,15 +80,6 @@ func start() error {
|
||||||
return err
|
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
|
// DEPRECATED: remove in v0.7
|
||||||
// cache clearing
|
// cache clearing
|
||||||
err = module.RegisterEventHook(
|
err = module.RegisterEventHook(
|
||||||
|
|
|
@ -88,6 +88,10 @@ func GetNameRecord(domain, question string) (*NameRecord, error) {
|
||||||
|
|
||||||
// DeleteNameRecord deletes a NameRecord from the database.
|
// DeleteNameRecord deletes a NameRecord from the database.
|
||||||
func DeleteNameRecord(domain, question string) error {
|
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)
|
key := makeNameRecordKey(domain, question)
|
||||||
return recordDatabase.Delete(key)
|
return recordDatabase.Delete(key)
|
||||||
}
|
}
|
||||||
|
@ -107,24 +111,30 @@ func (rec *NameRecord) Save() error {
|
||||||
|
|
||||||
// clearNameCache clears all dns caches from the database.
|
// clearNameCache clears all dns caches from the database.
|
||||||
func clearNameCache(ar *api.Request) (msg string, err error) {
|
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))
|
n, err := recordDatabase.Purge(ar.Context(), query.New(nameRecordsKeyPrefix))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debugf("resolver: cleared %d entries from dns cache", n)
|
||||||
return fmt.Sprintf("cleared %d dns cache entries", n), nil
|
return fmt.Sprintf("cleared %d dns cache entries", n), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DEPRECATED: remove in v0.7
|
// DEPRECATED: remove in v0.7
|
||||||
func clearNameCacheEventHandler(ctx context.Context, _ interface{}) error {
|
func clearNameCacheEventHandler(ctx context.Context, _ interface{}) error {
|
||||||
log.Debugf("resolver: dns cache clearing started...")
|
log.Debugf("resolver: dns cache clearing started...")
|
||||||
|
|
||||||
|
recordDatabase.FlushCache()
|
||||||
|
recordDatabase.ClearCache()
|
||||||
n, err := recordDatabase.Purge(ctx, query.New(nameRecordsKeyPrefix))
|
n, err := recordDatabase.Purge(ctx, query.New(nameRecordsKeyPrefix))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("resolver: cleared %d entries in dns cache", n)
|
log.Debugf("resolver: cleared %d entries from dns cache", n)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -317,9 +317,8 @@ func resolveAndCache(ctx context.Context, q *Query, oldCache *RRCache) (rrCache
|
||||||
// check if we are online
|
// check if we are online
|
||||||
if netenv.GetOnlineStatus() == netenv.StatusOffline {
|
if netenv.GetOnlineStatus() == netenv.StatusOffline {
|
||||||
if !netenv.IsConnectivityDomain(q.FQDN) {
|
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
|
// 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)
|
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
|
return nil, err
|
||||||
case netenv.GetOnlineStatus() == netenv.StatusOffline &&
|
case netenv.GetOnlineStatus() == netenv.StatusOffline &&
|
||||||
!netenv.IsConnectivityDomain(q.FQDN):
|
!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
|
// we are offline and this is not an online check query
|
||||||
return nil, ErrOffline
|
return oldCache, ErrOffline
|
||||||
case errors.Is(err, ErrContinue):
|
case errors.Is(err, ErrContinue):
|
||||||
continue
|
continue
|
||||||
case errors.Is(err, ErrTimeout):
|
case errors.Is(err, ErrTimeout):
|
||||||
|
|
|
@ -41,6 +41,9 @@ type Resolver struct {
|
||||||
// - `zeroip`: Answer only contains zeroip
|
// - `zeroip`: Answer only contains zeroip
|
||||||
Server string
|
Server string
|
||||||
|
|
||||||
|
// Source describes from where the resolver configuration originated.
|
||||||
|
Source string
|
||||||
|
|
||||||
// Name is the name of the resolver as passed via
|
// Name is the name of the resolver as passed via
|
||||||
// ?name=.
|
// ?name=.
|
||||||
Name string
|
Name string
|
||||||
|
@ -65,12 +68,9 @@ type Resolver struct {
|
||||||
// Special Options
|
// Special Options
|
||||||
VerifyDomain string
|
VerifyDomain string
|
||||||
Search []string
|
Search []string
|
||||||
SkipFQDN string
|
|
||||||
|
|
||||||
Source string
|
|
||||||
|
|
||||||
// logic interface
|
// logic interface
|
||||||
Conn ResolverConn
|
Conn ResolverConn `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsBlockedUpstream returns true if the request has been blocked
|
// IsBlockedUpstream returns true if the request has been blocked
|
||||||
|
|
|
@ -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")
|
extra = addExtra(ctx, extra, "async request to refresh the cache has been started")
|
||||||
}
|
}
|
||||||
if rrCache.IsBackup {
|
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.
|
// Add information about filtered entries.
|
||||||
|
|
|
@ -208,10 +208,6 @@ func (q *Query) checkCompliance() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (resolver *Resolver) checkCompliance(_ context.Context, q *Query) error {
|
func (resolver *Resolver) checkCompliance(_ context.Context, q *Query) error {
|
||||||
if q.FQDN == resolver.SkipFQDN {
|
|
||||||
return errSkip
|
|
||||||
}
|
|
||||||
|
|
||||||
if noInsecureProtocols(q.SecurityLevel) {
|
if noInsecureProtocols(q.SecurityLevel) {
|
||||||
switch resolver.ServerType {
|
switch resolver.ServerType {
|
||||||
case ServerTypeDNS:
|
case ServerTypeDNS:
|
||||||
|
|
22
resolver/test/resolving.bash
Normal file
22
resolver/test/resolving.bash
Normal 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
|
Loading…
Add table
Reference in a new issue