Nameserver glue/IP changes are never detected — README claims they trigger a notification #105
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The README lists, under DNS Domain Monitoring, two events that trigger a notification (
README.md:30-32):> - NS added to or removed from the delegation.
> - NS IP address changed (glue record change).
The first works. The second is not implemented at all — not partially, not noisily, not at all.
Current state (audited against
origin/main, commit9347a28)detectNSChanges(internal/watcher/watcher.go:272-311) diffs only the sorted list of nameserver hostnames returned byLookupNS/FindAuthoritativeNameservers. There is nowhere to put an IP even if it wanted to:state.DomainState(internal/state/state.go:38-41) holds exactlyThere is no field for per-nameserver resolved addresses, and no code path anywhere diffs nameserver IPs.
So a delegation where
ns1.example.comsilently repoints from one IP to another — which is precisely the hijack/misconfiguration signal a DNS monitor should scream about, and which is invisible to anyone only watching NS names — produces no notification of any kind.Definition of done
state.DomainStategains storage for each nameserver's resolved IP addresses, keyed by nameserver hostname. Sort the addresses so the comparison is stable and the persisted JSON is deterministic.ResolveIPAddressesand the existing NS-host-to-IP logic used byQueryNameserveralready exist; do not hand-roll a third resolver.internal/state/state.go:64-101already contains a backward-compatiblePortStateunmarshaling shim — follow that established precedent. A test must cover loading a pre-change state file.README.md:307-363) is updated to show the new field, in the same commit.make checkis green andmake testwall time stays well under the 20-second policy ceiling — note this change adds nameserver address resolution to the domain check path, so watch that the watcher tests do not grow slower.TODO.mdis updated in the same commit.The finishing commit's title must end with
(closes #N)referencing this issue.Hard constraints
internal/watcher/interfaces.go; that seam sits above DNS and is the established pattern..golangci.yml, and do not change the golangci-lint version pin.Out of scope
Per-nameserver query status / NS failure / NS recovery is tracked in #104. That issue also touches
internal/stateand the watcher's detection functions, so expect to rebase onto it — coordinate rather than duplicating its changes.[manager] CORRECTION to the "Hard constraints" section — read this before starting.
I wrote that "watcher-level tests may continue to use the existing resolver-interface fakes in
internal/watcher/interfaces.go; that seam sits above DNS and is the established pattern." That was true ofmainand is wrong for the state this issue will be implemented against.PR #97 is
merge-readyand awaiting @sneak, and it deletesmockResolveroutright. Verified:origin/main'sinternal/watcher/watcher_test.gohas 6 references to it; PR #97's head (a535ae8) has zero and instead builds the real iterative resolver viaresolver.NewFromLogger(log). #97 also removesresolver.NewFromLoggerWithClientand deletes theTESTING.mdcarve-out that used to permit DNS mocks in packages consuming the resolver. The live-DNS policy now applies to every package.Following my original wording would reinstate precisely what #97 removed.
The pattern to follow instead
Same as the correction on #104: seed the state store with a synthetic previous observation live DNS cannot match, then run against live DNS and assert the transition. #97 uses reserved
.invalidnameserver names and RFC 5737 documentation addresses (192.0.2.0/24,203.0.113.0/24) for that synthetic prior state.This issue is a particularly good fit for that approach, because a glue change is by definition "the NS hostname stayed the same, the address set moved":
.invalidnameserver alongside the real ones — the.invalidone exercises the add/remove path while the real ones stay stable.internal/state.Keep assertions tolerant. Live DNS may return multiple A records, AAAA records, or a set that changes between runs — assert that a glue-change notification fired and that it names the right nameserver, not that the new address set equals a hardcoded list. #97 generalised its assertions for exactly this reason.
Consequences
mockResolver,NewFromLoggerWithClient, or any fake/stub DNS client, resolver, transport, or nameserver. DNS is never mocked in this repository, full stop. The port checker, TLS checker, and notifier remain legitimate test doubles — they are not DNS.main.make testceiling. Measure before and after and report both numbers in the PR.Still blocked on #97 merging, and additionally sequenced behind #104, which changes the same state and detection code. I am not dispatching an implementer for this yet.