Calm down and relax dns query check

This commit is contained in:
Daniel 2022-05-20 16:37:19 +02:00
parent ea1b189330
commit e178b732bc
4 changed files with 94 additions and 44 deletions

View file

@ -34,7 +34,7 @@ func checkTunneling(ctx context.Context, conn *network.Connection, pkt packet.Pa
case conn.Process().Pid == ownPID: case conn.Process().Pid == ownPID:
// Bypass tunneling for certain own connections. // Bypass tunneling for certain own connections.
switch { switch {
case captain.ClientBootstrapping(): case !captain.ClientReady():
return return
case captain.IsExcepted(conn.Entity.IP): case captain.IsExcepted(conn.Entity.IP):
return return

View file

@ -37,13 +37,12 @@ var (
PortalTestIP = net.IPv4(192, 0, 2, 1) PortalTestIP = net.IPv4(192, 0, 2, 1)
PortalTestURL = fmt.Sprintf("http://%s/", PortalTestIP) PortalTestURL = fmt.Sprintf("http://%s/", PortalTestIP)
DNSTestDomain = "one.one.one.one." DNSTestDomain = "online-check.safing.io."
DNSTestExpectedIP = net.IPv4(1, 1, 1, 1) DNSTestExpectedIP = net.IPv4(0, 65, 67, 75) // Ascii: \0ACK
DNSTestQueryFunc func(ctx context.Context, fdqn string) (ips []net.IP, ok bool, err error)
DNSFallbackTestDomain = "dns-check.safing.io."
DNSFallbackTestExpectedIP = net.IPv4(0, 65, 67, 75) // Ascii: \0ACK
ConnectedToSPN = abool.New() ConnectedToSPN = abool.New()
ConnectedToDNS = abool.New()
// SpecialCaptivePortalDomain is the domain name used to point to the detected captive portal IP // 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, // or the captive portal test IP. The default value should be overridden by the resolver package,
@ -53,8 +52,6 @@ var (
// ConnectivityDomains holds all connectivity domains. This slice must not be modified. // ConnectivityDomains holds all connectivity domains. This slice must not be modified.
ConnectivityDomains = []string{ ConnectivityDomains = []string{
SpecialCaptivePortalDomain, SpecialCaptivePortalDomain,
DNSTestDomain, // Internal DNS Check
DNSFallbackTestDomain, // Internal DNS Check
// Windows // Windows
"dns.msftncsi.com.", // DNS Check "dns.msftncsi.com.", // DNS Check
@ -380,20 +377,20 @@ func monitorOnlineStatus(ctx context.Context) error {
func getDynamicStatusTrigger() <-chan time.Time { func getDynamicStatusTrigger() <-chan time.Time {
switch GetOnlineStatus() { switch GetOnlineStatus() {
case StatusOffline: case StatusOffline:
// Will be triggered by network change anyway. // Will also be triggered by network change.
return time.After(20 * time.Second) return time.After(10 * time.Second)
case StatusLimited, StatusPortal: case StatusLimited, StatusPortal:
// Change will not be detected otherwise, but impact is minor. // Change will not be detected otherwise, but impact is minor.
return time.After(5 * time.Second) return time.After(5 * time.Second)
case StatusSemiOnline: case StatusSemiOnline:
// Very small impact. // Very small impact.
return time.After(20 * time.Second) return time.After(60 * time.Second)
case StatusOnline: case StatusOnline:
// Don't check until resolver reports problems. // Don't check until resolver reports problems.
return nil return nil
case StatusUnknown: case StatusUnknown:
return time.After(5 * time.Second) fallthrough
default: // other unknown status default:
return time.After(5 * time.Minute) return time.After(5 * time.Minute)
} }
} }
@ -407,13 +404,18 @@ func checkOnlineStatus(ctx context.Context) {
return StatusUnknown return StatusUnknown
}*/ }*/
// 0) check if connected to SPN // 0) check if connected to SPN and/or DNS.
if ConnectedToSPN.IsSet() { if ConnectedToSPN.IsSet() {
updateOnlineStatus(StatusOnline, nil, "connected to SPN") updateOnlineStatus(StatusOnline, nil, "connected to SPN")
return return
} }
if ConnectedToDNS.IsSet() {
updateOnlineStatus(StatusOnline, nil, "connected to DNS")
return
}
// 1) check for addresses // 1) check for addresses
ipv4, ipv6, err := GetAssignedAddresses() ipv4, ipv6, err := GetAssignedAddresses()
@ -508,34 +510,28 @@ func checkOnlineStatus(ctx context.Context) {
// 3) resolve a query // 3) resolve a query
// Check with primary dns check domain. // Check if we can resolve the dns check domain.
ips, err := net.LookupIP(DNSTestDomain) if DNSTestQueryFunc == nil {
if err != nil { updateOnlineStatus(StatusOnline, nil, "all checks passed, dns query check disabled")
log.Warningf("netenv: dns check query failed: %s", err) return
} else { }
// check for expected response ips, ok, err := DNSTestQueryFunc(ctx, DNSTestDomain)
for _, ip := range ips { switch {
if ip.Equal(DNSTestExpectedIP) { case ok && err != nil:
updateOnlineStatus(StatusOnline, nil, fmt.Sprintf(
"all checks passed, acceptable result for dns query check: %s",
err,
))
case ok && len(ips) >= 1 && ips[0].Equal(DNSTestExpectedIP):
updateOnlineStatus(StatusOnline, nil, "all checks passed") updateOnlineStatus(StatusOnline, nil, "all checks passed")
return case ok && len(ips) >= 1:
log.Warningf("netenv: dns query check response mismatched: got %s", ips[0])
updateOnlineStatus(StatusOnline, nil, "all checks passed, dns query check response mismatched")
case ok:
log.Warningf("netenv: dns query check response mismatched: empty response")
updateOnlineStatus(StatusOnline, nil, "all checks passed, dns query check response was empty")
default:
log.Warningf("netenv: dns query check failed: %s", err)
updateOnlineStatus(StatusOffline, nil, "dns query check failed")
} }
} }
}
// If that did not work, check with fallback dns check domain.
ips, err = net.LookupIP(DNSFallbackTestDomain)
if err != nil {
log.Warningf("netenv: dns fallback check query failed: %s", err)
updateOnlineStatus(StatusLimited, nil, "dns fallback check query failed")
return
}
// check for expected response
for _, ip := range ips {
if ip.Equal(DNSFallbackTestExpectedIP) {
updateOnlineStatus(StatusOnline, nil, "all checks passed")
return
}
}
// unexpected response
updateOnlineStatus(StatusSemiOnline, nil, "dns check query response mismatched")
}

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"net"
"sync" "sync"
"time" "time"
@ -318,8 +319,8 @@ func resolveAndCache(ctx context.Context, q *Query, oldCache *RRCache) (rrCache
} }
// check if we are online // check if we are online
if primarySource != ServerSourceEnv && netenv.GetOnlineStatus() == netenv.StatusOffline { if netenv.GetOnlineStatus() == netenv.StatusOffline && primarySource != ServerSourceEnv {
if !netenv.IsConnectivityDomain(q.FQDN) { if q.FQDN != netenv.DNSTestDomain && !netenv.IsConnectivityDomain(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 oldCache, ErrOffline return oldCache, ErrOffline
} }
@ -358,6 +359,7 @@ resolveLoop:
// some resolvers might also block // some resolvers might also block
return nil, err return nil, err
case netenv.GetOnlineStatus() == netenv.StatusOffline && case netenv.GetOnlineStatus() == netenv.StatusOffline &&
q.FQDN != netenv.DNSTestDomain &&
!netenv.IsConnectivityDomain(q.FQDN): !netenv.IsConnectivityDomain(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 oldCache, ErrOffline return oldCache, ErrOffline
@ -478,3 +480,45 @@ func shouldResetCache(q *Query) (reset bool) {
return false return false
} }
func init() {
netenv.DNSTestQueryFunc = testConnectivity
}
// testConnectivity test if resolving a query succeeds and returns whether the
// query itself succeeded, separate from interpreting the result.
func testConnectivity(ctx context.Context, fdqn string) (ips []net.IP, ok bool, err error) {
q := &Query{
FQDN: fdqn,
QType: dns.Type(dns.TypeA),
NoCaching: true,
}
if !q.check() {
return nil, false, ErrInvalid
}
rrCache, err := resolveAndCache(ctx, q, nil)
switch {
case err == nil:
switch rrCache.RCode {
case dns.RcodeNameError:
return nil, true, ErrNotFound
case dns.RcodeRefused:
return nil, true, errors.New("refused")
default:
ips := rrCache.ExportAllARecords()
if len(ips) > 0 {
return ips, true, nil
}
return nil, true, ErrNotFound
}
case errors.Is(err, ErrNotFound):
return nil, true, err
case errors.Is(err, ErrBlocked):
return nil, true, err
case errors.Is(err, ErrNoCompliance):
return nil, true, err
default:
return nil, false, err
}
}

View file

@ -206,8 +206,8 @@ func (brc *BasicResolverConn) init() {
// ReportFailure reports that an error occurred with this resolver. // ReportFailure reports that an error occurred with this resolver.
func (brc *BasicResolverConn) ReportFailure() { func (brc *BasicResolverConn) ReportFailure() {
// Don't mark resolver as failed if we are offline.
if !netenv.Online() { if !netenv.Online() {
// don't mark failed if we are offline
return return
} }
@ -224,6 +224,11 @@ func (brc *BasicResolverConn) ReportFailure() {
// the fail. // the fail.
brc.networkChangedFlag.Refresh() brc.networkChangedFlag.Refresh()
} }
// Report to netenv that a configured server failed.
if brc.resolver.Info.Source == ServerSourceConfigured {
netenv.ConnectedToDNS.UnSet()
}
} }
// IsFailing returns if this resolver is currently failing. // IsFailing returns if this resolver is currently failing.
@ -255,4 +260,9 @@ func (brc *BasicResolverConn) ResetFailure() {
defer brc.failLock.Unlock() defer brc.failLock.Unlock()
brc.fails = 0 brc.fails = 0
} }
// Report to netenv that a configured server succeeded.
if brc.resolver.Info.Source == ServerSourceConfigured {
netenv.ConnectedToDNS.Set()
}
} }