//go:build windows package cli import ( "errors" "strings" "sync" "testing" "time" "github.com/Control-D-Inc/ctrld" ) // fakeNRPTOps records the Windows side effects a transition would cause and serves // scripted probe results, so the ownership decisions can be driven without touching the // registry or the DNS Client. type fakeNRPTOps struct { mu sync.Mutex // probeResults is consumed in order; the last value repeats once exhausted. probeResults []bool probeCalls int flushCalls int gpRule string // rule name findGPRule reports, "" for none gpConflict bool // whether an external catch-all targets another resolver parentEmpty bool // whether the GP parent key reads as present but empty cleanCalls int gpConflictCalls int ctrldRule bool // whether ctrld's own catch-all exists existsCalls int addCalls int removeCalls int signalCalls int addErr error removeErr error // beforeAdd runs inside addRule, while the transition lock is held. beforeAdd func() // onProbe runs after the nth probe (1-based) is answered, so a test can change the // GP store mid-probe the way a Group Policy refresh would. onProbe func(call int) // onGPConflicts runs after each gpConflicts check, which is the last ops call before // the two-phase re-add queues for the transition lock. onGPConflicts func(call int) // waitHook stands in for a cancellable wait: returning false models the intercept // being retired mid-wait. waitHook func() bool } func (f *fakeNRPTOps) ops() *nrptOps { return &nrptOps{ probe: func(*wfpState) bool { f.mu.Lock() f.probeCalls++ call := f.probeCalls result := false if len(f.probeResults) > 0 { result = f.probeResults[0] if len(f.probeResults) > 1 { f.probeResults = f.probeResults[1:] } } onProbe := f.onProbe f.mu.Unlock() if onProbe != nil { onProbe(call) } return result }, ruleExists: func() bool { f.mu.Lock() defer f.mu.Unlock() f.existsCalls++ return f.ctrldRule }, addRule: func(string) error { f.mu.Lock() beforeAdd, err := f.beforeAdd, f.addErr f.addCalls++ if err == nil { f.ctrldRule = true } f.mu.Unlock() if beforeAdd != nil { beforeAdd() } return err }, removeRule: func() error { f.mu.Lock() defer f.mu.Unlock() f.removeCalls++ if f.removeErr != nil { return f.removeErr } f.ctrldRule = false return nil }, signal: func() { f.mu.Lock() defer f.mu.Unlock() f.signalCalls++ }, findGPRule: func(string) string { f.mu.Lock() defer f.mu.Unlock() return f.gpRule }, gpRuleMatches: func(ruleName, _ string) bool { f.mu.Lock() defer f.mu.Unlock() return ruleName != "" && ruleName == f.gpRule }, gpConflicts: func(*wfpState, string) bool { f.mu.Lock() f.gpConflictCalls++ call := f.gpConflictCalls conflict, hook := f.gpConflict, f.onGPConflicts f.mu.Unlock() if hook != nil { hook(call) } return conflict }, loopback: func(*wfpState) error { return nil }, // No real waiting in tests; report "still live" unless a test says otherwise. wait: func(*wfpState, time.Duration) bool { f.mu.Lock() hook := f.waitHook f.mu.Unlock() if hook != nil { return hook() } return true }, startWFP: func(*wfpState) error { return nil }, flush: func() { f.mu.Lock() defer f.mu.Unlock() f.flushCalls++ }, parentEmpty: func(string) bool { f.mu.Lock() defer f.mu.Unlock() return f.parentEmpty }, cleanParent: func() bool { f.mu.Lock() defer f.mu.Unlock() f.cleanCalls++ wasEmpty := f.parentEmpty f.parentEmpty = false return wasEmpty }, } } func (f *fakeNRPTOps) counts() (add, remove, signal, probe int) { f.mu.Lock() defer f.mu.Unlock() return f.addCalls, f.removeCalls, f.signalCalls, f.probeCalls } func (f *fakeNRPTOps) ruleExistsCount() int { f.mu.Lock() defer f.mu.Unlock() return f.existsCalls } func (f *fakeNRPTOps) flushCount() int { f.mu.Lock() defer f.mu.Unlock() return f.flushCalls } // setGPRule replaces what the fake GP store reports, as a policy refresh would. func (f *fakeNRPTOps) setGPRule(ruleName string, conflicting bool) { f.mu.Lock() defer f.mu.Unlock() f.gpRule = ruleName f.gpConflict = conflicting } // hasCtrldRule reports whether ctrld's catch-all is currently installed. func (f *fakeNRPTOps) hasCtrldRule() bool { f.mu.Lock() defer f.mu.Unlock() return f.ctrldRule } // installedFakeNRPTOps is the fake currently standing in for the production NRPT // operations, so fixtures can hand it to tests and can tell whether one is installed at // all. var installedFakeNRPTOps *fakeNRPTOps // installFakeNRPTOps swaps in the fake for the duration of the test. func installFakeNRPTOps(t *testing.T, f *fakeNRPTOps) { t.Helper() nrptOpsForTest = f.ops() installedFakeNRPTOps = f t.Cleanup(func() { nrptOpsForTest = nil installedFakeNRPTOps = nil }) } // fakeNRPTSeed is the starting state a test wants the fixture's fake to have. It is a // separate type so the fake - which carries a mutex - is never copied. type fakeNRPTSeed struct { probeResults []bool gpRule string gpConflict bool parentEmpty bool ctrldRule bool addErr error removeErr error } // configure re-arms the fake the fixture installed with a test's own starting state, so // the fixture keeps ownership of installation - and therefore of the production-call guard. func (f *fakeNRPTOps) configure(seed fakeNRPTSeed) { f.mu.Lock() defer f.mu.Unlock() f.probeResults = seed.probeResults f.gpRule = seed.gpRule f.gpConflict = seed.gpConflict f.parentEmpty = seed.parentEmpty f.ctrldRule = seed.ctrldRule f.addErr = seed.addErr f.removeErr = seed.removeErr } // requireFakeNRPTOpsInstalled proves the fake is the table production code will actually // consult, and fails the test immediately if it is not. // // Identity is not enough on its own, and neither is asserting side-effect counters after // the fact: a fixture that never installed the fake, or a seam that stopped consulting the // override, leaves those counters at zero and every "no side effects" assertion passes // while the real registry and signalling functions run. So this calls through // prog.nrptOps() and requires the call to land on the fake. The probe is only made once an // override is known to exist, so it can never reach the host itself. func requireFakeNRPTOpsInstalled(t *testing.T, f *fakeNRPTOps) { t.Helper() if err := checkFakeNRPTOpsInstalled(f); err != nil { t.Fatal(err) } } // checkFakeNRPTOpsInstalled is the precondition itself, as a predicate so it can be tested // like any other logic - see TestFakeNRPTOpsPreconditionDetectsBypass. It returns nil only // when f is the table prog.nrptOps() hands out. func checkFakeNRPTOpsInstalled(f *fakeNRPTOps) error { if nrptOpsForTest == nil { return errors.New("fake NRPT operations are not installed: production registry and signalling functions would run") } if installedFakeNRPTOps != f { return errors.New("the installed fake is not the one this fixture returned") } before := f.ruleExistsCount() _ = (&prog{}).nrptOps().ruleExists() if f.ruleExistsCount() == before { return errors.New("prog.nrptOps() did not route to the installed fake: the seam is not in effect") } return nil } // fakeNRPTOpsForTest returns the installed fake, installing a default one if the test has // not already done so. // // Every fixture goes through this before publishing state, so no test can reach the // production registry and signalling functions. That matters because those are not // read-only: on a host where ctrld's deterministic key exists, one unfaked call can delete // live NRPT policy and force a Group Policy refresh, a Dnscache paramchange and a cache // flush. A clean runner is not a safety boundary. func fakeNRPTOpsForTest(t *testing.T) *fakeNRPTOps { t.Helper() if installedFakeNRPTOps != nil { return installedFakeNRPTOps } f := &fakeNRPTOps{} installFakeNRPTOps(t, f) return f } func newHandbackTestProg(t *testing.T) (*prog, *wfpState, *fakeNRPTOps) { t.Helper() // Never let a test fall through to the production NRPT operations, and prove it here // rather than trusting that installation happened. f := fakeNRPTOpsForTest(t) requireFakeNRPTOpsInstalled(t, f) state := &wfpState{stopCh: make(chan struct{}), listenerIP: "127.0.0.1"} p := &prog{} p.dnsInterceptState = state return p, state, f } // TestHandbackRestoresFallbackWhenGPCannotRoute is the behaviour test for the handback // contract. The first probe runs while ctrld's fallback is still installed, so that // fallback can satisfy it; only a probe taken after ctrld's keys are gone says anything // about the GP rule. With results [true, false] - the GP child looks fine until ctrld // steps aside - the transition must put ctrld's rule back and keep ctrld ownership. // // Getting this wrong deletes the last working route and declares external ownership: in // hard mode a machine-wide DNS outage, because WFP keeps blocking outbound DNS with // nothing redirecting it to ctrld. func TestHandbackRestoresFallbackWhenGPCannotRoute(t *testing.T) { f := &fakeNRPTOps{probeResults: []bool{true, false}, gpRule: "{GP-RULE}", ctrldRule: true} installFakeNRPTOps(t, f) p, state, _ := newHandbackTestProg(t) state.setNRPTPolicyOwner(nrptRuleOwnerCtrld, "") if got := p.tryAdoptMatchingGPNRPT(state); got { t.Error("tryAdoptMatchingGPNRPT() = true for a GP rule that cannot route without ctrld's rule") } if owner, _ := state.nrptPolicyOwner(); owner != nrptRuleOwnerCtrld { t.Errorf("NRPT owner = %v, want nrptRuleOwnerCtrld: ownership must not move to a rule that failed the post-removal probe", owner) } add, remove, _, _ := f.counts() if remove != 1 { t.Errorf("removeRule calls = %d, want 1: the handback probe must run with ctrld's keys gone", remove) } if add != 1 { t.Errorf("addRule calls = %d, want 1: the ctrld fallback must be restored after the probe fails", add) } if !f.hasCtrldRule() { t.Error("ctrld's NRPT rule is missing after a failed handback; the host has no working route") } } // TestHandbackAcceptsGPWhenItRoutesWithoutCtrld is the other half of the contract: when // the post-removal probe passes, external policy really is carrying DNS, so ctrld hands // ownership over and leaves its keys off the machine. func TestHandbackAcceptsGPWhenItRoutesWithoutCtrld(t *testing.T) { f := &fakeNRPTOps{probeResults: []bool{true, true}, gpRule: "{GP-RULE}", ctrldRule: true} installFakeNRPTOps(t, f) p, state, _ := newHandbackTestProg(t) state.setNRPTPolicyOwner(nrptRuleOwnerCtrld, "") if !p.tryAdoptMatchingGPNRPT(state) { t.Fatal("tryAdoptMatchingGPNRPT() = false for a GP rule that routes on its own") } owner, ruleName := state.nrptPolicyOwner() if owner != nrptRuleOwnerGroupPolicy || ruleName != "{GP-RULE}" { t.Errorf("owner = %v, rule = %q, want GroupPolicy/{GP-RULE}", owner, ruleName) } add, remove, _, _ := f.counts() if remove != 1 || add != 0 { t.Errorf("removeRule = %d, addRule = %d, want 1/0: ctrld's rule must be removed and not restored", remove, add) } if f.hasCtrldRule() { t.Error("ctrld's rule is still installed beside the adopted GP catch-all") } } // TestIneffectiveGPRuleTriggersNoSignalling holds ctrld to #576's contract for a // present-but-ineffective external rule: warn and retry WFP-only, with no policy // refresh, no Dnscache paramchange and no cache flush. signalNRPTChange is all three at // once, so on a permanently dead GP rule a signalling loop would force machine Group // Policy on a schedule. func TestIneffectiveGPRuleTriggersNoSignalling(t *testing.T) { f := &fakeNRPTOps{probeResults: []bool{false}, gpRule: "{GP-RULE}"} installFakeNRPTOps(t, f) p, state, _ := newHandbackTestProg(t) state.setNRPTPolicyOwner(nrptRuleOwnerGroupPolicy, "{GP-RULE}") // The verification path a health tick and the heal cycle both take. if got := p.nrptHandbackToExternal(state, "{GP-RULE}", "test"); got != nrptHandbackUnverified { t.Fatalf("nrptHandbackToExternal() = %v, want nrptHandbackUnverified", got) } if p.healBlockedLoopbackDNS(state, "test") { t.Error("healBlockedLoopbackDNS() = true although the probe never succeeds") } add, remove, signal, _ := f.counts() if signal != 0 { t.Errorf("signal calls = %d, want 0: ctrld must not force GP refresh, paramchange or a cache flush while external policy owns NRPT", signal) } if add != 0 || remove != 0 { t.Errorf("addRule = %d, removeRule = %d, want 0/0: external policy must be left untouched", add, remove) } if owner, _ := state.nrptPolicyOwner(); owner != nrptRuleOwnerGroupPolicy { t.Errorf("owner = %v, want nrptRuleOwnerGroupPolicy: ctrld must not seize a namespace an administrator owns", owner) } } // TestConcurrentFallbackActivationWritesOnce drives two health paths - the monitor and a // delayed recheck - into activation at the same moment. Without a transition lock they // both observe "rule missing" and both write and signal. func TestConcurrentFallbackActivationWritesOnce(t *testing.T) { f := &fakeNRPTOps{probeResults: []bool{true}} installFakeNRPTOps(t, f) p, state, _ := newHandbackTestProg(t) state.setNRPTPolicyOwner(nrptRuleOwnerCtrld, "") // Hold both callers at the barrier until each is inside activateCtrldNRPTFallback. start := make(chan struct{}) var wg sync.WaitGroup results := make([]bool, 2) for i := range results { wg.Add(1) go func(i int) { defer wg.Done() <-start results[i] = p.activateCtrldNRPTFallback(state, "concurrent test") }(i) } close(start) wg.Wait() add, _, signal, _ := f.counts() if add != 1 { t.Errorf("addRule calls = %d, want 1: concurrent activations must not both write the catch-all", add) } if signal != 1 { t.Errorf("signal calls = %d, want 1: the DNS Client must be signalled once per transition", signal) } if results[0] == results[1] { t.Errorf("both callers reported %v; exactly one transition should report having written the rule", results[0]) } if owner, _ := state.nrptPolicyOwner(); owner != nrptRuleOwnerCtrld { t.Errorf("owner = %v, want nrptRuleOwnerCtrld", owner) } } // waitFor polls cond until it holds, failing the test if it never does. func waitFor(t *testing.T, what string, cond func() bool) { t.Helper() deadline := time.Now().Add(5 * time.Second) for !cond() { if time.Now().After(deadline) { t.Fatalf("timed out waiting for %s", what) } time.Sleep(time.Millisecond) } } // TestActivationLosingRaceWithStopWritesNothing is the interleaving a revocation check // alone cannot catch: the activation passes its check, and the stop then revokes the // state, removes NRPT and finishes before the write lands. Windows would be left routing // every query to a listener that is gone. // // The test asserts the transition lock is load-bearing, in three steps. The barrier sits // inside addRule, so the stop starts while a transition is mid-write; the stop is then // shown to be unable to progress past its own NRPT cleanup while that transition holds // the lock; and once the transition releases, the stop must actually have removed the // rule before a later activation attempt is refused. Dropping the lock on either side - // activation or stop cleanup - makes the second step fail, because the stop's removeRule // then runs while the transition is still in flight. func TestActivationLosingRaceWithStopWritesNothing(t *testing.T) { f := &fakeNRPTOps{probeResults: []bool{true}} installFakeNRPTOps(t, f) p, state, _ := newHandbackTestProg(t) state.setNRPTPolicyOwner(nrptRuleOwnerCtrld, "") stopDone := make(chan struct{}) f.beforeAdd = func() { // Runs with the transition lock held, from the test goroutine. go func() { defer close(stopDone) _ = p.stopDNSIntercept() }() // The stop closes stopCh before it reaches the transition lock, so once that is // closed the stop can only be waiting on this transition. Waiting on stopCh // rather than interceptStateRevoked matters: the latter also reports the // stop-requested flag, which is set before the stop has done anything. waitFor(t, "the stop to revoke the intercept state", func() bool { select { case <-state.stopCh: return true default: return false } }) // Give it a window in which it would visibly proceed if the lock were not held, // then prove it did not: no NRPT removal, and the stop has not finished. time.Sleep(100 * time.Millisecond) if _, remove, _, _ := f.counts(); remove != 0 { t.Errorf("stop removed NRPT (removeRule calls = %d) while a transition held the lock", remove) } select { case <-stopDone: t.Error("stop completed while an NRPT transition was still in flight") default: } } if !p.activateCtrldNRPTFallback(state, "activation racing a stop") { t.Fatal("the in-flight activation should have completed its transition") } <-stopDone // The stop ran after the transition released the lock, so its cleanup must have // removed the rule this transition wrote. if f.hasCtrldRule() { t.Error("ctrld's NRPT rule survived the stop: the machine is left pointing at a listener that is gone") } if _, remove, _, _ := f.counts(); remove != 1 { t.Errorf("removeRule calls = %d, want 1: the stop must clean up the rule written by the racing transition", remove) } if p.dnsInterceptState != nil { t.Error("the stop did not complete after the in-flight transition released the lock") } // A second attempt models the other health path arriving after the stop finished. addBefore, _, signalBefore, _ := f.counts() if p.activateCtrldNRPTFallback(state, "activation after the stop completed") { t.Error("activateCtrldNRPTFallback() = true after shutdown completed") } addAfter, _, signalAfter, _ := f.counts() if addAfter != addBefore || signalAfter != signalBefore { t.Errorf("addRule %d->%d, signal %d->%d: a post-shutdown transition wrote NRPT policy for a dead listener", addBefore, addAfter, signalBefore, signalAfter) } if f.hasCtrldRule() { t.Error("a post-shutdown transition re-created ctrld's NRPT rule") } } // TestOwnedRecoveryDefersToIneffectiveExternalPolicy is the caller-level case for the // ownership boundary: state says ctrld-owned, ctrld's key has disappeared, and an exact // GP child is present but not routing. // // The handback records Group Policy ownership and reports Unverified. That is terminal: // an administrator's catch-all owns the namespace whether or not it currently routes, so // the owned-recovery flow must not go on to signal the DNS Client or delete and recreate // ctrld's rule beside it. Only loopback WFP protect is allowed, and it touches no policy. func TestOwnedRecoveryDefersToIneffectiveExternalPolicy(t *testing.T) { f := &fakeNRPTOps{probeResults: []bool{false}, gpRule: "{GP-RULE}", ctrldRule: false} installFakeNRPTOps(t, f) p, state, _ := newHandbackTestProg(t) state.setNRPTPolicyOwner(nrptRuleOwnerCtrld, "") p.nrptProbeAndHeal(state) add, remove, signal, _ := f.counts() if add != 0 || remove != 0 || signal != 0 { t.Errorf("addRule = %d, removeRule = %d, signal = %d, want 0/0/0: owned recovery must stop once external policy owns the namespace", add, remove, signal) } if f.hasCtrldRule() { t.Error("owned recovery recreated ctrld's catch-all beside the administrator's rule") } owner, ruleName := state.nrptPolicyOwner() if owner != nrptRuleOwnerGroupPolicy || ruleName != "{GP-RULE}" { t.Errorf("owner = %v, rule = %q, want GroupPolicy/{GP-RULE}", owner, ruleName) } } // TestHandbackAbortedWhenRuleCannotBeRemoved keeps a failed registry step from being // read as a verdict about external policy. func TestHandbackAbortedWhenRuleCannotBeRemoved(t *testing.T) { p, state, f := newHandbackTestProg(t) f.configure(fakeNRPTSeed{ probeResults: []bool{true, true}, gpRule: "{GP-RULE}", ctrldRule: true, removeErr: errors.New("access denied"), }) state.setNRPTPolicyOwner(nrptRuleOwnerCtrld, "") if got := p.nrptHandbackToExternal(state, "{GP-RULE}", "test"); got != nrptHandbackAborted { t.Fatalf("nrptHandbackToExternal() = %v, want nrptHandbackAborted", got) } if owner, _ := state.nrptPolicyOwner(); owner != nrptRuleOwnerCtrld { t.Errorf("owner = %v, want nrptRuleOwnerCtrld: a failed removal is not proof about external policy", owner) } } // TestHandbackRestoresFallbackWhenGPChildDisappearsMidProbe covers a Group Policy refresh // landing during the post-removal probe: the child ctrld was testing is simply gone. There // is no external policy left to hand ownership to, so ctrld's rule has to come back. func TestHandbackRestoresFallbackWhenGPChildDisappearsMidProbe(t *testing.T) { f := &fakeNRPTOps{probeResults: []bool{true, false}, gpRule: "{GP-RULE}", ctrldRule: true} installFakeNRPTOps(t, f) f.onProbe = func(call int) { if call == 2 { f.setGPRule("", false) // the administrator removed the catch-all } } p, state, _ := newHandbackTestProg(t) state.setNRPTPolicyOwner(nrptRuleOwnerCtrld, "") if got := p.nrptHandbackToExternal(state, "{GP-RULE}", "test"); got != nrptHandbackKeptCtrld { t.Fatalf("nrptHandbackToExternal() = %v, want nrptHandbackKeptCtrld", got) } if owner, _ := state.nrptPolicyOwner(); owner != nrptRuleOwnerCtrld { t.Errorf("owner = %v, want nrptRuleOwnerCtrld", owner) } if !f.hasCtrldRule() { t.Error("ctrld's rule was not restored after the GP child disappeared; the host has no route") } if add, remove, _, _ := f.counts(); add != 1 || remove != 1 { t.Errorf("addRule = %d, removeRule = %d, want 1/1", add, remove) } } // TestHandbackWritesNoSiblingWhenGPChildTurnsConflicting is the case the same-child // re-read exists for. ctrld removes its keys to test an exact catch-all, and during the // probe Group Policy replaces it with one that targets another resolver (or a malformed // one). Restoring ctrld's rule would create exactly the competing sibling beside // administrator policy that this must never write, so the rule stays off and ownership // goes to nobody. func TestHandbackWritesNoSiblingWhenGPChildTurnsConflicting(t *testing.T) { f := &fakeNRPTOps{probeResults: []bool{true, false}, gpRule: "{GP-RULE}", ctrldRule: true} installFakeNRPTOps(t, f) f.onProbe = func(call int) { if call == 2 { // The child now names another resolver: not a match for this listener, and // reported as a conflict by the GP classifier. f.setGPRule("", true) } } p, state, _ := newHandbackTestProg(t) state.setNRPTPolicyOwner(nrptRuleOwnerCtrld, "") if got := p.nrptHandbackToExternal(state, "{GP-RULE}", "test"); got != nrptHandbackConflict { t.Fatalf("nrptHandbackToExternal() = %v, want nrptHandbackConflict", got) } if add, _, _, _ := f.counts(); add != 0 { t.Errorf("addRule calls = %d, want 0: restoring here writes a competing rule beside administrator policy", add) } if f.hasCtrldRule() { t.Error("ctrld's rule is installed beside a GP catch-all that targets another resolver") } if owner, _ := state.nrptPolicyOwner(); owner != nrptRuleOwnerNone { t.Errorf("owner = %v, want nrptRuleOwnerNone: ctrld owns nothing once external policy points elsewhere", owner) } } // TestHandbackWithoutCtrldRuleClassifiesAfterFailedProbe covers the other short-circuit: // with no ctrld rule to remove, a failed probe must still be classified before anything is // recorded. A child that changed into a conflicting catch-all is not an ineffective ctrld // candidate, and must not be recorded as one. func TestHandbackWithoutCtrldRuleClassifiesAfterFailedProbe(t *testing.T) { f := &fakeNRPTOps{probeResults: []bool{false}, gpRule: "{GP-RULE}"} installFakeNRPTOps(t, f) f.onProbe = func(call int) { if call == 1 { f.setGPRule("", true) } } p, state, _ := newHandbackTestProg(t) state.setNRPTPolicyOwner(nrptRuleOwnerCtrld, "") if got := p.nrptHandbackToExternal(state, "{GP-RULE}", "test"); got != nrptHandbackConflict { t.Fatalf("nrptHandbackToExternal() = %v, want nrptHandbackConflict", got) } if add, remove, signal, _ := f.counts(); add != 0 || remove != 0 || signal != 0 { t.Errorf("addRule = %d, removeRule = %d, signal = %d, want 0/0/0", add, remove, signal) } owner, ruleName := state.nrptPolicyOwner() if owner != nrptRuleOwnerNone || ruleName != "" { t.Errorf("owner = %v, rule = %q, want nrptRuleOwnerNone/\"\": a conflicting child must not be recorded as ctrld's external owner", owner, ruleName) } } // TestHandbackGivesReplacementChildItsOwnPass keeps an unproved child from inheriting a // verdict that was measured against a different one. With no ctrld rule installed the // replacement is simply probed on its own pass; failing that pass records it as external // policy that is not routing, and writes nothing either way. func TestHandbackGivesReplacementChildItsOwnPass(t *testing.T) { f := &fakeNRPTOps{probeResults: []bool{false, false}, gpRule: "{GP-RULE}"} installFakeNRPTOps(t, f) f.onProbe = func(call int) { if call == 1 { f.setGPRule("{NEW-GP-RULE}", false) } } p, state, _ := newHandbackTestProg(t) if got := p.nrptHandbackToExternal(state, "{GP-RULE}", "test"); got != nrptHandbackUnverified { t.Fatalf("nrptHandbackToExternal() = %v, want nrptHandbackUnverified", got) } if add, remove, signal, _ := f.counts(); add != 0 || remove != 0 || signal != 0 { t.Errorf("addRule = %d, removeRule = %d, signal = %d, want 0/0/0", add, remove, signal) } owner, ruleName := state.nrptPolicyOwner() if owner != nrptRuleOwnerGroupPolicy || ruleName != "{NEW-GP-RULE}" { t.Errorf("owner = %v, rule = %q, want GroupPolicy/{NEW-GP-RULE}: the verdict must name the child it was measured against", owner, ruleName) } } // TestHandbackWritesNoSiblingWhenReplacementChildTakesOver is the case where ctrld's rule // was removed for the probe and a *different* exact catch-all took the namespace while it // was off. // // Restoring ctrld's rule here is not a return to the status quo: the replacement was not // there when the transition started, and addNRPTCatchAllRule writes ctrld's own GP // catch-all whenever another GP rule exists, so the restore would plant a sibling beside // the administrator's new rule. The replacement gets its own probe instead, and a failed // one leaves NRPT to Group Policy with nothing of ctrld's written. func TestHandbackWritesNoSiblingWhenReplacementChildTakesOver(t *testing.T) { // [pre-probe true, post-removal probe false, replacement's own pass false] f := &fakeNRPTOps{probeResults: []bool{true, false, false}, gpRule: "{GP-RULE}", ctrldRule: true} installFakeNRPTOps(t, f) f.onProbe = func(call int) { if call == 2 { f.setGPRule("{NEW-GP-RULE}", false) // a policy refresh swaps the child } } p, state, _ := newHandbackTestProg(t) state.setNRPTPolicyOwner(nrptRuleOwnerCtrld, "") if got := p.nrptHandbackToExternal(state, "{GP-RULE}", "test"); got != nrptHandbackUnverified { t.Fatalf("nrptHandbackToExternal() = %v, want nrptHandbackUnverified", got) } if add, remove, _, _ := f.counts(); add != 0 || remove != 1 { t.Errorf("addRule = %d, removeRule = %d, want 0/1: ctrld's rule must not be restored beside a replacement catch-all", add, remove) } if f.hasCtrldRule() { t.Error("ctrld's rule is installed beside an administrator catch-all that took the namespace") } owner, ruleName := state.nrptPolicyOwner() if owner != nrptRuleOwnerGroupPolicy || ruleName != "{NEW-GP-RULE}" { t.Errorf("owner = %v, rule = %q, want GroupPolicy/{NEW-GP-RULE}", owner, ruleName) } } // TestHandbackAdoptsReplacementChildThatRoutes is the same swap where the replacement does // carry DNS on its own: it is adopted, still with no ctrld write. func TestHandbackAdoptsReplacementChildThatRoutes(t *testing.T) { f := &fakeNRPTOps{probeResults: []bool{true, false, true}, gpRule: "{GP-RULE}", ctrldRule: true} installFakeNRPTOps(t, f) f.onProbe = func(call int) { if call == 2 { f.setGPRule("{NEW-GP-RULE}", false) } } p, state, _ := newHandbackTestProg(t) state.setNRPTPolicyOwner(nrptRuleOwnerCtrld, "") if got := p.nrptHandbackToExternal(state, "{GP-RULE}", "test"); got != nrptHandbackVerified { t.Fatalf("nrptHandbackToExternal() = %v, want nrptHandbackVerified", got) } if add, _, _, _ := f.counts(); add != 0 { t.Errorf("addRule calls = %d, want 0", add) } owner, ruleName := state.nrptPolicyOwner() if owner != nrptRuleOwnerGroupPolicy || ruleName != "{NEW-GP-RULE}" { t.Errorf("owner = %v, rule = %q, want GroupPolicy/{NEW-GP-RULE}", owner, ruleName) } } // TestHandbackChurnFailsSafeToCurrentExternalOwner bounds the extra pass without letting // exhaustion become a licence to write. // // When the probe budget runs out with the store still changing, the transition classifies // the store as it stands and proves no route. Reporting "undecided" instead would be // unsafe: that disposition is not terminal external ownership, so startup and owned // recovery fall through to writing ctrld's rule, and addNRPTCatchAllRule puts it in the GP // path beside whichever exact catch-all is there now. func TestHandbackChurnFailsSafeToCurrentExternalOwner(t *testing.T) { f := &fakeNRPTOps{probeResults: []bool{true, false, false}, gpRule: "{GP-RULE}", ctrldRule: true} installFakeNRPTOps(t, f) f.onProbe = func(call int) { switch call { case 2: f.setGPRule("{GP-RULE-2}", false) case 3: f.setGPRule("{GP-RULE-3}", false) } } p, state, _ := newHandbackTestProg(t) state.setNRPTPolicyOwner(nrptRuleOwnerCtrld, "") got := p.nrptHandbackToExternal(state, "{GP-RULE}", "test") if got != nrptHandbackUnverified { t.Fatalf("nrptHandbackToExternal() = %v, want nrptHandbackUnverified", got) } if !nrptExternalOwns(got) { t.Error("the churn disposition is not terminal external ownership; callers would fall through and write a sibling") } if add, _, _, _ := f.counts(); add != 0 { t.Errorf("addRule calls = %d, want 0: no sibling write while the GP store is churning", add) } owner, ruleName := state.nrptPolicyOwner() if owner != nrptRuleOwnerGroupPolicy || ruleName != "{GP-RULE-3}" { t.Errorf("owner = %v, rule = %q, want GroupPolicy/{GP-RULE-3}: exhaustion must record the store as it stands", owner, ruleName) } } // TestHandbackChurnFailsSafeToConflict covers exhaustion where the store has settled on a // catch-all that does not target ctrld: still terminal, still no write. func TestHandbackChurnFailsSafeToConflict(t *testing.T) { f := &fakeNRPTOps{probeResults: []bool{true, false, false}, gpRule: "{GP-RULE}", ctrldRule: true} installFakeNRPTOps(t, f) f.onProbe = func(call int) { switch call { case 2: f.setGPRule("{GP-RULE-2}", false) case 3: f.setGPRule("", true) // now an administrator catch-all for another resolver } } p, state, _ := newHandbackTestProg(t) state.setNRPTPolicyOwner(nrptRuleOwnerCtrld, "") if got := p.nrptHandbackToExternal(state, "{GP-RULE}", "test"); got != nrptHandbackConflict { t.Fatalf("nrptHandbackToExternal() = %v, want nrptHandbackConflict", got) } if add, _, _, _ := f.counts(); add != 0 { t.Errorf("addRule calls = %d, want 0", add) } if owner, _ := state.nrptPolicyOwner(); owner != nrptRuleOwnerNone { t.Errorf("owner = %v, want nrptRuleOwnerNone", owner) } } // TestHandbackChurnRestoresFallbackWhenNamespaceFreed is the third exhaustion outcome: the // store ended up with no external catch-all at all, so the namespace is free and ctrld's // rule is the one that belongs there. func TestHandbackChurnRestoresFallbackWhenNamespaceFreed(t *testing.T) { f := &fakeNRPTOps{probeResults: []bool{true, false, false}, gpRule: "{GP-RULE}", ctrldRule: true} installFakeNRPTOps(t, f) f.onProbe = func(call int) { switch call { case 2: f.setGPRule("{GP-RULE-2}", false) case 3: f.setGPRule("", false) // the administrator removed the policy entirely } } p, state, _ := newHandbackTestProg(t) state.setNRPTPolicyOwner(nrptRuleOwnerCtrld, "") if got := p.nrptHandbackToExternal(state, "{GP-RULE}", "test"); got != nrptHandbackKeptCtrld { t.Fatalf("nrptHandbackToExternal() = %v, want nrptHandbackKeptCtrld", got) } if !f.hasCtrldRule() { t.Error("ctrld's rule was not restored although no external catch-all remains") } if owner, _ := state.nrptPolicyOwner(); owner != nrptRuleOwnerCtrld { t.Errorf("owner = %v, want nrptRuleOwnerCtrld", owner) } } // TestActivationWritesNoSiblingWhileGPStoreChurns is the caller-level closure for the churn // path. activateCtrldNRPTFallback is the function both startup fallback and owned recovery // reach when ctrld's rule is missing, and it is where a non-terminal churn disposition // would turn into addNRPTCatchAllRule writing a GP sibling beside the current catch-all. func TestActivationWritesNoSiblingWhileGPStoreChurns(t *testing.T) { f := &fakeNRPTOps{probeResults: []bool{false, false}, gpRule: "{GP-RULE}"} installFakeNRPTOps(t, f) f.onProbe = func(call int) { switch call { case 1: f.setGPRule("{GP-RULE-2}", false) case 2: f.setGPRule("{GP-RULE-3}", false) } } p, state, _ := newHandbackTestProg(t) // Startup and the owner-None retry both arrive here with no ownership recorded. state.setNRPTPolicyOwner(nrptRuleOwnerNone, "") if p.activateCtrldNRPTFallback(state, "churn test") { t.Error("activateCtrldNRPTFallback() = true while an external catch-all owns the namespace") } add, remove, signal, _ := f.counts() if add != 0 || signal != 0 { t.Errorf("addRule = %d, signal = %d, want 0/0: ctrld must not write beside the administrator's catch-all", add, signal) } if remove != 0 { t.Errorf("removeRule calls = %d, want 0: there was no ctrld rule to remove", remove) } if owner, _ := state.nrptPolicyOwner(); owner != nrptRuleOwnerGroupPolicy { t.Errorf("owner = %v, want nrptRuleOwnerGroupPolicy: the current external catch-all owns the namespace", owner) } } // TestStopLeavesGPManagedPolicyAlone is the central promise of this work: an // administrator's catch-all is a deployment contract that must survive service stop, // restart and uninstall. A regression that removed NRPT for every owner would delete that // rule and take the fleet's DNS policy with it. func TestStopLeavesGPManagedPolicyAlone(t *testing.T) { f := &fakeNRPTOps{gpRule: "{GP-RULE}"} installFakeNRPTOps(t, f) p, state, _ := newHandbackTestProg(t) state.setNRPTPolicyOwner(nrptRuleOwnerGroupPolicy, "{GP-RULE}") if err := p.stopDNSIntercept(); err != nil { t.Fatalf("stopDNSIntercept() = %v", err) } add, remove, signal, _ := f.counts() if remove != 0 || signal != 0 || add != 0 { t.Errorf("removeRule = %d, signal = %d, addRule = %d, want 0/0/0: shutdown must not touch externally owned NRPT policy", remove, signal, add) } if f.flushCount() != 0 { t.Errorf("flush calls = %d, want 0: no cache flush is owed for a rule ctrld does not own", f.flushCount()) } if p.dnsInterceptState != nil { t.Error("dnsInterceptState survived shutdown") } } // TestStopRemovesOrphanWhileLeavingGPPolicy is the same shutdown with a ctrld rule left // behind by an earlier unclean exit. External policy still stays, but the orphan must go: // while GP mode hides the local store this stop is the last chance anything looks there. func TestStopRemovesOrphanWhileLeavingGPPolicy(t *testing.T) { f := &fakeNRPTOps{gpRule: "{GP-RULE}", ctrldRule: true} installFakeNRPTOps(t, f) p, state, _ := newHandbackTestProg(t) state.setNRPTPolicyOwner(nrptRuleOwnerGroupPolicy, "{GP-RULE}") if err := p.stopDNSIntercept(); err != nil { t.Fatalf("stopDNSIntercept() = %v", err) } add, remove, signal, _ := f.counts() if remove != 1 || signal != 1 { t.Errorf("removeRule = %d, signal = %d, want 1/1: the orphaned ctrld rule must be removed on the way out", remove, signal) } if add != 0 { t.Errorf("addRule calls = %d, want 0", add) } if f.hasCtrldRule() { t.Error("the orphaned ctrld rule survived shutdown; a later GP removal would activate it") } if f.flushCount() != 0 { t.Errorf("flush calls = %d, want 0: the ctrld-owned branch is what owes a flush, not the GP branch", f.flushCount()) } } // TestRemoveOrphanedCtrldNRPTRule covers the sweep directly, including that it is a no-op // when there is nothing of ctrld's on disk - it runs on shutdown paths where the common // case is a clean machine. func TestRemoveOrphanedCtrldNRPTRule(t *testing.T) { t.Run("removes and signals when a ctrld rule exists", func(t *testing.T) { f := &fakeNRPTOps{ctrldRule: true} installFakeNRPTOps(t, f) p, _, _ := newHandbackTestProg(t) p.removeOrphanedCtrldNRPTRule("test") add, remove, signal, _ := f.counts() if remove != 1 || signal != 1 || add != 0 { t.Errorf("removeRule = %d, signal = %d, addRule = %d, want 1/1/0", remove, signal, add) } if f.hasCtrldRule() { t.Error("the ctrld rule is still installed") } }) t.Run("does nothing when no ctrld rule exists", func(t *testing.T) { f := &fakeNRPTOps{} installFakeNRPTOps(t, f) p, _, _ := newHandbackTestProg(t) p.removeOrphanedCtrldNRPTRule("test") add, remove, signal, _ := f.counts() if remove != 0 || signal != 0 || add != 0 { t.Errorf("removeRule = %d, signal = %d, addRule = %d, want 0/0/0: nothing to sweep must cost no registry writes and no signalling", remove, signal, add) } }) } // TestHandbackThrottleBlocksTheSecondAttempt exercises the throttle through the // transition, not just its accessor. Each attempt removes the live catch-all for a probe, // so a second attempt inside the window must cost nothing at all: no probe, no removal. func TestHandbackThrottleBlocksTheSecondAttempt(t *testing.T) { f := &fakeNRPTOps{probeResults: []bool{true, false}, gpRule: "{GP-RULE}", ctrldRule: true} installFakeNRPTOps(t, f) p, state, _ := newHandbackTestProg(t) state.setNRPTPolicyOwner(nrptRuleOwnerCtrld, "") if got := p.nrptHandbackToExternal(state, "{GP-RULE}", "first"); got != nrptHandbackKeptCtrld { t.Fatalf("first nrptHandbackToExternal() = %v, want nrptHandbackKeptCtrld", got) } _, removeAfterFirst, _, probesAfterFirst := f.counts() if got := p.nrptHandbackToExternal(state, "{GP-RULE}", "second"); got != nrptHandbackAborted { t.Fatalf("second nrptHandbackToExternal() = %v, want nrptHandbackAborted", got) } _, remove, _, probes := f.counts() if probes != probesAfterFirst { t.Errorf("probe calls went from %d to %d: a throttled attempt must not probe", probesAfterFirst, probes) } if remove != removeAfterFirst { t.Errorf("removeRule calls went from %d to %d: a throttled attempt must not remove the live rule", removeAfterFirst, remove) } if owner, _ := state.nrptPolicyOwner(); owner != nrptRuleOwnerCtrld { t.Errorf("owner = %v, want nrptRuleOwnerCtrld", owner) } } // TestHealLadderRunsRetriesThenTwoPhaseRecovery drives the ctrld-owned heal cycle end to // end: the immediate probe, the signal-and-backoff retries, then the two-phase delete and // re-add, with the last probe finally succeeding. Each nrptTransition phase is asserted // through its side effects, since these are its only call sites. func TestHealLadderRunsRetriesThenTwoPhaseRecovery(t *testing.T) { // Every probe fails until the one after the re-add. f := &fakeNRPTOps{ probeResults: []bool{false, false, false, false, true}, ctrldRule: true, } installFakeNRPTOps(t, f) p, state, _ := newHandbackTestProg(t) state.setNRPTPolicyOwner(nrptRuleOwnerCtrld, "") p.nrptProbeAndHeal(state) add, remove, signal, probes := f.counts() if remove != 1 { t.Errorf("removeRule calls = %d, want 1: phase one of the two-phase recovery deletes ctrld's rule once", remove) } if add != 1 { t.Errorf("addRule calls = %d, want 1: phase two re-adds it once", add) } if !f.hasCtrldRule() { t.Error("ctrld's rule is missing after the two-phase recovery") } // Three backoff rounds signal, then the delete phase and the re-add phase. if signal < 5 { t.Errorf("signal calls = %d, want at least 5 (three retries plus both recovery phases)", signal) } if probes < 5 { t.Errorf("probe calls = %d, want at least 5", probes) } if owner, _ := state.nrptPolicyOwner(); owner != nrptRuleOwnerCtrld { t.Errorf("owner = %v, want nrptRuleOwnerCtrld", owner) } } // TestHealLadderStopsWhenGPAppearsBeforeTwoPhase covers a Group Policy catch-all landing // during the backoff retries. The ladder must hand back and stop before the destructive // delete-and-re-add phase, and must not write ctrld's rule beside the new policy. func TestHealLadderStopsWhenGPAppearsBeforeTwoPhase(t *testing.T) { f := &fakeNRPTOps{probeResults: []bool{false}, ctrldRule: true} installFakeNRPTOps(t, f) // The administrator's rule appears while the fourth probe is in flight, which is the // last one before the two-phase recovery. f.onProbe = func(call int) { if call == 4 { f.setGPRule("{GP-RULE}", false) } } p, state, _ := newHandbackTestProg(t) state.setNRPTPolicyOwner(nrptRuleOwnerCtrld, "") p.nrptProbeAndHeal(state) add, remove, _, _ := f.counts() if remove != 0 { t.Errorf("removeRule calls = %d, want 0: the ladder must stop before the delete half of the two-phase recovery", remove) } if add != 0 { t.Errorf("addRule calls = %d, want 0: the ladder must not recreate ctrld's rule beside a new GP catch-all", add) } if !f.hasCtrldRule() { t.Error("ctrld's rule was deleted after an administrator catch-all appeared") } owner, ruleName := state.nrptPolicyOwner() if owner != nrptRuleOwnerGroupPolicy || ruleName != "{GP-RULE}" { t.Errorf("owner = %v, rule = %q, want GroupPolicy/{GP-RULE}: the ladder must defer rather than continue", owner, ruleName) } } // TestHealLadderCleansEmptyGPParentBeforeRetrying pins the empty-GP-parent shortcut: an // empty parent key puts the DNS Client in GP mode where ctrld's local rule is invisible, so // signalling retries cannot succeed until it is deleted. func TestHealLadderCleansEmptyGPParentBeforeRetrying(t *testing.T) { f := &fakeNRPTOps{probeResults: []bool{false, true}, ctrldRule: true, parentEmpty: true} installFakeNRPTOps(t, f) p, state, _ := newHandbackTestProg(t) state.setNRPTPolicyOwner(nrptRuleOwnerCtrld, "") p.nrptProbeAndHeal(state) if f.cleanCalls != 1 { t.Errorf("cleanParent calls = %d, want 1: the empty GP parent must be removed before burning retries", f.cleanCalls) } if add, remove, _, _ := f.counts(); add != 0 || remove != 0 { t.Errorf("addRule = %d, removeRule = %d, want 0/0: the probe passed after the cleanup, so no recovery was needed", add, remove) } } // TestRepairMissingWFPRetriesHardModeWithNoEngine covers the entry point a startup WFP // failure depends on. In hard mode a closed engine means nothing is being blocked, so the // health monitor must keep retrying; in dns mode a closed engine is the normal state and // must not trigger anything. func TestRepairMissingWFPRetriesHardModeWithNoEngine(t *testing.T) { originalRebuild, originalHard := rebuildDNSInterceptFn, hardIntercept t.Cleanup(func() { rebuildDNSInterceptFn, hardIntercept = originalRebuild, originalHard }) var reasons []string rebuildDNSInterceptFn = func(_ *prog, _ *wfpState, reason string) interceptRebuildResult { reasons = append(reasons, reason) return interceptRebuildDone } t.Run("hard mode retries when the engine never opened", func(t *testing.T) { reasons = nil hardIntercept = true p, state, _ := newHandbackTestProg(t) if !p.repairMissingWFP(state) { t.Error("repairMissingWFP() = false; the monitor must hand over to the rebuilt intercept") } if len(reasons) != 1 { t.Fatalf("rebuild requests = %d, want 1: hard mode with no WFP engine must be retried", len(reasons)) } if !strings.Contains(reasons[0], "no WFP engine") { t.Errorf("rebuild reason = %q, want it to name the missing engine", reasons[0]) } }) t.Run("dns mode leaves a closed engine alone", func(t *testing.T) { reasons = nil hardIntercept = false p, state, _ := newHandbackTestProg(t) if p.repairMissingWFP(state) { t.Error("repairMissingWFP() = true in dns mode; a closed engine is normal there") } if len(reasons) != 0 { t.Errorf("rebuild requests = %d, want 0", len(reasons)) } }) } // TestPhaseTwoRevalidatesOwnershipAfterWaitingForTheLock is the ordering a pre-lock check // cannot cover. Phase one removes ctrld's rule and releases the transition lock; the // pre-re-add checks see no GP rule; another health path then takes the lock, hands the // namespace to a GP catch-all that has just arrived, and records GroupPolicy. Phase two // acquires the lock afterwards. // // Without a re-read inside the locked closure, phase two re-adds ctrld's rule beside the // administrator's catch-all and stamps ctrld ownership over theirs - exactly the competing // policy this work exists to prevent. func TestPhaseTwoRevalidatesOwnershipAfterWaitingForTheLock(t *testing.T) { p, state, f := newHandbackTestProg(t) // Every probe fails, so the ladder runs to the two-phase recovery. f.configure(fakeNRPTSeed{probeResults: []bool{false}, ctrldRule: true}) state.setNRPTPolicyOwner(nrptRuleOwnerCtrld, "") // The last ops call before phase two queues for the lock. Hold the lock from another // goroutine and complete the competing transition while phase two waits for it. f.onGPConflicts = func(call int) { if call != 1 { return } locked := make(chan struct{}) go func() { p.nrptTransitionMu.Lock() close(locked) f.setGPRule("{GP-RULE}", false) state.setNRPTPolicyOwner(nrptRuleOwnerGroupPolicy, "{GP-RULE}") // Hold it long enough that phase two must queue behind this transition. time.Sleep(100 * time.Millisecond) p.nrptTransitionMu.Unlock() }() <-locked } p.nrptProbeAndHeal(state) if add, _, _, _ := f.counts(); add != 0 { t.Errorf("addRule calls = %d, want 0: phase two re-added ctrld's rule beside a GP catch-all that took ownership while it waited for the lock", add) } if f.hasCtrldRule() { t.Error("ctrld's rule is installed beside the administrator's catch-all") } owner, ruleName := state.nrptPolicyOwner() if owner != nrptRuleOwnerGroupPolicy || ruleName != "{GP-RULE}" { t.Errorf("owner = %v, rule = %q, want GroupPolicy/{GP-RULE}: phase two overwrote a newer ownership transition", owner, ruleName) } } // TestRetiredHealDoesNotDeleteSuccessorRule covers the other side of a rebuild. The heal // cycle re-adds ctrld's rule, then its final settle wait observes that its own state was // retired - while a successor intercept is already running with the same deterministic key. // // The key is process-global, so any cleanup from here would delete the successor's route // and leave hard mode blocking DNS with nothing redirecting it. Teardown of the retired // state owns that cleanup instead. func TestRetiredHealDoesNotDeleteSuccessorRule(t *testing.T) { p, state, f := newHandbackTestProg(t) f.configure(fakeNRPTSeed{probeResults: []bool{false}, ctrldRule: true}) state.setNRPTPolicyOwner(nrptRuleOwnerCtrld, "") successor := &wfpState{stopCh: make(chan struct{}), listenerIP: "127.0.0.1"} // Retire this cycle's state during the final wait - the one after the re-add, which is // the only wait where both a removal and an add have happened - and publish a successor // that owns the same deterministic key. f.waitHook = func() bool { if add, remove, _, _ := f.counts(); add == 0 || remove == 0 { return true } close(state.stopCh) // a rebuild retired the old state p.dnsInterceptState = successor successor.setNRPTPolicyOwner(nrptRuleOwnerCtrld, "") return false } p.nrptProbeAndHeal(state) if !f.hasCtrldRule() { t.Error("the successor's NRPT rule was deleted by a retired heal cycle") } if owner, _ := successor.nrptPolicyOwner(); owner != nrptRuleOwnerCtrld { t.Errorf("successor owner = %v, want nrptRuleOwnerCtrld", owner) } } // TestStartupReportsFailureWhenExternalPolicyNeverRoutes is the readiness direction of the // GP-managed contract, driven through the real startup control flow. // // An exact GP child is present and no probe ever reaches ctrld. Startup must keep the // recovery state and the monitor - the rule may start routing later, and only external // policy can fix it - must leave adapter DNS alone, and must not write an owned fallback. // What it must not do is return success: setDNS records readiness from a nil error, so // reporting ready here publishes "service healthy" while the DNS Client is not delivering // queries to ctrld, and in hard mode WFP is blocking every other resolver at the same time. func TestStartupReportsFailureWhenExternalPolicyNeverRoutes(t *testing.T) { originalCfg, originalMode, originalIntercept, originalHard := cfg, interceptMode, dnsIntercept, hardIntercept t.Cleanup(func() { cfg, interceptMode, dnsIntercept, hardIntercept = originalCfg, originalMode, originalIntercept, originalHard }) cfg = ctrld.Config{} cfg.Listener = map[string]*ctrld.ListenerConfig{"0": {IP: "127.0.0.1", Port: 53}} interceptMode, dnsIntercept, hardIntercept = "dns", true, false p, _, f := newHandbackTestProg(t) p.cfg = &cfg p.dnsInterceptState = nil // startup publishes its own state // An exact GP child that never answers a probe. f.configure(fakeNRPTSeed{probeResults: []bool{false}, gpRule: "{GP-RULE}"}) err := p.startDNSInterceptLocked() if err == nil { t.Fatal("startDNSInterceptLocked() = nil: startup reported success while no probe reached ctrld, so setDNS would mark the service ready") } if !interceptFailedUnderExternalDNSPolicy(err) { t.Errorf("err = %v; the failure must tell setDNS to leave adapter DNS untouched", err) } if interceptFailedWithVerifiedExternalDNS(err) { t.Errorf("err = %v; this route was never verified, so it must not read as the verified case", err) } if add, remove, signal, _ := f.counts(); add != 0 || remove != 0 || signal != 0 { t.Errorf("addRule = %d, removeRule = %d, signal = %d, want 0/0/0: no owned fallback may be written beside an administrator catch-all", add, remove, signal) } // Recovery must stay live so the rule can be re-tested. state, ok := p.dnsInterceptState.(*wfpState) if !ok || state == nil { t.Fatal("intercept state was not published; nothing would keep re-testing the external rule") } if owner, ruleName := state.nrptPolicyOwner(); owner != nrptRuleOwnerGroupPolicy || ruleName != "{GP-RULE}" { t.Errorf("owner = %v, rule = %q, want GroupPolicy/{GP-RULE}", owner, ruleName) } // Stop the monitor startup launched. _ = p.stopDNSIntercept() } // TestFakeNRPTOpsPreconditionDetectsBypass guards the guard. The fixtures' promise is that // no test can reach the production registry and signalling functions, and that promise is // only as good as this precondition: if it stops detecting a bypass, every "no side // effects" assertion in the Windows suite silently becomes vacuous while the real host // policy is at risk. func TestFakeNRPTOpsPreconditionDetectsBypass(t *testing.T) { f := &fakeNRPTOps{} // The exact bypass to catch: a fixture that returns a fake it never installed. if err := checkFakeNRPTOpsInstalled(f); err == nil { t.Error("an uninstalled fake passed the precondition; a test could fall through to production NRPT operations") } installFakeNRPTOps(t, f) if err := checkFakeNRPTOpsInstalled(f); err != nil { t.Errorf("an installed fake failed the precondition: %v", err) } // A different fake than the installed one must not pass either: side-effect counters // would be read from an object nothing consults. if err := checkFakeNRPTOpsInstalled(&fakeNRPTOps{}); err == nil { t.Error("a fake that is not the installed one passed the precondition") } }