Router-hosts-generated unbound zone replaces dnsmasq overlay
Date: 2026-07-07 Status: Accepted Decision: hl-fyy5 Deciders: Sean Brandt
Context
Section titled “Context”The Firewalla’s per-name dnsmasq local=/address= overlay (ADR hl-qra1) only covers A/AAAA; pre-2.90 dnsmasq forwards unrecognized RR types (e.g. type-65 HTTPS/ECH) regardless of local=, causing a third recurrence of the split-horizon leak (AAAA: hl-zh3w/hl-qra1; HTTPS/ECH: hl-hqyv, which broke id.fzymgc.house in Chromium and blocked all mcp-gw OAuth). The zone was also hand-applied during the incident and had a dual-source inventory gap (k8s-cluster.fzymgc.house NXDOMAIN).
Decision
Section titled “Decision”Make router-hosts’s native unbound_conf_path output (per-name local-zone "<name>." static + local-data) the single generator of the fzymgc.house zone from a single complete inventory (tf/router-hosts); a host-side systemd .path unit reloads unbound on change; the dnsmasq per-name overlay retires so dnsmasq becomes a pure interceptor forwarding to unbound.
Rationale
Section titled “Rationale”- Reuses the already-hardened Go generator (dnsmasqconf.go pattern, rh#326-328) instead of a from-scratch Ansible/Jinja re-implementation that cannot regenerate between deploys.
- Decouples generation (container, cannot systemctl) from activation (host path unit, correct regardless of writer).
- Per-name
staticis the only mode empirically verified to NODATA missing RR types without NXDOMAINing unmanaged siblings or the Tailscale MagicDNS path (the hl-2t4o regression). - One declarative inventory eliminates the dual-source drift that caused the k8s-cluster gap.
Alternatives Considered
Section titled “Alternatives Considered”- router-hosts-native unbound_conf_path + host systemd .path-unit reload (chosen): hardened generator, regenerates on startup and every host mutation, activation works regardless of who writes.
- Ansible/Jinja zone generation in this repo (rejected): reimplements the existing 110-line Go generator (two point releases to harden startup/regen semantics) and cannot regenerate between deploys.
- Zone-wide local-zone static / typetransparent (rejected): typetransparent passes missing types to recursion (leak not closed); zone-wide static NXDOMAINs unmanaged siblings including MagicDNS (hl-2t4o regression).
- Keep dual dnsmasq + unbound authority (rejected): dual sources drift (caused the k8s-cluster NXDOMAIN gap); if unbound is ever bypassed the dnsmasq layer silently reopens the RR-type leak.
Consequences
Section titled “Consequences”- Positive: closes the recurring split-horizon RR-type leak class for all RR types; zone activates automatically on every inventory change; single source of truth for .house names.
- Negative: new external dependency on router-hosts rh#349 (two-PR rollout gated on that release); new host-level systemd components on the Firewalla; if unbound is down, dnsmasq forwards .house upstream (unchanged platform risk, now visible instead of masked).
- Neutral: dnsmasq remains the client-facing interceptor but carries no .house records of its own.