106 Commits
Author SHA1 Message Date
Johnathan Corgan d0a0824768 Merge master into next after the v0.4.1 release
Same split as the maint-to-master merge: version identity stays with the
receiving branch, project state flows up.

Kept next's: Cargo.toml and Cargo.lock at 0.6.0-dev, the status badge, and
the paragraph identifying next as the wire-format-breaking line that will not
interoperate with v0.2.x, v0.3.x, or v0.4.x peers. Only the shipped-release
pointer inside it moved from v0.4.0 to v0.4.1.

The changelog conflict was additive rather than competing, and resolving it
either way would have lost real content. Next's [Unreleased] Fixed section
carries the XX rekey divergence and dual-initiation work; master brought the
[0.4.1] section. Git could not tell these were adjacent rather than rival, so
both were kept in order, with next's Breaking block and its own [Unreleased]
entries untouched.

Took from master: the bloom FPR default change and its duplicate-definition
fix, the docs describing them, the v0.4.1 release notes, the v0.4.0 date
correction, and the root RELEASE-NOTES.md mirror.

Checked before merging that no incoming content names the Noise handshake
pattern, since next is XX where master is IK. The two "Noise IK" strings on
master both predate v0.4.0 and next already carries its own wording for them,
so nothing needed rewording here.

Quartet green: 1698 tests passed, clippy clean with -D warnings.
2026-07-19 19:03:40 +00:00
Johnathan Corgan a47ddbd5a5 Merge maint into master after the v0.4.1 release
Carries the v0.4.1 content up the one-way branch flow. The release itself
belongs to maint, so the parts of this merge that identify a version are
resolved in master's favor and the parts that describe the project's state
are taken from maint.

Kept master's: Cargo.toml and Cargo.lock at 0.5.0-dev, the README status
badge, and the "FIPS is at v0.5.0-dev on the master branch" line. A patch
release consumes no minor version and master's development line is
unaffected by it.

Took from maint: the bloom antipoison FPR default change and the
duplicate-definition fix it rests on, the two docs that describe them, the
new v0.4.1 release notes, the correction to the v0.4.0 release date, and the
[0.4.1] changelog section, which slots below master's own [Unreleased] and
above [0.4.0]. The root RELEASE-NOTES.md mirror moves to v0.4.1 because it
tracks the latest shipped release, which also clears the stale provisional
date it had been carrying.

One line needed splitting rather than choosing: the README said "v0.4.0 has
shipped" inside the paragraph that identifies master as 0.5.0-dev. The
version identity stays master's and the shipped-release pointer moves to
v0.4.1.

Quartet green on the result: 1645 tests passed, clippy clean with -D warnings.
2026-07-19 18:57:52 +00:00
Johnathan Corgan bda327b5f5 Raise the bloom antipoison FPR cap default to 0.20
The inbound FilterAnnounce cap at 0.10 rejects aggregates that are
legitimately near their operating ceiling, before the network reaches
the fixed-filter capacity limit. Raise the default to 0.20, which
corresponds to fill 0.7248 at k=5, about 2,114 entries on the 1 KB
filter (Swamidass-Baldi).

Also remove the duplicate default definitions in BloomConfig. Each
default was written twice, once in impl Default and once in the serde
default function, with nothing enforcing that they agree, so a config
file that omits the key took a different path from one that sets it.
impl Default now delegates to the serde default functions, leaving a
single source of truth.
2026-07-19 17:18:49 +00:00
Johnathan Corgan 7ea5718270 Merge branch 'master' into next 2026-07-18 02:57:30 +00:00
ArjenandJohnathan Corgan cf62cff5f4 feat: Android-ready core: target_os gating and app-owned TUN seam
Make the FIPS core build and run as an embedded Android library. The host
app owns the TUN (e.g. an Android VpnService) and FIPS performs no
system-TUN or CAP_NET_ADMIN operations.

Squashed from the following changes:

- gate desktop transports/TUN by target_os, not features: a plain
  `cargo build` now compiles for every target with no flags. Ethernet (raw
  AF_PACKET / BPF) is gated to linux/macos, so Android (target_os =
  "android", not "linux") self-excludes it as Windows already did; real
  system-TUN ops are gated per linux/macos and Android gets a no-op stub;
  the ipi6_ifindex cast handles it being i32 on Android vs u32 on macOS. No
  Cargo features are introduced; desktop builds are unchanged.

- app-owned TUN seam: Node::enable_app_owned_tun() lets an embedder that
  owns the TUN fd exchange IPv6 packet bytes with FIPS over channels
  instead of FIPS creating a system TUN device. It returns (app_outbound_tx,
  app_inbound_rx): the embedder pushes packets read from its fd into the
  outbound sender (app -> mesh) and pulls packets destined for its fd from
  the inbound receiver (mesh -> app). start() gates system-TUN creation on
  tun_tx being unset, so with the channels pre-installed it skips device
  creation and does no system-TUN ops; both directions reuse the existing
  inbound-shim and run_rx_loop wiring. Packets entering via app_outbound_tx
  bypass handle_tun_packet, so the embedder must push only fd00::/8-destined
  packets and clamp TCP MSS on outbound SYNs; the rustdoc and the
  IPv6-adapter design doc spell this out.

- keep the android target warning-clean so the cross-compile check passes
  clippy -D warnings.

- add an Android cross-compile CI check: cross-compile the library for
  aarch64-linux-android via cargo-ndk and run clippy -D warnings. Android
  ships as an embedded library (the host app owns the TUN), so there is no
  daemon binary to package; this is a check job, not a packaging one.

- docs: list Android as a supported platform.

Tests: app_owned_tun_seam_wires_channels covers the channel round-trip and
the Active state; start_skips_system_tun_when_app_owned runs start() and
asserts no named system device is created.
2026-07-17 18:25:55 +00:00
Johnathan Corgan de72bf76ea Merge branch 'refactor-node' into refactor-node-next
Forward-merge the dataplane/session concept-home reorganization onto
the next line. The moved files carry next's (XX-line) content; the one
hand-resolved surface is src/peer/active.rs, where the source-location
citation sits in different surrounding text than on the master line —
resolved by keeping next's body and applying the same node/session.rs
-> node/session/mod.rs citation update.
2026-07-12 19:24:07 +00:00
Johnathan Corgan 434b9726aa node: establish dataplane/ and session/ concept homes (behavior-neutral)
Reorganize the node module tree by concept rather than by
message-handling verb, as the first step of the node runtime
decomposition. Pure relocation: no wire, config, metric, or log
semantics change; the lib test count is unchanged (1577 passed).

Moves (git mv, 100% rename similarity):
- handlers/{forwarding,rx_loop,connected_udp,dispatch,encrypted}.rs
  -> node/dataplane/ — the whole RX hot path (the select! run loop,
  transit/local forwarding, the link-message router, the RX decrypt
  path with responder K-bit cutover + roam writes, and connected-UDP
  fast-path activation) now lives in one home.
- node/session.rs -> node/session/mod.rs — establishes the session
  concept home for the data/state types. The message-behavior file
  handlers/session.rs stays put for now (folds in with the later FSP
  session step).

The IK/XX-divergent establishment files (handlers/{handshake,rekey,
timeout}.rs) and the deferred-home files (handlers/{mmp,lookup}.rs)
deliberately stay in handlers/, to move once rather than twice.

Every module is reached through impl Node methods, so no call site or
re-export shim was needed. Updated in lockstep with the moves: the
module_path!-derived tracing targets in the two mesh-lab compose-trace
overlays, a structural test's include_str! source path, doc-comments
in proto/routing and the mesh-lab docs, and the stale source-location
citations (node/handlers/{forwarding,rx_loop,encrypted}.rs and
node/session.rs) in doc-comments and the discovery design doc.
2026-07-12 19:22:08 +00:00
Johnathan Corgan 6f8590cd92 docs: flag the bloom-filter design doc as describing the retired v1.5
The code on next is currently v1 after the v1.5 removal during the
sans-IO refactor, but this doc still described the abandoned v1.5 design
as implemented, which is misleading. Add an interim status banner
pointing at v2 as the pending target until the doc is rewritten to v2.
2026-07-12 15:36:54 +00:00
Johnathan Corgan 033957f23d Merge branch 'refactor-transport' into refactor-next
Forward-merge the transport neighbor-beacon rename (ethernet + BLE
discovery -> neighbor module/buffer rename; ethernet config discovery
flag -> listen with serde alias). Hand-resolved the module files against
next's identity-out-of-beacon beacon rewrite by starting from next's
bodies and re-applying the rename by identifier, so next's beacon logic
(no-arg build_beacon, bool parse_beacon, computed BEACON_SIZE, one-arg
add_peer, deleted BLE add_peer_with_pubkey) is preserved unchanged.
2026-07-11 23:20:50 +00:00
Johnathan Corgan cbc089b820 transport/ethernet: rename config discovery flag to listen
Rename the Ethernet per-interface config flag from discovery to listen,
so the receive/transmit toggle pair reads as the symmetric announce
(transmit) / listen (receive) neighbor-beacon vocabulary. The old
discovery: key is still accepted via a serde alias, so deployed configs
load unchanged; to_yaml re-emits it under the canonical listen: name.
Marked deprecated for removal at the v2 cutover.

Updates the config field + accessor, the transport listen_enabled local,
the one struct-literal test consumer, the chaos sim config generator,
packaged fips.yaml examples, and the classified operator-facing docs
(ethernet neighbor-beacon subsystem prose; the generic Transport
discovery capability prose is left unchanged). Adds a compat parse test
asserting the legacy alias, the new key, and that deny_unknown_fields
still rejects unknown keys. Behavior-neutral.
2026-07-11 23:13:28 +00:00
Johnathan Corgan 784e22ab1f Merge branch 'master' into next
Forward-merge the v0.4.0 pre-release content (dependency refresh, CI
deb-install + AUR-build legs, docs refresh, doc-comment fixes, and the
Phase 4 source-content squash: CHANGELOG, release notes, README,
fips.yaml, Cargo.toml metadata, reference docs) up the one-way flow.

