Files
fips/docs/design
ArjenandJohnathan Corgan 672d828ef5 feat(node): publish the DNS responder's bound address for embedders
An embedder that owns the TUN fd has no system DNS socket to point at the
built-in `.fips` responder. On Android specifically, `VpnService.Builder`
exposes `addDnsServer(address)` with no port — the OS resolver always uses
53, which an unprivileged app UID cannot bind — and it aims the resolver
*into* the tunnel, so `.fips` queries surface as IPv6/UDP packets on the
app's own fd rather than at any socket FIPS holds.

The app can still use the responder rather than reimplementing resolution:
lift the DNS payload out of the packet it read, send it to the responder
over an ordinary UDP socket of its own, and splice the answer back into a
reply packet. Nothing in the responder's start-up is desktop-specific —
`bind_dns_socket` is plain socket2, `lookup_mesh_ifindex` returns None with
no system TUN so the mesh filter self-disables, and `HostMapReloader` on an
absent hosts file settles at a no-op stat. What was missing is the address
to dial and whether anything is listening at it.

`dns_local_addr()` answers both, as a one-shot read taken after `start()`
returns and before the node is moved into a background task. That is the
only window in which an embedder running `run_rx_loop` holds a `&Node` to
call it on, and the value is settled by then: the responder is either up for
the rest of the node's life or it never came up. It reports the address read
back off the bound socket, so a `dns.port = 0` config yields the port the
kernel assigned rather than 0. Config alone cannot answer the second
question — `dns.enabled` with a failed bind leaves `bind_addr` naming a
plausible target nothing is listening on, and a bind failure only warns
rather than failing node start.

It is deliberately not a liveness feed. Watching a responder that dies later
needs a way to read live node state from a backgrounded `run_rx_loop`, which
is a general gap and not one an accessor should try to close.

Routing through the responder rather than resolving in the app is what keeps
route warming intact. Answering a `<npub>.fips` query is what puts that
peer's public key in the node's identity cache, and a FipsAddress is
SHA-256(pubkey) truncated twice: the key cannot be recovered from the IPv6
address. With no cache entry the first packet to a freshly-resolved name is
rejected with ICMPv6 "No route" — a failure that direct neighbours mask
entirely, since their identity arrives with the Noise handshake and never
needed resolving.

The address is retracted on `stop()`. `retract_child_publications` also
handles a responder that exits on its own at runtime, where the FSM's
`ChildExited` handling republishes node health but touches no per-child
handles. That consumer is dormant as written and documented as such:
`run_dns_responder` is an unconditional loop whose every failure arm
continues, so it never returns and the `Child::Dns` send after it is
unreachable. It lands here so a producer fix does not have to rediscover the
consuming side. A panicking responder is not covered either way, since the
unwind goes past the send rather than through it — true of every child
producer, not just this one.

The IPv6-adapter design doc gains an App-Owned DNS Path section beside the
App-Owned TUN one it mirrors, plus implementation-status rows for both.

Three tests. `dns_responder_serves_a_proxying_embedder` is the load-bearing
one: port-0 read-back, a proxied query answered with the right AAAA, and the
resolved identity arriving on the channel `run_rx_loop` drains into
`register_identity`. `dns_local_addr_stays_none_when_the_bind_fails` forces
`EADDRINUSE` against a socket the test holds open — `bind_dns_socket` sets
neither `SO_REUSEADDR` nor `SO_REUSEPORT`, so that is deterministic, where
naming a non-local address is not: `net.ipv4.ip_nonlocal_bind = 1` is
ordinary on hosts running keepalived or HAProxy and makes the bind succeed.
`retract_child_publications_clears_the_dns_address` is scoped and named for
the helper rather than the scenario, because deleting the `run_rx_loop` call
site leaves it green; that wiring is covered by nothing.

Full suite 1672 passed, 0 failed. fmt, `clippy --all-targets -D warnings`
and the Android `cargo ndk clippy --lib -D warnings` gate are clean.

The changelog entry was added at merge rather than in the pull request:
the app-owned TUN seam it mirrors gained an Unreleased entry in the
master-only sweep, so this one would otherwise recreate that debt.
2026-08-13 10:19:52 +00:00
..
2026-06-07 23:30:35 +00:00

FIPS Design

Architectural and protocol-level explanations for FIPS — the why and the how behind the wire and the system. For wire formats and configuration keys, see reference/. For task recipes, see how-to/. For end-to-end lessons, see tutorials/.

Reading Order

Start with fips-concepts.md for the novice-friendly framing of what FIPS is and why, then move to fips-architecture.md for the protocol stack, identity model, and two-layer encryption walkthrough. From there, follow the protocol stack from bottom to top. After the stack, fips-mesh-operation.md explains how the pieces work together at runtime. Cross-cutting and supporting documents cover specific subsystems in detail.

Foundations

Document Description
fips-concepts.md What FIPS is, why it exists, mental model
fips-architecture.md Protocol stack, identity, two-layer encryption
fips-prior-work.md Designs and protocols FIPS builds on

Protocol Stack

Document Description
fips-transport-layer.md Transport layer: datagram delivery over arbitrary media
fips-mesh-layer.md FIPS Mesh Protocol (FMP): peer authentication, link encryption, forwarding
fips-session-layer.md FIPS Session Protocol (FSP): end-to-end encryption, sessions
fips-ipv6-adapter.md IPv6 adaptation: TUN interface, DNS, MTU enforcement

Cross-Cutting

Document Description
fips-mmp.md Metrics Measurement Protocol (link + session)
fips-mtu.md Path MTU model, encapsulation overhead, PMTUD
fips-security.md fips0 interface threat model and default-deny baseline

Mesh Behavior

Document Description
fips-mesh-operation.md How the mesh operates: routing, discovery, error recovery
fips-nostr-discovery.md Optional Nostr-mediated peer discovery and UDP NAT hole-punch
port-advertisement-and-nat-traversal.md Nostr-signaled port advertisement and UDP NAT-traversal protocol; generic, with FIPS as an example implementation

Deeper Dives

Document Description
fips-spanning-tree.md Spanning tree algorithms: root discovery, parent selection, coordinates
fips-bloom-filters.md Bloom filter properties: FPR analysis, size classes, split-horizon
spanning-tree-dynamics.md Spanning tree walkthroughs: convergence scenarios, worked examples

Adjacent Components

Document Description
fips-gateway.md fips-gateway service: outbound (LAN-to-mesh) DNS-proxy + virtual-IP NAT and inbound (mesh-to-LAN) port-forwarding, sharing one nftables table