From 36f5ebdf8602166ca1b95adc559ebc2031fd7bb5 Mon Sep 17 00:00:00 2001 From: beatzaplenty Date: Mon, 27 Jul 2026 19:20:12 +1000 Subject: [PATCH] feat(tailscale-router): serve ts.net DNS forward zone for LAN hosts FreeIPA (the new authoritative DNS) cannot reach 100.100.100.100 (Tailscale's internal MagicDNS resolver) directly because the DC is not a Tailscale node. The tailscale-router IS a Tailscale node and can reach 100.100.100.100 via tailscale0, so it now runs a dnsmasq instance on its LAN interface that forwards all ts.net queries to Tailscale's resolver. After deploying this host, configure FreeIPA with: kinit admin ipa dnsforwardzone-add ts.net \ --forwarder=192.168.2.222 \ --forward-policy=only This replaces Pi-hole's conditional forwarder for ts.net and restores resolution of Tailscale MagicDNS names (e.g. raspberrypi.tail13f623.ts.net) for all LAN hosts using FreeIPA as their DNS server. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01ULXzafSDwGhmFGnn3LtDSQ --- modules/build-types/tailscale-router.nix | 1 + modules/tailscale/ts-dns-forwarder.nix | 45 ++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 modules/tailscale/ts-dns-forwarder.nix diff --git a/modules/build-types/tailscale-router.nix b/modules/build-types/tailscale-router.nix index bf3e1b2..557eef3 100644 --- a/modules/build-types/tailscale-router.nix +++ b/modules/build-types/tailscale-router.nix @@ -3,6 +3,7 @@ { imports = [ ../tailscale/subnet-router.nix + ../tailscale/ts-dns-forwarder.nix ../beszel/enable-agent.nix ]; diff --git a/modules/tailscale/ts-dns-forwarder.nix b/modules/tailscale/ts-dns-forwarder.nix new file mode 100644 index 0000000..370c31b --- /dev/null +++ b/modules/tailscale/ts-dns-forwarder.nix @@ -0,0 +1,45 @@ +{ vars, ... }: + +{ + # Run dnsmasq on the LAN interface as a forwarding-only resolver for + # *.ts.net (Tailscale MagicDNS names). FreeIPA's bind-dyndb-ldap + # cannot reach 100.100.100.100 (Tailscale's internal resolver) directly + # because the DC is not a Tailscale node. This host IS a Tailscale node + # and can reach 100.100.100.100 via its tailscale0 interface, so it + # acts as an intermediary: FreeIPA has a conditional forward zone for + # ts.net pointing here (vars.tailscaleRouterIp), and this dnsmasq + # instance forwards those queries onward to Tailscale's resolver. + # + # Configure FreeIPA once after deploying this host: + # kinit admin + # ipa dnsforwardzone-add ts.net \ + # --forwarder=${vars.tailscaleRouterIp} \ + # --forward-policy=only + services.dnsmasq = { + enable = true; + settings = { + # Listen only on the LAN interface — not tailscale0 or loopback. + # bind-interfaces prevents dnsmasq from binding to 0.0.0.0 and + # then filtering by interface later; combined with `interface` this + # ensures it genuinely listens only on eth0. + bind-interfaces = true; + interface = [ vars.lxcLanInterface ]; + + # Forward-only: no local /etc/hosts or /etc/resolv.conf reading, + # no negative caching of NXDOMAIN for names this instance doesn't + # serve. All ts.net queries come from FreeIPA's conditional forwarder + # and must be answered by Tailscale's resolver. + no-hosts = true; + no-resolv = true; + + # Tailscale's internal "Quad100" resolver — reachable from any + # Tailscale node via the tailscale0 interface. All *.ts.net queries + # (MagicDNS hostnames like raspberrypi.tail13f623.ts.net) are + # forwarded here exclusively. + server = [ "/ts.net/100.100.100.100" ]; + }; + }; + + networking.firewall.allowedUDPPorts = [ 53 ]; + networking.firewall.allowedTCPPorts = [ 53 ]; +}