dns intercept: port DNS-less network recovery to master

Port !997 from v1.0 onto the context-aware master recovery lifecycle. Preserve master logging and resolver APIs while adding macOS default-route DHCP detection, temporary DNS-target cleanup, and atomic recovery ownership.

Includes parser, lifecycle, failure, and concurrency regressions plus the corrected macOS QA helper. Relates to #533 and #597.
This commit is contained in:
Dev Scribe
2026-09-02 16:54:35 +07:00
committed by Cuong Manh Le
parent f96868c266
commit 80d1acdfd5
19 changed files with 1386 additions and 352 deletions
+40 -3
View File
@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"net"
"slices"
"strings"
"testing"
@@ -81,9 +82,12 @@ func TestInterfaceDNSFallbackViable(t *testing.T) {
// than depending on the runner denying a privileged operation.
type interceptFallbackHarness struct {
interceptCalls int
ensureTargetCalls int
ensuredNameservers []string
installedNameservers []string
installCalls int
resetCalls int
removeTargetCalls int
refusals []string
}
@@ -91,11 +95,11 @@ func newInterceptFallbackHarness(t *testing.T, lc *ctrld.ListenerConfig) *interc
t.Helper()
h := &interceptFallbackHarness{}
origStart, origInstall := startDNSInterceptFn, setDnsForRunningIfaceFn
origStart, origEnsure, origRemove, origInstall := startDNSInterceptFn, ensureInterceptDNSTargetFn, removeInterceptDNSTargetFn, setDnsForRunningIfaceFn
origReset, origFatal := resetDNSFn, refuseFallbackFatal
origCfg, origMode, origIntercept, origHard := cfg, interceptMode, dnsIntercept, hardIntercept
t.Cleanup(func() {
startDNSInterceptFn, setDnsForRunningIfaceFn = origStart, origInstall
startDNSInterceptFn, ensureInterceptDNSTargetFn, removeInterceptDNSTargetFn, setDnsForRunningIfaceFn = origStart, origEnsure, origRemove, origInstall
resetDNSFn, refuseFallbackFatal = origReset, origFatal
cfg, interceptMode, dnsIntercept, hardIntercept = origCfg, origMode, origIntercept, origHard
})
@@ -106,6 +110,11 @@ func newInterceptFallbackHarness(t *testing.T, lc *ctrld.ListenerConfig) *interc
h.interceptCalls++
return errors.New("dns intercept: injected start failure")
}
ensureInterceptDNSTargetFn = func(_ *prog, nameservers []string) {
h.ensureTargetCalls++
h.ensuredNameservers = slices.Clone(nameservers)
}
removeInterceptDNSTargetFn = func(_ *prog, _ string) { h.removeTargetCalls++ }
setDnsForRunningIfaceFn = func(_ *prog, nameservers []string) *net.Interface {
h.installCalls++
h.installedNameservers = nameservers
@@ -129,7 +138,29 @@ func (h *interceptFallbackHarness) run(t *testing.T) {
t.Helper()
p := &prog{cfg: &cfg}
p.logger.Store(mainLog.Load())
p.setDNS()
p.setDNS(nil)
}
func TestSetDNSEnsuresInterceptTargetAfterSuccessfulStart(t *testing.T) {
h := newInterceptFallbackHarness(t, &ctrld.ListenerConfig{IP: "127.0.0.1", Port: 5354})
startDNSInterceptFn = func(_ *prog) error {
h.interceptCalls++
return nil
}
want := []string{"fe80::1"}
p := &prog{cfg: &cfg}
p.logger.Store(mainLog.Load())
p.setDNS(want)
if h.interceptCalls != 1 || h.ensureTargetCalls != 1 {
t.Fatalf("intercept calls=%d ensure calls=%d, want 1 each", h.interceptCalls, h.ensureTargetCalls)
}
if !slices.Equal(h.ensuredNameservers, want) {
t.Fatalf("system nameservers = %v, want %v", h.ensuredNameservers, want)
}
if h.installCalls != 0 {
t.Fatalf("interface-DNS fallback installed %d time(s) after successful intercept start", h.installCalls)
}
}
func TestSetDNSExplicitOffOverridesConfig(t *testing.T) {
@@ -146,6 +177,9 @@ func TestSetDNSExplicitOffOverridesConfig(t *testing.T) {
if h.installCalls != 1 {
t.Fatalf("interface DNS installed %d time(s), want 1", h.installCalls)
}
if h.removeTargetCalls != 1 {
t.Fatalf("stale intercept DNS target cleanup called %d time(s), want 1", h.removeTargetCalls)
}
}
// TestSetDNSRefusesUnreachableFallback is the behaviour test for the reported outage: it
@@ -168,6 +202,9 @@ func TestSetDNSRefusesUnreachableFallback(t *testing.T) {
if h.resetCalls == 0 {
t.Error("host DNS was not restored before refusing, leaving the interface pointed at a ctrld that is not serving")
}
if h.removeTargetCalls != 1 {
t.Errorf("stale intercept DNS target cleanup called %d time(s), want 1 after intercept failure", h.removeTargetCalls)
}
if len(h.refusals) == 0 {
t.Fatal("refusal was not surfaced: startup must fail loudly rather than silently skip the fallback")
}