Conflict fixups (keep next's identity, fold in master's improvements):
- Version: keep next's 0.5.0-dev (Cargo.toml/lock).
- README status: keep next's v0.5.0-dev / wire-format-breaking framing
  and the Breaking-section pointer; fold in the Nym transport and the
  "global, public test mesh of thousands of nodes" description.
- docs/reference/cli-fips.md: keep next's 0.5.0-dev version example.
- CHANGELOG: keep next's XX-handshake admission entry (no early cap gate
  on XX) and drop master's IK early-cap-at-handle_msg1 entry, which
  describes IK-only behavior that does not apply on next; take master's
  OR-union mesh-size rewrite (next carries the OR-union code); restore
  the Tor connect_refused and MMP receiver-report entries that the
  Fixed-section consolidation would otherwise have dropped.
- lifecycle.rs: keep the mDNS/LAN handshake doc-comment as Noise XX
  (next unifies on XX), not master's XX-to-IK correction.

Quartet green on the merged tree: fmt, build, clippy -D warnings, and
cargo test --lib (1434 passed).
2026-06-14 18:24:32 +00:00
Johnathan Corgan 507086e39d docs: refresh tutorials, how-to, design, reference, and examples for v0.4.0
Pre-cut documentation pass for the 0.4.0 release, verified against current source.

Corrections:
- fipsctl: stale 'show identities'/'show node' -> 'show status'
  (host-a-service, run-as-unprivileged-user)
- mesh address derivation: first 16 bytes of SHA-256(pubkey) with the leading
  byte set to 0xfd, not a fixed fd97: prefix (reach-mesh-services,
  ipv6-adapter-walkthrough)
- gateway control socket mode 0660 -> 0770 (troubleshoot-gateway)
- Tor example: add advertised_port: 8443 so the published port matches the
  prose (enable-nostr-discovery)
- bloom mesh-size estimate rewritten to the OR-union-of-peer-filters algorithm;
  plus mtu deep-link, gateway pool wording, and a NAT failure-mode line
- examples: delete orphaned nostr-rs-relay config, accept inbound to the local
  8443 TCP listener, fix fd::/8 -> fd00::/8 typos, dotless wireguard alias

Additions:
- new Nym mixnet transport section (fips-transport-layer) and the architecture
  transport list
- new LAN/mDNS discovery section (fips-nostr-discovery)
- reference docs: Nym transport, LAN discovery, and new control/stats surfaces;
  drop ble from the connect transport list
2026-06-14 15:14:05 +00:00
Johnathan Corgan 6aded6e365 Merge branch 'master' into next 2026-06-09 11:33:51 +00:00
Johnathan Corgan f3eb5bf4c2 bloom: raise max_inbound_fpr antipoison cap from 0.05 to 0.10
The inbound FilterAnnounce FPR cap rejects filters whose false-positive
rate (fill^k) exceeds the configured maximum. On the fixed 1 KB / k=5
filter, 0.05 corresponds to fill 0.549 (~1,300 reachable entries), and
the busiest nodes' aggregates were beginning to hit that ceiling as the
mesh grew. Raise the default to 0.10 (fill 0.631, ~1,630 entries) to
restore headroom toward the fixed-filter capacity limit. A saturated or
poisoned filter is ~100% FPR and remains rejected, so the antipoison
gate is not materially weakened.

Updates the config default, the config-reference and bloom-filter
design docs, and the changelog.
2026-06-09 11:32:58 +00:00
Johnathan Corgan aed64f6d80 Merge master into next (MMP stale-report rejection; convergence-gate near-budget hold) 2026-06-08 00:19:37 +00:00
Martti MalmiandJohnathan Corgan d548add18d Reject stale MMP receiver reports
Ignore duplicate or counter-regressed ReceiverReports before updating
RTT, loss, goodput, or ETX, so a delayed or reordered report can no
longer poison link metrics. Compute the RTT-from-echo sample with
checked timestamp arithmetic and reject zero, negative, or out-of-range
results instead of risking wrap or underflow on untrusted wire values.

On the sender side, when receiver dwell time overflows the u16 wire
field, suppress the timestamp echo (send 0) and saturate dwell to
u16::MAX rather than truncating, so a bogus small RTT cannot be formed.

Adds duplicate, out-of-order, wrapped-add, and future-dated (checked_sub)
sample tests, asserts loss and goodput stay unchanged on a dropped
duplicate, and covers the dwell-overflow echo suppression. Documents the
behavior in the MMP design note and CHANGELOG.

Co-authored-by: Johnathan Corgan <johnathan@corganlabs.com>
2026-06-07 23:30:35 +00:00
Johnathan Corgan 7807516b8d Merge branch 'master' into next 2026-05-10 22:04:29 +00:00
Johnathan Corgan 77fdd52fe0 docs: reviewer feedback pass on Nostr-discovery surface
Walk through reviewer feedback on the Nostr-discovery docs and
land 18 items.

Bulk patterns:

- `external_addr` / `public: true` semantics consistently
  misdescribed. The advert path is gated on `cfg.is_public()`;
  inside that branch the daemon picks an address by precedence
  (`external_addr`, non-wildcard `bind_addr`, STUN). The docs
  treated `public: true` and `external_addr` as alternatives when
  they are stacked: `public: true` is the master switch and
  `external_addr` populates the address inside it. Reconciled
  across `enable-nostr-discovery.md` and `advertise-your-node.md`:
  add `public: true` to the `external_addr` examples; replace
  "STUN as a logging cross-check" with "STUN is skipped entirely";
  fix "neither flag is needed" for direct public bind (both flags
  still required); make the publish-tutorial Step 3 conditional
  on the chosen Step 2 path (STUN runs only on the `public: true`
  path); rewrite the troubleshooting "wrong public IP advertised"
  bullet with two coherent fixes.

