Restructures /docs/ by reader purpose (tutorials, how-to, reference, design), adds the new-user-progression and operator-recipe content the prior layout lacked, runs an accuracy pass against current source across the pre-existing design docs, and rewrites the gateway feature-set documentation end-to-end around its actual operational profile (a niche feature designed for systems already serving DHCP/DNS to a LAN, with two independent halves — outbound LAN→mesh, inbound mesh→LAN — sharing one nftables table, one binary, and one control socket). Top-level README and getting-started rewritten around two equally-weighted deployment modes (overlay on existing IP networks; ground-up over non-IP transports). ## Additions - 11 new tutorials in docs/tutorials/: an 8-step new-user progression from single-daemon test-mesh peering through to a ground-up two-device mesh, an IPv6-adapter side-trip walkthrough, an Advanced Tutorials index, and a hand-held OpenWrt walk-through for fips-gateway deployment that exercises both halves of the feature. - 12 new how-tos in docs/how-to/: firewall activation, Nostr discovery (resolve / advertise / open across five scenarios), Tor onion (directory + control_port modes), UDP buffer tuning, unprivileged-user setup, persistent identity, host aliases, Bluetooth LE peering, MTU diagnostics, manual Linux-host gateway deployment (covers both halves), gateway troubleshooting (organised by half), and a section index. - 9 new reference docs in docs/reference/: configuration, wire formats, control-socket protocol, four CLI references (fips, fipsctl, fipstop, fips-gateway), security posture matrix, and Nostr events catalog. Configuration and wire-formats are renamed-and-extended from prior design/ versions; the other seven are net-new. - 6 new design docs: fips-concepts, fips-architecture, and fips-prior-work split out of the deleted fips-intro.md; consolidated fips-mmp and fips-mtu aggregations; and a new generic port-advertisement-and-nat-traversal doc (Nostr-signaled port advertisement plus UDP NAT-traversal protocol, FIPS as an example implementation, suitable for eventual NIP submission). - Top-level docs/getting-started.md walking through the binary-installer-only Install story. - packaging/common/hosts pre-populated with the eight public test-mesh nodes so shortnames resolve out of the box on every fresh install. ## Changes - 23 wire-format diagrams relocated to reference/diagrams/ alongside the wire-formats move. - 4 design diagrams corrected against source code (fips-protocol-stack, fips-identity-derivation, fips-coordinate-discovery, fips-routing-decision). - 10 pre-existing design docs reconciled with current source. Numeric corrections: stale link-MMP report bounds (now [1s, 5s] with 200 ms cold-start floor); UDP default MTU (now 1280, IPv6 minimum); node_addr formula (SHA-256(pubkey)[..16]); Noise patterns (IK at link, XK at session); peer-ACL semantics (strict allowlist requires ALL in peers.deny); daemon DNS upstream ([::1]:5354); on-the-wire bloom-filter size (1,071 bytes); obsolete Cargo-feature references (PR #79 dropped them) removed. - Transport framing tightened across the docs: TCP is for UDP-filtered networks (not NAT traversal); Tor is a deployment mode (not failover); WebSocket dropped (not a shipped FIPS transport); WiFi promoted to Implemented via Ethernet in infrastructure mode; classic-Bluetooth row removed (BLE is the only Bluetooth-mode transport). - docs/design/fips-gateway.md rewritten end-to-end to lead with the niche-feature framing and the two-halves structure. Title moved from "FIPS Outbound LAN Gateway" to "FIPS Gateway"; architecture section describes the common machinery (the fips-gateway service, the nftables table, the control socket) before splitting into separate "Outbound Half" and "Inbound Half" sections of equal weight; security considerations split per-half; no Future Work section (speculative directions live in the project tracker, not in protocol design docs). Inbound port forwarding is a first-class half rather than a buried "Implemented Extensions" subsection. - Gateway terminology unified across all gateway docs as a separate Linux service running alongside the fips daemon (its own systemd unit / OpenWrt init script). Container- pattern terms (sidecar) are reserved for the Docker/Kubernetes sidecar deployment examples — the testing/sidecar/ tree, examples/k8s-sidecar/, examples/sidecar-nostr-relay/, examples/wireguard-sidecar-macos/, and the related CHANGELOG / top-level README entries — where the term carries its standard container meaning. - Net-new design body content: rekey section in fips-mesh-layer (Noise IK msg1/msg2 over the established link, K-bit cutover, drain window, smaller-NodeAddr-wins tie-breaker on dual-init); Mesh Size Estimation and Antipoison FPR Cap sections in fips-bloom-filters; Mesh-Interface Query Filter subsection in fips-ipv6-adapter; failure-suppression knobs and clock- skew tolerance in fips-nostr-discovery; loop-rejection and mid-chain ancestor swap added to spanning-tree propagation / stability rules; Priority Chain in fips-mesh-operation renumbered to match the routing-decision diagram. - Top-level README: dropped the stale nostr-discovery cargo-feature parenthetical. docs/README.md and the four section READMEs (tutorials, how-to, reference, design) refreshed for the new structure; index rows reflect both halves of the gateway feature and the new fips-gateway CLI reference. - Cargo.toml [package.metadata.deb] assets path updated for the fips-security.md move; .gitignore /reference/ rule anchored to repo root so docs/reference/ is trackable. - packaging/openwrt-ipk/files/etc/fips/fips.yaml configuration-doc URL updated to the new docs/reference/configuration.md location. ## Deletions - docs/design/fips-intro.md (split into the three new intro design docs). - docs/design/document-relationships.svg (orphan, no longer referenced). - docs/proposals/ tree removed; the only proposal it contained (the Nostr UDP hole-punch protocol) was rewritten as the new generic design/port-advertisement-and-nat-traversal.md.
11 KiB
Security Reference
Consolidated security reference covering the nftables baseline, peer ACL file format, cryptographic primitives, rekey defaults, replay window, filesystem permissions, threat-resistance matrix, and default network exposures per transport. For the threat-model design and rationale, see ../design/fips-security.md. For the operator activation steps and drop-in recipes, see ../how-to/enable-mesh-firewall.md.
nftables Baseline
The shipped baseline is /etc/fips/fips.nft. It defines a single
nftables table inet fips with one chain hooked at input, structured
as follows:
| Step | Rule | Effect |
|---|---|---|
| 1 | iifname != "fips0" return |
Match only traffic arriving on fips0; everything else short-circuits. |
| 2 | ct state established,related accept |
Allow conntrack replies and related ICMPv6 errors. |
| 3 | icmpv6 type echo-request accept |
Allow IPv6 echo (ping6 reachability). |
| 4 | include "/etc/fips/fips.d/*.nft" |
Splice in operator drop-ins (empty matches nothing). |
| 5 | counter drop |
Default-deny everything else; counter increments on every drop. |
Outbound from fips0 is unrestricted. The baseline is a documented
dpkg conffile — operator edits to /etc/fips/fips.nft are preserved
across upgrades.
The systemd unit is fips-firewall.service (oneshot). It is not
enabled by default; activation is an explicit operator gesture
documented in
../how-to/enable-mesh-firewall.md.
Drop-In File Format
Operator extensions live under /etc/fips/fips.d/ with the .nft
suffix. Each file is included inline into the inbound chain at the
marked point and may contain any nftables rule lines valid in that
context.
Naming convention: <purpose>-from-<source>.nft keeps drop-ins easy
to scan. Examples shipped in the design discussion:
ssh-from-bastion.nft— accept TCP/22 from a single mesh-node addresshttp-from-cluster.nft— accept TCP/80 from a/64mesh-address prefixdns-public.nft— accept UDP/53 and TCP/53 from any mesh nodegit-from-trusted.nft— accept TCP/9418 from a set of mesh-node addresses
After editing, reload via
sudo systemctl reload-or-restart fips-firewall.service (or
equivalently sudo nft -f /etc/fips/fips.nft since the file is
idempotent).
Cryptographic Primitives
| Component | Choice | Where Used |
|---|---|---|
| Curve | secp256k1 | FMP IK, FSP XK, Schnorr signatures |
| Diffie-Hellman | ECDH on secp256k1 (x-only normalized) | Noise IK, Noise XK |
| AEAD | ChaCha20-Poly1305 | FMP link encryption, FSP session encryption |
| Hash | SHA-256 | NodeAddr derivation, Noise transcript |
| Key derivation | HKDF-SHA256 | Noise key schedule |
| Signatures | secp256k1 Schnorr | TreeAnnounce, LookupResponse proof, Nostr adverts |
| Noise pattern (link) | Noise_IK_secp256k1_ChaChaPoly_SHA256 |
FMP link layer (IK with epoch payload) |
| Noise pattern (session) | Noise_XK_secp256k1_ChaChaPoly_SHA256 |
FSP session layer (XK with epoch payload) |
These choices align with the Nostr cryptographic stack (secp256k1 + ChaCha20-Poly1305 + SHA-256) and the NIP-44 encrypted messaging standard.
Rekey Defaults
Both link-layer and session-layer Noise sessions rekey under one of
two triggers, configurable under node.rekey.*:
| Parameter | Default | Description |
|---|---|---|
enabled |
true |
Master switch. |
after_secs |
120 |
Time-based rekey threshold. |
after_messages |
65536 |
Message-count rekey threshold. |
In addition to the configurable triggers, the daemon retains the old
session keys for a fixed 10-second drain window after each
cutover (compile-time constant DRAIN_WINDOW_SECS in
src/node/handlers/rekey.rs). Rekey rotates the Noise key schedule
and the session indices; old session keys are kept in
previous_session for the drain window so in-flight packets
encrypted under the old keys still decrypt.
Replay Window
Both layers use explicit per-packet counters with a sliding bitmap
window for replay protection. The bitmap is 2048 entries at both
layers — large enough to accommodate UDP reordering and packet loss
without false-positive replay rejection. Counters older than the
window are rejected. The same ReplayWindow and
decrypt_with_replay_check() implementation is used at both the FMP
and FSP layers.
Peer ACL
Mesh-level ACL files at /etc/fips/peers.allow and
/etc/fips/peers.deny give the operator allowlist/blocklist control
over which npubs may complete the FMP Noise IK link handshake.
File format:
- One entry per line. An entry is either a bech32
npub1..., an alias defined in/etc/fips/hosts, or the literalALLwildcard (case-insensitive). - Lines beginning with
#are comments. - Blank lines are ignored.
Evaluation order (first match wins, default-allow on no match):
peers.allow— if the peer matches an entry here (orALLis inpeers.allow), the handshake is admitted, regardless of anypeers.denyentry.peers.deny— if the peer matches an entry here (orALLis inpeers.deny), the handshake is refused.- Otherwise the peer is admitted.
peers.allow is not an exclusive gate on its own: an unlisted
peer falls through to step 3 and is admitted unless it appears in
peers.deny. To turn peers.allow into a strict allowlist, place
ALL in peers.deny so every unlisted peer is rejected at step 2.
The ALL wildcard makes the operator's posture explicit:
ALLinpeers.allowadmits every peer (same effect as the default-allow behavior, but documented in the file).ALLinpeers.denyblocks every peer except those listed inpeers.allow— the "allowlist-strict" posture.
In practice this collapses to a few common postures:
- Default-allow with denylist: leave
peers.allowempty; populatepeers.deny. All npubs may peer except those listed. - Allowlist-strict: populate
peers.allowand putALLinpeers.deny. Only the listed npubs may peer; everyone else is rejected at step 2.
A populated peers.allow with an empty peers.deny is not a
strict allowlist — it is equivalent to default-allow plus an
explicit "always-admit" set. The strict variant requires ALL
in peers.deny.
Aliases are resolved through /etc/fips/hosts at file-load
time. If peers.allow lists core-vm and /etc/fips/hosts
maps core-vm to a specific npub, that npub is admitted. If
core-vm is later remapped to a different npub, the ACL
re-resolves on the next mtime change. Operators should be aware
that ACL semantics follow the hosts-file aliasing, not just
the literal npubs visible in the file.
Both files are reloaded automatically when their mtime changes — no daemon restart or signal is needed. ACL evaluation runs after msg1 decryption but before any further peer-state mutation; rate-limited msg1s never reach the ACL.
Filesystem Permissions
| Path | Owner | Mode | Purpose |
|---|---|---|---|
/etc/fips/fips.key |
root:root | 0600 |
Persistent identity private key (sensitive). |
/etc/fips/fips.pub |
root:root | 0644 |
Public key (npub). |
/etc/fips/fips.yaml |
root:root | 0644 |
Daemon configuration (dpkg conffile). |
/etc/fips/fips.nft |
root:root | 0644 |
nftables baseline (dpkg conffile). |
/etc/fips/fips.d/ |
root:root | 0755 |
Operator drop-in directory. |
/etc/fips/hosts |
root:root | 0644 |
Optional hostname → npub map (dpkg conffile). |
/etc/fips/peers.allow |
root:root | 0644 |
Optional peer allowlist. |
/etc/fips/peers.deny |
root:root | 0644 |
Optional peer denylist. |
/run/fips/control.sock |
root:fips | 0770 |
Control socket (members of fips group can use fipsctl). |
/run/fips/ |
root:fips | 0750 |
Control socket parent directory. |
Adding a user to the fips group grants fipsctl access without
requiring root. The daemon chowns the control socket and its parent
directory at bind time.
Threat-Resistance Matrix
The link layer's threat-resistance matrix is consolidated here from the FMP design document:
| Threat | Mitigation |
|---|---|
| Connection exhaustion | Token-bucket rate limit + connection count limit |
| CPU exhaustion (msg1 flood) | Rate limit before crypto operations |
| Replay attacks | Counter-based nonces with sliding window (2048 entries) |
| State confusion | Strict handshake state machine validation |
| Spoofed encrypted packets | Index lookup + AEAD verification |
| Spoofed msg2 | Index lookup + Noise ephemeral key binding |
| Address spoofing | Cryptographic authority, not address-based |
| Session correlation | Index rotation on rekey |
Inbound exposure on fips0 |
Default-deny nftables baseline (operator opt-in) |
| Sybil identities | Discretionary peering + handshake rate limiting + optional peer ACL |
| Eclipse attack | Diverse peering across independent operators and transports |
| Unauthorized peer admission | Optional peers.allow allowlist consulted before handshake |
See ../design/fips-mesh-layer.md for the unauthenticated-attack-surface analysis (only handshake msg1 is reachable by unauthenticated parties), and ../design/fips-mesh-operation.md for the metadata-privacy model and the rejection of onion routing.
Default Network Exposures by Transport
| Transport | Default Inbound | Default Bind | Opt-in |
|---|---|---|---|
| UDP | None until bind_addr set |
0.0.0.0:2121 typical |
Operator sets transports.udp.bind_addr |
| TCP | None until bind_addr set |
None — outbound-only without bind | Operator sets transports.tcp.bind_addr |
| Ethernet | Listens on configured interface (raw AF_PACKET) |
EtherType 0x2121 on selected interface | Per-flag discovery, announce, auto_connect, accept_connections |
| Tor | None until directory_service configured |
127.0.0.1:8443 (loopback only) |
Operator sets transports.tor.directory_service and configures HiddenServiceDir in torrc |
| BLE | Off by default | n/a | Operator enables transports.ble.* |
| Nostr discovery | Off by default | n/a (relay client, not a listener) | Operator sets node.discovery.nostr.enabled: true |
The mesh-layer fips0 interface is reachable from any mesh node that
can route to you, not only direct peers — your direct peers forward
traffic from any reachable mesh node onto your fips0. The
default-deny nftables baseline (operator opt-in) is the recommended
way to restrict inbound traffic on fips0. See
../how-to/enable-mesh-firewall.md.
See also
- ../design/fips-security.md — threat
model and design rationale for the
fips0baseline - ../design/fips-mesh-layer.md — FMP link encryption, replay protection, rate limiting
- ../design/fips-session-layer.md — FSP end-to-end encryption, Noise XK, replay window
- ../how-to/enable-mesh-firewall.md — operator activation and drop-in recipes
- configuration.md — full
node.rekey.*,node.rate_limit.*parameter tables