gateway: change dns.listen default to [::1]:5353

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.
This commit is contained in:
Johnathan Corgan
2026-05-08 14:02:05 +00:00
parent 9112c8f7f0
commit 0fcf0f6f8f
9 changed files with 77 additions and 47 deletions
+11
View File
@@ -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
+10 -5
View File
@@ -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
+17 -9
View File
@@ -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.
+11 -16
View File
@@ -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
+2 -2
View File
@@ -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
+5 -6
View File
@@ -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
+1 -1
View File
@@ -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,
@@ -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
+17 -4
View File
@@ -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<String>,
@@ -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);