- `udp:nat` overpromised as a symmetric-NAT solution. Symmetric
  NAT on either side typically defeats the punch. Reframe
  `udp:nat` as best-effort hole-punching for nodes without a
  directly reachable UDP endpoint in the how-to, the publish
  tutorial (intro, callout, section heading rewrite from "If
  you're behind symmetric NAT" to "If your direct UDP advert
  isn't reachable"), the consume tutorial's "What's next"
  pointer, and `tutorials/README.md`. Promote reachability over
  named NAT classes: STUN can confirm the public IP but not that
  the listener-port mapping is open.

- YAML "silently ignores unknown keys" is wrong. Config parser
  rejects unknown fields via `serde(deny_unknown_fields)` on the
  per-section structs; misspelled fields refuse the daemon's
  start with a parse-error line in the journal. Fixed in the
  publish tutorial's troubleshooting and the open-discovery
  tutorial's `policy` typo bullet.

Mechanical fixes:

- Repoint stale anchors. `getting-started.md` and
  `configuration.md` linked to `#installation` / `#inspect` on
  the README; the README has no such headings. Repoint to
  `#quick-start` and `cli-fipsctl.md`. Two stale anchors in the
  publish tutorial pointing at non-existent sub-scenarios in the
  how-to (`#sub-scenario-2c-...`,
  `#sub-scenario-2b-tor-onion-node`) repointed to the correct
  anchors.

- Drop the `fipsctl show status` claim from the open-discovery
  troubleshooting bullet (`show_status` doesn't include
  `discovery.nostr.policy`). Replace with daemon startup logs.

- Fix the `advertise: false` parenthetical in the consume-only
  tutorial (`default_advertise()` returns `true`; we set `false`
  explicitly for the consume-only path).

- Drop the "supplies a relay list" overstatement in two
  activation paragraphs (the how-to and the design doc). Default
  relay / STUN-server lists ship in the config; both are
  optional overrides.

- Add the missing `transports.udp.public` entry to the
  open-discovery tutorial's prerequisites checklist. Tutorial
  users coming out of advertise-your-node could be on either the
  direct-UDP (`public: true`) or `udp:nat` (`public: false`)
  path; list both.

Files: docs/getting-started.md, docs/reference/configuration.md,
docs/how-to/enable-nostr-discovery.md, docs/tutorials/README.md,
docs/tutorials/advertise-your-node.md,
docs/tutorials/resolve-peers-via-nostr.md,
docs/tutorials/open-discovery.md,
docs/design/fips-nostr-discovery.md.
2026-05-10 21:56:09 +00:00
Johnathan Corgan 45a8a4cd9b Merge branch 'master' into next
Forward-merge of 12 master commits past the previous merge
(823b830, master @ 18019bb): dep-audit bumps (rand,
clap, tun, rtnetlink, windows-service, plus the bump-safe
lockfile batch), bloom-storm chaos scenario, control-socket
resolver consolidation, gateway dns.listen default change,
OpenWrt ipk README refresh, gateway tutorial review, Ethernet
MTU rustdoc fix, dead session-variant drop, CHANGELOG prep.

Conflict resolution:

- CHANGELOG.md: both bullets kept under [Unreleased] / Fixed.
  Master's spanning-tree internal-path-propagation fix precedes
  next's tree-ancestry-test determinism entry and the
  responder-Disconnect XX-handshake entry.
- src/protocol/session.rs: kept next's SessionSetup/SessionAck
  variants and rustdoc. Master's drop of those variants suits
  v0.3.0's FSP phase-byte dispatch but is undone by next's
  v0.4.0 wire format, which retains the variants and uses the
  inner msg_type byte for handshake identification.
- src/transport/ethernet/mod.rs: kept next's "interface MTU - 4"
  comment. Master corrected the v0.3.0 3-byte rustdoc; next
  redesigned the framing to a 4-byte header (type/flags/length)
  for shared-media beacons, so master's correction does not
  apply to next's format.

Auto-merged cleanly: Cargo.toml (next's 0.4.0-dev + the new dep
pins from master), Cargo.lock, all gateway docs,
docs/reference/configuration.md, packaging files,
.github/workflows/ci.yml, testing/chaos/sim/* and
testing/ci-local.sh (bloom-storm additions), src/config/*.

Local verification: cargo build --release, cargo test (1252
passed, 4 ignored), cargo clippy -D warnings, cargo fmt --check
all green.
2026-05-08 18:47:13 +00:00
Johnathan Corgan 0fcf0f6f8f 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.
2026-05-08 14:02:05 +00:00
Johnathan Corgan 823b830289 Merge branch 'master' into next
Forward-merge of the docs-overhaul squash (5abf9a9) and top-level
README rewrite (18019bb). Conflict resolution:

- README.md: master's rewritten feature lists adopted, with the encryption
  bullets reflecting next's two-layer Noise XX (replacing the IK/XK pair
  master describes for v0.3.0).
- 6 design/reference markdown files (fips-bloom-filters.md, fips-mesh-layer.md,
  fips-mesh-operation.md, fips-session-layer.md, fips-transport-layer.md,
  reference/wire-formats.md): master's reorg taken, next's protocol details
  preserved (XX handshake naming, bloom v2 RLE/delta wire format,
  v2 LookupRequest sizing).
- fips-intro.md modify/delete: accepted master's split into
  fips-architecture.md / fips-concepts.md / fips-prior-work.md, then
  re-applied next's IK/XK -> XX transition and spin-bit removal across
  the relevant split files. Same pass swept docs/reference/security.md,
  docs/design/fips-mmp.md, docs/design/fips-security.md,
  docs/design/fips-nostr-discovery.md,
  docs/design/port-advertisement-and-nat-traversal.md,
  docs/how-to/enable-nostr-discovery.md, and the affected tutorials so
  no IK/XK or spin-bit prose remains in current-state docs.
- Diagram path conflicts: noise-ik-msg{1,2}.svg removed (IK is gone);
  noise-xx-msg{1,2,3}.svg moved from docs/design/diagrams/ to
  docs/reference/diagrams/ to match master's diagram reorg. The
  wire-formats.md image references resolve correctly to the new path.

Local verification: cargo build --release, cargo test (1265 passed,
4 ignored), cargo clippy -D warnings, cargo fmt --check all green.
2026-05-08 13:45:04 +00:00
Johnathan Corgan 5abf9a9325 docs: four-section /docs/ restructure with new-user content, accuracy pass, and gateway feature-set rewrite
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.
2026-05-08 03:02:12 +00:00
Johnathan Corgan e7fc4f7d10 Merge branch 'master' into next 2026-05-02 02:14:36 +00:00
Johnathan Corgan ab2edec2c6 Add Nostr open-discovery startup sweep with diagnostic logging
Under `node.discovery.nostr.policy: open`, the per-tick auto-dial in
`queue_open_discovery_retries` was supposed to pick up adverts cached
from the relay subscription backlog at startup, but in practice only
adverts arriving live (after the daemon was up) were being dialed.
Backlog adverts sat in the in-memory cache until they aged out.

Adds a one-shot startup sweep that runs once per daemon start, gated
identically to the per-tick sweep (`enabled` && `policy == open`),
after a configurable settle delay so the relay subscription backlog
has time to populate the advert cache. The sweep iterates the cache
with the same skip-filters as the per-tick path (statically-configured
peers, already-connected, retry-pending, connecting) plus a tighter
age filter: only adverts whose `created_at` is within
`startup_sweep_max_age_secs` of now are queued.

Two new config fields under `node.discovery.nostr`:
- `startup_sweep_delay_secs` (default 5)
- `startup_sweep_max_age_secs` (default 3600 = one hour)

Both are only consulted when `policy == open`; under any other
policy the sweep is a no-op.

Adds diagnostic logging to the open-discovery sweep so operators can
verify what the auto-dial path is doing on each daemon bring-up:
info-level on each retry-queued enqueue (with peer short-npub and
advert age), and a one-line summary on every startup sweep and on
any per-tick sweep that queues at least one retry. The summary
breaks down skipped candidates by reason (age, configured, self,
already-connected, retry-pending, connecting, no-endpoints,
invalid-npub) — currently the path was silent so there was no
operator-visible signal that the cache iteration was running.

Refactors the existing `queue_open_discovery_retries` body into a
shared `run_open_discovery_sweep(max_age_secs, caller)` helper so
the per-tick and startup paths share filter/queue logic and only
differ in the age filter and log label. Surfaces `created_at` from
`NostrDiscovery::cached_open_discovery_candidates` (return tuple
extended) so the age filter has the data it needs.

Three new unit tests in `config::node::tests` cover the new defaults,
YAML override round-trip, and partial-YAML default fallback.
2026-05-01 18:09:50 +00:00
Johnathan Corgan 239cbdc4ba Fix Tor onion adverts missing port in Nostr overlay discovery
The Nostr overlay advert publisher serialized `transport: tor`
endpoints as a bare `<onion>.onion` hostname with no port. The Tor
address parser requires `<host>:<port>` form and rejected the bare
shape with `expected host:port`. Any peer receiving a Tor-only
advert went into a persistent retry-fail loop on jittered backoff
until the advert aged out of the discovery cache. The bug had been
latent for as long as Tor adverts have been published on Nostr, and
was masked in deployments where every node also advertised a
non-Tor transport (peers fell through to the working endpoint).
Surfaced first on a deployment where Tor was the only advert path.

Publisher now emits `<onion>.onion:<port>` using a new
`transports.tor.advertised_port` config field that defaults to 443,
matching the Tor `HiddenServicePort 443 127.0.0.1:<bind_port>`
convention. Operators whose torrc uses a non-default virtual port
can override.

Adds a unit test that pins the publisher/parser contract: formats
the advert exactly as the publisher does and asserts `parse_tor_addr`
accepts the result; asserts the bare-onion form (the bug) does not
parse, catching any future regression that drops the port again.

Parser is unchanged (already correct).
2026-05-01 16:54:10 +00:00
Johnathan Corgan 5a5dbe36da Merge branch 'master' into next
# Conflicts:
#	src/node/lifecycle.rs
#	testing/static/scripts/rekey-test.sh
2026-04-27 16:10:11 +00:00
34e00b9f6e Add Nostr-mediated overlay discovery and UDP NAT traversal (#53)
Optional peer discovery and NAT hole-punching path gated behind a new
`nostr-discovery` cargo feature. Nodes publish signed overlay endpoint
adverts to public Nostr relays, consume peer adverts to populate
fallback dial addresses, and use STUN-assisted UDP hole punching with
NIP-59 gift-wrap offer/answer signaling to establish direct UDP paths
between NATed peers. Once a punched socket is up, it is handed into
the existing FIPS UDP transport and the standard Noise/FMP session
stack takes over unchanged.

The cargo feature is in the default feature set
(`default = ["nostr-discovery"]`) so stock builds include it; a
build that explicitly disables default features (or selects a
feature set without `nostr-discovery`) does not link the nostr /
nostr-sdk crates and does not emit a no-op poll in the tick loop.
Runtime behavior is independently gated by
`node.discovery.nostr.enabled`, which defaults to false; if the
config enables Nostr on a non-feature build, startup logs a
warning and continues without it.

== Cargo feature and dependencies

- New cargo feature `nostr-discovery = ["dep:nostr", "dep:nostr-sdk"]`.
  Not in the default feature set.
- New optional Linux-only dependencies: `nostr 0.44` (features: std,
  nip59) and `nostr-sdk 0.44`. Gift-wrap unwrap is hand-rolled in
  `src/discovery/nostr/signal.rs` rather than relying on the SDK's
  rumor-author check, which FIPS sidesteps by trusting `seal.pubkey`
  exclusively.

== Wire format

Overlay advert event: `kind 37195`, parameterized replaceable
(NIP-01 application-defined replaceable range 30000-39999), with
`d = "fips-overlay-v1"`. The digits visually spell FIPS (7=F, 1=I,
9=P, 5=S); a relay survey confirmed the kind is unused.

Advert content carries the version tag, endpoint list
(`udp|tcp|tor` + addr), optional signal-relay and stun-server
metadata, and `issuedAt` / `expiresAt` timestamps. Endpoint
`addr: "nat"` is the sentinel that triggers traversal on the peer
side. NIP-40 `expiration` tag bounds staleness on permanent
shutdown. Lifecycle relies on parameterized-replaceable
supersession; the daemon does not emit NIP-09 kind-5 deletes —
strict relays (Damus, Primal) race delete-against-replace and can
silently drop the replacement.

Gift-wrapped signal event: `kind 21059`. Punch packets carry magic
values `PUNCH_MAGIC` / `PUNCH_ACK_MAGIC`, a sequence number, and a
16-byte session hash.

== Discovery surface

- `src/discovery.rs` (always compiled)
  - `EstablishedTraversal`: bound UDP socket + selected remote +
    peer npub + optional transport name/config tuning overrides.
  - `BootstrapHandoffResult`: returned on successful handoff —
    allocated transport id, local/remote addrs, peer NodeAddr,
    session id.
- `src/discovery/nostr/` (`#![cfg(feature = "nostr-discovery")]`)
  - `types.rs`: wire and control types described above. `ADVERT_KIND`
    constant. `BootstrapError` enumerates failure modes (disabled,
    missing advert, missing NAT endpoint, no usable relays, invalid
    advert, invalid npub, signal timeout, punch timeout, replay,
    STUN failure, protocol, nostr, io, serde, event-parse).
  - `runtime.rs`: `NostrDiscovery` coordinator. Owns the shared
    nostr-sdk `Client`, subscribes to advert + signal event kinds,
    maintains a bounded advert cache and a bounded seen-sessions
    replay set, drains `BootstrapEvent::{Established, Failed}` for
    the node to consume, exposes `update_local_advert`,
    `request_connect`, `advert_endpoints_for_peer`,
    `cached_open_discovery_candidates`, and `shutdown`.
  - `signal.rs`: NIP-59 gift-wrap encode/decode. Outbound wraps are
    built against per-attempt ephemeral keys; inbound events are
    unwrapped against the node identity.
  - `stun.rs`: RFC 5389/8489 Binding Request client with
    XOR-MAPPED-ADDRESS parsing for both IPv4 and IPv6; used only to
    observe the initiator's own reflexive address against its
    locally configured STUN list (peer-advertised STUN is
    informational, never an egress target).
  - `traversal.rs`: per-attempt candidate-pair punch planner.
    Allocates a fresh `0.0.0.0:0` UDP socket per attempt, enumerates
    LAN-private and ULA interface addresses alongside the STUN
    reflexive address, schedules probe/ack exchanges at the
    configured interval for the configured duration, and picks the
    first candidate pair that authenticates end-to-end.

Strategy ordering is Reflexive↔Reflexive first, then LAN, then
Mixed. The STUN-observed pair is the only candidate that's reliable
across arbitrary network topologies; trying it first prevents the
planner from latching onto a misleading host-candidate path before
the reflexive path gets a chance. There is no catch-all
Local↔Local strategy: a previous design that paired every local
host candidate from one side with every local host candidate from
the other could declare success on a one-way reachable asymmetric
L3 path (corporate VPN, Tailscale subnet route, overlapping private
address space), only for the FMP handshake to stall because the
return path didn't match. The legitimate `Lan` strategy still pairs
candidates that share a subnet.

== Configuration surface

`node.discovery.nostr.*` (`NostrDiscoveryConfig`), all `serde(default)`
with `deny_unknown_fields`:

- `enabled` (default false), `advertise` (default true)
- `advert_relays`, `dm_relays`, `stun_servers`: defaults are
  `wss://relay.damus.io`, `wss://nos.lol`, `wss://offchain.pub`
  for both relay lists, and Google / Cloudflare / Twilio for STUN.
  Operators are expected to override for production. Other
  verified-working public relays for reference:
  `nostr.bitcoiner.social`, `nostr-pub.wellorder.net`,
  `nostr.oxtr.dev`, `nostr.mom`.
- `app` (default `"fips-overlay-v1"`), `signal_ttl_secs` (120)
- `policy`: `NostrDiscoveryPolicy::{Disabled, ConfiguredOnly (default),
  Open}` — controls whether advert-derived endpoints are consumed
  only for peers carrying `via_nostr = true`, or also for
  non-configured peers within a budget cap.
- `share_local_candidates` (default false) — when false, the offer's
  `local_addresses` list is empty and peers see only the reflexive
  address. Enable per-node only for genuinely same-LAN deployments;
  off-by-default eliminates the misleading-path failure mode for
  the common case where peers are not on the same broadcast domain.
- `open_discovery_max_pending` (64) — caps queued open-discovery
  retries; bounded by available outbound slots.
- `max_concurrent_incoming_offers` (16) — semaphore against offer
  spam; excess offers are debug-logged and dropped.
- `advert_cache_max_entries` (2048) and `seen_sessions_max_entries`
  (2048) — bound memory under ambient relay volume; overflow
  evictions are debug-logged.
- `attempt_timeout_secs` (10), `replay_window_secs` (300)
- `punch_start_delay_ms` (2000), `punch_interval_ms` (200),
  `punch_duration_ms` (10000)
- `advert_ttl_secs` (3600), `advert_refresh_secs` (1800)

Per-peer and per-transport flags:

- `PeerConfig.via_nostr: bool` — when true (and Nostr is enabled),
  advert-derived addresses are appended as fallback dial candidates
  after static addresses for that peer.
- `PeerConfig.addresses` is now `serde(default)` and may be empty
  when `via_nostr: true`; validation requires at least one of the
  two to be present per peer, and the error message names the
  peer's npub.
- `UdpConfig.advertise_on_nostr: Option<bool>` and
  `UdpConfig.public: Option<bool>` — UDP transports can be
  advertised either as direct `host:port` (public = true) or as the
  `addr: "nat"` sentinel that triggers rendezvous on the peer side.
- `TcpConfig.advertise_on_nostr` and `TorConfig.advertise_on_nostr`
  — TCP and Tor onion endpoints can be advertised as directly
  reachable.
- A reserved peer address `transport: udp, addr: "nat"` parses without
  special-casing in YAML and routes through the bootstrap runtime.

Cross-field validation (`Config::validate`, called from `Node::new`
and `Node::with_identity`):

- Any transport with `advertise_on_nostr = true` requires
  `node.discovery.nostr.enabled = true`.
- Any peer with `via_nostr = true` requires
  `node.discovery.nostr.enabled = true`.
- A non-public UDP advert (`advertise_on_nostr = true`,
  `public = false` — i.e. `udp:nat`) additionally requires at least
  one `dm_relay` and at least one `stun_server`.
  Surfaced as `ConfigError::Validation`.

== Node integration

`src/node/lifecycle.rs` is the main integration point.

- At node start (after transports are up, before TUN), if Nostr is
  enabled and the feature is compiled in, `NostrDiscovery::start` is
  invoked, the initial local overlay advert is built from the live
  transport set and published, and the runtime handle is stored.
- The rx tick loop calls `poll_nostr_discovery` (feature-gated both
  at method definition and call site), which refreshes the local
  advert, drains bootstrap events, adopts established traversals,
  schedules retries for failed traversals, and — under `policy:
  open` — enqueues outbound retries for non-configured peers
  visible in the advert cache, bounded by
  `open_discovery_max_pending` and the remaining outbound slots.
- Outbound peer dialing is refactored to `try_peer_addresses`, which
  first exhausts the static address list in priority order and only
  then appends advert-derived fallback addresses; both lists run
  through the same `attempt_peer_address_list` code path. The
  `udp:nat` sentinel address triggers `NostrDiscovery::request_connect`
  for the peer instead of a direct dial and returns `Ok(())`.
- `build_overlay_advert` walks operational transports, consults
  per-instance `UdpConfig` / `TcpConfig` / `TorConfig` (matching by
  optional transport instance name), and emits an `OverlayAdvert`
  including `signalRelays` and `stunServers` when any UDP endpoint
  is advertised as NAT.
- `adopt_established_traversal` is the bootstrap handoff API:
  allocates a new `TransportId`, constructs a `UdpTransport` with
  the user-supplied (or default) `UdpConfig`, calls the new
  `adopt_socket_async` to reuse the punched socket verbatim,
  registers the transport in the normal transport map, records it
  in `bootstrap_transports`, and calls `initiate_connection` so the
  normal handshake path runs. On failure, the transport is stopped
  and removed cleanly and the set membership is rolled back.
- On clean shutdown, `NostrDiscovery::shutdown` is awaited so
  background tasks stop before transports are torn down. (The
  advert is not explicitly retracted; NIP-40 expiration plus the
  next refresh from any live publisher supersedes it.)

New `Node` fields:

- `nostr_discovery: Option<Arc<NostrDiscovery>>` (feature-gated).
- `bootstrap_transports: HashSet<TransportId>` — per-peer UDP
  transports adopted from NAT traversal, cleaned up via
  `cleanup_bootstrap_transport_if_unused` whenever the link,
  connection, peer, or pending-connect referencing them is removed.

Retry and error surface:

- `RetryState.expires_at_ms: Option<u64>` — optional absolute expiry
  for a retry entry. `pump_retries` drops expired entries with an
  info log. Used for open-discovery retries, which expire at two
  times the advert TTL.
- New `NodeError::BootstrapHandoff(String)` returned from
  `adopt_established_traversal` when the underlying transport
  adoption fails or local address discovery fails.
- New `ConfigError::Validation(String)`.
- A small refactor extracts `Node::now_ms()` and reuses it across
  lifecycle, rx-loop tick, and timeout bookkeeping.

== UDP transport

`src/transport/udp/`:

- `UdpRawSocket::adopt(std::net::UdpSocket, recv_buf, send_buf)`:
  adopts an externally bound socket, makes it non-blocking, applies
  the configured buffer sizes (warning if the kernel clamps), and
  reports the resulting local address. Preserves the NAT mapping —
  no rebind.
- `UdpTransport::adopt_socket_async(std::net::UdpSocket)`: the
  `start_async` analogue for an already-bound socket, wiring the
  async socket and recv task exactly as the fresh-bind path would.
- `Drop` impl for `UdpTransport`: if a transport is dropped while
  still holding a recv task or socket (for example on error
  teardown), aborts the task, clears the socket, and emits a debug
  log so the cleanup is visible in tracing rather than silent.

== Logging and observability

Default `EnvFilter` demotes third-party relay-pool DEBUG output to
TRACE-only: `nostr_relay_pool`, `nostr_sdk`, and `nostr` are pinned
at INFO when our level is anything below TRACE, and at TRACE when
our level is TRACE — so the raw frames are still reachable when
explicitly asked for. RUST_LOG continues to override completely.

Concise one-line DEBUG events are emitted at the meaningful points
in the discovery / hole-punch sequence:

- `advert: published` (event id, relay count, endpoints, ttl)
- `advert: peer cached` (notify-loop ingress for non-self)
- `advert: resolved` (cache hit / relay fetch outcome)
- `traversal: initiator starting`
- `traversal: initiator STUN observed` (reflexive, local count)
- `traversal: offer sent` (session id, relay count, event id)
- `traversal: answer received` (accepted, reflexive, local)
- `traversal: initiator punch succeeded` (remote addr)
- `traversal: offer received` (responder side)
- `traversal: responder STUN observed`
- `traversal: answer sent`
- `traversal: responder punch succeeded`

Npubs are shortened to `npub1<4>..<4>` and event/session ids to
their first 8 hex characters.

Other operator-facing logs:

- `UdpTransport` adoption and drop paths log at info / debug.
- `adopt_established_traversal` logs at debug on entry and info on
  successful return, tagged with peer npub, session id, transport
  id, and both socket endpoints, so the bootstrap handoff is
  traceable end-to-end alongside the `UdpTransport::drop` log.
- `cleanup_bootstrap_transport_if_unused` logs at debug when the
  reference-count check drops an adopted transport.
- `connect_peer` tags its entry `debug!` with `peer_npub` so
  downstream STUN, punch, and handshake logs for the same peer
  correlate for operators.
- Advert-cache and seen-sessions overflow evictions log at debug so
  mis-sized caps are visible under ambient relay volume.
- Gift-wrap unwrap failures on `SIGNAL_KIND` events log at trace
  (hot path: fires for every unrelated signal event on the same
  relay).
- Traversal-offer handler failures log at debug. Expected conditions
  such as punch timeout on symmetric NAT are covered there; real
  problems are reported upstream via `BootstrapEvent::Failed`.
- Inbound-offer rate-limit messages name the governing config field
  (`max_concurrent_incoming_offers`) and state that the offer was
  rate-limited rather than failing.

== Tests

- 18 new unit tests in `src/discovery/nostr/tests.rs` covering advert
  encoding, signal envelope round-trip, STUN parsing, punch-packet
  codec, and replay-window enforcement. Run under the
  `nostr-discovery` feature.
- Config-validation tests in `src/config/mod.rs` covering the three
  cross-field invariants and YAML parsing of the full
  `node.discovery.nostr` block plus `peers[].via_nostr`, empty
  `addresses` with `via_nostr: true`, and a `udp: nat` address.
- `src/node/tests/bootstrap.rs` integration tests that drive a
  synthetic traversal (bound UDP socket pair + synthetic peer
  identity) through `adopt_established_traversal` and assert the
  Noise handshake completes over the adopted socket.
- Punch-planner tests assert reflexive-before-LAN ordering and that
  same-LAN scenarios still include the LAN target in the plan.
- `testing/nat/` Docker NAT lab harness:
  - Local `strfry` relay, local STUN responder, and one or two
    router containers performing `iptables` NAT.
  - Node LAN interfaces are provisioned with explicit `veth` pairs
    injected into the node and router namespaces so every packet
    traverses the router namespace (plain Docker bridges are not
    used for the LAN).
  - `cone` scenario: both peers behind full-cone-emulation NAT
    (SNAT with source-port preservation, inbound DNAT back to the
    single LAN host regardless of remote source); asserts UDP
    traversal succeeds and link remote addresses are on the router
    WAN subnet.
  - `symmetric` scenario: `MASQUERADE --random-fully`; asserts UDP
    traversal fails and TCP fallback converges over router-
    published WAN addresses.
  - `lan` scenario: both peers share a LAN subnet; asserts LAN
    addresses are preferred over reflexive ones.
  - Cleanup tears down all profile-gated services
    (`--profile cone --profile symmetric --profile lan`) so no
    orphan containers survive a run.
- `testing/scripts/build.sh` builds the Docker test image with
  `--features "tui nostr-discovery"` by default so NAT-harness
  binaries include bootstrap support.

== CI

- Linux release build and nextest unit-test job both use
  `--features "gateway nostr-discovery"` so the feature-gated code
  and its unit tests compile and run in CI.
- Three new integration matrix entries (`nat-cone`, `nat-symmetric`,
  `nat-lan`) invoke `testing/nat/scripts/nat-test.sh`, collect
  `docker compose logs` on failure, and always stop containers.

== Packaging and operations

- `packaging/common/fips.yaml` ships a fully commented
  `node.discovery.nostr.*` block, plus documented
  `advertise_on_nostr` / `public` examples under the UDP transport,
  an `advertise_on_nostr` example under TCP, and a `via_nostr: true`
  example under the static peer section with both a direct
  `host:port` UDP address and a `udp: nat` fallback.
- `.github/workflows/package-openwrt.yml`: NIP-94 release event
  publishes target the new default relay set.

== Documentation

- `README.md`: overlay discovery + NAT traversal moved from
  "Near-term priorities" into "What works today".
- `docs/design/fips-intro.md`: rewrites the paragraphs that
  previously described Nostr discovery and NAT traversal as future
  work; describes the shipped mechanism and the feature gate.
- `docs/design/fips-transport-layer.md`: drops the "(future
  direction)" qualifier from the Nostr Relay Discovery section,
  expands with the `udp:nat` advertisement and bootstrap handoff
  description, and updates the Current State callout.
- `docs/design/fips-mesh-layer.md`: notes that mid-session NAT
  rebinding (roaming) and initial NAT traversal (Nostr path) are
  distinct mechanisms.
- `docs/design/fips-configuration.md`: documents the full
  `node.discovery.nostr.*` surface, including the three resource
  caps and `share_local_candidates`.
- `docs/design/fips-nostr-discovery.md`: design and configuration
  reference for the shipped mechanism, including the empty-
  `addresses`-with-`via_nostr` shorthand.
- `docs/proposals/nostr-udp-hole-punch-protocol.md`: adds an
  Implemented status callout, clarifies that the punch socket is
  per-peer and per-attempt rather than shared with the application
  listener, aligns field names with the shipped JSON
  (`sessionId`, `issuedAt` / `expiresAt`, `reflexiveAddress`,
  `localAddresses`, `stunServer`), sets the `d`-tag to
  `fips-overlay-v1`, names the kind as 37195, and notes that
  advertised STUN entries are informational.
- `docs/proposals/README.md`: adds a Status column and marks the
  hole-punching proposal Implemented.
- `CHANGELOG.md`: Unreleased > Added entry covering the discovery
  path, STUN/punch path, configuration surface, and Docker NAT lab.

Co-authored-by: Johnathan Corgan <johnathan@corganlabs.com>
2026-04-27 08:15:58 -07:00
Johnathan Corgan f16b837a12 Tune overly aggressive discovery rate limiting
The default 30s post-failure backoff (300s cap, doubling per
consecutive failure) was set to bound traffic from chatty apps
looking up unreachable targets, but in practice it dominates
cold-start mesh convergence: a single timed-out lookup during
initial bloom-filter propagation suppresses any retry for 30s, and
the existing reset triggers (parent change, new peer, first RTT,
reconnection) don't fire on a stable post-handshake topology. The
suppression window winds up dictating the protocol's effective
time-to-converge instead of bounding repeat traffic.

Replaces the single-lookup-with-internal-retry model
(`timeout_secs`/`retry_interval_secs`/`max_attempts`) with a
per-attempt timeout sequence in `node.discovery.attempt_timeouts_secs`,
defaulting to `[1, 2, 4, 8]`. Each attempt sends a fresh LookupRequest
with a new random request_id so successive attempts can take different
forwarding paths as the bloom and tree state evolve. The destination
is declared unreachable only after the sequence is exhausted (15s
total at the default).

Disables post-failure suppression by default (`backoff_base_secs`/
`backoff_max_secs` now `0`/`0`). The `DiscoveryBackoff` machinery
stays in tree (inert at zero base/cap); operators with chatty apps
generating repeat lookups against unreachable destinations can opt
back in.

`PendingLookup` field shape unchanged so the control-socket
`show_routing` JSON (`pending_lookups[].attempt`/`initiated_ms`/
`last_sent_ms`) keeps the same schema for fipstop and external
consumers; `last_sent_ms` now means "current-attempt start" under
the new state machine.
2026-04-26 19:12:19 +00:00
Johnathan Corgan fe6f9aaa78 Merge branch 'master' into next
# Conflicts:
#	src/node/mod.rs
2026-04-11 17:51:24 +00:00
OceanSlimandGitHub 774e33fd27 Add Windows platform support (#45)
Gate platform-specific code behind cfg attributes and add full Windows
  support: TUN device via wintun, TCP control socket on localhost:21210,
  Windows Service lifecycle (--install-service/--uninstall-service/--service),
  CI build and test matrix, and packaging with ZIP builder and PowerShell
  service management scripts.

  Key changes:

  - Cargo.toml: move tun/libc/rtnetlink behind cfg(unix); add wintun and
    windows-service dependencies for Windows
  - upper/tun.rs: wintun-based TUN implementation with netsh configuration
    for IPv6 address, MTU, and fd00::/8 routing
  - control/mod.rs: split into unix_impl/windows_impl; Windows uses TCP on
    localhost:21210 with shared connection handler
  - bin/fips.rs: refactor main() into run_daemon() accepting a shutdown
    signal; add Windows Service support via windows-service crate
  - transport/udp/socket.rs: platform-gated modules; Windows uses
    tokio::net::UdpSocket (kernel drop count unavailable, returns 0)
  - transport/ethernet: gate to cfg(unix); add Windows stub types
  - config: platform-conditional default paths (socket, hosts) for Windows
  - CI: add windows-latest to build matrix and test-windows job with
    cargo-nextest
  - packaging/windows: build-zip.ps1, install-service.ps1,
    uninstall-service.ps1, and package-windows.yml workflow
  - README/docs: Windows build instructions, service management, and
    control socket platform differences

  Linux and macOS behavior is unchanged.
2026-04-11 18:31:48 +01:00
Johnathan Corgan 44667d9cc2 Update design docs and diagrams for Noise XX and forklift changes
Update CHANGELOG Breaking section with all v0.4.0 wire format
changes. Comprehensive update to fips-wire-formats.md (handshake,
MMP, FilterAnnounce, discovery, size tables).

Update fips-mesh-layer.md, fips-session-layer.md, fips-intro.md
for IK/XK→XX transition and spin bit removal. Document
disable_routing and leaf_only profile config in
fips-configuration.md. Update fips-transport-layer.md for 4-byte
beacon and unified header. Update fips-bloom-filters.md for delta
compression and variable sizing. Update fips-mesh-operation.md for
node profiles and discovery min_mtu.

Create/update 8 SVG diagrams (XX msg1/2/3, handshake flow, session
setup/ack/msg3, filter-announce, sender/receiver report). Update
README.md Noise pattern references.
2026-04-11 13:21:47 +00:00
Johnathan Corgan 60e5fefb1f Implement outbound LAN gateway
Add fips-gateway binary: a separate daemon that allows unmodified LAN
hosts to reach FIPS mesh destinations via DNS-allocated virtual IPs
and kernel nftables NAT.

Gateway DNS resolver: forwarding proxy on [::]:53 that intercepts
.fips queries, forwards to daemon resolver (localhost:5354), allocates
virtual IPs from pool, returns AAAA records. Always sends AAAA upstream
regardless of client query type, returns proper NODATA for non-AAAA.

Virtual IP pool: fd01::/112 pool with state machine lifecycle
(Allocated → Active → Draining → Free), TTL-based reclamation,
conntrack integration for session tracking.

NAT manager: nftables DNAT/SNAT rules via rustables netlink API,
per-mapping rule lifecycle, fips0 masquerade for LAN client source
address rewriting.

Network setup: local pool route, proxy NDP for virtual IPs on LAN
interface, IPv6 forwarding validation.

Control socket at /run/fips/gateway.sock with show_gateway and
show_mappings queries. fipstop Gateway tab with pool summary gauge
and mappings table.

Gateway config section in fips.yaml with pool CIDR, LAN interface,
DNS upstream, TTL, and grace period settings.

Design doc at docs/design/fips-gateway.md.

Integration test (testing/static/scripts/gateway-test.sh): three
containers verifying DNS resolution, end-to-end HTTP, NAT state,
TTL expiration, SERVFAIL fallback, and clean shutdown.
2026-04-09 16:53:32 +00:00
Johnathan Corgan d801fd0052 BLE continuous advertising, probe cooldown replaces burst beacon
Replace burst beacon pattern (1s on / 30s off) with continuous
advertising. The burst pattern caused L2CAP connect timeouts because
the remote side was no longer connectable when the probe fired after
jitter delay. BLE advertising overhead is negligible (~0.15% duty
cycle on advertising channels).

Replace the seen HashSet + jitter delay queue with a simple cooldown
map. After probing an address (success or failure), suppress re-probe
for 30s (configurable via probe_cooldown_secs). Connected peers are
filtered by pool membership check. This eliminates the bug where
failed probes permanently blacklisted addresses for the session
lifetime.

Remove config fields: scan_interval_secs, beacon_interval_secs,
beacon_duration_secs. Add: probe_cooldown_secs.
2026-03-25 15:25:10 +00:00
Johnathan Corgan 89352d3218 Add BLE L2CAP transport with scan-based auto-connect
BLE transport implementation using L2CAP Connection-Oriented Channels
(SeqPacket mode) via the bluer crate, behind cfg(feature = "ble").

Core transport:
- BleTransport<I> generic over BleIo trait (BluerIo prod, MockBleIo test)
- Connection pool with priority eviction (static > discovered, max 7)
- Connect-on-send via connect_inline() matching TCP behavior
- Per-connection receive loops with pool cleanup on disconnect

Discovery and probing:
- Combined scan_probe_loop using select! over scanner events and a
  BinaryHeap delay queue with per-entry random jitter (0-5s) to prevent
  herd effects when multiple nodes see the same beacon simultaneously
- Pre-handshake pubkey exchange ([0x00][pubkey:32]) for IK identity
- Cross-probe tie-breaker: smaller NodeAddr's outbound wins (same
  convention as FMP/FSP rekey dual-initiation)
- Probed peers reported to DiscoveryBuffer; pool fills through normal
  node-layer auto-connect -> send_async -> connect_inline path

Beacon management:
- Periodic advertising: 1s burst every 30s (configurable via
  beacon_interval_secs / beacon_duration_secs)
- FIPS service UUID for scan filtering

Configuration (all fields optional with defaults):
- adapter, psm, mtu, max_connections, connect_timeout_ms
- advertise, scan, auto_connect, accept_connections
- beacon_interval_secs (30), beacon_duration_secs (1)

Hardware validated with two BLE nodes:
- 2048-byte MTU, ~60-160ms RTT, zero-config auto-connect
- BLE spike tool at testing/ble/ for standalone adapter validation

42 unit tests + 4 node-level integration tests, all CI-compatible
via MockBleIo (no hardware required). tokio test-util added for
time-dependent scan/probe tests.
2026-03-25 04:21:46 +00:00
Johnathan Corgan 7f33e5f867 Fix lookup-request.svg: remove stale visited bloom filter
The visited_bits field (hash_cnt + 256 bytes) was removed from the
LookupRequest wire format in the discovery-rework (bloom-guided tree
routing replaced the visited filter). Update the diagram to match the
current 46 + 16n byte format.
2026-03-25 03:07:46 +00:00
Johnathan Corgan a16370e78d Update changelog, version, and design docs for v0.2.0
Bump version to 0.2.0 and finalize changelog with discovery rework,
Tor transport, connect/disconnect commands, reproducible builds, and
12 bug fixes.

Update design documentation for discovery protocol rework:
- fips-wire-formats.md: remove visited bloom filter from LookupRequest,
  update size calculations
- fips-mesh-operation.md: replace flooding description with bloom-guided
  tree routing, add retry/backoff/rate-limiting subsections
- fips-configuration.md: add 5 new discovery config parameters, update
  control socket description for connect/disconnect commands
2026-03-22 20:26:09 +00:00
Johnathan Corgan 6c90cf6c02 Implement Tor transport with operator visibility
Add TorTransport in src/transport/tor/ supporting three operating modes:

Outbound (socks5 mode):
- Non-blocking SOCKS5 connect via tokio-socks with per-destination
  circuit isolation (IsolateSOCKSAuth)
- TorAddr enum for .onion and clearnet address types
- Connection pool with per-connection receive tasks, reuses TCP
  stream FMP framing
- connect_async()/connection_state_sync()/promote_connection() follow
  the same non-blocking polling pattern as TCP transport

Inbound (directory mode — recommended for production):
- Tor manages the onion service via HiddenServiceDir in torrc
- FIPS reads .onion address from hostname file at startup
- No control port needed — enables Tor Sandbox 1 (seccomp-bpf)
- Accept loop mirrors TCP pattern with DirectoryServiceConfig

Monitoring (control_port mode and optional in directory mode):
- Async control port client supporting TCP and Unix socket connections
  via Box<dyn AsyncRead/Write> trait objects
- AUTHENTICATE with cookie or password auth
- 8 GETINFO queries: bootstrap, circuits, traffic, liveness, version,
  dormant state, SOCKS listeners
- Background monitoring task polls every 10s, caches TorMonitoringInfo
  in Arc<RwLock> for synchronous query access
- Bootstrap milestone logging (25/50/75/100%), stall warning (>60s),
  network liveness transitions, dormant mode entry
- Directory mode optionally connects to control port when control_addr
  is configured (non-fatal on failure)

Operator visibility:
- show_transports query exposes tor_mode, onion_address, tor_monitoring
  (bootstrap, circuit_established, traffic, liveness, version, dormant)
- fipstop transport detail view: Tor mode, onion address, SOCKS5/control
  errors, connection stats, Tor daemon status section
- fipstop table view: tor(mode) label with truncated onion address hint

Security hardening:
- Per-destination circuit isolation via IsolateSOCKSAuth
- Unix socket default for control port (/run/tor/control)
- Reference torrc with HiddenServiceDir, VanguardsLiteEnabled,
  ConnectionPadding, DoS protections (PoW + intro rate limiting)

Config:
- TorConfig with socks5, control_port, and directory modes
- DirectoryServiceConfig: hostname_file, bind_addr
- control_addr, control_auth, cookie_path, connect_timeout,
  max_inbound_connections

Testing:
- 69 unit + integration tests with mock SOCKS5 and control servers
- Docker tests: socks5-outbound (clearnet via Tor) and directory-mode
  (HiddenServiceDir onion service)

Documentation:
- Transport layer design doc: Tor architecture, directory mode
- Configuration doc: Tor config tables and examples
2026-03-15 16:19:54 +00:00
Johnathan Corgan c0f30d8fe8 Add DNS hostname support in peer addresses for UDP and TCP transports
Add resolve_socket_addr() with IP fast path and tokio::net::lookup_host()
fallback for DNS hostnames. Peer addresses can now use hostnames like
"peer1.example.com:2121" alongside IP addresses.

UDP transport adds a per-transport DNS cache (60s TTL) to avoid
per-packet resolution. TCP resolves at connect time (one-shot).

Update design docs, config examples, and changelog to reflect hostname
support in transport addressing.
2026-03-15 00:49:54 +00:00
Johnathan Corgan d873d0e00e Update design docs for non-blocking transport connect and cleanup stale references
- Rewrite TCP "Connect-on-Send" section to document non-blocking connect
  model (ConnectingPool, PendingConnect, poll_pending_connects)
- Add connect() and connection_state() to Transport trait surface
- Expand Connection Lifecycle section with ConnectionState enum
- Remove phantom TCP socks5_proxy field (removed from code, superseded
  by TorConfig)
- Fix "future Tor transport" references (stream reader already shared)
- Replace misleading "tor:" named TCP instance example
- Update fips-intro.md implementation status (TCP and Ethernet are
  implemented, not "under active design")
2026-03-13 16:37:00 +00:00
Johnathan Corgan 6ab8b35755 Implement FSP port multiplexing and IPv6 header compression
Breaking wire format change: DataPacket payloads inside the AEAD envelope
now carry a 4-byte port header [src_port:2 LE][dst_port:2 LE] before the
service payload. The receiver dispatches by destination port.

Port multiplexing:
- send_session_data() takes src_port/dst_port params, prepends port header
- New send_ipv6_packet() compresses IPv6 header and sends on port 256
- Receive path dispatches DataPackets by port: port 256 decompresses IPv6
  header from session context and delivers to TUN, unknown ports dropped
- Port constants: FSP_PORT_HEADER_SIZE (4 bytes), FSP_PORT_IPV6_SHIM (256)

IPv6 header compression:
- New ipv6_shim module with compress_ipv6()/decompress_ipv6() pure functions
- Strips src/dst addresses (32 bytes) and payload length (2 bytes) from each
  packet, preserving traffic class, flow label, next header, and hop limit
  as 6-byte residual fields
- Addresses reconstructed from session context on receive side
- Net savings: 29 bytes per packet (overhead 106 → 77 bytes)
- FIPS_IPV6_OVERHEAD constant (77 bytes), effective_ipv6_mtu() updated
- 16 unit tests for round-trip fidelity, field preservation, error cases

Documentation:
- fips-wire-formats: DataPacket port header, port registry, IPv6 shim
  format tables, updated encapsulation walkthrough and overhead budget
- fips-ipv6-adapter: FIPS_IPV6_OVERHEAD (77 bytes), updated MTU numbers,
  TUN reader/writer flow with compression steps, impl status
- fips-session-layer: port-based service dispatch section, data transfer
  description, impl status
- fips-intro: IPv6 adapter as port 256 service, node architecture updated
- fips-mesh-operation: packet size summary with compressed overhead
- DataPacket doc updated with port header and dispatch model
- session_wire.rs module doc: DataPacket Port Multiplexing section
2026-03-11 12:53:32 +00:00
Johnathan Corgan f37eb4b846 Fix documentation drift from recent feature additions
Update 9 documentation files to match current implementation:
- Add missing rekey config section (node.rekey.*) and host mapping
  section to fips-configuration.md
- Update Ethernet frame format from [type:1][payload] to
  [type:1][length:2 LE][payload] in wire-formats and transport docs
- Fix Ethernet effective MTU from interface-1 to interface-3
- Mark rekey as Implemented in mesh-layer and session-layer status tables
- Change TCP default port examples from 443 to 8443
- Add rekey, persistent identity, host mapping, mesh size estimation to
  README features and status sections
- Update chaos scenario count from 16 to 20, add rekey topology to
  static test docs
2026-03-11 03:11:12 +00:00
Johnathan Corgan 79feb41a88 Update docs for persistent identity, ECN, and multi-transport
- fips-configuration.md: add node.identity.persistent parameter and
  three-tier identity resolution documentation
- fips-intro.md: update ECN description from "reserves space" to
  reflect implemented hop-by-hop CE signaling
- README.md: update transport list (UDP, TCP, Ethernet), add
  persistent identity mention
2026-03-06 21:20:16 +00:00
Johnathan Corgan 56d39f223b Add ECN congestion signaling and transport congestion detection
Implement hop-by-hop ECN congestion signaling through the FMP layer,
transport-level congestion detection via kernel drop counters, and
chaos harness integration for end-to-end validation.

FMP/session ECN plumbing:

- Thread ce_flag parsed at link layer through dispatch_link_message,
  handle_session_datagram, handle_session_payload, and
  handle_encrypted_session_msg to session delivery
- Replace hardcoded false in session-layer record_recv() with actual
  ce_flag, activating ecn_ce_count tracking in session MMP

ECN congestion detection and CE relay:

- Add EcnConfig (node.ecn.*) with configurable loss_threshold (5%)
  and etx_threshold (3.0) for transit congestion detection
- Add send_encrypted_link_message_with_ce() that ORs FLAG_CE into FMP
  header flags; original method delegates with ce_flag=false
- Compute outgoing_ce = incoming_ce || local congestion on next-hop
  link, enabling hop-by-hop CE relay through transit nodes

IPv6 ECN-CE marking:

- Mark ECN-CE (0b11) in IPv6 Traffic Class on received DataPackets
  before TUN delivery when FMP CE flag is set
- Only marks ECN-capable packets (ECT(0)/ECT(1)); Not-ECT packets
  unchanged per RFC 3168

Transport congestion abstraction and UDP kernel drop detection:

- Add TransportCongestion struct to transport layer for transport-
  agnostic local congestion indicators
- Replace tokio::UdpSocket with AsyncFd<socket2::Socket> using
  libc::recvmsg() with ancillary data parsing
- Enable SO_RXQ_OVFL for kernel receive buffer drop counter on every
  packet, wiring up previously-stubbed UdpStats.kernel_drops
- Add TransportDropState for per-transport delta tracking with 1s
  tick sampling via sample_transport_congestion()
- Extend detect_congestion() with transport kernel drop check
  alongside MMP loss metrics

Congestion monitoring and control:

- Add CongestionStats (ce_forwarded, ce_received, congestion_detected,
  kernel_drop_events) to NodeStats with snapshot serialization
- Wire counters into forwarding path, session handler, and transport
  drop sampling with rate-limited warn logging (5s interval)
- Expose congestion data in show_routing control query and
  ecn_ce_count in show_mmp peer entries
- Add congestion counters to fipstop routing tab in two-column layout

Chaos harness integration:

- Add query_routing(), query_transports(), snapshot_all_congestion()
  to chaos control module
- Add congestion/kernel-drop log analysis in logs module
- Add congestion-stress scenario: 10-node tree, 1 Mbps bandwidth,
  5-10% netem loss, heavy iperf3 traffic
- Add IngressConfig for tc ingress policing with per-peer policer
  filters simulating upstream bandwidth bottlenecks
- Add iperf3 JSON result capture to traffic manager for throughput
  measurement across scenarios
- Add ECN A/B test scenarios (ecn-ab-on/off.yaml) with ingress
  policing and comparison script
- Enable TCP ECN negotiation (tcp_ecn=1 sysctl) in container
  entrypoint for end-to-end CE propagation

Tests:

- 10 ECN unit/integration tests: mark_ipv6_ecn_ce variants, CE relay
  chain (3-node propagation), EcnConfig serde roundtrip
- 3 transport drop congestion detection unit tests

Documentation:

- Update fips-mesh-layer.md: replace outdated CE Echo stub with full
  ECN Congestion Signaling section covering detection logic, CE relay,
  IPv6 marking, session tracking, and monitoring counters
- Update fips-configuration.md: add node.ecn.* parameter table and
  ecn block in complete reference YAML
- Update fips-transport-layer.md: add Congestion Reporting section
  with TransportCongestion struct, congestion() trait method, and
  per-transport status; document AsyncFd/recvmsg/SO_RXQ_OVFL in UDP
- Update chaos README: add congestion/ECN scenario docs, ingress
  traffic control, and iperf3 JSON capture sections
- Update README.md: add ECN to features list and "What works today";
  update transport and tooling entries
2026-03-05 17:05:57 +00:00
Johnathan Corgan 77ac8c822e Add fipstop TUI monitoring tool with smoothed metrics and quality indices
fipstop: ratatui-based TUI for real-time monitoring of a running FIPS daemon.

Tabs and navigation:
- 8 navigable tabs: Node, Peers, Transports, Sessions, Tree, Filters,
  Performance, Routing
- Tab/BackTab navigation with group separators in tab bar
- Table views with selectable rows, detail drill-down panels, and scrollbars

Node tab:
- Runtime info: pid, exe path, uptime, control socket path, TUN adapter name
- Identity: npub, node_addr, ipv6 address
- State summary with peer/session/link/transport/connection counts
- TUN IPv6 traffic and forwarded transit traffic counters

Peers tab:
- Table with Name, Address, Conn, Depth, SRTT, Loss, LQI, Pkts Tx/Rx
- Detail panel: identity, connection info, transport cross-reference,
  tree/bloom state, link stats, MMP metrics with LQI

Sessions tab:
- Table with Name, Remote Addr, State, Role, SRTT, Loss, SQI, Path MTU,
  Last Activity
- Detail panel: identity, session info, traffic stats, MMP metrics with SQI

Transports tab:
- Hierarchical tree view: expandable transport parents with nested links
  (▼/▶ indicators, ├─/└─ tree chars, Space/Arrow to expand/collapse)
- Transport detail: type-specific stats (UDP/TCP/Ethernet)
- Link detail: peer cross-reference with MMP metrics and LQI

Performance tab:
- Link-layer MMP: SRTT, loss, ETX, LQI, goodput per peer
- Session-layer MMP: SRTT, loss, ETX, SQI, path MTU per session
- Trend indicators (rising/falling/stable) with context-aware coloring

Routing tab:
- Routing state: cache sizes, pending lookups, recent requests
- Coordinate cache: entries, fill ratio, TTL, expiry, avg age
- Statistics: forwarding, discovery request/response, error signal counters

Tree tab:
- Spanning tree position with 16 announce stats (inbound/outbound/cumulative)

Filters tab:
- Bloom filter announce stats, per-peer fill ratio and estimated node count

MMP metrics enhancements:
- Add etx_trend DualEwma for smoothed ETX tracking
- Add smoothed_loss() and smoothed_etx() accessors (long-term EWMA)
- LQI (Link Quality Index) = smoothed_etx * (1 + srtt_ms / 100)
- SQI (Session Quality Index) = same formula for session layer
- All loss/ETX displays prefer smoothed values with raw fallback

Control socket:
- Add smoothed_loss, smoothed_etx, lqi/sqi to show_peers, show_sessions,
  and show_mmp JSON responses
- Rename fips_address to ipv6_addr in show_status and show_peers
- Add tun_name and control_socket to show_status
- FHS-compliant 3-tier default path: $XDG_RUNTIME_DIR, /run/fips, /tmp

Node extensions:
- Add started_at/uptime() to Node
- Add tun_name() accessor

Docker sidecar updates:
- TCP transport support via FIPS_PEER_TRANSPORT env var
- Build scripts include fipstop binary
2026-03-01 16:33:33 +00:00
Johnathan Corgan ec64a0dce1 Add TCP transport implementation and test harness support
Implement TCP transport for FIPS enabling firewall traversal and serving
as the foundation for future Tor transport. This is the first
connection-oriented transport in the system.

Key design decisions:
- FMP header-based framing: reuses existing 4-byte FMP common prefix for
  packet boundary recovery with zero framing overhead
- Session survives TCP reconnection: Noise/MMP/FSP state bound to npub,
  not TCP connection; MMP liveness is sole authority for peer death
- Connect-on-send: fresh connection on first send, transparent reconnect
- close_connection() trait method for cross-connection deduplication cleanup

New transport files:
- src/transport/tcp/mod.rs: TcpTransport, connection pool, accept loop
- src/transport/tcp/stream.rs: FMP-aware stream reader (shared with Tor)

Modified: transport trait (close_connection), TcpConfig, TransportHandle
match arms, create_transports(), initiate_connection() for connection-
oriented links, cross-connection tie-breaker cleanup, design docs.

Tree announce loop and TCP stability fixes:
- Preserve tree announce rate-limit state across reconnection: carry
  forward last_tree_announce_sent_ms when a peer reconnects so the
  rate-limit window isn't reset to zero
- Drop oversize TCP packets at sender: pre-send MTU check returns
  MtuExceeded instead of writing to the stream, preventing receiver-side
  connection teardown and reset-reconnect cycles

Chaos harness:
- TCP transport support: tcp_edges/has_tcp/tcp_peers in SimTopology,
  transport-aware config_gen with per-edge transport type, TCP port 443,
  pure-TCP node support
- Include all non-Ethernet edges in directed_outbound()
- Fix netem/links log messages to say "IP-based" instead of "UDP"
- Add tcp-chain, tcp-only, and tcp-mesh scenario files

Static harness:
- Transport-aware config generation (get_default_transport, transport_port)
- TCP transport injection via Python post-processing
- Add tcp-chain topology and docker-compose profile
2026-02-27 00:41:37 +00:00
Johnathan Corgan daf1e629df Change default UDP port to 2121 and EtherType to 0x2121
Update the default UDP bind port from 4000 to 2121 (decimal) and the
default Ethernet EtherType from 0x88B5 to 0x2121 across all source
code, documentation, configuration templates, test fixtures, and
scripts. Remove references to "IEEE 802 experimental range" since
0x2121 is not in that range.
2026-02-26 13:22:09 +00:00
Johnathan Corgan d29da442ac Add Ethernet transport with beacon discovery
Implement raw Ethernet transport using AF_PACKET SOCK_DGRAM on Linux
with EtherType 0x88B5 (IEEE experimental range) and 1-byte frame type
prefix (0x00=data, 0x01=beacon).

Transport implementation:
- EthernetConfig with interface, ethertype, MTU, buffer sizes, and
  four independent discovery knobs (discovery, announce, auto_connect,
  accept_connections)
- PacketSocket/AsyncPacketSocket wrappers with ioctl helpers for
  interface index, MAC address, and MTU queries
- EthernetTransport with Transport trait impl, async start/stop/send,
  receive loop dispatching data frames and discovery beacons
- Discovery beacons (34 bytes: type + version + x-only pubkey) with
  DiscoveryBuffer for peer accumulation and dedup
- Atomic statistics counters (frames, bytes, errors, beacons)
- Platform-gated with #[cfg(target_os = "linux")]

Transport-layer discovery integration:
- Promote auto_connect() and accept_connections() to Transport trait
  with default implementations and TransportHandle dispatch
- Extract initiate_connection() so both static peer config and
  discovery auto-connect share the same handshake initiation path
- Add poll_transport_discovery() to the tick handler to drain
  discovery buffers and auto-connect to discovered peers
- Enforce accept_connections() in handle_msg1() — transports with
  accept_connections=false silently drop inbound handshakes

Node integration:
- create_transports() handles Ethernet named instances
- resolve_ethernet_addr() parses "interface/mac" address format
- transport_mtu() generalized for multi-transport operation

Test harness:
- VethPair RAII struct for veth pair lifecycle management
- Three #[ignore] integration tests requiring root/CAP_NET_RAW:
  two-node handshake, data exchange, mixed transport coexistence
- Chaos harness: transport-aware topology model, VethManager for
  veth pairs between Docker containers, Ethernet-aware config gen,
  netem split (HTB+u32 for UDP, root netem for veth), transport-aware
  link flaps and node churn with veth re-setup
- Container entrypoint waits for configured Ethernet interfaces
  before starting FIPS (handles veth creation timing)
- New scenarios: ethernet-only (4-node ring), ethernet-mesh (6-node
  mixed UDP+Ethernet with netem and link flaps)

Documentation:
- fips-transport-layer.md: Ethernet section, beacon discovery, WiFi
  compatibility, updated discovery state, trait surface additions,
  implementation status table
- fips-configuration.md: Ethernet parameter table, named instances,
  peer address format, mixed UDP+Ethernet example, complete reference
- fips-wire-formats.md: Ethernet frame type prefix note
2026-02-26 00:03:14 +00:00
Johnathan Corgan 7260ad2878 Improve README and enable DNS resolver by default
Rework top-level README: add badges, status/roadmap section, config
search path docs, minimal two-node example with transports, DNS setup
instructions with systemd-resolved and resolv.conf examples, and
connectivity test walkthrough.

Replace ASCII document-relationship diagram with SVG in design docs.

Change DNS resolver default from disabled to enabled (port 5354).
Update config merge to allow higher-priority configs to disable it.
2026-02-24 18:58:32 +00:00
Johnathan Corgan 00e26765bd Design documentation illustration and review pass
Wire format diagrams:
- Add 24 SVG diagrams covering every FMP and FSP wire format: common
  prefix, established frame headers, Noise IK handshake messages,
  handshake flow, TreeAnnounce, AncestryEntry, FilterAnnounce,
  LookupRequest/Response, SessionDatagram, Disconnect, SenderReport,
  ReceiverReport, FSP complete message, SessionSetup/Ack/Msg3,
  PathMtuNotification, CoordsRequired, PathBroken, and MtuExceeded
- Replace ASCII art in fips-wire-formats.md with SVG references
- Apply text edits to fips-mesh-layer.md, fips-mesh-operation.md,
  fips-transport-layer.md, and fips-ipv6-adapter.md

Spanning tree dynamics:
- Add 12 topology SVG diagrams: node join (overview + 3-panel steps),
  three-node convergence (4-panel), link addition with depth labels,
  link removal, partition formation, and 6 real-world example diagrams
  (office, mixed-link, two-site WAN topologies)
- Rewrite all code blocks to narrative prose with diagram references
- Add inline prior art attributions distinguishing Yggdrasil-derived
  concepts from FIPS-novel contributions
- Add 3 new references (De Couto ETX, IEEE 802.1D, RFC 2328 OSPF) and
  Prior Art summary
- Remove outdated sections: indirect partition note, integration test
  gaps, DHT-based lookup reference
- Change "must elect a new root" to "must rediscover its new root"

Spanning tree design review (fips-spanning-tree.md):
- Rename "Root Election" to "Root Discovery" across docs
- Add "What Is a Spanning Tree?" introductory section
- Add parent selection intro explaining self-organization role
- Fix tree distance example: 4 hops, not 2
- Clarify timestamp field as advisory only
- Remove unimplemented ROOT_TIMEOUT and TREE_ENTRY_TTL from timing
  parameters and implementation status tables

Bloom filter design review (fips-bloom-filters.md):
- Add "What Is a Bloom Filter?" intro section
- Rewrite Purpose section to frame filters as routing path identification
- Correct FPR analysis (old values were 3-50x overstated)
- Add Filter Occupancy Model based on network size and tree position
- Fix filter expiration to describe actual MMP-based cleanup
- Combine Scale Considerations with Size Classes after Wire Format
- Fix stale FPR values in src/bloom/mod.rs comments

Session layer review (fips-session-layer.md):
- Add inline prior art attributions: Noise Protocol Framework, WireGuard,
  DTLS (RFC 6347), IKEv2 (RFC 7296), RFC 1191 PMTUD, Yggdrasil, NIP-44
- Replace warmup state machine ASCII art with SVG diagram
- Convert CoordsWarmup wire format code block to prose
- Add External References section with full citations

Level 5 implementation doc cleanup:
- Delete fips-software-architecture.md (redundant with protocol layer docs)
- Delete fips-state-machines.md (Rust tutorial, not protocol design)
- Add fipsctl command reference to README.md
- Update cross-references in fips-intro.md, docs/design/README.md,
  fips-transport-layer.md, fips-configuration.md

Fixes:
- Correct fd::/8 to fd00::/8 in fips-session-layer.md,
  fips-identity-derivation.svg, and fips-node-architecture.svg
- Fix config example MTU: 1197 → 1472 in fips-configuration.md

File organization:
- Move all SVG diagrams into docs/design/diagrams/ subdirectory
- Update all diagram references to use new paths
2026-02-24 17:32:37 +00:00
Johnathan Corgan c1820e689d Update design docs for replay suppression, MMP backoff, and flap dampening
- fips-configuration.md: add flap_threshold, flap_window_secs,
  flap_dampening_secs to tree config table and YAML reference
- fips-spanning-tree.md: add flap dampening to Stability Mechanisms,
  Timing Parameters, and Implementation Status tables
- spanning-tree-dynamics.md: update Known Limitations to reflect flap
  dampening implementation, revise impact text
- fips-session-layer.md: add Send Failure Backoff subsection under
  Session-Layer MMP
- fips-mesh-layer.md: add log suppression note to Replay Protection
2026-02-23 20:28:17 +00:00