diff --git a/dns/sysresolver.go b/dns/sysresolver.go index 999047a..ec2dd63 100644 --- a/dns/sysresolver.go +++ b/dns/sysresolver.go @@ -177,11 +177,13 @@ func (m *SystemDNSMonitor) applyCandidates(raw []string) { m.mu.Lock() if dnsSlicesEqual(m.lastRaw, raw) { m.mu.Unlock() + logger.Debug("System DNS candidates unchanged, skipping health check: %v", raw) return } m.lastRaw = raw m.mu.Unlock() + logger.Debug("System DNS candidates changed, health-checking: %v", raw) validated := filterUnreachable(raw) if len(validated) == 0 { logger.Warn("None of the detected DNS servers answered a health-check query, keeping previous value: %v", raw) @@ -201,8 +203,8 @@ func (m *SystemDNSMonitor) applyCandidates(raw []string) { } } -// dnsServerReachable is a seam for tests; production code always uses probeDNSServer. -var dnsServerReachable = probeDNSServer +// dnsServerReachable is a seam for tests; production code always uses probeDNSServerErr. +var dnsServerReachable = probeDNSServerErr // filterUnreachable validates that each candidate server actually answers a // DNS query before it's trusted, rather than statically guessing from the @@ -215,12 +217,13 @@ func filterUnreachable(servers []string) []string { } reachable := make([]bool, len(servers)) + errs := make([]error, len(servers)) var wg sync.WaitGroup for i, server := range servers { wg.Add(1) go func(i int, server string) { defer wg.Done() - reachable[i] = dnsServerReachable(server) + reachable[i], errs[i] = dnsServerReachable(server) }(i, server) } wg.Wait() @@ -230,22 +233,23 @@ func filterUnreachable(servers []string) []string { if reachable[i] { result = append(result, server) } else { - logger.Debug("Discarding DNS server %s: failed health check", server) + logger.Debug("Discarding DNS server %s: failed health check: %v", server, errs[i]) } } return result } -// probeDNSServer sends a minimal root NS query to confirm a candidate server +// probeDNSServerErr sends a minimal root NS query to confirm a candidate server // actually answers, without depending on any specific external hostname being // reachable (which could itself be blocked/filtered independently of whether -// the resolver works). -func probeDNSServer(server string) bool { +// the resolver works). The returned error is kept (rather than just a bool) so +// callers can log why a candidate was rejected (unreachable route, timeout, etc.). +func probeDNSServerErr(server string) (bool, error) { client := &dns.Client{Timeout: dnsHealthCheckTimeout} msg := new(dns.Msg) msg.SetQuestion(".", dns.TypeNS) _, _, err := client.Exchange(msg, server) - return err == nil + return err == nil, err } // dnsSlicesEqual reports whether two server lists are equal regardless of order. diff --git a/dns/sysresolver_test.go b/dns/sysresolver_test.go index 8625f8a..27d5a86 100644 --- a/dns/sysresolver_test.go +++ b/dns/sysresolver_test.go @@ -12,7 +12,7 @@ import ( func stubReachable(t *testing.T, fn func(server string) bool) { t.Helper() orig := dnsServerReachable - dnsServerReachable = fn + dnsServerReachable = func(server string) (bool, error) { return fn(server), nil } t.Cleanup(func() { dnsServerReachable = orig }) } diff --git a/olm/olm.go b/olm/olm.go index df4fb3f..ef00414 100644 --- a/olm/olm.go +++ b/olm/olm.go @@ -404,6 +404,9 @@ func (o *Olm) StartTunnel(config TunnelConfig) { // with whatever DNS the host network is currently using. upstreamFromConfig := len(config.UpstreamDNS) > 0 && !(len(config.UpstreamDNS) == 1 && config.UpstreamDNS[0] == "8.8.8.8:53") + if upstreamFromConfig { + logger.Info("UpstreamDNS is statically configured (%v); automatic system DNS detection will only update PublicDNS, DNS forwarding will keep using the configured value even if it becomes unreachable on a new network", config.UpstreamDNS) + } // Start the system DNS monitor. The callback fires synchronously once with // the initial values so that PublicDNS (and optionally UpstreamDNS) are @@ -433,6 +436,8 @@ func (o *Olm) StartTunnel(config TunnelConfig) { if o.dnsProxy != nil { o.dnsProxy.SetUpstreamDNS(servers) } + } else { + logger.Debug("Not updating UpstreamDNS: statically configured to %v", config.UpstreamDNS) } }) o.dnsMonitor.Start(o.olmCtx) @@ -897,6 +902,7 @@ func (o *Olm) SetPostures(data map[string]any) { // changes. The list is applied through the same exclude-IP filtering and // change detection as the internally-polled SystemDNSMonitor. func (o *Olm) SetSystemDNS(servers []string) { + logger.Info("SetSystemDNS called with: %v", servers) if o.dnsMonitor == nil { // StartTunnel hasn't created the monitor yet (mobile platforms may push a // value the moment they start observing, before the tunnel goroutine has @@ -905,6 +911,7 @@ func (o *Olm) SetSystemDNS(servers []string) { o.pendingSystemDNSMu.Lock() o.pendingSystemDNS = servers o.pendingSystemDNSMu.Unlock() + logger.Debug("dnsMonitor not yet started, queued SetSystemDNS value") return } o.dnsMonitor.ReportExternal(servers)