From 0fcf0f6f8f8195130e8ea1086ac01eebfb2dc180 Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Fri, 8 May 2026 14:01:19 +0000 Subject: [PATCH] gateway: change dns.listen default to [::1]:5353 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The gateway is designed for systems already serving DHCP and DNS to a LAN segment (canonically an OpenWrt AP). On those systems port 53 is already taken by the existing resolver, so the prior `[::]:53` default conflicted with the gateway's intended deployment target out of the box. The OpenWrt ipk previously overrode this in its packaged config as a workaround; matching the source default to what the canonical deployment actually wants makes the override redundant and removes a foot-gun for fresh manual Linux-host installs. The redundant `dns.listen` line in `packaging/openwrt-ipk/files/etc/fips/fips.yaml` is dropped along with this change. Operators on a host without a pre-existing resolver on port 53 can opt back into the wildcard bind by setting `dns.listen: "[::]:53"` explicitly. The new default binds IPv6 loopback only — Linux IPv6 sockets bound to explicit `::1` do not accept v4-mapped traffic, so forwarders that reach the gateway over IPv4 loopback need to be pointed at an explicit IPv4 listen address instead. Touches the gateway config struct and its default-value test, the commented-out gateway example in the Debian common fips.yaml, the OpenWrt ipk config (override removed), the gateway reference / how-to / design / tutorial / troubleshoot docs, and a CHANGELOG entry under [Unreleased] -> Changed. --- CHANGELOG.md | 11 ++++++++ docs/design/fips-gateway.md | 15 +++++++---- docs/how-to/deploy-gateway.md | 26 +++++++++++------- docs/how-to/troubleshoot-gateway.md | 27 ++++++++----------- docs/reference/configuration.md | 4 +-- docs/tutorials/deploy-fips-gateway.md | 11 ++++---- packaging/common/fips.yaml | 2 +- .../openwrt-ipk/files/etc/fips/fips.yaml | 7 +++-- src/config/gateway.rs | 21 ++++++++++++--- 9 files changed, 77 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fa8f99..5ec6aa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -302,6 +302,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 decrypt-failure thresholds, STUN parser, gateway, NAT traversal, packaging install paths) alongside CI-side hardening for the new Windows and macOS platforms. +- Gateway `dns.listen` source default changed from `[::]:53` to + `[::1]:5353` to match the canonical deployment model (a host + already serving DHCP/DNS to a LAN segment, where port 53 is + taken by the existing resolver and `.fips` queries are forwarded + to the gateway over loopback). The OpenWrt ipk previously + overrode this in its packaged config; the override is now + redundant and has been dropped. Operators on a host without a + pre-existing resolver on port 53 can opt back into the wildcard + bind by setting `dns.listen: "[::]:53"` explicitly. The new + default binds IPv6 loopback only — forwarders that reach the + gateway over IPv4 loopback need an explicit IPv4 listen address. ### Fixed diff --git a/docs/design/fips-gateway.md b/docs/design/fips-gateway.md index b514f95..bdadccd 100644 --- a/docs/design/fips-gateway.md +++ b/docs/design/fips-gateway.md @@ -130,7 +130,7 @@ There is no `fipsctl gateway` subcommand; clients (including │ │ │ ┌──────────────┐ ┌───────────┐ │ │ │ DNS proxy │ │ Virtual │ │ - │ │ ([::]:53) │─▶│ IP pool │ │ + │ │ ([::1]:5353) │─▶│ IP pool │ │ │ │ .fips only │ │ (state │ │ │ └──────┬───────┘ │ machine) │ │ │ │ └─────┬─────┘ │ @@ -182,13 +182,18 @@ involving the DNS proxy or the pool. ### DNS Resolution Flow 1. A LAN client sends a DNS query to the gateway's listener (default - `[::]:53`, configurable via `gateway.dns.listen`). + `[::1]:5353`, configurable via `gateway.dns.listen`). The default + is loopback-only on an unprivileged port: the canonical deployment + has another resolver on the host (dnsmasq, systemd-resolved, BIND) + holding port 53 and forwarding `.fips` queries to the gateway over + loopback. Operators on a host without a pre-existing resolver on + 53 can override the listen value to `"[::]:53"` to let LAN clients + query the gateway directly. 2. If the question is not for a `.fips` domain, the gateway replies `REFUSED`. The proxy is intentionally narrow — it does not resolve public DNS, and the LAN's primary resolver should hold port 53 on - the gateway host (the OpenWrt init script wires this up by binding - the gateway listener to a non-conflicting port and configuring - dnsmasq to forward `.fips` queries there). + the gateway host (the OpenWrt init script wires dnsmasq to forward + `.fips` queries to the loopback listener automatically). 3. The gateway forwards the query to the daemon resolver (`gateway.dns.upstream`, default `[::1]:5354`). The daemon must match: an IPv6 socket bound to `[::1]` does not accept v4-mapped diff --git a/docs/how-to/deploy-gateway.md b/docs/how-to/deploy-gateway.md index 75f266f..9b335df 100644 --- a/docs/how-to/deploy-gateway.md +++ b/docs/how-to/deploy-gateway.md @@ -142,7 +142,7 @@ pick a different `fdXX::/N`). The `/112` size yields 65 536 virtual IPs, which is the gateway's hard cap regardless of CIDR width. This minimum config is enough to start the gateway. The `dns.*` block -is optional and defaults to `listen: "[::]:53"` and +is optional and defaults to `listen: "[::1]:5353"` and `upstream: "[::1]:5354"`. The full block — including `dns.*`, `pool_grace_period`, `conntrack.*`, and `port_forwards[]` — is documented in @@ -195,20 +195,28 @@ Constraints: ```yaml gateway: dns: - listen: "[::]:53" + listen: "[::1]:5353" upstream: "[::1]:5354" ttl: 60 ``` Common cases: -- **No other resolver on the host:** `listen: "[::]:53"` is the - default and works. -- **systemd-resolved is on port 53:** either disable its stub - listener (`DNSStubListener=no` in - `/etc/systemd/resolved.conf`) or move the gateway to a different - port (e.g., `[::]:5353`) and put a forwarder on 53 that delegates - `.fips` to the gateway. See +- **Another resolver on the host (the canonical case):** the default + `listen: "[::1]:5353"` is loopback-only on an unprivileged port, + so it never conflicts with dnsmasq, systemd-resolved, or BIND + holding 53. Configure the existing resolver to forward `.fips` + queries to `[::1]:5353` and you are done — this is what the + OpenWrt ipk does automatically. +- **No other resolver on the host:** set `listen: "[::]:53"` + explicitly and LAN clients can query the gateway directly. +- **systemd-resolved is on port 53:** the default already side-steps + this — leave the listen address at `[::1]:5353` and configure the + stub or a small forwarder to delegate `.fips` to the gateway. If + you would rather have the gateway on 53 directly, disable the + systemd stub listener (`DNSStubListener=no` in + `/etc/systemd/resolved.conf`) and switch `listen` to `"[::]:53"`. + See [troubleshoot-gateway.md](troubleshoot-gateway.md#port-conflict-on-the-dns-listen-port). - **Bind on the LAN address only:** `listen: "192.168.1.1:53"` exposes the resolver only to LAN clients, not loopback. diff --git a/docs/how-to/troubleshoot-gateway.md b/docs/how-to/troubleshoot-gateway.md index 324735f..0293ec1 100644 --- a/docs/how-to/troubleshoot-gateway.md +++ b/docs/how-to/troubleshoot-gateway.md @@ -82,10 +82,13 @@ for the full flag list. ### Port conflict on the DNS listen port Symptom: gateway fails to start with "address already in use" on -port 53 (or whatever `gateway.dns.listen` is set to). +the configured `gateway.dns.listen` address. -Another DNS server (systemd-resolved, dnsmasq, BIND) is bound to -the port. Identify it: +The default `[::1]:5353` is loopback-only on an unprivileged port and +should not collide with any standard resolver. If you have overridden +`dns.listen` to bind port 53 (or a LAN-side address) and another DNS +server (systemd-resolved, dnsmasq, BIND) is already bound there, +identify it: ```sh sudo ss -tulnp | grep ':53' @@ -93,18 +96,10 @@ sudo ss -tulnp | grep ':53' Two options: -- **Use an alternate listen address.** Pick a non-conflicting port - and update the gateway config: - - ```yaml - gateway: - dns: - listen: "192.168.1.1:5353" - ``` - - Then either point LAN clients at the alternate port directly, or - run a forwarding stub on port 53 that delegates `.fips` queries to - the gateway. +- **Stay on the loopback default.** Drop the override and let the + gateway use `[::1]:5353`. Configure the existing resolver to + forward `.fips` queries to it (the canonical OpenWrt deployment + works this way out of the box). - **Relocate the conflicting resolver.** Move it to a different port (or disable it if not needed) and let the gateway bind 53. @@ -189,7 +184,7 @@ not running or not enabled. Check that the daemon config has **Step 2.** Verify the gateway is listening on its DNS port: ```sh -sudo ss -tulnp | grep -E ':53\b' +sudo ss -tulnp | grep -E ':(53|5353)\b' ``` If nothing is listening on the configured `dns.listen` address, the diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 1692ca3..761717d 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -699,7 +699,7 @@ Non-`.fips` queries are answered with `REFUSED`. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| -| `gateway.dns.listen` | string | `"[::]:53"` | LAN-facing DNS listen address. Bind on the LAN-side IP (e.g., `"192.168.1.1:53"`) or on all interfaces (`"[::]:53"`) for LAN clients to query. Bind to a non-53 port if another resolver already owns 53 on the host (see [../how-to/troubleshoot-gateway.md](../how-to/troubleshoot-gateway.md)). | +| `gateway.dns.listen` | string | `"[::1]:5353"` | DNS listen address. The default binds IPv6 loopback on an unprivileged port, matching the canonical deployment where another resolver on the host (dnsmasq, systemd-resolved, BIND) holds port 53 and forwards `.fips` queries to the gateway over loopback. Bind on the LAN-side IP (e.g., `"192.168.1.1:53"`) or wildcard (`"[::]:53"`) only on hosts with no other resolver on 53 and where LAN clients query the gateway directly. See [../how-to/troubleshoot-gateway.md](../how-to/troubleshoot-gateway.md). | | `gateway.dns.upstream` | string | `"[::1]:5354"` | Upstream FIPS daemon resolver. **Must match the daemon's `dns.bind_addr` and `dns.port`.** Defaults match the daemon defaults (`::1:5354`). A v4 upstream (`"127.0.0.1:5354"`) cannot reach a daemon bound on `[::1]:5354` — Linux IPv6 sockets bound to explicit `::1` do not accept v4-mapped traffic. If you change the daemon's `dns.bind_addr`, update this field accordingly. | | `gateway.dns.ttl` | u32 | `60` | TTL in seconds on AAAA responses returned to LAN clients. Smaller values let the gateway recycle pool addresses faster; larger values reduce LAN-side query traffic. | @@ -746,7 +746,7 @@ gateway: pool: "fd01::/112" lan_interface: "enp3s0" dns: - listen: "[::]:53" + listen: "[::1]:5353" upstream: "[::1]:5354" ttl: 60 pool_grace_period: 60 diff --git a/docs/tutorials/deploy-fips-gateway.md b/docs/tutorials/deploy-fips-gateway.md index 11f6aeb..e4d4ba5 100644 --- a/docs/tutorials/deploy-fips-gateway.md +++ b/docs/tutorials/deploy-fips-gateway.md @@ -132,7 +132,6 @@ gateway: pool: "fd01::/112" # virtual IP range (up to 65535 addresses) lan_interface: "br-lan" # LAN-facing interface for proxy NDP dns: - listen: "[::1]:5353" # gateway DNS listener (dnsmasq forwards here) upstream: "[::1]:5354" # FIPS daemon DNS resolver (matches daemon default) ttl: 60 # DNS TTL and mapping lifetime (seconds) pool_grace_period: 60 # seconds after last session before reclaiming @@ -147,10 +146,11 @@ Three things to notice: - `lan_interface: "br-lan"` — the OpenWrt LAN bridge. The gateway installs proxy-NDP entries on this interface so LAN clients can ARP-equivalent for pool addresses. -- `dns.listen: "[::1]:5353"` — the gateway's DNS listener is bound - to IPv6 loopback only. dnsmasq, which owns LAN port 53, forwards - `.fips` queries to it. The init script wires up that forwarding; - you don't bind to a LAN address yourself. +- No `dns.listen` line — the source default `[::1]:5353` is exactly + what OpenWrt wants. The gateway listens on IPv6 loopback only; + dnsmasq, which owns LAN port 53, forwards `.fips` queries to it. + The init script wires up that forwarding; you don't bind to a LAN + address yourself. For the full reference, see [../reference/configuration.md § Gateway](../reference/configuration.md#gateway-gateway). @@ -321,7 +321,6 @@ gateway: pool: "fd01::/112" lan_interface: "br-lan" dns: - listen: "[::1]:5353" upstream: "[::1]:5354" ttl: 60 pool_grace_period: 60 diff --git a/packaging/common/fips.yaml b/packaging/common/fips.yaml index 0f730ef..ba3de65 100644 --- a/packaging/common/fips.yaml +++ b/packaging/common/fips.yaml @@ -94,7 +94,7 @@ transports: # pool: "fd01::/112" # lan_interface: "eth0" # dns: -# listen: "[::]:53" +# listen: "[::1]:5353" # # upstream must match the daemon's dns.bind_addr above. The # # default "[::1]:5354" matches the daemon's default. If you set # # the daemon to bind on a wildcard ("::") or specific address, diff --git a/packaging/openwrt-ipk/files/etc/fips/fips.yaml b/packaging/openwrt-ipk/files/etc/fips/fips.yaml index 7d6f2d3..764785e 100644 --- a/packaging/openwrt-ipk/files/etc/fips/fips.yaml +++ b/packaging/openwrt-ipk/files/etc/fips/fips.yaml @@ -76,16 +76,15 @@ peers: [] # Allows unmodified LAN hosts to reach FIPS mesh destinations via # DNS-allocated virtual IPs and kernel nftables NAT. # -# The gateway DNS listens on port 5353 by default so it does not conflict -# with dnsmasq on port 53. The init script configures dnsmasq to forward -# .fips queries to the gateway automatically. +# The gateway DNS listens on `[::1]:5353` by default (set in source) so it +# does not conflict with dnsmasq on port 53. The init script configures +# dnsmasq to forward .fips queries to the gateway automatically. gateway: enabled: true pool: "fd01::/112" # virtual IP range (up to 65535 addresses) lan_interface: "br-lan" # LAN-facing interface for proxy NDP dns: - listen: "[::1]:5353" # gateway DNS listener (dnsmasq forwards here) upstream: "[::1]:5354" # FIPS daemon DNS resolver (matches daemon default) ttl: 60 # DNS TTL and mapping lifetime (seconds) pool_grace_period: 60 # seconds after last session before reclaiming diff --git a/src/config/gateway.rs b/src/config/gateway.rs index 4d1248f..d524176 100644 --- a/src/config/gateway.rs +++ b/src/config/gateway.rs @@ -8,7 +8,20 @@ use std::net::SocketAddrV6; use serde::{Deserialize, Serialize}; /// Default gateway DNS listen address. -const DEFAULT_DNS_LISTEN: &str = "[::]:53"; +/// +/// Loopback-only on the unprivileged port 5353. The canonical +/// gateway deployment is a host already serving DHCP/DNS to a LAN +/// segment (e.g., an OpenWrt AP), where port 53 is taken by the +/// existing resolver and `.fips` queries are forwarded to the +/// gateway over loopback. Operators on a host without a pre-existing +/// resolver on 53 can opt back into the wildcard bind by setting +/// `dns.listen: "[::]:53"` explicitly. +/// +/// `[::1]` is IPv6 loopback only; Linux IPv6 sockets bound to +/// explicit `::1` do not accept v4-mapped traffic. Forwarders that +/// reach the gateway over IPv4 loopback (`127.0.0.1`) need to be +/// pointed at an explicit IPv4 listen address instead. +const DEFAULT_DNS_LISTEN: &str = "[::1]:5353"; /// Default upstream DNS resolver (FIPS daemon). /// @@ -118,7 +131,7 @@ pub struct PortForward { /// Gateway DNS resolver configuration (`gateway.dns.*`). #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct GatewayDnsConfig { - /// Listen address and port (default: `0.0.0.0:53`). + /// Listen address and port (default: `[::1]:5353`). #[serde(default, skip_serializing_if = "Option::is_none")] pub listen: Option, @@ -133,7 +146,7 @@ pub struct GatewayDnsConfig { } impl GatewayDnsConfig { - /// Get the listen address (default: `0.0.0.0:53`). + /// Get the listen address (default: `[::1]:5353`). pub fn listen(&self) -> &str { self.listen.as_deref().unwrap_or(DEFAULT_DNS_LISTEN) } @@ -205,7 +218,7 @@ lan_interface: "eth0" assert!(!config.enabled); assert_eq!(config.pool, "fd01::/112"); assert_eq!(config.lan_interface, "eth0"); - assert_eq!(config.dns.listen(), "[::]:53"); + assert_eq!(config.dns.listen(), "[::1]:5353"); assert_eq!(config.dns.upstream(), "[::1]:5354"); assert_eq!(config.dns.ttl(), 60); assert_eq!(config.grace_period(), 60);