mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-09-04 13:36:35 +02:00
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:
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user