Split docs/ into project-based subdirectories:
- docs/proxmox/ fleet ops, hardware, k8s (PROJECT.md, TODO.md, K8S.md)
- docs/server-build/ provisioning, security, DNS (SECURITY.md, tailscale.md,
DEPLOYMENT.md, TSYS-2FA-GUIDE.md, DEVELOPMENT-GUIDELINES.md)
- docs/archive/ historical AI reviews, completed todos, pre-refactor docs
docmap.md rewritten with new paths. All cross-references in AGENTS.md,
README.md, STATUS.md, and dns-cluster-setup/README.md updated.
Code directories unchanged — scripts stay where BASH_SOURCE expects them.
🤖 Generated with [Crush](https://github.com/charmassociates/crush)
Assisted-by: GLM-5 via Crush <crush@charm.land>
113 lines
5.0 KiB
Markdown
113 lines
5.0 KiB
Markdown
# Tailscale vs. Managed DNS — Architecture Analysis
|
|
|
|
> **Status:** **RESOLVED.** The pfv-netinfra-01/02 pair now runs production
|
|
> Technitium DNS with all `knel.net` records replicated from tailscale-router
|
|
> via the DNS cluster setup. Both LAN IPs serve authoritative records for
|
|
> `knel.net` and recurse externally. This document records the original
|
|
> conflict, how it was resolved, and the recommended client configuration.
|
|
|
|
## 1. Executive summary
|
|
|
|
Every host in this build runs the Tailscale client, and Tailscale's MagicDNS
|
|
manages `/etc/resolv.conf` by default (pointing at `100.100.100.100`). This
|
|
previously conflicted with a managed `resolv.conf` pointing at the LAN
|
|
resolvers. The root cause was that the LAN Technitium instances did not have
|
|
the `knel.net` zone populated — **that is now fixed.**
|
|
|
|
The pfv-netinfra-01/02 pair now serves identical, authoritative `knel.net`
|
|
records (replicated from production via [`dns-cluster-setup/`](../dns-cluster-setup/README.md)).
|
|
Both LAN IPs resolve `knel.net` device names and recurse externally. The
|
|
managed `resolv.conf` is now safe to deploy.
|
|
|
|
**Recommendation:** Deploy the managed `resolv.conf` (`.252`/`.253`) on hosts
|
|
where you want tunnel-independent DNS. Leave Tailscale managing DNS on hosts
|
|
where MagicDNS device names must resolve without a LAN path (e.g. laptops off
|
|
-network). See [§5](#5-recommendation) for details.
|
|
|
|
## 2. How name resolution works today (post-cluster-setup)
|
|
|
|
Probed from `sectestbed-sandbox` (192.168.3.50) after the DNS cluster was
|
|
deployed:
|
|
|
|
| Query path | External name (`github.com`) | `knel.net` device name (`pfv-netinfra-01.knel.net`) |
|
|
|---|---|---|
|
|
| Via Tailscale resolver (`100.100.100.100`) | resolves | resolves → `100.70.181.72` (Tailscale CGNAT) |
|
|
| Direct `dig @192.168.3.252` (Technitium primary, LAN) | resolves (recurses) | **resolves** → `100.70.181.72` |
|
|
| Direct `dig @192.168.3.253` (Technitium secondary, LAN) | resolves (recurses) | **resolves** → `100.70.181.72` |
|
|
|
|
**Both LAN resolvers now serve `knel.net` records identically.** The
|
|
Technitium zone is no longer stale — it was replicated from production
|
|
(tailscale-router) as part of the DNS cluster setup.
|
|
|
|
### What changed
|
|
|
|
Previously (before the DNS cluster setup), querying the LAN IPs returned
|
|
NXDOMAIN for `knel.net` device names because the Technitium `knel.net` zone
|
|
was empty (SOA serial `2025062313`, dated 2025-06-23). After replicating
|
|
production config to both netinfra hosts, all 124 zones — including
|
|
`knel.net` with all current device records — are served authoritatively on
|
|
both `.252` and `.253`.
|
|
|
|
## 3. The DNS server pair
|
|
|
|
| Host | IP | Role | Services |
|
|
|------|----|------|----------|
|
|
| pfv-netinfra-01 | 192.168.3.252 | **Primary** | Technitium (authoritative, port 5300) + Pi-hole (recursive, port 53) |
|
|
| pfv-netinfra-02 | 192.168.3.253 | **Secondary** | Technitium (replicated via rsync, port 5300) + Pi-hole (recursive, port 53) |
|
|
|
|
Zone replication is rsync-based (every 60s via systemd timer) because
|
|
Technitium's AXFR uses port 53, which is occupied by Pi-hole on these hosts.
|
|
See [`dns-cluster-setup/README.md`](../dns-cluster-setup/README.md) for
|
|
full details.
|
|
|
|
## 4. NTP (fully resolved)
|
|
|
|
NTP is independent of DNS: `provisioning/ConfigFiles/NTP/ntp.conf` points
|
|
directly at the LAN IPs with no DNS dependency:
|
|
|
|
```
|
|
server 192.168.3.252 iburst
|
|
server 192.168.3.253 iburst
|
|
```
|
|
|
|
Both servers respond with stratum 2/3. The client config uses `restrict`
|
|
rules (not `interface listen`) to avoid the loopback-binding bug that
|
|
prevented sync. This is safe under both Tailscale-managed and LAN-pinned
|
|
resolver configurations.
|
|
|
|
## 5. Recommendation
|
|
|
|
### On fixed servers (always on-LAN)
|
|
|
|
**Deploy the managed `resolv.conf`** (`provisioning/ConfigFiles/Resolv/`):
|
|
- Points at `.252`/`.253` with failover
|
|
- `knel.net` records resolve on both servers
|
|
- External names recurse on both servers
|
|
- DNS survives `tailscaled` outages (unlike Tailscale-managed DNS)
|
|
|
|
To prevent Tailscale from overwriting the managed file:
|
|
```bash
|
|
tailscale up --accept-dns=false
|
|
```
|
|
|
|
### On laptops / roaming hosts
|
|
|
|
**Let Tailscale manage DNS** (default `accept-dns=true`):
|
|
- MagicDNS resolves `knel.net` device names via the tunnel
|
|
- No dependency on LAN reachability
|
|
- Accept the `tailscaled` dependency (if the tunnel is down, you're off-network anyway)
|
|
|
|
## 6. Known items / future work
|
|
|
|
1. **Pi-hole upstream configuration.** Pi-hole on both hosts should forward
|
|
to the local Technitium instance (port 5300) for `knel.net` and to an
|
|
external resolver for everything else. Verify this is configured on both
|
|
nodes.
|
|
2. **Zone transfer via AXFR.** Currently using rsync because Technitium's
|
|
AXFR expects port 53. If Technitium's listen port can be changed, or
|
|
Pi-hole can be configured to proxy AXFR, the rsync timer could be
|
|
replaced with native DNS zone transfer.
|
|
3. **`accept-dns=false` automation.** The provisioning code should set
|
|
`--accept-dns=false` on Tailscale during setup (after deploying the
|
|
managed `resolv.conf`) so Tailscale doesn't overwrite it on reboot.
|