diff --git a/firewall/tunnel.go b/firewall/tunnel.go index 32d60de1..08adff33 100644 --- a/firewall/tunnel.go +++ b/firewall/tunnel.go @@ -34,7 +34,7 @@ func checkTunneling(ctx context.Context, conn *network.Connection, pkt packet.Pa case conn.Process().Pid == ownPID: // Bypass tunneling for certain own connections. switch { - case captain.ClientBootstrapping(): + case !captain.ClientReady(): return case captain.IsExcepted(conn.Entity.IP): return diff --git a/netenv/online-status.go b/netenv/online-status.go index a7589a13..0ef612fd 100644 --- a/netenv/online-status.go +++ b/netenv/online-status.go @@ -37,13 +37,12 @@ var ( PortalTestIP = net.IPv4(192, 0, 2, 1) PortalTestURL = fmt.Sprintf("http://%s/", PortalTestIP) - DNSTestDomain = "one.one.one.one." - DNSTestExpectedIP = net.IPv4(1, 1, 1, 1) - - DNSFallbackTestDomain = "dns-check.safing.io." - DNSFallbackTestExpectedIP = net.IPv4(0, 65, 67, 75) // Ascii: \0ACK + DNSTestDomain = "online-check.safing.io." + DNSTestExpectedIP = net.IPv4(0, 65, 67, 75) // Ascii: \0ACK + DNSTestQueryFunc func(ctx context.Context, fdqn string) (ips []net.IP, ok bool, err error) ConnectedToSPN = abool.New() + ConnectedToDNS = abool.New() // 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, @@ -53,8 +52,6 @@ var ( // ConnectivityDomains holds all connectivity domains. This slice must not be modified. ConnectivityDomains = []string{ SpecialCaptivePortalDomain, - DNSTestDomain, // Internal DNS Check - DNSFallbackTestDomain, // Internal DNS Check // Windows "dns.msftncsi.com.", // DNS Check @@ -380,20 +377,20 @@ func monitorOnlineStatus(ctx context.Context) error { func getDynamicStatusTrigger() <-chan time.Time { switch GetOnlineStatus() { case StatusOffline: - // Will be triggered by network change anyway. - return time.After(20 * time.Second) + // Will also be triggered by network change. + return time.After(10 * time.Second) case StatusLimited, StatusPortal: // Change will not be detected otherwise, but impact is minor. return time.After(5 * time.Second) case StatusSemiOnline: // Very small impact. - return time.After(20 * time.Second) + return time.After(60 * time.Second) case StatusOnline: // Don't check until resolver reports problems. return nil case StatusUnknown: - return time.After(5 * time.Second) - default: // other unknown status + fallthrough + default: return time.After(5 * time.Minute) } } @@ -407,13 +404,18 @@ func checkOnlineStatus(ctx context.Context) { return StatusUnknown }*/ - // 0) check if connected to SPN + // 0) check if connected to SPN and/or DNS. if ConnectedToSPN.IsSet() { updateOnlineStatus(StatusOnline, nil, "connected to SPN") return } + if ConnectedToDNS.IsSet() { + updateOnlineStatus(StatusOnline, nil, "connected to DNS") + return + } + // 1) check for addresses ipv4, ipv6, err := GetAssignedAddresses() @@ -508,34 +510,28 @@ func checkOnlineStatus(ctx context.Context) { // 3) resolve a query - // Check with primary dns check domain. - ips, err := net.LookupIP(DNSTestDomain) - if err != nil { - log.Warningf("netenv: dns check query failed: %s", err) - } else { - // check for expected response - for _, ip := range ips { - if ip.Equal(DNSTestExpectedIP) { - updateOnlineStatus(StatusOnline, nil, "all checks passed") - return - } - } - } - - // 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") + // Check if we can resolve the dns check domain. + if DNSTestQueryFunc == nil { + updateOnlineStatus(StatusOnline, nil, "all checks passed, dns query check disabled") return } - // check for expected response - for _, ip := range ips { - if ip.Equal(DNSFallbackTestExpectedIP) { - updateOnlineStatus(StatusOnline, nil, "all checks passed") - return - } + ips, ok, err := DNSTestQueryFunc(ctx, DNSTestDomain) + switch { + 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") + 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") } - // unexpected response - updateOnlineStatus(StatusSemiOnline, nil, "dns check query response mismatched") } diff --git a/resolver/resolve.go b/resolver/resolve.go index 8663a32e..1b0b429e 100644 --- a/resolver/resolve.go +++ b/resolver/resolve.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "net" "sync" "time" @@ -318,8 +319,8 @@ func resolveAndCache(ctx context.Context, q *Query, oldCache *RRCache) (rrCache } // check if we are online - if primarySource != ServerSourceEnv && netenv.GetOnlineStatus() == netenv.StatusOffline { - if !netenv.IsConnectivityDomain(q.FQDN) { + if netenv.GetOnlineStatus() == netenv.StatusOffline && primarySource != ServerSourceEnv { + if q.FQDN != netenv.DNSTestDomain && !netenv.IsConnectivityDomain(q.FQDN) { // we are offline and this is not an online check query return oldCache, ErrOffline } @@ -358,6 +359,7 @@ resolveLoop: // some resolvers might also block return nil, err case netenv.GetOnlineStatus() == netenv.StatusOffline && + q.FQDN != netenv.DNSTestDomain && !netenv.IsConnectivityDomain(q.FQDN): // we are offline and this is not an online check query return oldCache, ErrOffline @@ -478,3 +480,45 @@ func shouldResetCache(q *Query) (reset bool) { 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 + } +} diff --git a/resolver/resolver.go b/resolver/resolver.go index 56d4b6cc..1f12c8f2 100644 --- a/resolver/resolver.go +++ b/resolver/resolver.go @@ -206,8 +206,8 @@ func (brc *BasicResolverConn) init() { // ReportFailure reports that an error occurred with this resolver. func (brc *BasicResolverConn) ReportFailure() { + // Don't mark resolver as failed if we are offline. if !netenv.Online() { - // don't mark failed if we are offline return } @@ -224,6 +224,11 @@ func (brc *BasicResolverConn) ReportFailure() { // the fail. 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. @@ -255,4 +260,9 @@ func (brc *BasicResolverConn) ResetFailure() { defer brc.failLock.Unlock() brc.fails = 0 } + + // Report to netenv that a configured server succeeded. + if brc.resolver.Info.Source == ServerSourceConfigured { + netenv.ConnectedToDNS.Set() + } }