fix(dns): end the recurring DNS outages, and put pi3 in git #207
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/dns-hardening"
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?
Ends the recurring house-wide DNS outages, puts pi3 under Ansible, and adds the monitoring that is now the only mitigation for AdGuard being a single point of failure.
Full reasoning, including the two approaches that were tried and reverted, is in
ansible/roles/adguard/README.md.What was wrong
teams.microsoft.com.no-resolvwith AdGuard as its only upstream, so pi3 down means the house has no DNS.docs/architecture.mdclaimed the opposite..193was a dynamic DHCP lease that the router hardcodes as that upstream.AdGuardHome.yamlexisted only on the card.Verified on the live system
changed=0after AdGuard rewrites the file(healthy)on the pinned tagTwo things tried and REVERTED
A public resolver as a dnsmasq fallback cannot work. Measured over 18 queries for domains AdGuard blocks:
strict-orderdnsmasq prefers the faster server and public anycast always beats a Pi 3. There is no setting in between, so pi3 stays a SPOF. Removing it needs a second filtering resolver, not a public one: two AdGuards with
strict_order: falsegives both properties, because there is nothing left to bypass. That is a follow-up, not this PR.EDNS Client Subnet does not give per-device attribution. AdGuard uses ECS outbound only and keys clients off the transport source IP (AdGuardHome#1727 open, milestoned v0.108.0). It also forwards an inbound ECS option upstream unstripped, which would disclose LAN addressing to Quad9, and OpenWrt 23.05's dnsmasq has no
addsubnetoption so the uci key is inert anyway. Per-device visibility needs a topology change (DHCP option 6, or a NAT redirect of :53). The ratelimit side effect is fixed.Monitoring
Three blackbox DNS probes against the router - the previous checks only hit the Pi, so they could not see the chain - plus four alerts. The canary resolves a domain AdGuard blocks and requires
0.0.0.0; it is the only signal that separates "DNS works" from "DNS works and is still filtering".DNSProbesAbsentcovers the probes themselves vanishing.Two latent bugs fixed on the way
openwrt.shmangled multi-value uci lists (uci showrenders them on one line; the parser stripped only the outer quotes). Invisible with one upstream, permanent false drift with two.10.43.199.233, a ClusterIP from when AdGuard ran in-cluster. No Service has held it since.Note on the commit range
Branched from
origin/master, which was 4 commits ahead offorgejo/master(#46, #47, #49, #50 - merged on GitHub, never deployed, since Flux reconciles only from forgejo). This is deliberate rather than incidental: #49 adds thereleaselabel toprometheus-rules-node-health.yaml, and without it the rule this PR extends would apply cleanly and never be evaluated. Merging here also finally ships those four.Risk
Applied and verified live. Revertable:
infrastructure/openwrt/{dhcp,openwrt}.shsnapshot uci before each sync (dhcp-20260901-112811.uci), and the Ansible template task keepsbackup: true.The homelab-node-health PrometheusRule (NodeDown, NodeExporterAbsent, and the new DiskPressure/root-fs alerts) was never loaded by Prometheus: its ruleSelector matches {release: kube-prometheus-stack} and this rule lacked that label. It has been dead since creation ~22d ago, so no node-health alert has ever fired. Adding the label makes the operator select it. Verified: the label is the only difference from the working stock node-exporter PrometheusRule.