diff --git a/Tech/Sessions/dns-knot-pihole-end-to-end.md b/Tech/Sessions/dns-knot-pihole-end-to-end.md new file mode 100644 index 0000000..4ce0bdb --- /dev/null +++ b/Tech/Sessions/dns-knot-pihole-end-to-end.md @@ -0,0 +1,116 @@ +--- +project: dns-knot-pihole-end-to-end +type: session-notes +status: completed +path: Tech/Sessions +tags: + - homelab + - dns + - knot + - pihole + - openwrt + - traefik +created: 2026-05-08 +updated: 2026-05-08 +--- +# DNS — Knot + Pi-hole End-to-End + +## Outcome + +Phase 1 of the Traefik project closed. Internal DNS architecture from +the homelab DNS decision doc is live: Knot DNS authoritative for +`herbylab.dev` on LXC 103, Pi-hole conditionally forwarding the zone, +OpenWrt passing answers through. All six initial records resolve +cleanly from a normal LAN client through the full chain. + +## Architecture (as built) + + +Client → OpenWrt (10.0.11.1) → Pi-hole (10.0.11.50) + ├─ herbylab.dev → Knot (10.0.11.10) + └─ everything else → Unbound → root + + +## Records served + +| Name | IP | VLAN | +|---|---|---| +| ns1 | 10.0.11.10 | Lab | +| immich | 10.0.11.62 | Lab | +| pihole | 10.0.11.50 | Lab | +| plex | 10.0.11.54 | Lab | +| pve | 10.0.21.188 | Private | +| traefik | 10.0.21.181 | Private | + +Migrated from `.lan` to `.dev` to enable LE wildcard via Cloudflare +DNS-01 in Phase 2. + +## Key Learnings + +- **`knotc reload` does not re-bind sockets.** It reloads the config +file but listening sockets only get re-evaluated on full daemon +restart. If `listen:` directives change (or were missing on first +start), `systemctl restart knot` is required. Symptom: zone loads, +`zone-status` reports correctly, but `dig` returns "connection +refused." +- **Knot config order matters for references.** `acl` blocks must be +defined before `zone` blocks that reference them — YAML validation is +single-pass, no forward references. Same pattern likely applies to +other ID-based references. +- **Fresh PVE LXCs inherit Tailscale resolvers from the host.** +`/etc/resolv.conf` gets populated with `100.100.100.100` by default. +If the LXC isn't on the tailnet, name resolution silently fails until +pointed at a reachable resolver. Permanent fix: configure DNS in the +LXC's PVE config or, for a DNS server LXC, point it at itself once +it's serving. +- **OpenWrt rebind protection silently filters RFC1918 answers.** When +dnsmasq has `rebind_protection='1'`, any DNS answer pointing to a +private IP gets stripped — query returns NOERROR with ANSWER:0 and EDE +15 (Blocked). The fix is whitelisting the domain via `rebind_domain`. +Easy to miss because the block happens *after* the upstream answers +correctly, so direct queries to Pi-hole or Knot work fine while +client-path queries fail. +- **EDE 15 (Blocked) doesn't always mean a blocklist match.** It's a +generic "this answer was filtered" code. Pi-hole's blocklists, +OpenWrt's rebind protection, and several other filters all use it. +Pi-hole's query log saying "Allowed" while `dig` reports EDE 15 is the +tell — block is happening downstream of Pi-hole. +- **Pi-hole v6 renamed conditional forwarding.** It's now under +reverse-server config with format +`,,,`. Same plumbing, different +UI surface. +- **Direct-vs-chain queries are the diagnostic shortcut.** When a DNS +chain fails, querying each link directly with `dig @` +immediately localizes the broken hop. Saved real time tonight +isolating the OpenWrt rebind issue. + +## Decisions Locked + +- Zone file lives at `/var/lib/knot/herbylab.dev.zone` on LXC 103, +owned by `knot:knot` +- Single NS record (`ns1`) for now; secondary will be added when NAS +instance comes up +- TTL strategy: 300 during buildout (current), raise to 3600 once stable +- SOA serial format: `YYYYMMDDNN` (current: `2026050801`) +- ACL allows transfer/notify from localhost only — will expand when +secondary is added +- Conditional forwarding scoped to `10.0.0.0/8` in Pi-hole — +VLAN/firewall layer enforces actual access boundaries + +## Open Items + +- [ ] LXC `/etc/resolv.conf` is a manual edit pointing at Pi-hole — +repoint at itself (10.0.11.10) with Pi-hole as fallback, make +persistent +- [ ] Knot zone file into git as source of truth +- [ ] Ansible role for Knot primary deployment +- [ ] Knot secondary on the NAS (after primary proven stable) +- [ ] Second Pi-hole instance for redundancy +- [ ] Pi-hole sync method decision (gravity-sync vs Teleporter) +- [ ] Move Traefik VM to Lab VLAN (currently on Private from original +bridge setup) +- [ ] Raise TTLs to 3600 once buildout settles + +## Next Session + +Phase 2 — wildcard cert via Traefik + Cloudflare DNS-01. DNS layer is done. \ No newline at end of file