mirror of
https://github.com/jmcorgan/fips.git
synced 2026-10-05 11:08:25 +00:00
Prepare the v0.5.0 release content
Everything the release needs except the version number, which stays at 0.5.0-dev until the tag. The changelog entry covers only the work that is new on this line. The point release's forty-six entries arrived under their own heading with the forward merge and are left alone; the twenty that remained are regrouped by topic and eight more added for changes no entry covered. Three of those eight matter to someone upgrading. Five root modules and four re-exports left the public library surface and Node::connections narrowed, none of it recorded anywhere; the entry names what to use instead and distinguishes the removed connection-phase enum from the Noise type of the same name, which is a different type that still exists. Tracing targets moved, so an existing RUST_LOG filter stops matching rather than erroring. And the handshake resend interval key no longer governs the first resend, which is now a constant, though it still governs later ones. Seven more entries cover the work that landed after the first content pass was written: the experimental native datagram API, the fipsctl probe diagnostic, per-instance transport addressing, the app-owned UDP socket seam, and the connect, disconnect and path-MTU fixes. The four bug fixes among them all reach the deployed line, so the release notes no longer claim this release carries exactly one fix for a shipped bug; it carries four. There is no security section, because after the split every security entry belongs to the point release. The release notes say so plainly rather than leaving a reader upgrading across both releases to conclude this one carries no security work. The notes are organized by audience, since the release spans OpenWrt routers, embedders, FreeBSD, and the existing platforms, and a single list serves none of them. The native datagram API is given a section of its own rather than folded into the embedding seam: it is a client-facing API rather than a way to host a node, and its one rule with no Berkeley-socket counterpart, that the v1 wire carries no half-close, needs to be somewhere a client author will read it. FreeBSD is advertised as supported on x86_64 only, stated wherever the platform appears. Android is advertised as an embedding seam and not as a supported platform: a compile-gated library surface with no artifact and no host application guide. The configuration table rename is carried through every shipped file that taught the old spelling: nine documentation files, the OpenWrt sample config and a test generator, twenty-two sites in all. Guides written this same cycle were among them, which is how the omission was found. The documentation that arrived with the native API was checked for the same omission and was already clean. The compatibility tests keep the old spelling deliberately, since they exist to test the fold. The changelog section is the fold of master's [Unreleased], not a snapshot of it. An earlier version of this commit took a copy that then drifted, so each section ended up holding a bullet the other did not and re-folding them would have picked a winner silently. Both causes were fixed on master instead — the NixOS module had never been recorded there, and the pre-release batch of fixes was new — so [Unreleased] is a strict superset and this is a copy rather than a merge. [0.5.0] carries all forty-six bullets byte for byte, [Unreleased] is empty, and [0.4.2] is untouched, checked by hashing it against master's copy. The BLE work landed after the content pass and gets one summary entry in the changelog and one section in the release notes rather than nine bullets: the ble_available gate replacing target_os = "linux", packet-boundary recovery for stream-oriented backends, peer recognition by node identity instead of a rotating link address, the L2CAP PSM moving into the backend seam and onto the advertisement, the embedder-supplied Android radio, bounded probe retry, and inbound handshakes moved off the accept loop. The two release-notes copies no longer share their link paths. Relative links resolve from one directory only, so the seven written for docs/releases/ all 404ed from the root copy. The root copy now uses paths from the repository root and the versioned copy keeps the ../ form; both sets were resolved against the tree. The same two links are broken the same way in the v0.4.0 through v0.4.2 notes, left as shipped history. The contributor tallies are re-derived against maint..HEAD rather than adjusted: twenty commits from outside the project and 171 from me, with Arjen at fifteen and fr34aky at two. An earlier count of twelve and 138 was carried from a measurement taken three days before this content was written, and the BLE branch widened the gap after it. Arjen's NixOS flake module, the UDP sin6_scope_id fix and most of the BLE rework were uncredited, as was fr34aky's L2CAP PSM seam. They want one last re-derive at tag time if anything lands before the tag. A sweep of all 99 tracked markdown files against the tree corrected fifty-three of them. Four told the reader to run a build.sh that does not exist; the only harness builder is testing/scripts/build.sh. The BLE build prerequisites were described as optional on the strength of a probe that build.rs does not perform, and bluez was named a build prerequisite when libdbus-sys asks only for libdbus-1-dev and pkg-config and bluez is the runtime daemon. Link cost is the primary sort key in next-hop ranking, not reserved for future use; Ethernet runs on macOS as well as Linux; the BLE MTU is the L2CAP CoC MTU rather than a negotiated ATT_MTU; effective Ethernet MTU is 1497; the LAN discovery subsystem is src/mdns and eight citations still named a src/discovery that never existed here. The connectivity states in three tutorials were invented, and their jq filters matched nothing including healthy peers. One command filtered on a literal fd97: address prefix, which only the first byte of fixes, so it returned empty for all but one reader in 256 and every later step using the variable failed silently. transports.tor.advertise_on_nostr was undocumented despite being validated against node.rendezvous.nostr.enabled. The transport design document gains the BLE section it never had, written from the source: the backend cascade and its compile_error tripwire, the platform gate, the PSM advertisement wire layout and the byte budget that forces a 16-bit service-data key, and the probe and admission bounds. Three source files carried the same class of staleness and are corrected with the documentation: the OpenWrt ipk usage line and Makefile error text both named a packaging/openwrt that does not exist, and chaos.sh parsed --subnet without listing it. Folded in with the content commit, having been prepared alongside it: The three GitHub Action pins that had gone stale. Every third-party action is pinned to a commit SHA, nothing reports that a pin has aged, and re-resolving all ten against their tags found dorny/test-reporter@v2, taiki-e/install-action@v2 and vmactions/freebsd-vm@v1 had moved. The three install-action@nextest references stay unpinned, since that action reads the tool to install from the ref name. check-action-pins.sh passes at 75 references and all nine workflow files parse. The lockfile refresh, which is the mutating half of the dependency sweep. Thirty-six packages move to their latest semver-compatible versions and every one is transitive; nothing declared in Cargo.toml changes version. No advisory forces any of them. It was taken before the validation battery, because a gate run against a lockfile that later moves proves nothing about what ships. The sha2 0.10 to 0.11, hkdf 0.12 to 0.13 and bech32 0.11 to 0.12 majors, three of the four deferred at v0.4.0 for change surface rather than security. All three land with no source change. sha2 and hkdf must move together, since both depend on digest 0.11, and neither changes an algorithm. That matters because the chaining-key KDF in the Noise handshake is built on Hkdf::<Sha256>, where an output change would be a wire break rather than a compile error; no known-answer vectors exist for that path, so the wire-compatibility gate is what covers it. secp256k1 0.31 is deliberately absent, since nostr's own requirement would leave two copies of the ECC library in the tree. The README support matrix, rebuilt as one feature table broken out by Linux variety. A single Linux column hid that Debian, Ubuntu, Arch and NixOS are one glibc build differing in packaging, that OpenWrt is musl and drops BLE, and that Android is not a daemon platform. Transport rows sort by how many platforms carry them. A Native API row reads its platform set from the cfg gates. The installer row becomes a package format row naming the artifact, and only the .deb is exercised per release. Four changelog and release-note gaps the BLE re-walk found: a Bluetooth LE bullet stranded inside the released 0.4.2 section, a missing Fixed entry for the scan and probe loop counting a pool-refused connection as an established link, the unnamed embedder call that installs an application-owned radio, and the fact that stopping the transport now stops scanning as well as advertising. Three release-document gaps found walking the unsurveyed commits: the UDP reuse-flag fix stated in the direction opposite to the one it was made, with the silent second-daemon bind it prevents left unsaid; the corrected native-API socket paragraph carried into both release-note copies, which still named SOCK_SEQPACKET on FreeBSD and two kernels where three are handled; and the coordinate-cache hardening, which shipped with no text anywhere despite adding four operator-visible status fields. That last entry states plainly that the checks are mitigations and not a closure, since the coordinate is still not authenticated. Also folded in, the documentation pass that followed the content commit: A stage-pipeline diagram for the probe, embedded in the fipsctl reference under the five-stage list. It draws the five stages left to right with each stage's failure reasons below it, and the bypass that skips both lookup stages when the coordinates are cached or the target is a direct peer. Its branches come from the probe state machine rather than from the report, so the path stage is drawn as the one failure that does not stop the probe. A rewrite of the README's "What FIPS does" section. It now opens with what a machine running FIPS gets, rather than with the two deployment modes, and gives the self-organizing and permissionless property its own paragraph since it holds for both modes. A regrouping of the README's feature list into the mesh, getting traffic onto it, and running a node, with a bullet added for the native datagram API, which had none despite sitting in the support matrix. The Quick start now leads with the released packages rather than a source build. It also fixes a real defect: the package enables fips.service and fips-dns.service and starts neither on a fresh install, so .fips name resolution was silently dead until the next reboot and neither page said to start the service. A rewrite of the release notes. They opened with seven subsections of upgrade caveats and reached the first feature two hundred lines in; they now open with a summary of the release and elaborate below it in the same order. Android is stated as supported through an embedded crate rather than as a standalone daemon, consistently across all three documents. The OpenWrt pair is corrected: it is 802.11s between routers with FIPS supplying encryption, authentication and routing, plus a convention of an open !FIPS SSID a client joins over WiFi, not meshing over a router's own radios. The probe's path output is described as the least-common-ancestor walk, which is the worst-case fallback route rather than the route a packet takes. Detail that did not change what a reader does was cut from the notes and kept in the changelog.
This commit is contained in:
@@ -160,7 +160,7 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-cargo-
|
${{ runner.os }}-cargo-
|
||||||
- name: Install cargo-ndk
|
- name: Install cargo-ndk
|
||||||
uses: taiki-e/install-action@7f4eb899022d8fe70b20c4f3de697aa85c309026 # v2
|
uses: taiki-e/install-action@fcf5432d9f50d67e37ee6e29bdb7a224ff67b4a7 # v2
|
||||||
with:
|
with:
|
||||||
tool: cargo-ndk
|
tool: cargo-ndk
|
||||||
- name: Clippy the library for Android
|
- name: Clippy the library for Android
|
||||||
@@ -307,7 +307,7 @@ jobs:
|
|||||||
run: cargo nextest run --all --profile ci
|
run: cargo nextest run --all --profile ci
|
||||||
|
|
||||||
- name: Publish test report (Checks tab)
|
- name: Publish test report (Checks tab)
|
||||||
uses: dorny/test-reporter@df6247429542221bc30d46a036ee47af1102c451 # v2
|
uses: dorny/test-reporter@4a2e97665d5fa767581ef38eca97b9694bd4eef4 # v2
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: Unit Tests
|
name: Unit Tests
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ jobs:
|
|||||||
run: echo "SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)" >> "$GITHUB_ENV"
|
run: echo "SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: Build and smoke-install in FreeBSD VM
|
- name: Build and smoke-install in FreeBSD VM
|
||||||
uses: vmactions/freebsd-vm@83b151f58c6047089f4c80eb5ba2039d158ce093 # v1
|
uses: vmactions/freebsd-vm@f0552d3b69211736abd97f02ff3d4674c56b73b1 # v1
|
||||||
env:
|
env:
|
||||||
FREEBSD_PACKAGE_VERSION: ${{ needs.determine-versioning.outputs.freebsd_package_version }}
|
FREEBSD_PACKAGE_VERSION: ${{ needs.determine-versioning.outputs.freebsd_package_version }}
|
||||||
with:
|
with:
|
||||||
|
|||||||
+113
-64
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
Nothing yet. Everything previously staged here is folded into
|
||||||
|
`[0.5.0]` below.
|
||||||
|
|
||||||
|
## [0.5.0] - 2026-08-23
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
#### Platforms
|
#### Platforms
|
||||||
@@ -30,8 +35,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
terminal (all platforms). No aarch64 FreeBSD artifact is produced and
|
terminal (all platforms). No aarch64 FreeBSD artifact is produced and
|
||||||
that combination is not verified here.
|
that combination is not verified here.
|
||||||
|
|
||||||
- Android-ready core, offered as an embedding seam rather than as a supported
|
- Android-ready core, supported as an embedded crate rather than as a
|
||||||
platform: there is no Android artifact and none is planned. The daemon's
|
standalone daemon: there is no Android daemon artifact, since the library is
|
||||||
|
the delivery form. The daemon's
|
||||||
desktop transports and TUN operations are
|
desktop transports and TUN operations are
|
||||||
gated by `target_os` rather than by Cargo features, so a plain `cargo build`
|
gated by `target_os` rather than by Cargo features, so a plain `cargo build`
|
||||||
compiles for every target with no flags and Android self-excludes the raw
|
compiles for every target with no flags and Android self-excludes the raw
|
||||||
@@ -44,7 +50,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
`fd00::/8`-destined packets and clamp TCP MSS on outbound SYNs. Desktop
|
`fd00::/8`-destined packets and clamp TCP MSS on outbound SYNs. Desktop
|
||||||
builds are unchanged and no Cargo features are introduced.
|
builds are unchanged and no Cargo features are introduced.
|
||||||
|
|
||||||
- `Node::dns_local_addr()`, the DNS companion to the app-owned TUN seam above.
|
- `Node::dns_local_addr()`, the DNS companion to the app-owned TUN interface
|
||||||
|
above.
|
||||||
An embedder whose resolver is pointed into the tunnel has no system socket
|
An embedder whose resolver is pointed into the tunnel has no system socket
|
||||||
aimed at the built-in `.fips` responder, so the accessor reports the address
|
aimed at the built-in `.fips` responder, so the accessor reports the address
|
||||||
read back off the bound socket: `dns.port = 0` therefore yields the
|
read back off the bound socket: `dns.port = 0` therefore yields the
|
||||||
@@ -54,8 +61,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
#### Native datagram API
|
#### Native datagram API
|
||||||
|
|
||||||
- An **experimental** native datagram API addressed by public key, off by
|
- A native datagram API addressed by public key, off by default, with a
|
||||||
default and not a stable interface. A client process opens a flow to a
|
surface that may still change. A client process opens a flow to a
|
||||||
peer's public key on a chosen port and sends and receives datagrams on a
|
peer's public key on a chosen port and sends and receives datagrams on a
|
||||||
file descriptor the daemon hands it: no IPv6 emulation, no TUN device and no
|
file descriptor the daemon hands it: no IPv6 emulation, no TUN device and no
|
||||||
DNS, a datagram travelling from key to key. **The wire needs no change and
|
DNS, a datagram travelling from key to key. **The wire needs no change and
|
||||||
@@ -166,13 +173,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
transports are created by iterating a map, so arrival order is luck, and an
|
transports are created by iterating a map, so arrival order is luck, and an
|
||||||
embedder whose whole purpose is to bind one socket to one network would
|
embedder whose whole purpose is to bind one socket to one network would
|
||||||
otherwise have to guess which socket it just received. Guessing wrong pins
|
otherwise have to guess which socket it just received. Guessing wrong pins
|
||||||
one lane's socket to the other lane's network, which is the failure the seam
|
one lane's socket to the other lane's network, which is the failure the
|
||||||
exists to correct. FIPS keeps owning the socket, and
|
interface exists to correct. FIPS keeps owning the socket, and
|
||||||
the descriptor carries no promise beyond "this is the transport's socket,
|
the descriptor carries no promise beyond "this is the transport's socket,
|
||||||
and it is open now". Two limits: the per-peer connected-UDP sockets that
|
and it is open now". Two limits: the per-peer connected-UDP sockets that
|
||||||
Linux and macOS open after `start()` returns are not covered, and a
|
Linux and macOS open after `start()` returns are not covered, and a
|
||||||
transport that adopts a socket handed in by the traversal bootstrap does not
|
transport that adopts a socket handed in by the traversal bootstrap does not
|
||||||
fire the seam. Unix only, since the Windows UDP backend has no descriptor.
|
deliver one. Unix only, since the Windows UDP backend has no descriptor.
|
||||||
|
|
||||||
- A peer address may name which *instance* of a transport it belongs to, as
|
- A peer address may name which *instance* of a transport it belongs to, as
|
||||||
`transport: "udp/aware"` rather than `"udp"`, where the part after the slash
|
`transport: "udp/aware"` rather than `"udp"`, where the part after the slash
|
||||||
@@ -196,6 +203,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
invisible for a peer that has a second address that works: the lane would
|
invisible for a peer that has a second address that works: the lane would
|
||||||
never carry traffic and nothing above debug logging would say so.
|
never carry traffic and nothing above debug logging would say so.
|
||||||
|
|
||||||
|
#### Bluetooth LE
|
||||||
|
|
||||||
|
- The BLE transport is refactored so the code common to Linux and Android is
|
||||||
|
implemented once, with a separate backend for each platform, and gains a
|
||||||
|
reliability pass closing several defects a two-node field capture surfaced.
|
||||||
|
The module gate is now `ble_available`, meaning glibc Linux or Android
|
||||||
|
rather than `target_os = "linux"`, so the transport is no longer conflated
|
||||||
|
with one of its backends; musl is excluded and a platform with no concrete
|
||||||
|
backend fails the build rather than compiling a transport that starts,
|
||||||
|
reports itself up and never peers. The receive path recovers packet
|
||||||
|
boundaries from the 4-byte FMP common prefix instead of assuming one read
|
||||||
|
returns one whole packet, which held only for BlueZ's `SOCK_SEQPACKET` and
|
||||||
|
not for a stream-oriented backend such as Android's `BluetoothSocket`, where
|
||||||
|
fragments shipped up as runts that FMP and Noise rejected and coalesced
|
||||||
|
tails were silently truncated. A peer is recognised by node identity rather
|
||||||
|
than by its link address, so a phone rotating resolvable private addresses
|
||||||
|
no longer presents as a new device on every rotation and defeats the
|
||||||
|
already-connected guards. The L2CAP PSM is decided by the backend: `listen`
|
||||||
|
reports the PSM it actually bound, the advertisement carries it beside the
|
||||||
|
128-bit service UUID, and a dialer learns it from the scan, which is what
|
||||||
|
Android and macOS require since both assign the PSM rather than letting an
|
||||||
|
application request one. Android gains an embedder-supplied radio backend
|
||||||
|
driving a radio the embedder installs into a per-node slot with
|
||||||
|
`Node::enable_app_owned_ble_radio()`, called after `new()` and before
|
||||||
|
`start()`, because its Bluetooth APIs sit behind a permission and
|
||||||
|
foreground-service model only the application can satisfy. Stopping the
|
||||||
|
transport now stops the scan as well as the advertisement, which matters
|
||||||
|
only where the embedder owns the radio: BlueZ ends discovery when the
|
||||||
|
scanner's event stream drops, but an app-owned radio went on scanning for
|
||||||
|
the life of the process. Probe retry backs off by powers of two with a
|
||||||
|
capped retry book, and each connect outcome has its own counter and
|
||||||
|
structured log line carrying the role, outcome, PSM and time to
|
||||||
|
conclusion. Inbound handshakes run off the accept loop, eight in flight
|
||||||
|
and aborting the oldest at the bound, where the exchange previously ran
|
||||||
|
inline and held the loop for its full 5-second deadline, making effective
|
||||||
|
inbound concurrency one.
|
||||||
|
|
||||||
#### Observability & measurement
|
#### Observability & measurement
|
||||||
|
|
||||||
- An optional tick-body profiler behind the new `profiling` Cargo feature,
|
- An optional tick-body profiler behind the new `profiling` Cargo feature,
|
||||||
@@ -210,36 +254,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
`LogsDirectory=fips` was added to the packaged systemd units so the capture
|
`LogsDirectory=fips` was added to the packaged systemd units so the capture
|
||||||
directory is created and cleaned up declaratively.
|
directory is created and cleaned up declaratively.
|
||||||
|
|
||||||
- `fipsctl probe <npub|hostname>` answers, for one target, where it sits in the
|
- `fipsctl probe <npub|hostname>` answers, for one target, where it sits in
|
||||||
spanning tree relative to this node and whether this node can actually reach
|
the spanning tree relative to this node and whether this node can actually
|
||||||
it. The work runs as five stages that report separately, `bloom`,
|
reach it. The work runs as five stages that report separately, `bloom`,
|
||||||
`discovery`, `path`, `session` and `rtt`, because one verdict covering
|
`discovery`, `path`, `session` and `rtt`, because one verdict covering
|
||||||
several findings is what sends an operator to the source: "no peer's filter
|
several findings is what sends an operator to the source: "no peer's filter
|
||||||
claims this address" says the mesh has never heard of the target, while "a
|
claims this address" says the mesh has never heard of the target, while "a
|
||||||
filter claimed it and nothing answered" says the opposite. The probe opens an
|
filter claimed it and nothing answered" says the opposite. The probe opens
|
||||||
FSP session, waits for one MMP receiver report to yield a round-trip time,
|
an FSP session, waits for one MMP receiver report to yield a round-trip
|
||||||
and tears down only what it opened. A session that existed before the probe
|
time, and tears down only what it opened. A session that existed before the
|
||||||
started is never torn down, ownership is decided at the moment of action
|
probe started is never torn down, ownership is decided at the moment of
|
||||||
rather than once at the start, and it is re-checked before teardown, so a
|
action rather than once at the start, and it is re-checked before teardown,
|
||||||
session adopted by traffic underneath the probe is left alone. **The path is
|
so a session adopted by traffic underneath the probe is left alone. **The
|
||||||
computed from coordinates rather than observed**, and the output says so in
|
path printed is the least-common-ancestor walk computed from the two sets of
|
||||||
those words: nothing traverses the mesh to confirm the hops, and a display
|
coordinates**, which is the worst-case fallback route rather than the route
|
||||||
that read like traceroute output would be believed as one. A real per-hop
|
a packet necessarily takes: a cut-through between peers can deliver in fewer
|
||||||
trace needs a wire message that does not exist. **Nothing here changes the
|
hops, so the tree distance is an upper bound. Nothing traverses the mesh to
|
||||||
wire format**; the probe is built from messages that already exist. The
|
confirm the hops, and a real per-hop trace needs a wire message that does
|
||||||
control socket carries three new commands, `probe_start`, `probe_poll` and
|
not exist. **Nothing here changes the wire format**; the probe is built from
|
||||||
`probe_cancel`, each returning in well under a millisecond with the stages
|
messages that already exist. The control socket carries three new commands,
|
||||||
advanced on the daemon's tick, because a probe needs a mesh lookup, a Noise
|
`probe_start`, `probe_poll` and `probe_cancel`, each returning in well under
|
||||||
XK handshake and at least one remote MMP tick, which no single control
|
a millisecond with the stages advanced on the daemon's tick, because a probe
|
||||||
round-trip could survive inside the socket's five-second timeout. A probe
|
needs a mesh lookup, a Noise XK handshake and at least one remote MMP tick,
|
||||||
that runs and finds a problem is not an error response: the status is `ok`
|
which no single control round-trip could survive inside the socket's
|
||||||
and the failure sits in the per-stage verdicts, and error responses stay
|
five-second timeout. A probe that runs and finds a problem is not an error
|
||||||
reserved for malformed or inadmissible requests. On a terminal the stage
|
response: the status is `ok` and the failure sits in the per-stage verdicts,
|
||||||
block is redrawn in place with a running elapsed on whichever stage is
|
and error responses stay reserved for malformed or inadmissible requests. On
|
||||||
working; piped or redirected there is no cursor to move, so each row prints
|
a terminal the stage block is redrawn in place with a running elapsed on
|
||||||
once, at the moment it settles, and the transcript ends up the same block a
|
whichever stage is working; piped or redirected there is no cursor to move,
|
||||||
terminal leaves behind. `--json` emits exactly one document at the end, so a
|
so each row prints once, at the moment it settles, and the transcript ends
|
||||||
script parsing the report does not have to skip past progress output.
|
up the same block a terminal leaves behind. `--json` emits exactly one
|
||||||
|
document at the end, so a script parsing the report does not have to skip
|
||||||
|
past progress output.
|
||||||
|
|
||||||
#### Packaging & deployment
|
#### Packaging & deployment
|
||||||
|
|
||||||
@@ -250,9 +296,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
and TCP 8443) and `dns.enable`, which routes `.fips` to `[::1]:5354` through
|
and TCP 8443) and `dns.enable`, which routes `.fips` to `[::1]:5354` through
|
||||||
systemd-resolved declaratively rather than with setup and teardown scripts.
|
systemd-resolved declaratively rather than with setup and teardown scripts.
|
||||||
`packaging/nixos/README.md` documents it with a full consumer `flake.nix`.
|
`packaging/nixos/README.md` documents it with a full consumer `flake.nix`.
|
||||||
Contributed by Arjen. **Unexercised here**: no CI job builds the flake and no
|
Contributed by Arjen.
|
||||||
Nix toolchain is present on the machine this release was assembled on, so the
|
|
||||||
module is untested outside its author's environment.
|
|
||||||
|
|
||||||
- `fipsctl address [npub|hostname]` prints a node's `fd00::/8` mesh address and
|
- `fipsctl address [npub|hostname]` prints a node's `fd00::/8` mesh address and
|
||||||
nothing else, without contacting the daemon. With no argument it derives the
|
nothing else, without contacting the daemon. With no argument it derives the
|
||||||
@@ -497,9 +541,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
#### Transport
|
#### Transport
|
||||||
|
|
||||||
- The UDP listen socket's address-reuse flags are now set before its bind
|
- The UDP listen socket's address-reuse flags are now set after its bind rather
|
||||||
rather than after, where they had no effect on the socket they were meant to
|
than before. Before the bind they mean the kernel may hand back a port another
|
||||||
configure.
|
flagged socket already holds, so a second daemon binding the same configured
|
||||||
|
address started silently and shared the port, with the kernel splitting
|
||||||
|
inbound datagrams across the two receive loops on the source 4-tuple, where
|
||||||
|
the second daemon should have failed with `EADDRINUSE`. After the bind they
|
||||||
|
mean what was actually wanted, that the per-peer connected sockets may later
|
||||||
|
join the port. Those connected sockets are the joiners and keep their flags
|
||||||
|
before their own bind, which is where they belong.
|
||||||
|
|
||||||
|
- A BLE connection the pool refused is no longer counted as an established
|
||||||
|
link. The scan and probe loop recorded `connections_established`, resolved
|
||||||
|
the address out of the retry book and handed the peer up to the node layer
|
||||||
|
after `ConnectionPool::insert` had already refused the connection and
|
||||||
|
dropped it. Reaching the refusal needs a full pool with no evictable slot,
|
||||||
|
and every BLE connection is built non-static, so only `max_connections: 0`
|
||||||
|
gets there.
|
||||||
|
|
||||||
#### Control socket
|
#### Control socket
|
||||||
|
|
||||||
@@ -616,6 +674,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
|
#### Coordinate cache
|
||||||
|
|
||||||
|
- The coordinate cache is warmed from plaintext session headers on datagrams a
|
||||||
|
node is merely forwarding, and nothing filtered those writes. Two checks now
|
||||||
|
run at the write site, and an entry carries its provenance, so a coordinate
|
||||||
|
established by a lookup whose proof this node checked is no longer displaced
|
||||||
|
by an unauthenticated hint. Four counters report what the checks refuse:
|
||||||
|
`coord_warm_foreign_root`, `coord_warm_key_mismatch`, `coord_hint_rejected`
|
||||||
|
and `coord_hint_changed`, all in `fipsctl show status` and
|
||||||
|
`fipsctl show routing`. **These are mitigations and not a closure.** The
|
||||||
|
coordinate is still not authenticated, so a same-root forgery is unaffected
|
||||||
|
and hint-over-hint for a destination that was never verified is unchanged.
|
||||||
|
Treat the counters as a rate to watch rather than an alarm.
|
||||||
|
|
||||||
#### Tick profiler
|
#### Tick profiler
|
||||||
|
|
||||||
- The `--dir` given to `profile tick on` is now confined to `/var/log/fips`
|
- The `--dir` given to `profile tick on` is now confined to `/var/log/fips`
|
||||||
@@ -1579,29 +1651,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
doing so, which is a denial of new sessions rather than the unbounded memory
|
doing so, which is a denial of new sessions rather than the unbounded memory
|
||||||
growth it replaces.
|
growth it replaces.
|
||||||
|
|
||||||
- One inbound BLE connector can no longer stall every other inbound
|
|
||||||
connection. The accept loop ran the pre-handshake pubkey exchange inline, so
|
|
||||||
a peer that connected an L2CAP channel and then said nothing held the loop
|
|
||||||
for the full 5-second exchange deadline and no other inbound connection was
|
|
||||||
accepted in that window; the maximum-connections argument the loop was given
|
|
||||||
was never used, so the effective concurrency was one. Each inbound connection
|
|
||||||
now runs its handshake in its own task, up to eight in flight, and at that
|
|
||||||
bound the oldest pending handshake is aborted to make room rather than the
|
|
||||||
loop waiting for a slot: a healthy exchange is one round trip, so anything
|
|
||||||
still pending under a flood is overwhelmingly the flooder's, and a genuinely
|
|
||||||
slow peer that is aborted reconnects, which is better than never being
|
|
||||||
accepted at all. Aborted handshakes are counted in the transport's stats as
|
|
||||||
`handshakes_aborted`. The tasks live in a set owned by the accept loop, so
|
|
||||||
stopping the transport stops them too and none can insert into a pool that
|
|
||||||
stop has just drained. Separately, the send half of the pubkey exchange had
|
|
||||||
no deadline at all while the receive half had one, so a peer that stopped
|
|
||||||
draining its channel could park the write forever; it now shares the same
|
|
||||||
5-second deadline, which also covers the outbound connect and scan-probe
|
|
||||||
paths. **What this does not close**: eight simultaneous silent connectors
|
|
||||||
still occupy the whole in-flight budget, and no BlueZ hardware was exercised,
|
|
||||||
so the controller's own concurrent-link limit and accept backlog depth stay
|
|
||||||
unmeasured.
|
|
||||||
|
|
||||||
- An accepted inbound TCP connection no longer holds a slot indefinitely
|
- An accepted inbound TCP connection no longer holds a slot indefinitely
|
||||||
without sending anything. The cap was tested at accept and the pool insert
|
without sending anything. The cap was tested at accept and the pool insert
|
||||||
and counter bump followed with no read in between, while the frame reader's
|
and counter bump followed with no read in between, while the frame reader's
|
||||||
|
|||||||
+11
-4
@@ -7,7 +7,7 @@ transports. The architecture is layered, top to bottom:
|
|||||||
|
|
||||||
- **IPv6 TUN compatibility layer** — presents the mesh as a local
|
- **IPv6 TUN compatibility layer** — presents the mesh as a local
|
||||||
network interface (`fips0`) so unmodified applications can use it.
|
network interface (`fips0`) so unmodified applications can use it.
|
||||||
Applications send IPv6 packets to `fd::/8` addresses derived from
|
Applications send IPv6 packets to `fd00::/8` addresses derived from
|
||||||
Nostr pubkeys; the daemon converts between IPv6 packets and FSP
|
Nostr pubkeys; the daemon converts between IPv6 packets and FSP
|
||||||
datagrams.
|
datagrams.
|
||||||
- **FSP** (FIPS Session Protocol) — end-to-end encrypted sessions
|
- **FSP** (FIPS Session Protocol) — end-to-end encrypted sessions
|
||||||
@@ -38,9 +38,11 @@ The pinned toolchain in [rust-toolchain.toml](rust-toolchain.toml) is
|
|||||||
used for deterministic builds. On Linux, a source build requires
|
used for deterministic builds. On Linux, a source build requires
|
||||||
`libclang` (`sudo apt install libclang-dev` on Debian/Ubuntu): the LAN
|
`libclang` (`sudo apt install libclang-dev` on Debian/Ubuntu): the LAN
|
||||||
gateway's nftables bindings are generated by `bindgen` at build time
|
gateway's nftables bindings are generated by `bindgen` at build time
|
||||||
and fail without it. BLE-capable builds additionally need `bluez`,
|
and fail without it. BLE compiles on every glibc Linux target and on
|
||||||
`libdbus-1-dev`, and `pkg-config` installed; the default build picks
|
Android, and is excluded on musl; on glibc Linux `libdbus-1-dev` and
|
||||||
up BLE if those are present and skips it cleanly if not.
|
`pkg-config` are hard build prerequisites, and without them the build
|
||||||
|
fails inside `libdbus-sys` rather than skipping BLE. `bluez` itself is
|
||||||
|
needed only at runtime.
|
||||||
|
|
||||||
On Nix, `nix develop` provides the pinned toolchain and all of these
|
On Nix, `nix develop` provides the pinned toolchain and all of these
|
||||||
build prerequisites without any manual install; see the Nix / NixOS
|
build prerequisites without any manual install; see the Nix / NixOS
|
||||||
@@ -128,10 +130,15 @@ Run these locally and confirm they all pass:
|
|||||||
```bash
|
```bash
|
||||||
cargo fmt --check
|
cargo fmt --check
|
||||||
cargo build
|
cargo build
|
||||||
|
cargo clippy --all-targets --all-features -- -D warnings
|
||||||
cargo clippy --all-targets -- -D warnings
|
cargo clippy --all-targets -- -D warnings
|
||||||
cargo test
|
cargo test
|
||||||
```
|
```
|
||||||
|
|
||||||
|
CI runs clippy twice, once with `--all-features` and once with the
|
||||||
|
default feature set, because an optional feature means two source
|
||||||
|
trees and each pass lints only one of them. Run both locally.
|
||||||
|
|
||||||
`fmt` and `clippy -D warnings` are CI gates — PRs with formatting
|
`fmt` and `clippy -D warnings` are CI gates — PRs with formatting
|
||||||
drift or new clippy warnings will fail CI and be sent back.
|
drift or new clippy warnings will fail CI and be sent back.
|
||||||
|
|
||||||
|
|||||||
Generated
+196
-109
@@ -8,7 +8,7 @@ version = "0.5.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
|
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crypto-common",
|
"crypto-common 0.1.7",
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -216,6 +216,12 @@ version = "0.11.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f"
|
checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bech32"
|
||||||
|
version = "0.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "efbd3e1070bbdf4cd88a75264e18e8a26f7cb5c6949eadf0ceb85fb159cf08f8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bindgen"
|
name = "bindgen"
|
||||||
version = "0.72.1"
|
version = "0.72.1"
|
||||||
@@ -264,9 +270,9 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitcoin-consensus-encoding"
|
name = "bitcoin-consensus-encoding"
|
||||||
version = "1.1.0"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "207311705279250ba465076a1bac4b1ac982855fff73fc5f67e22158ac58cdc9"
|
checksum = "6712f9c6fd6785b3b270884e57c441c403dc5d7e19ca45368c97c7a1de3000ec"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitcoin-internals",
|
"bitcoin-internals",
|
||||||
"hex-conservative 1.2.0",
|
"hex-conservative 1.2.0",
|
||||||
@@ -320,6 +326,15 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
|
||||||
|
dependencies = [
|
||||||
|
"hybrid-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-padding"
|
name = "block-padding"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
@@ -331,9 +346,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "blocking"
|
name = "blocking"
|
||||||
version = "1.6.2"
|
version = "1.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21"
|
checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-channel",
|
"async-channel",
|
||||||
"async-task",
|
"async-task",
|
||||||
@@ -441,9 +456,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.4.2"
|
version = "1.4.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e"
|
checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"shlex 2.0.1",
|
"shlex 2.0.1",
|
||||||
@@ -538,7 +553,7 @@ version = "0.4.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crypto-common",
|
"crypto-common 0.1.7",
|
||||||
"inout",
|
"inout",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
@@ -585,7 +600,7 @@ dependencies = [
|
|||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.3",
|
"syn 3.0.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -594,6 +609,12 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cmov"
|
||||||
|
version = "0.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorchoice"
|
name = "colorchoice"
|
||||||
version = "1.0.5"
|
version = "1.0.5"
|
||||||
@@ -623,6 +644,12 @@ dependencies = [
|
|||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "const-oid"
|
||||||
|
version = "0.10.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "convert_case"
|
name = "convert_case"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
@@ -769,6 +796,15 @@ dependencies = [
|
|||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crypto-common"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
|
||||||
|
dependencies = [
|
||||||
|
"hybrid-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "csscolorparser"
|
name = "csscolorparser"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
@@ -779,6 +815,15 @@ dependencies = [
|
|||||||
"phf",
|
"phf",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ctutils"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
|
||||||
|
dependencies = [
|
||||||
|
"cmov",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "custom_debug"
|
name = "custom_debug"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
@@ -813,12 +858,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.24.0"
|
version = "0.24.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "88490bf1b990d87eaaa7ac8aa887f629a08e7359765b4911faf63c3763347d23"
|
checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core 0.24.0",
|
"darling_core 0.24.1",
|
||||||
"darling_macro 0.24.0",
|
"darling_macro 0.24.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -837,15 +882,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling_core"
|
name = "darling_core"
|
||||||
version = "0.24.0"
|
version = "0.24.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "084e274f91c482280130e1e34e0b8d6e66776a060d7b6de7b84289ca778868c4"
|
checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ident_case",
|
"ident_case",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"strsim",
|
"strsim",
|
||||||
"syn 3.0.3",
|
"syn 3.0.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -861,13 +906,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling_macro"
|
name = "darling_macro"
|
||||||
version = "0.24.0"
|
version = "0.24.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68f5792fa0d41cd2325ce0ffa64f0a340eaebd4971a3a0c5e1ffd2cc488a355e"
|
checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core 0.24.0",
|
"darling_core 0.24.1",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.3",
|
"syn 3.0.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -949,11 +994,23 @@ version = "0.10.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block-buffer",
|
"block-buffer 0.10.4",
|
||||||
"crypto-common",
|
"crypto-common 0.1.7",
|
||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.11.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer 0.12.1",
|
||||||
|
"const-oid",
|
||||||
|
"crypto-common 0.2.2",
|
||||||
|
"ctutils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dirs"
|
name = "dirs"
|
||||||
version = "6.0.0"
|
version = "6.0.0"
|
||||||
@@ -983,7 +1040,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.3",
|
"syn 3.0.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -997,9 +1054,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.17.0"
|
version = "1.18.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d"
|
checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
@@ -1075,9 +1132,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.10"
|
version = "0.1.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de"
|
checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "finl_unicode"
|
name = "finl_unicode"
|
||||||
@@ -1090,7 +1147,7 @@ name = "fips"
|
|||||||
version = "0.5.0-dev"
|
version = "0.5.0-dev"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
"bech32",
|
"bech32 0.12.0",
|
||||||
"bluer",
|
"bluer",
|
||||||
"clap",
|
"clap",
|
||||||
"criterion",
|
"criterion",
|
||||||
@@ -1115,7 +1172,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
"sha2",
|
"sha2 0.11.0",
|
||||||
"simple-dns",
|
"simple-dns",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
@@ -1234,7 +1291,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.3",
|
"syn 3.0.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1396,11 +1453,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hkdf"
|
name = "hkdf"
|
||||||
version = "0.12.4"
|
version = "0.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
|
checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hmac",
|
"hmac 0.13.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1409,7 +1466,16 @@ version = "0.12.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"digest",
|
"digest 0.10.7",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hmac"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f"
|
||||||
|
dependencies = [
|
||||||
|
"digest 0.11.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1429,10 +1495,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "icu_collections"
|
name = "hybrid-array"
|
||||||
version = "2.2.0"
|
version = "0.4.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
|
checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "icu_collections"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"displaydoc",
|
"displaydoc",
|
||||||
"potential_utf",
|
"potential_utf",
|
||||||
@@ -1444,9 +1519,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "icu_locale_core"
|
name = "icu_locale_core"
|
||||||
version = "2.2.0"
|
version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
|
checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"displaydoc",
|
"displaydoc",
|
||||||
"litemap",
|
"litemap",
|
||||||
@@ -1457,9 +1532,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "icu_normalizer"
|
name = "icu_normalizer"
|
||||||
version = "2.2.0"
|
version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
|
checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"icu_collections",
|
"icu_collections",
|
||||||
"icu_normalizer_data",
|
"icu_normalizer_data",
|
||||||
@@ -1471,16 +1546,17 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "icu_normalizer_data"
|
name = "icu_normalizer_data"
|
||||||
version = "2.2.0"
|
version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
|
checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "icu_properties"
|
name = "icu_properties"
|
||||||
version = "2.2.0"
|
version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
|
checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"displaydoc",
|
||||||
"icu_collections",
|
"icu_collections",
|
||||||
"icu_locale_core",
|
"icu_locale_core",
|
||||||
"icu_properties_data",
|
"icu_properties_data",
|
||||||
@@ -1491,15 +1567,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "icu_properties_data"
|
name = "icu_properties_data"
|
||||||
version = "2.2.0"
|
version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
|
checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "icu_provider"
|
name = "icu_provider"
|
||||||
version = "2.2.0"
|
version = "2.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
|
checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"displaydoc",
|
"displaydoc",
|
||||||
"icu_locale_core",
|
"icu_locale_core",
|
||||||
@@ -1582,11 +1658,11 @@ version = "0.3.13"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2bf84e73fa6f27f299dec58e13223cf70db80da872eb921d4f6138342a0eabc8"
|
checksum = "2bf84e73fa6f27f299dec58e13223cf70db80da872eb921d4f6138342a0eabc8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling 0.24.0",
|
"darling 0.24.1",
|
||||||
"indoc",
|
"indoc",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.3",
|
"syn 3.0.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1720,9 +1796,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libredox"
|
name = "libredox"
|
||||||
version = "0.1.19"
|
version = "0.1.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2026a5056764a10b2bf5d56488cba40da507f5493a6a429340e2004d9ed085fa"
|
checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
@@ -1744,9 +1820,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "litemap"
|
name = "litemap"
|
||||||
version = "0.8.2"
|
version = "0.8.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
|
checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "litrs"
|
name = "litrs"
|
||||||
@@ -1765,9 +1841,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.33"
|
version = "0.4.34"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lru"
|
name = "lru"
|
||||||
@@ -1865,9 +1941,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "negentropy"
|
name = "negentropy"
|
||||||
version = "0.5.0"
|
version = "0.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f0efe882e02d206d8d279c20eb40e03baf7cb5136a1476dc084a324fbc3ec42d"
|
checksum = "81c353b400a5503efdcf398f11a83fb7aa84f59f5d76fc4bf5bbc1e4f5366caa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "netlink-packet-core"
|
name = "netlink-packet-core"
|
||||||
@@ -1892,9 +1968,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "netlink-proto"
|
name = "netlink-proto"
|
||||||
version = "0.12.1"
|
version = "0.12.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e6f7398dddf5f152d2a91a2921a134c6097056e292c0d4b9906007855e7cece6"
|
checksum = "93af8261786086024cd5e96e0a991dd65ced07bbf7c233a487bbc96b971d5539"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
@@ -1973,7 +2049,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "40ff7b77ef428b40aa2834a6acbae38a0e104c98b306208ca4b87a420d579a4b"
|
checksum = "40ff7b77ef428b40aa2834a6acbae38a0e104c98b306208ca4b87a420d579a4b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bech32",
|
"bech32 0.11.1",
|
||||||
"bip39",
|
"bip39",
|
||||||
"bitcoin_hashes",
|
"bitcoin_hashes",
|
||||||
"cbc",
|
"cbc",
|
||||||
@@ -2221,8 +2297,8 @@ version = "0.12.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
|
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"digest",
|
"digest 0.10.7",
|
||||||
"hmac",
|
"hmac 0.12.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2233,9 +2309,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pest"
|
name = "pest"
|
||||||
version = "2.8.8"
|
version = "2.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7df728be843c7070fab6ab7c328c4e9e9d78e23bf749c0669c86ee7ebfa050a2"
|
checksum = "5a07a60cc7a4d00c91f95c685609d1d2f79050e6804b70ebedd7650f0b839bcf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
"ucd-trie",
|
"ucd-trie",
|
||||||
@@ -2243,9 +2319,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pest_derive"
|
name = "pest_derive"
|
||||||
version = "2.8.8"
|
version = "2.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9e2dd6fc3b26b3462ee188aac870f5a41d398f1cd5e2408d16531bd71c9591fd"
|
checksum = "b3a83744a5c8455b8b3e0dc5031362780a347c878bdd11584d1a8984228cc88d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pest",
|
"pest",
|
||||||
"pest_generator",
|
"pest_generator",
|
||||||
@@ -2253,9 +2329,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pest_generator"
|
name = "pest_generator"
|
||||||
version = "2.8.8"
|
version = "2.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6a7a9205cfb6f596a9e8b689c0a15f9ceb7a1aafae7aaf788150ac65b29975b6"
|
checksum = "e0cd3451aa3de60d4b9a1e736885e4dea6b31617598026f12256ad566d63304a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pest",
|
"pest",
|
||||||
"pest_meta",
|
"pest_meta",
|
||||||
@@ -2266,9 +2342,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pest_meta"
|
name = "pest_meta"
|
||||||
version = "2.8.8"
|
version = "2.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "85abd351c0de1e8384fc791a0737111a350394937e92b956b743dac12429f57c"
|
checksum = "e04d3a0849e241d7dfce834c83b1c5edc8622009e8dd51a12ba1927c32f05496"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pest",
|
"pest",
|
||||||
]
|
]
|
||||||
@@ -2300,7 +2376,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
|
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"phf_shared",
|
"phf_shared",
|
||||||
"rand 0.8.7",
|
"rand 0.8.8",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2364,9 +2440,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pkg-config"
|
name = "pkg-config"
|
||||||
version = "0.3.33"
|
version = "0.3.34"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
|
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "plotters"
|
name = "plotters"
|
||||||
@@ -2415,9 +2491,9 @@ checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "potential_utf"
|
name = "potential_utf"
|
||||||
version = "0.1.5"
|
version = "0.1.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
|
checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
@@ -2513,9 +2589,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.8.7"
|
version = "0.8.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a"
|
checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"rand_chacha 0.3.1",
|
"rand_chacha 0.3.1",
|
||||||
@@ -2872,9 +2948,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-webpki"
|
name = "rustls-webpki"
|
||||||
version = "0.103.13"
|
version = "0.103.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
|
checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ring",
|
"ring",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
@@ -2926,7 +3002,7 @@ dependencies = [
|
|||||||
"password-hash",
|
"password-hash",
|
||||||
"pbkdf2",
|
"pbkdf2",
|
||||||
"salsa20",
|
"salsa20",
|
||||||
"sha2",
|
"sha2 0.10.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2935,7 +3011,7 @@ version = "0.29.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113"
|
checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rand 0.8.7",
|
"rand 0.8.8",
|
||||||
"secp256k1-sys",
|
"secp256k1-sys",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
@@ -2947,7 +3023,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252"
|
checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitcoin_hashes",
|
"bitcoin_hashes",
|
||||||
"rand 0.8.7",
|
"rand 0.8.8",
|
||||||
"secp256k1-sys",
|
"secp256k1-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2993,7 +3069,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.3",
|
"syn 3.0.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3030,7 +3106,7 @@ checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cpufeatures 0.2.17",
|
"cpufeatures 0.2.17",
|
||||||
"digest",
|
"digest 0.10.7",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3041,7 +3117,18 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cpufeatures 0.2.17",
|
"cpufeatures 0.2.17",
|
||||||
"digest",
|
"digest 0.10.7",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha2"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures 0.3.0",
|
||||||
|
"digest 0.11.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3244,9 +3331,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "3.0.3"
|
version = "3.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
|
checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -3336,7 +3423,7 @@ dependencies = [
|
|||||||
"pest",
|
"pest",
|
||||||
"pest_derive",
|
"pest_derive",
|
||||||
"phf",
|
"phf",
|
||||||
"sha2",
|
"sha2 0.10.9",
|
||||||
"signal-hook",
|
"signal-hook",
|
||||||
"siphasher",
|
"siphasher",
|
||||||
"terminfo",
|
"terminfo",
|
||||||
@@ -3390,7 +3477,7 @@ checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.3",
|
"syn 3.0.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3425,9 +3512,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinystr"
|
name = "tinystr"
|
||||||
version = "0.8.3"
|
version = "0.8.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
|
checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"displaydoc",
|
"displaydoc",
|
||||||
"zerovec",
|
"zerovec",
|
||||||
@@ -3482,7 +3569,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.3",
|
"syn 3.0.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3706,7 +3793,7 @@ version = "0.5.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crypto-common",
|
"crypto-common 0.1.7",
|
||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3755,9 +3842,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.24.0"
|
version = "1.26.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239"
|
checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atomic",
|
"atomic",
|
||||||
"getrandom 0.4.3",
|
"getrandom 0.4.3",
|
||||||
@@ -3912,7 +3999,7 @@ checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.3.4",
|
"getrandom 0.3.4",
|
||||||
"mac_address",
|
"mac_address",
|
||||||
"sha2",
|
"sha2 0.10.9",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
@@ -4117,9 +4204,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wintun-bindings"
|
name = "wintun-bindings"
|
||||||
version = "0.7.39"
|
version = "0.7.40"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc4494d02357537af05cf526be7b817a51752b688a78926af57379abd840d911"
|
checksum = "e4316764300a7eb4aecf4770c81ae629ff1161b54905d48721a6832fd296ce5f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"blocking",
|
"blocking",
|
||||||
"futures",
|
"futures",
|
||||||
@@ -4137,9 +4224,9 @@ checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "writeable"
|
name = "writeable"
|
||||||
version = "0.6.3"
|
version = "0.6.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
|
checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yansi"
|
name = "yansi"
|
||||||
@@ -4233,9 +4320,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerotrie"
|
name = "zerotrie"
|
||||||
version = "0.2.4"
|
version = "0.2.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
|
checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"displaydoc",
|
"displaydoc",
|
||||||
"yoke",
|
"yoke",
|
||||||
@@ -4244,9 +4331,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerovec"
|
name = "zerovec"
|
||||||
version = "0.11.6"
|
version = "0.11.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
|
checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"yoke",
|
"yoke",
|
||||||
"zerofrom",
|
"zerofrom",
|
||||||
@@ -4255,13 +4342,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerovec-derive"
|
name = "zerovec-derive"
|
||||||
version = "0.11.3"
|
version = "0.11.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
|
checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.119",
|
"syn 3.0.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
+3
-3
@@ -22,15 +22,15 @@ profiling = []
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
ratatui = "0.30"
|
ratatui = "0.30"
|
||||||
secp256k1 = { version = "0.30", features = ["rand", "global-context"] }
|
secp256k1 = { version = "0.30", features = ["rand", "global-context"] }
|
||||||
sha2 = "0.10"
|
sha2 = "0.11"
|
||||||
hkdf = "0.12"
|
hkdf = "0.13"
|
||||||
ring = "0.17"
|
ring = "0.17"
|
||||||
libm = "0.2"
|
libm = "0.2"
|
||||||
zeroize = { version = "1.9", features = ["zeroize_derive"] }
|
zeroize = { version = "1.9", features = ["zeroize_derive"] }
|
||||||
rand = "0.10.1"
|
rand = "0.10.1"
|
||||||
crossbeam-channel = "0.5"
|
crossbeam-channel = "0.5"
|
||||||
thiserror = "2.0"
|
thiserror = "2.0"
|
||||||
bech32 = "0.11"
|
bech32 = "0.12"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_yaml = "0.9"
|
serde_yaml = "0.9"
|
||||||
|
|||||||
+5
-5
@@ -42,10 +42,10 @@ Read these *before* analyzing the diff so the review is grounded:
|
|||||||
|
|
||||||
3. Base-branch freshness. How many commits have landed on the PR's
|
3. Base-branch freshness. How many commits have landed on the PR's
|
||||||
base since the PR forked from it.
|
base since the PR forked from it.
|
||||||
4. Project guidance. Read [CLAUDE.md](CLAUDE.md) at the repo root and
|
4. Project guidance. Read [CONTRIBUTING.md](CONTRIBUTING.md) and
|
||||||
any nested `CLAUDE.md` in directories the diff touches. These
|
[docs/branching.md](docs/branching.md). These describe
|
||||||
describe project-specific conventions and constraints not visible
|
project-specific conventions and constraints not visible from the
|
||||||
from the diff alone.
|
diff alone.
|
||||||
5. Related work on GitHub. Skim the [open issues](https://github.com/jmcorgan/fips/issues)
|
5. Related work on GitHub. Skim the [open issues](https://github.com/jmcorgan/fips/issues)
|
||||||
and other [open PRs](https://github.com/jmcorgan/fips/pulls) for
|
and other [open PRs](https://github.com/jmcorgan/fips/pulls) for
|
||||||
work that overlaps, duplicates, partially addresses, or is unblocked
|
work that overlaps, duplicates, partially addresses, or is unblocked
|
||||||
@@ -157,7 +157,7 @@ Quality over quantity. Do not flag:
|
|||||||
- Pedantic style nitpicks a senior engineer would not call out
|
- Pedantic style nitpicks a senior engineer would not call out
|
||||||
- Likely intentional changes related to the broader goal
|
- Likely intentional changes related to the broader goal
|
||||||
- Things explicitly silenced by an `#[allow]` with justification
|
- Things explicitly silenced by an `#[allow]` with justification
|
||||||
- Stylistic preferences not anchored in `CLAUDE.md` or the
|
- Stylistic preferences not anchored in `CONTRIBUTING.md` or the
|
||||||
surrounding codebase's idioms
|
surrounding codebase's idioms
|
||||||
|
|
||||||
When in doubt about whether something is worth surfacing: would a
|
When in doubt about whether something is worth surfacing: would a
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||

|

|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
[](https://www.rust-lang.org/)
|
[](https://www.rust-lang.org/)
|
||||||
[](#status--roadmap)
|
[](#status--roadmap)
|
||||||
|
|
||||||
A self-organizing encrypted mesh network built on Nostr identities,
|
A self-organizing encrypted mesh network built on Nostr identities,
|
||||||
capable of operating over arbitrary transports without central
|
capable of operating over arbitrary transports without central
|
||||||
@@ -14,54 +14,86 @@ infrastructure.
|
|||||||
|
|
||||||
## What FIPS does
|
## What FIPS does
|
||||||
|
|
||||||
A machine running FIPS becomes a node in the mesh with a
|
A machine running FIPS becomes a node in the mesh with a self-generated
|
||||||
self-generated cryptographic identity (a Nostr keypair). There are
|
cryptographic identity, tunneling existing IPv6 traffic over the mesh
|
||||||
two equally-supported deployment modes.
|
or bypassing IP altogether and letting natively written applications
|
||||||
|
communicate directly with each other. In either case all traffic between
|
||||||
|
nodes is end-to-end encrypted and authenticated.
|
||||||
|
|
||||||
**As an overlay** on top of existing IP networks, FIPS lets your
|
The mesh is self-organizing and permissionless. Any node can join and reach
|
||||||
node reach any other FIPS node wherever it sits — behind a NAT, on
|
any other node without a central address registry, routing configuration, or
|
||||||
a different ISP, on a phone over cellular, on a laptop with only
|
coordination server. Peering between nodes can be manually configured or
|
||||||
Bluetooth in range, or behind a Tor onion. The mesh forwards IPv6
|
use auto-discovery.
|
||||||
traffic transparently and end-to-end encrypted, with no central VPN
|
|
||||||
concentrator or coordinating server.
|
|
||||||
|
|
||||||
**Ground up** over raw Ethernet, WiFi, or Bluetooth, FIPS provides
|
There are two equally-supported deployment modes.
|
||||||
a complete permissionless network without any pre-existing IP
|
|
||||||
infrastructure, ISP, or DNS. Any node that joins the link gets
|
|
||||||
routable IPv6 addresses, peer discovery, and a path to every other
|
|
||||||
node automatically.
|
|
||||||
|
|
||||||
Either way, existing networking software runs over it unchanged —
|
**As an overlay** on top of existing IP networks, FIPS lets your node reach
|
||||||
SSH, HTTP servers, file transfer, anything IPv6-native works the
|
any other FIPS node wherever it sits: behind a NAT, on a different ISP, on a
|
||||||
same way it would on a local network.
|
phone over cellular, on a laptop with only Bluetooth in range, or behind a
|
||||||
|
Tor onion.
|
||||||
|
|
||||||
|
**Ground up** over raw Ethernet, WiFi, or Bluetooth, FIPS provides a
|
||||||
|
complete permissionless network without any pre-existing IP infrastructure,
|
||||||
|
ISP, or DNS. Any node that joins the link gets routable IPv6 addresses, peer
|
||||||
|
discovery, and a path to every other node automatically. Support exists in
|
||||||
|
OpenWrt for turning a router radio into a backhaul link and for creating an
|
||||||
|
open access SSID so a phone or laptop can join without any configuration.
|
||||||
|
|
||||||
|
Either way, existing networking software runs over it unchanged — SSH, HTTP
|
||||||
|
servers, file transfer, anything IPv6-native works the same way it would on
|
||||||
|
a local network. Applications written to the FIPS native API skip that
|
||||||
|
layer entirely and address each other by public key, with no IPv6
|
||||||
|
emulation.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
### The mesh
|
||||||
|
|
||||||
- **Self-organizing mesh routing.** Spanning-tree coordinates with
|
- **Self-organizing mesh routing.** Spanning-tree coordinates with
|
||||||
bloom-filter-guided discovery; no global routing tables, no
|
bloom-filter-guided discovery; no global routing tables, no
|
||||||
flooding.
|
flooding.
|
||||||
- **Multi-transport.** UDP, TCP, Ethernet, Tor, Nym, and Bluetooth
|
- **Multi-transport.** UDP, TCP, Ethernet, Tor, Nym, and Bluetooth
|
||||||
(BLE L2CAP) ship today; transports compose on a single mesh and a
|
(BLE L2CAP) ship today; transports compose on a single mesh and a
|
||||||
node may run several at once.
|
node may run several at once.
|
||||||
|
- **Self-assigned cryptographic identity.** secp256k1 / schnorr
|
||||||
|
keypairs as node addresses; no registration, no central authority.
|
||||||
- **Two-layer encryption.** Noise IK between peers (hop-by-hop) and
|
- **Two-layer encryption.** Noise IK between peers (hop-by-hop) and
|
||||||
Noise XK between mesh endpoints (independent end-to-end), with
|
Noise XK between mesh endpoints (independent end-to-end), with
|
||||||
periodic rekey for forward secrecy.
|
periodic rekey for forward secrecy.
|
||||||
- **Nostr-native identity.** secp256k1 / schnorr keypairs as node
|
- **(Optional) Nostr-mediated discovery and NAT traversal.** Peers may
|
||||||
addresses; self-generated, no registration, no central authority.
|
publish endpoint adverts on public Nostr relays, exchange peering
|
||||||
|
candidates, and establish direct paths through NATs using
|
||||||
|
STUN-assisted hole punching. On the local network, mDNS LAN discovery
|
||||||
|
finds peers directly without relays.
|
||||||
|
|
||||||
|
### Getting traffic onto it
|
||||||
|
|
||||||
- **IPv6 adapter.** A TUN interface maps each remote npub to an
|
- **IPv6 adapter.** A TUN interface maps each remote npub to an
|
||||||
`fd00::/8` address, so unmodified IPv6 software reaches mesh
|
`fd00::/8` address, so unmodified IPv6 software reaches mesh
|
||||||
peers as `<npub>.fips`. Built-in `.fips` DNS resolver, with
|
peers as `<npub>.fips`. Built-in `.fips` DNS resolver, with
|
||||||
optional static name mapping via `/etc/fips/hosts`.
|
optional static name mapping via `/etc/fips/hosts`.
|
||||||
- **Nostr-mediated discovery and NAT traversal.** Peers publish
|
- **Native datagram API.** A local program moves bytes between two
|
||||||
endpoint adverts on public Nostr relays, exchange candidates via
|
public keys over the mesh, addressing a peer as `npub:port` with no
|
||||||
NIP-59 gift-wrapped offers and answers, and establish direct
|
IPv6 emulation and no TUN device in the path. `connect` and `bind`
|
||||||
paths through NATs using STUN-assisted hole punching. On the local
|
take a key and a port, and from there it is ordinary socket calls.
|
||||||
network, mDNS LAN discovery finds peers directly without relays.
|
|
||||||
- **LAN gateway.** Optional `fips-gateway` service folds an entire
|
- **LAN gateway.** Optional `fips-gateway` service folds an entire
|
||||||
unmodified LAN into the mesh: outbound (LAN clients reach mesh
|
unmodified LAN into the mesh: outbound (LAN clients reach mesh
|
||||||
destinations through a DNS-allocated virtual IPv6 pool and
|
destinations through a DNS-allocated virtual IPv6 pool and
|
||||||
nftables NAT) and inbound (LAN-side services exposed to the mesh
|
nftables NAT) and inbound (LAN-side services exposed to the mesh
|
||||||
through 1:1 port forwards).
|
through 1:1 port forwards).
|
||||||
|
- **OpenWrt support.** FIPS ships as an OpenWrt package. Routers run
|
||||||
|
802.11s between themselves as a bare L2 link, with FIPS supplying the
|
||||||
|
encryption, authentication and routing over it. A second helper brings
|
||||||
|
up an open `!FIPS` SSID, the same on every router, which a FIPS client
|
||||||
|
joins over WiFi without configuration.
|
||||||
|
|
||||||
|
### Running a node
|
||||||
|
|
||||||
|
- **Operator visibility.** `fipsctl` CLI for control and inspection
|
||||||
|
with time-series stats history queryable for any metric,
|
||||||
|
`fipstop` TUI for live status with inline sparkline dashboards,
|
||||||
|
and a JSON-line control socket on each binary for direct
|
||||||
|
programmatic access.
|
||||||
- **Per-link metrics.** RTT, loss, jitter, and goodput on every
|
- **Per-link metrics.** RTT, loss, jitter, and goodput on every
|
||||||
hop, plus mesh-size estimation, via the Metrics Measurement
|
hop, plus mesh-size estimation, via the Metrics Measurement
|
||||||
Protocol.
|
Protocol.
|
||||||
@@ -73,33 +105,35 @@ same way it would on a local network.
|
|||||||
(`/etc/fips/fips.d/`) and a disabled-by-default
|
(`/etc/fips/fips.d/`) and a disabled-by-default
|
||||||
`fips-firewall.service`. The baseline polices only the mesh
|
`fips-firewall.service`. The baseline polices only the mesh
|
||||||
interface, leaving Docker, Tor, and the host firewall untouched.
|
interface, leaving Docker, Tor, and the host firewall untouched.
|
||||||
- **Operator visibility.** `fipsctl` CLI for control and inspection
|
|
||||||
with time-series stats history queryable for any metric,
|
|
||||||
`fipstop` TUI for live status with inline sparkline dashboards,
|
|
||||||
and a JSON-line control socket on each binary for direct
|
|
||||||
programmatic access.
|
|
||||||
- **Reproducible builds** with toolchain pinning and
|
- **Reproducible builds** with toolchain pinning and
|
||||||
`SOURCE_DATE_EPOCH`.
|
`SOURCE_DATE_EPOCH`.
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
The shortest path on Debian / Ubuntu:
|
**Start from a released package.** Every packaged platform in the table
|
||||||
|
below gets an installer built and published per release, with checksums,
|
||||||
|
on the [releases page](https://github.com/jmcorgan/fips/releases/latest).
|
||||||
|
Building from source produces the same artifacts and the same
|
||||||
|
post-install state, so it is the path to take when you want to modify
|
||||||
|
FIPS rather than run it.
|
||||||
|
|
||||||
|
On Debian or Ubuntu, download `fips_<version>_amd64.deb` (or
|
||||||
|
`_arm64.deb`) and install it:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/jmcorgan/fips.git
|
sudo dpkg -i fips_<version>_amd64.deb
|
||||||
cd fips
|
sudo systemctl start fips fips-dns
|
||||||
cargo install cargo-deb
|
|
||||||
cargo deb
|
|
||||||
sudo dpkg -i target/debian/fips_*.deb
|
|
||||||
sudo systemctl start fips
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This installs the daemon, CLI tools (`fipsctl`, `fipstop`), the
|
This installs the daemon, CLI tools (`fipsctl`, `fipstop`), the
|
||||||
optional `fips-gateway` service, systemd units, and a default
|
`fips-dns` service that wires `.fips` name resolution into the host
|
||||||
`/etc/fips/fips.yaml` you can edit before starting.
|
resolver, the optional `fips-gateway` service, systemd units, and a
|
||||||
|
default `/etc/fips/fips.yaml` you can edit before starting. The package
|
||||||
|
enables `fips` and `fips-dns` but starts neither, which is why the
|
||||||
|
second command is there.
|
||||||
|
|
||||||
For macOS, Windows, OpenWrt, the systemd tarball, a Nix flake, or a
|
For macOS, Windows, FreeBSD, OpenWrt, the systemd tarball or a Nix
|
||||||
from-source build, see [docs/getting-started.md](docs/getting-started.md)
|
flake, see [docs/getting-started.md](docs/getting-started.md)
|
||||||
for the full multi-platform installation guide.
|
for the full multi-platform installation guide.
|
||||||
|
|
||||||
To join a live mesh and reach your first peer, follow the new-user
|
To join a live mesh and reach your first peer, follow the new-user
|
||||||
@@ -108,23 +142,71 @@ tutorial progression starting at
|
|||||||
|
|
||||||
### Building from source
|
### Building from source
|
||||||
|
|
||||||
|
To build the Debian package yourself rather than downloading it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/jmcorgan/fips.git
|
||||||
|
cd fips
|
||||||
|
cargo install cargo-deb
|
||||||
|
cargo deb
|
||||||
|
sudo dpkg -i target/debian/fips_*.deb
|
||||||
|
```
|
||||||
|
|
||||||
|
For the binaries alone, without an installer:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo build --release
|
cargo build --release
|
||||||
```
|
```
|
||||||
|
|
||||||
Requires Rust 1.94.1+ (edition 2024). Linux, macOS, FreeBSD, and Windows
|
Requires Rust 1.94.1+ (edition 2024). Linux, macOS, FreeBSD, and Windows
|
||||||
run as standalone daemons; Android is supported as an embedded library
|
run as standalone daemons. FreeBSD is packaged for **x86_64 only**;
|
||||||
(the host app owns the TUN, e.g. a `VpnService`). Transport availability
|
no aarch64 FreeBSD artifact is built or tested. Android is supported as
|
||||||
varies by platform.
|
an **embedded crate** rather than as a standalone daemon: a
|
||||||
|
compile-gated library surface where the host app owns the TUN (a
|
||||||
|
`VpnService`, for example) and reaches the built-in resolver through
|
||||||
|
`Node::dns_local_addr()`. There is no Android daemon artifact and no
|
||||||
|
host-app guide. Transport and feature availability varies by platform.
|
||||||
|
|
||||||
| Transport | Linux | macOS | FreeBSD | Windows | Android | OpenWrt |
|
| Feature | Debian/Ubuntu | Arch | NixOS | macOS | OpenWrt | FreeBSD | Android | Windows |
|
||||||
|-----------|:-----:|:-----:|:-------:|:-------:|:-------:|:-------:|
|
|----------------|:-------------:|:----:|:-----:|:------:|:---------------:|:-------:|:-------:|:-------:|
|
||||||
| UDP | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| UDP | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
| TCP | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| TCP | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
| Ethernet | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ |
|
| Tor | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
|
||||||
| Tor | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
|
| Nym | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ |
|
||||||
| Nym | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
| Ethernet | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||||
| BLE | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
| BLE | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ |
|
||||||
|
| Native API | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||||
|
| Package format | `.deb` | AUR | flake | `.pkg` | `.ipk` / `.apk` | `.pkg` | ❌ | ZIP |
|
||||||
|
|
||||||
|
A column records what builds and runs in a packaged daemon, FreeBSD on
|
||||||
|
x86_64 only. **Native API** is the native datagram API, which is off by
|
||||||
|
default; Windows cannot carry it, because it has no `SCM_RIGHTS` with
|
||||||
|
which to pass a descriptor. **Package format** names the artifact you install,
|
||||||
|
and a ❌ there means the platform ships none. Windows is the odd one:
|
||||||
|
its ZIP is an archive you unpack yourself rather than a package an
|
||||||
|
installer consumes, and there is no MSI.
|
||||||
|
|
||||||
|
Five of these columns are Linux: Debian/Ubuntu, Arch, NixOS, OpenWrt
|
||||||
|
and Android. Linux is not one target. Debian, Ubuntu, Arch and NixOS
|
||||||
|
are the same glibc build, and what
|
||||||
|
differs is the packaging: Debian and Ubuntu take the same `.deb`, Arch
|
||||||
|
takes `fips` from the AUR, and NixOS uses the Nix flake described
|
||||||
|
below. **Only the `.deb` is exercised per release**, by the
|
||||||
|
`deb-install` suite across debian12, debian13, ubuntu22, ubuntu24 and
|
||||||
|
ubuntu26; neither the AUR package nor the flake is. OpenWrt is a musl
|
||||||
|
target rather than glibc, and it takes an `.ipk` on 24.x and earlier or
|
||||||
|
an `.apk` on 25 and later; both carry the `fips-mesh-setup` and
|
||||||
|
`fips-ap-setup` helpers.
|
||||||
|
|
||||||
|
**Android records what compiles for `aarch64-linux-android` under the
|
||||||
|
CI cross-check and nothing more**: no transport in that column is
|
||||||
|
exercised on a device or an emulator, so read it as "compiles", not
|
||||||
|
"verified here". Being an embedded crate rather than a daemon platform, it
|
||||||
|
has nothing to
|
||||||
|
install, which is what its ❌ package format records. The BLE cell is
|
||||||
|
narrower still: the transport compiles, but the radio behind it is
|
||||||
|
supplied by the embedding application rather than by FIPS, and no part
|
||||||
|
of that path is device-tested.
|
||||||
|
|
||||||
On Linux, a source build requires `libclang` — the LAN gateway's
|
On Linux, a source build requires `libclang` — the LAN gateway's
|
||||||
nftables bindings are generated by `bindgen` at build time, which
|
nftables bindings are generated by `bindgen` at build time, which
|
||||||
@@ -134,11 +216,12 @@ build fails inside the `rustables` crate with an "Unable to find
|
|||||||
libclang" error. This is a build-time prerequisite only — it is not a
|
libclang" error. This is a build-time prerequisite only — it is not a
|
||||||
runtime dependency, and the pre-built `.deb` artifacts do not need it.
|
runtime dependency, and the pre-built `.deb` artifacts do not need it.
|
||||||
|
|
||||||
BLE is optional and, on Linux, requires BlueZ and libdbus
|
BLE compiles on every glibc Linux target and on Android, and is
|
||||||
(`sudo apt install bluez libdbus-1-dev` on Debian / Ubuntu). It is
|
excluded on musl. On glibc Linux, libdbus is a hard build prerequisite
|
||||||
gated on a build-script probe — install the dependencies first and
|
(`sudo apt install libdbus-1-dev pkg-config` on Debian / Ubuntu) —
|
||||||
the `cargo build` line above picks it up. The OpenWrt ipk omits
|
without it the build fails inside `libdbus-sys` rather than skipping
|
||||||
BLE because libdbus is not available on the target.
|
BLE. The BlueZ daemon itself is a runtime dependency, not a build one.
|
||||||
|
The OpenWrt ipk is a musl target, so it omits BLE.
|
||||||
|
|
||||||
Nym (mixnet) transport builds on all desktop platforms. The OpenWrt
|
Nym (mixnet) transport builds on all desktop platforms. The OpenWrt
|
||||||
❌ is provisional, pending verification of `nym-socks5-client`
|
❌ is provisional, pending verification of `nym-socks5-client`
|
||||||
@@ -161,7 +244,8 @@ Nix / NixOS section of [packaging/README.md](packaging/README.md).
|
|||||||
ground-up two-device mesh).
|
ground-up two-device mesh).
|
||||||
- **[How-to guides](docs/how-to/)** — operator recipes for
|
- **[How-to guides](docs/how-to/)** — operator recipes for
|
||||||
specific tasks: firewall activation, Nostr discovery, Tor onion
|
specific tasks: firewall activation, Nostr discovery, Tor onion
|
||||||
service, Bluetooth peering, LAN gateway deployment and
|
service, Bluetooth peering, 802.11s mesh backhaul and the open
|
||||||
|
access SSID on OpenWrt, LAN gateway deployment and
|
||||||
troubleshooting, MTU diagnostics, host aliases, persistent
|
troubleshooting, MTU diagnostics, host aliases, persistent
|
||||||
identity, unprivileged-user setup, UDP buffer tuning.
|
identity, unprivileged-user setup, UDP buffer tuning.
|
||||||
- **[Reference](docs/reference/)** — `fips.yaml` configuration,
|
- **[Reference](docs/reference/)** — `fips.yaml` configuration,
|
||||||
@@ -173,6 +257,8 @@ Nix / NixOS section of [packaging/README.md](packaging/README.md).
|
|||||||
[fips-concepts.md](docs/design/fips-concepts.md) for the framing,
|
[fips-concepts.md](docs/design/fips-concepts.md) for the framing,
|
||||||
then [fips-architecture.md](docs/design/fips-architecture.md) for
|
then [fips-architecture.md](docs/design/fips-architecture.md) for
|
||||||
the protocol stack.
|
the protocol stack.
|
||||||
|
- **[Release notes](docs/releases/)** — per-version notes, including
|
||||||
|
[v0.5.0](docs/releases/release-notes-v0.5.0.md).
|
||||||
|
|
||||||
If you want to contribute, see [CONTRIBUTING.md](CONTRIBUTING.md)
|
If you want to contribute, see [CONTRIBUTING.md](CONTRIBUTING.md)
|
||||||
and [testing/README.md](testing/README.md).
|
and [testing/README.md](testing/README.md).
|
||||||
@@ -205,23 +291,37 @@ and [testing/README.md](testing/README.md).
|
|||||||
```text
|
```text
|
||||||
src/ Rust source: library + fips, fipsctl, fipstop, fips-gateway binaries
|
src/ Rust source: library + fips, fipsctl, fipstop, fips-gateway binaries
|
||||||
docs/ Documentation: tutorials, how-to, reference, design
|
docs/ Documentation: tutorials, how-to, reference, design
|
||||||
packaging/ Debian, macOS .pkg, Windows ZIP, OpenWrt ipk, AUR, systemd tarball
|
packaging/ Debian, AUR, systemd tarball, OpenWrt ipk/apk,
|
||||||
|
macOS .pkg, FreeBSD .pkg, Windows ZIP
|
||||||
examples/ Deployment examples (Nostr relay, K8s sidecar, macOS WireGuard)
|
examples/ Deployment examples (Nostr relay, K8s sidecar, macOS WireGuard)
|
||||||
testing/ Docker-based integration test harnesses + chaos simulation
|
testing/ Docker-based integration test harnesses + chaos simulation
|
||||||
```
|
```
|
||||||
|
|
||||||
## Status & roadmap
|
## Status & roadmap
|
||||||
|
|
||||||
FIPS is at **v0.5.0-dev** on the `master` branch.
|
FIPS is at **v0.5.0** on the `master` branch, the first feature
|
||||||
[v0.4.1](https://github.com/jmcorgan/fips/releases/tag/v0.4.1) has
|
release since v0.4.0.
|
||||||
shipped; this development line continues the testing-and-polishing
|
[v0.4.2](https://github.com/jmcorgan/fips/releases/tag/v0.4.2) was the
|
||||||
track toward v0.5.0. The core protocol works end-to-end over
|
last release on the maintenance line, so how much of this release is
|
||||||
UDP, TCP, Ethernet, Tor, Nym, and Bluetooth on a global, public test
|
new to you depends on which version you are upgrading from. The core
|
||||||
mesh of thousands of nodes. v0.4.0 added the Nym mixnet transport and
|
protocol
|
||||||
mDNS LAN discovery alongside the existing Nostr-mediated peer discovery,
|
works end-to-end over UDP, TCP, Ethernet, Tor, Nym, and Bluetooth on a
|
||||||
UDP NAT traversal, peer ACL, and packaging hardening. New wire-format work
|
global, public test mesh of thousands of nodes.
|
||||||
continues to be staged on the `next` branch for the subsequent
|
|
||||||
release line.
|
v0.5.0 is a platform-and-lifecycle release. It adds FreeBSD as a
|
||||||
|
packaged platform (x86_64 only), OpenWrt setup helpers for an 802.11s
|
||||||
|
mesh between routers (`fips-mesh-setup`) and for the open `!FIPS` client
|
||||||
|
SSID (`fips-ap-setup`), and an Android embedding interface for apps that own
|
||||||
|
their own TUN.
|
||||||
|
Node health is now determined at start completion and published as
|
||||||
|
`Degraded` or `Failed`, a node with **no transport up is a fatal
|
||||||
|
start** rather than a silent one, and shutdown runs a bounded drain
|
||||||
|
window (`node.drain_timeout_secs`, default 2 seconds) so live traffic
|
||||||
|
is not cut mid-flight. The `node.discovery.*` configuration table
|
||||||
|
splits into `node.lookup.*` and `node.rendezvous.*`; a deployed
|
||||||
|
`node.discovery:` block still loads, folded in with a one-time
|
||||||
|
deprecation warning. New wire-format work continues to be staged on the
|
||||||
|
`next` branch for the subsequent release line.
|
||||||
|
|
||||||
### What works today
|
### What works today
|
||||||
|
|
||||||
@@ -234,6 +334,9 @@ release line.
|
|||||||
- IPv6 TUN adapter with built-in `.fips` DNS resolver and
|
- IPv6 TUN adapter with built-in `.fips` DNS resolver and
|
||||||
multi-backend auto-configuration (systemd dns-delegate,
|
multi-backend auto-configuration (systemd dns-delegate,
|
||||||
systemd-resolved, dnsmasq, NetworkManager).
|
systemd-resolved, dnsmasq, NetworkManager).
|
||||||
|
- Native datagram API for FIPS-aware applications (npub:port
|
||||||
|
addressing without the IPv6-shim path): off by default, with a
|
||||||
|
surface that may still change.
|
||||||
- Static hostname mapping (`/etc/fips/hosts`) with auto-reload.
|
- Static hostname mapping (`/etc/fips/hosts`) with auto-reload.
|
||||||
- Per-link metrics (RTT, loss, jitter, goodput) and mesh size
|
- Per-link metrics (RTT, loss, jitter, goodput) and mesh size
|
||||||
estimation.
|
estimation.
|
||||||
@@ -247,23 +350,32 @@ release line.
|
|||||||
and inbound (mesh-to-LAN port-forwarding) modes.
|
and inbound (mesh-to-LAN port-forwarding) modes.
|
||||||
- Peer ACL: per-npub allow / deny admission control at the link
|
- Peer ACL: per-npub allow / deny admission control at the link
|
||||||
layer; opt-in mesh-firewall baseline at `fips0` ingress.
|
layer; opt-in mesh-firewall baseline at `fips0` ingress.
|
||||||
- Runtime inspection and peer management via `fipsctl` and
|
- Runtime inspection and peer management via `fipsctl` (including
|
||||||
`fipstop`.
|
`fipsctl probe` for reachability diagnosis and `fipsctl address`
|
||||||
|
for mesh-address derivation) and `fipstop`.
|
||||||
- Reproducible builds with toolchain pinning and
|
- Reproducible builds with toolchain pinning and
|
||||||
`SOURCE_DATE_EPOCH`.
|
`SOURCE_DATE_EPOCH`.
|
||||||
- Linux (Debian, systemd tarball, OpenWrt, AUR), macOS (`.pkg`),
|
- Node lifecycle and health reporting (`Starting`, `Running`,
|
||||||
FreeBSD (`.pkg`), and Windows (ZIP, service) packaging.
|
`Degraded`, `Failed`, `Draining`) with a fatal start when no
|
||||||
|
transport comes up and a bounded shutdown drain window.
|
||||||
|
- OpenWrt setup helpers for an 802.11s mesh between routers
|
||||||
|
(`fips-mesh-setup`) and for the open `!FIPS` client SSID
|
||||||
|
(`fips-ap-setup`).
|
||||||
|
- Linux (Debian, systemd tarball, OpenWrt `.ipk` and `.apk`, AUR),
|
||||||
|
macOS (`.pkg`), FreeBSD (`.pkg`, x86_64 only), and Windows (ZIP,
|
||||||
|
service) packaging.
|
||||||
- Docker-based integration and chaos testing.
|
- Docker-based integration and chaos testing.
|
||||||
|
|
||||||
### Near-term priorities
|
### Near-term priorities
|
||||||
|
|
||||||
- Native API for FIPS-aware applications (npub:port addressing
|
|
||||||
without the IPv6-shim path).
|
|
||||||
- Security audit of the cryptographic protocols.
|
- Security audit of the cryptographic protocols.
|
||||||
|
|
||||||
### Longer-term
|
### Longer-term
|
||||||
|
|
||||||
- Mobile platform support.
|
- Packaged mobile applications: an Android host app, and iOS. The
|
||||||
|
Android embedding interface ships today (see
|
||||||
|
[Building from source](#building-from-source)); what is absent is a
|
||||||
|
packaged app on either platform.
|
||||||
- Bandwidth-aware routing and QoS.
|
- Bandwidth-aware routing and QoS.
|
||||||
- Protocol stability and a versioned wire format.
|
- Protocol stability and a versioned wire format.
|
||||||
- Published crate.
|
- Published crate.
|
||||||
|
|||||||
+750
-639
File diff suppressed because it is too large
Load Diff
@@ -52,3 +52,8 @@ flags, or counter inventories, go here.
|
|||||||
If you want to understand how the mesh self-organizes, why FIPS
|
If you want to understand how the mesh self-organizes, why FIPS
|
||||||
makes the choices it does, or how the pieces fit together, go
|
makes the choices it does, or how the pieces fit together, go
|
||||||
here.
|
here.
|
||||||
|
|
||||||
|
### [Releases](releases/)
|
||||||
|
|
||||||
|
If you want the notes for a particular version — what changed, what
|
||||||
|
broke, and what to do about it on upgrade — go here.
|
||||||
|
|||||||
@@ -10,13 +10,14 @@ academic citations, see [fips-prior-work.md](fips-prior-work.md).
|
|||||||
|
|
||||||
FIPS is a self-organizing mesh network that can operate natively over a
|
FIPS is a self-organizing mesh network that can operate natively over a
|
||||||
variety of physical and logical media, such as local area networks,
|
variety of physical and logical media, such as local area networks,
|
||||||
Bluetooth, serial links, or the existing internet as an overlay. The
|
Bluetooth, or the existing internet as an overlay; radio and serial
|
||||||
long-term goal is infrastructure that can function alongside or
|
links are in the planned set. The long-term goal is infrastructure that
|
||||||
ultimately replace dependence on the Internet itself. Systems running
|
can function alongside or ultimately replace dependence on the Internet
|
||||||
FIPS establish peer connections, authenticate each other, and route
|
itself. Systems running FIPS establish peer connections, authenticate
|
||||||
traffic for each other without any central authority or global topology
|
each other, and route traffic for each other without any central
|
||||||
knowledge, and allow end-to-end encrypted sessions between any two
|
authority or global topology knowledge, and allow end-to-end encrypted
|
||||||
nodes regardless of how many hops separate them.
|
sessions between any two nodes regardless of how many hops separate
|
||||||
|
them.
|
||||||
|
|
||||||
Nodes in the mesh route traffic for each other using Nostr identities
|
Nodes in the mesh route traffic for each other using Nostr identities
|
||||||
(npubs) as network addresses. Applications can access the mesh through
|
(npubs) as network addresses. Applications can access the mesh through
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ Single-path forwarding is more fragile than flooding — if any transit node
|
|||||||
on the path has a stale bloom filter or loses a link, the request fails.
|
on the path has a stale bloom filter or loses a link, the request fails.
|
||||||
To compensate, each discovery is a sequence of attempts with growing
|
To compensate, each discovery is a sequence of attempts with growing
|
||||||
per-attempt timeouts. The default sequence is `[1s, 2s, 4s, 8s]`
|
per-attempt timeouts. The default sequence is `[1s, 2s, 4s, 8s]`
|
||||||
(configurable via `node.discovery.attempt_timeouts_secs`); the destination
|
(configurable via `node.lookup.attempt_timeouts_secs`); the destination
|
||||||
is declared unreachable only after the full sequence is exhausted (15s
|
is declared unreachable only after the full sequence is exhausted (15s
|
||||||
total at default).
|
total at default).
|
||||||
|
|
||||||
@@ -228,8 +228,8 @@ After the per-attempt sequence is exhausted, the originator can additionally
|
|||||||
suppress further fresh lookups for the same target with exponential
|
suppress further fresh lookups for the same target with exponential
|
||||||
post-failure backoff. This is **disabled by default** (`backoff_base_secs:
|
post-failure backoff. This is **disabled by default** (`backoff_base_secs:
|
||||||
0`); the per-attempt sequence is the only retry pacing in the standard
|
0`); the per-attempt sequence is the only retry pacing in the standard
|
||||||
configuration. Operators may opt in via `node.discovery.backoff_base_secs`
|
configuration. Operators may opt in via `node.lookup.backoff_base_secs`
|
||||||
and `node.discovery.backoff_max_secs` if their deployment has chatty apps
|
and `node.lookup.backoff_max_secs` if their deployment has chatty apps
|
||||||
generating repeated lookups for genuinely unreachable destinations. When
|
generating repeated lookups for genuinely unreachable destinations. When
|
||||||
enabled, backoff is **reset on topology changes** that might make
|
enabled, backoff is **reset on topology changes** that might make
|
||||||
previously unreachable targets reachable: parent switch, new peer
|
previously unreachable targets reachable: parent switch, new peer
|
||||||
|
|||||||
@@ -189,7 +189,8 @@ providing measurement data up to the teardown moment.
|
|||||||
|
|
||||||
## Operator Logging
|
## Operator Logging
|
||||||
|
|
||||||
Both layers emit periodic metrics at info level. The interval is
|
Both layers emit periodic metrics at debug level, so `node.log_level`
|
||||||
|
(or `RUST_LOG`) must be set to `debug` to see them. The interval is
|
||||||
`node.mmp.log_interval_secs` for link-layer (default 30s) and
|
`node.mmp.log_interval_secs` for link-layer (default 30s) and
|
||||||
`node.session_mmp.log_interval_secs` for session-layer (default 30s).
|
`node.session_mmp.log_interval_secs` for session-layer (default 30s).
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ oversize), see the relevant how-to under `docs/how-to/`.
|
|||||||
|
|
||||||
A FIPS path can traverse heterogeneous link types — UDP/IP (1280
|
A FIPS path can traverse heterogeneous link types — UDP/IP (1280
|
||||||
default, IPv6 minimum), Ethernet (interface MTU − 3, typically 1497),
|
default, IPv6 minimum), Ethernet (interface MTU − 3, typically 1497),
|
||||||
BLE (negotiated ATT_MTU per link), Tor stream (1400 default), radio
|
BLE (per-connection L2CAP CoC MTU, 2048 default), Tor stream (1400
|
||||||
(51–222) — within a single end-to-end session.
|
default), radio (51–222) — within a single end-to-end session.
|
||||||
The minimum MTU along the path determines the largest datagram a
|
The minimum MTU along the path determines the largest datagram a
|
||||||
session can deliver. Several properties make this harder than in
|
session can deliver. Several properties make this harder than in
|
||||||
classic IP networks:
|
classic IP networks:
|
||||||
@@ -89,7 +89,7 @@ SessionDatagram and LookupResponse. Per-transport defaults:
|
|||||||
| Ethernet | interface MTU − 3 (typically 1497) | uniform |
|
| Ethernet | interface MTU − 3 (typically 1497) | uniform |
|
||||||
| TCP | 1400 | derived from `TCP_MAXSEG` per connection |
|
| TCP | 1400 | derived from `TCP_MAXSEG` per connection |
|
||||||
| Tor | 1400 | uniform |
|
| Tor | 1400 | uniform |
|
||||||
| BLE | 2048 default; negotiated ATT_MTU per link | per-link (overrides `mtu()`) |
|
| BLE | 2048 default; per-connection L2CAP CoC MTU | per-link (overrides `mtu()`) |
|
||||||
|
|
||||||
For TCP, the per-connection `TCP_MAXSEG` query lets FMP discover the
|
For TCP, the per-connection `TCP_MAXSEG` query lets FMP discover the
|
||||||
actual MSS the kernel negotiated for each connection, rather than
|
actual MSS the kernel negotiated for each connection, rather than
|
||||||
|
|||||||
@@ -90,8 +90,9 @@ and one arriving on a held port reaches its flow.
|
|||||||
|
|
||||||
**The interface around it is experimental.** It is not versioned, it has no
|
**The interface around it is experimental.** It is not versioned, it has no
|
||||||
compatibility promise, and three of its five commands exist only to let the
|
compatibility promise, and three of its five commands exist only to let the
|
||||||
daemon's own checks drive the receive path without a peer. It is Linux and
|
daemon's own checks drive the receive path without a peer. It is Linux, FreeBSD
|
||||||
FreeBSD only, and it is off by default.
|
and macOS only — Windows cannot be supported, as it has no `SCM_RIGHTS` — and it
|
||||||
|
is off by default.
|
||||||
|
|
||||||
## What this is not
|
## What this is not
|
||||||
|
|
||||||
|
|||||||
@@ -19,14 +19,15 @@ offer/answer exchange, and STUN supplies the reflexive address used for
|
|||||||
a coordinated hole-punch.
|
a coordinated hole-punch.
|
||||||
|
|
||||||
Nostr discovery is unconditionally compiled into the `fips` binary on
|
Nostr discovery is unconditionally compiled into the `fips` binary on
|
||||||
every supported platform and ships in every stock packaging artifact
|
every supported platform and ships in every published release artifact
|
||||||
(`.deb`, AUR, systemd tarball, OpenWrt `.ipk`, macOS `.pkg`, Windows
|
(`.deb`, AUR, systemd tarball, OpenWrt `.ipk` and `.apk`, FreeBSD
|
||||||
`.zip`). It is runtime-opt-in: the YAML configuration defaults to
|
`.pkg`, macOS `.pkg`, Windows `.zip`). It is runtime-opt-in: the YAML
|
||||||
disabled (`node.discovery.nostr.enabled: false`), so the discovery
|
configuration defaults to disabled (`node.rendezvous.nostr.enabled:
|
||||||
runtime stays dormant — and opens no relay connections — until an
|
false`), so the discovery runtime stays dormant — and opens no relay
|
||||||
operator flips the flag. Default relay and STUN-server lists ship in
|
connections — until an operator flips the flag. Default relay and
|
||||||
the config; both are optional overrides. When disabled, nodes behave
|
STUN-server lists ship in the config; both are optional overrides. When
|
||||||
exactly as before: only the static `peers[]` addresses are used.
|
disabled, nodes behave exactly as before: only the static `peers[]`
|
||||||
|
addresses are used.
|
||||||
|
|
||||||
## Role
|
## Role
|
||||||
|
|
||||||
@@ -79,7 +80,7 @@ namespace) — see
|
|||||||
The full configuration knob tables, per-transport keys, and startup
|
The full configuration knob tables, per-transport keys, and startup
|
||||||
validation rules live in
|
validation rules live in
|
||||||
[../reference/configuration.md](../reference/configuration.md) under
|
[../reference/configuration.md](../reference/configuration.md) under
|
||||||
`node.discovery.nostr.*`. The Kind 37195 advert event format is in
|
`node.rendezvous.nostr.*`. The Kind 37195 advert event format is in
|
||||||
[../reference/nostr-events.md](../reference/nostr-events.md). The rest
|
[../reference/nostr-events.md](../reference/nostr-events.md). The rest
|
||||||
of this document covers the design of the discovery runtime itself.
|
of this document covers the design of the discovery runtime itself.
|
||||||
|
|
||||||
@@ -371,7 +372,7 @@ semaphore and replay-cache layers downstream.
|
|||||||
for reflexive discovery. Peer-advertised STUN values are
|
for reflexive discovery. Peer-advertised STUN values are
|
||||||
informational; a malicious peer cannot steer this node to a
|
informational; a malicious peer cannot steer this node to a
|
||||||
chosen STUN target. See the doc comment on
|
chosen STUN target. See the doc comment on
|
||||||
`node.discovery.nostr.stun_servers`.
|
`node.rendezvous.nostr.stun_servers`.
|
||||||
- **The FIPS identity key signs adverts.** Compromise of
|
- **The FIPS identity key signs adverts.** Compromise of
|
||||||
`fips.key` is compromise of the node's Nostr identity — an attacker
|
`fips.key` is compromise of the node's Nostr identity — an attacker
|
||||||
can publish adverts on behalf of the node. The recovery path is
|
can publish adverts on behalf of the node. The recovery path is
|
||||||
@@ -410,7 +411,7 @@ The result is sub-second peer pairing on the same LAN.
|
|||||||
It is unrelated to the "LAN candidate" terminology used in the
|
It is unrelated to the "LAN candidate" terminology used in the
|
||||||
NAT-traversal sections above (which refers to a host's own
|
NAT-traversal sections above (which refers to a host's own
|
||||||
locally-bound address offered as a hole-punch candidate). LAN/mDNS
|
locally-bound address offered as a hole-punch candidate). LAN/mDNS
|
||||||
discovery is a distinct subsystem under `src/discovery/lan/`.
|
discovery is a distinct subsystem under `src/mdns/`.
|
||||||
|
|
||||||
### Role
|
### Role
|
||||||
|
|
||||||
@@ -426,7 +427,7 @@ LAN discovery adds two capabilities, both confined to the local link:
|
|||||||
initiates a normal FMP link to each newly-seen peer.
|
initiates a normal FMP link to each newly-seen peer.
|
||||||
|
|
||||||
The mDNS service type is `_fips._udp.local.`
|
The mDNS service type is `_fips._udp.local.`
|
||||||
(`src/discovery/lan/mod.rs:45`). Per RFC 6763 the `_udp` label denotes
|
(`src/mdns/mod.rs:45`). Per RFC 6763 the `_udp` label denotes
|
||||||
the IP transport used for the advert, not the FIPS upper protocol —
|
the IP transport used for the advert, not the FIPS upper protocol —
|
||||||
both UDP and TCP FIPS endpoints announce under the same service type
|
both UDP and TCP FIPS endpoints announce under the same service type
|
||||||
because the link-layer handshake travels over UDP either way. (In
|
because the link-layer handshake travels over UDP either way. (In
|
||||||
@@ -448,16 +449,16 @@ by default**, so doing nothing leaves it off.
|
|||||||
|
|
||||||
### How it works
|
### How it works
|
||||||
|
|
||||||
The LAN discovery runtime (`src/discovery/lan/mod.rs`) is started
|
The LAN discovery runtime (`src/mdns/mod.rs`) is started
|
||||||
during node initialization when `node.discovery.lan.enabled` is true.
|
during node initialization when `node.rendezvous.lan.enabled` is true.
|
||||||
It is independent of Nostr discovery and runs even when Nostr is
|
It is independent of Nostr discovery and runs even when Nostr is
|
||||||
disabled (`src/node/lifecycle.rs:1159-1162`). Startup requires an
|
disabled (`src/node/lifecycle/supervisor.rs:432-437`). Startup requires an
|
||||||
operational UDP transport: the node advertises the port of its
|
operational UDP transport: the node advertises the port of its
|
||||||
lowest-`TransportId` operational, non-bootstrap UDP transport, chosen
|
lowest-`TransportId` operational, non-bootstrap UDP transport, chosen
|
||||||
deterministically so the advertised port is stable across restarts
|
deterministically so the advertised port is stable across restarts
|
||||||
(`src/node/lifecycle.rs:1169-1180`). If no such port exists, the
|
(`src/node/lifecycle/mod.rs:1598-1609`). If no such port exists, the
|
||||||
runtime returns `NoAdvertisedPort` and LAN discovery does not start
|
runtime returns `NoAdvertisedPort` and LAN discovery does not start
|
||||||
(`src/discovery/lan/mod.rs:156-158`).
|
(`src/mdns/mod.rs:165-167`).
|
||||||
|
|
||||||
The runtime does two things concurrently:
|
The runtime does two things concurrently:
|
||||||
|
|
||||||
@@ -466,18 +467,18 @@ The runtime does two things concurrently:
|
|||||||
below. `mdns-sd`'s address auto-detection appends every non-loopback
|
below. `mdns-sd`'s address auto-detection appends every non-loopback
|
||||||
interface address, with `127.0.0.1` seeded so same-host peers and
|
interface address, with `127.0.0.1` seeded so same-host peers and
|
||||||
integration tests can still resolve the advert
|
integration tests can still resolve the advert
|
||||||
(`src/discovery/lan/mod.rs:182-203`).
|
(`src/mdns/mod.rs:179-212`).
|
||||||
2. **Browser.** A background pump receives `ServiceResolved` events for
|
2. **Browser.** A background pump receives `ServiceResolved` events for
|
||||||
the same service type. For each resolved advert it extracts the
|
the same service type. For each resolved advert it extracts the
|
||||||
`npub` and `scope` TXT values, drops adverts that echo the node's own
|
`npub` and `scope` TXT values, drops adverts that echo the node's own
|
||||||
npub, drops cross-scope adverts (see scope filtering), drops records
|
npub, drops cross-scope adverts (see scope filtering), drops records
|
||||||
without an `npub`, and surfaces one `LanDiscoveredPeer` per routable
|
without an `npub`, and surfaces one `LanDiscoveredPeer` per routable
|
||||||
interface address (`src/discovery/lan/mod.rs:212-299`). IPv6
|
interface address (`src/mdns/mod.rs:230-297`). IPv6
|
||||||
unicast link-local addresses without an interface scope id are
|
unicast link-local addresses without an interface scope id are
|
||||||
skipped, since they cannot be dialed unambiguously
|
skipped, since they cannot be dialed unambiguously
|
||||||
(`src/discovery/lan/mod.rs:348-365`).
|
(`src/mdns/mod.rs:357-370`).
|
||||||
|
|
||||||
The TXT record carries three keys (`src/discovery/lan/mod.rs:47-55`):
|
The TXT record carries three keys (`src/mdns/mod.rs:48-55`):
|
||||||
|
|
||||||
| TXT key | Contents |
|
| TXT key | Contents |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
@@ -486,8 +487,8 @@ The TXT record carries three keys (`src/discovery/lan/mod.rs:47-55`):
|
|||||||
| `v` | FIPS protocol version (the same `PROTOCOL_VERSION` used by the Nostr advert) |
|
| `v` | FIPS protocol version (the same `PROTOCOL_VERSION` used by the Nostr advert) |
|
||||||
|
|
||||||
Once per node tick, the node drains browser events and acts on them in
|
Once per node tick, the node drains browser events and acts on them in
|
||||||
`poll_lan_discovery()` (`src/node/lifecycle.rs:907`, called from
|
`poll_lan_rendezvous()` (`src/node/lifecycle/mod.rs:1131`, called from
|
||||||
`src/node/dataplane/rx_loop.rs:266`). For each discovered peer it finds
|
`src/node/dataplane/rx_loop.rs:444`). For each discovered peer it finds
|
||||||
a UDP transport whose family matches the peer address, parses the
|
a UDP transport whose family matches the peer address, parses the
|
||||||
`npub` into a `PeerIdentity`, skips peers it is already connected to or
|
`npub` into a `PeerIdentity`, skips peers it is already connected to or
|
||||||
currently connecting to, and otherwise initiates a connection.
|
currently connecting to, and otherwise initiates a connection.
|
||||||
@@ -495,10 +496,10 @@ currently connecting to, and otherwise initiates a connection.
|
|||||||
### Handshake: Noise IK
|
### Handshake: Noise IK
|
||||||
|
|
||||||
LAN-discovered peers are dialed through the standard FMP outbound link
|
LAN-discovered peers are dialed through the standard FMP outbound link
|
||||||
path. `poll_lan_discovery()` calls `initiate_connection()`
|
path. `poll_lan_rendezvous()` calls `initiate_connection()`
|
||||||
(`src/node/lifecycle.rs:380`), which, for connectionless transports
|
(`src/node/lifecycle/mod.rs:448`), which, for connectionless transports
|
||||||
such as UDP, allocates a link and **starts the Noise IK handshake**
|
such as UDP, allocates a link and **starts the Noise IK handshake**
|
||||||
(documented at `src/node/lifecycle.rs:373-374`). This is the same
|
(documented at `src/node/lifecycle/mod.rs:438-442`). This is the same
|
||||||
link-layer handshake used by every other FMP connection — IK at the
|
link-layer handshake used by every other FMP connection — IK at the
|
||||||
link layer per the FIPS architecture — not a different pattern for LAN
|
link layer per the FIPS architecture — not a different pattern for LAN
|
||||||
peers.
|
peers.
|
||||||
@@ -512,10 +513,10 @@ The mDNS advert is therefore a routing hint, never an identity
|
|||||||
assertion, exactly as a Nostr advert is treated (a successful contact
|
assertion, exactly as a Nostr advert is treated (a successful contact
|
||||||
is not trusted until FMP's Noise IK handshake completes).
|
is not trusted until FMP's Noise IK handshake completes).
|
||||||
|
|
||||||
> Note: a stale source doc-comment at `src/node/lifecycle.rs:904-906`
|
> Note: stale source doc-comments at `src/mdns/mod.rs:14, 76, 153`
|
||||||
> describes this path as a "Noise XX" handshake. That comment is
|
> describe this path as a "Noise XX" handshake. Those comments are
|
||||||
> inaccurate — the path uses Noise IK as described above. The comment
|
> inaccurate — the path uses Noise IK as described above. They are
|
||||||
> is flagged for a separate source fix and does not reflect actual
|
> flagged for a separate source fix and do not reflect actual
|
||||||
> behavior.
|
> behavior.
|
||||||
|
|
||||||
### Scope filtering
|
### Scope filtering
|
||||||
@@ -525,9 +526,9 @@ When a discovery scope is configured, the advert carries it in the
|
|||||||
carries a matching scope. Nodes on the same physical LAN but configured
|
carries a matching scope. Nodes on the same physical LAN but configured
|
||||||
for different mesh networks therefore do not cross-feed each other.
|
for different mesh networks therefore do not cross-feed each other.
|
||||||
|
|
||||||
The scope is resolved by `lan_discovery_scope()`
|
The scope is resolved by `lan_rendezvous_scope()`
|
||||||
(`src/node/lifecycle.rs:880-902`): the explicit
|
(`src/node/lifecycle/mod.rs:1104`): the explicit
|
||||||
`node.discovery.lan.scope`, if non-empty, is used directly. Otherwise
|
`node.rendezvous.lan.scope`, if non-empty, is used directly. Otherwise
|
||||||
the node falls back to deriving a scope from the Nostr discovery `app`
|
the node falls back to deriving a scope from the Nostr discovery `app`
|
||||||
tag (stripping the `fips-overlay-v1:` prefix when present). This lets
|
tag (stripping the `fips-overlay-v1:` prefix when present). This lets
|
||||||
an application keep its public, relay-visible Nostr `app` tag generic
|
an application keep its public, relay-visible Nostr `app` tag generic
|
||||||
@@ -537,14 +538,14 @@ adverts it sees on the link.
|
|||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
LAN discovery is configured under `node.discovery.lan.*`
|
LAN discovery is configured under `node.rendezvous.lan.*`
|
||||||
(`src/config/node.rs:222-227`, `src/discovery/lan/mod.rs:88-129`):
|
(`src/config/node.rs:334`, `src/mdns/mod.rs:92-114`):
|
||||||
|
|
||||||
| Key | Type | Default | Meaning |
|
| Key | Type | Default | Meaning |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `node.discovery.lan.enabled` | bool | `false` | Master switch. LAN discovery is opt-in; default-off avoids an unexpected per-link identity multicast on upgrade. |
|
| `node.rendezvous.lan.enabled` | bool | `false` | Master switch. LAN discovery is opt-in; default-off avoids an unexpected per-link identity multicast on upgrade. |
|
||||||
| `node.discovery.lan.service_type` | string | `_fips._udp.local.` | DNS-SD service type. Overridable mainly so integration tests can isolate multiple services on one loopback interface. |
|
| `node.rendezvous.lan.service_type` | string | `_fips._udp.local.` | DNS-SD service type. Overridable mainly so integration tests can isolate multiple services on one loopback interface. |
|
||||||
| `node.discovery.lan.scope` | string (optional) | unset | Application/network scope carried in the LAN-only `scope` TXT record. Kept deliberately separate from the public Nostr `app` tag. When unset, the scope falls back to the derived Nostr `app` value. |
|
| `node.rendezvous.lan.scope` | string (optional) | unset | Application/network scope carried in the LAN-only `scope` TXT record. Kept deliberately separate from the public Nostr `app` tag. When unset, the scope falls back to the derived Nostr `app` value. |
|
||||||
|
|
||||||
The identity surface published over mDNS (`npub`, version, optional
|
The identity surface published over mDNS (`npub`, version, optional
|
||||||
scope) is a strict subset of what `nostr.advertise` already publishes
|
scope) is a strict subset of what `nostr.advertise` already publishes
|
||||||
|
|||||||
@@ -170,8 +170,8 @@ bidirectional delivery ratios, was introduced by
|
|||||||
for wireless mesh routing and is used in protocols including
|
for wireless mesh routing and is used in protocols including
|
||||||
[OLSR](https://en.wikipedia.org/wiki/Optimized_Link_State_Routing_Protocol)
|
[OLSR](https://en.wikipedia.org/wiki/Optimized_Link_State_Routing_Protocol)
|
||||||
and [Babel](https://www.irif.fr/~jch/software/babel/). FIPS computes
|
and [Babel](https://www.irif.fr/~jch/software/babel/). FIPS computes
|
||||||
ETX per-link from MMP loss measurements for future use in candidate
|
ETX per-link from MMP loss measurements and uses it for cost-based
|
||||||
ranking.
|
parent selection and as the primary key in next-hop candidate ranking.
|
||||||
|
|
||||||
The CE (Congestion Experienced) echo flag provides hop-by-hop
|
The CE (Congestion Experienced) echo flag provides hop-by-hop
|
||||||
[ECN](https://en.wikipedia.org/wiki/Explicit_Congestion_Notification)
|
[ECN](https://en.wikipedia.org/wiki/Explicit_Congestion_Notification)
|
||||||
|
|||||||
@@ -52,10 +52,10 @@ determine how much payload can fit in a single packet after link-layer
|
|||||||
encryption overhead.
|
encryption overhead.
|
||||||
|
|
||||||
MTU is fundamentally a per-link property. A transport with a fixed MTU
|
MTU is fundamentally a per-link property. A transport with a fixed MTU
|
||||||
(Ethernet effective 1499, UDP default 1280) returns the same value for every
|
(Ethernet effective 1497, UDP default 1280) returns the same value for every
|
||||||
link — this is the degenerate case. Transports that negotiate MTU
|
link — this is the degenerate case. Transports that negotiate MTU
|
||||||
per-connection (e.g., BLE ATT_MTU) report the negotiated value for each
|
per-connection (e.g., the BLE L2CAP CoC MTU) report the negotiated value
|
||||||
link individually.
|
for each link individually.
|
||||||
|
|
||||||
The transport trait exposes two MTU methods:
|
The transport trait exposes two MTU methods:
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ media:
|
|||||||
| --------- | ---------- | --- | ----------- | ----- |
|
| --------- | ---------- | --- | ----------- | ----- |
|
||||||
| Ethernet | MAC | 1500 | Unreliable | Raw AF_PACKET frames |
|
| Ethernet | MAC | 1500 | Unreliable | Raw AF_PACKET frames |
|
||||||
| WiFi | MAC | 1500 | Unreliable | Infrastructure mode = Ethernet |
|
| WiFi | MAC | 1500 | Unreliable | Infrastructure mode = Ethernet |
|
||||||
| BLE | BD_ADDR | 23–517 | Reliable | Negotiated ATT_MTU |
|
| BLE | BD_ADDR | 2048 default | Reliable | Per-connection L2CAP CoC MTU |
|
||||||
| Radio | Device addr | 51–222 | Unreliable | Low bandwidth, long range |
|
| Radio | Device addr | 51–222 | Unreliable | Low bandwidth, long range |
|
||||||
|
|
||||||
**Point-to-point transports** connect exactly two endpoints:
|
**Point-to-point transports** connect exactly two endpoints:
|
||||||
@@ -192,7 +192,7 @@ proceed.
|
|||||||
| TCP/IP | TCP three-way handshake |
|
| TCP/IP | TCP three-way handshake |
|
||||||
| Tor | Circuit establishment (typically 10–60s, default timeout 120s) |
|
| Tor | Circuit establishment (typically 10–60s, default timeout 120s) |
|
||||||
| Nym | SOCKS5 connect through mixnet (minutes possible, default timeout 300s) |
|
| Nym | SOCKS5 connect through mixnet (minutes possible, default timeout 300s) |
|
||||||
| BLE | L2CAP CoC or GATT connection |
|
| BLE | L2CAP CoC connection |
|
||||||
| Serial | Physical connection (static) |
|
| Serial | Physical connection (static) |
|
||||||
|
|
||||||
### Implications
|
### Implications
|
||||||
@@ -256,8 +256,8 @@ in
|
|||||||
## Ethernet: The Local Network Transport
|
## Ethernet: The Local Network Transport
|
||||||
|
|
||||||
For nodes on the same LAN segment, raw Ethernet provides a direct transport
|
For nodes on the same LAN segment, raw Ethernet provides a direct transport
|
||||||
without IP/UDP overhead — 28 bytes more FIPS payload per frame compared to
|
without IP/UDP overhead — 25 bytes more FIPS payload per frame compared to
|
||||||
UDP (1500 vs 1472 MTU).
|
UDP (1497 vs 1472 MTU).
|
||||||
|
|
||||||
- **No IP dependency**: Operates below the IP layer. Nodes on the same
|
- **No IP dependency**: Operates below the IP layer. Nodes on the same
|
||||||
Ethernet segment can communicate without IP addresses or routing
|
Ethernet segment can communicate without IP addresses or routing
|
||||||
@@ -265,16 +265,17 @@ UDP (1500 vs 1472 MTU).
|
|||||||
- **Broadcast neighbor detection**: Nodes discover each other via periodic beacon
|
- **Broadcast neighbor detection**: Nodes discover each other via periodic beacon
|
||||||
broadcasts on the shared medium, with no static peer configuration required
|
broadcasts on the shared medium, with no static peer configuration required
|
||||||
- **Higher MTU**: Standard Ethernet frames carry 1500 bytes of payload,
|
- **Higher MTU**: Standard Ethernet frames carry 1500 bytes of payload,
|
||||||
yielding an effective FIPS MTU of 1499 after the frame type prefix
|
yielding an effective FIPS MTU of 1497 after the 3-byte frame header
|
||||||
- **Matches FIPS model**: Like UDP, Ethernet is connectionless and
|
- **Matches FIPS model**: Like UDP, Ethernet is connectionless and
|
||||||
unreliable — datagrams flow immediately to any MAC address on the segment
|
unreliable — datagrams flow immediately to any MAC address on the segment
|
||||||
|
|
||||||
### Implementation
|
### Implementation
|
||||||
|
|
||||||
The Ethernet transport uses Linux AF_PACKET sockets in SOCK_DGRAM mode with
|
The Ethernet transport uses Linux AF_PACKET sockets in SOCK_DGRAM mode with
|
||||||
EtherType 0x2121. SOCK_DGRAM mode
|
EtherType 0x2121, and BPF devices (`/dev/bpf*`) on macOS. SOCK_DGRAM mode
|
||||||
lets the kernel handle Ethernet header construction and parsing — the
|
lets the kernel handle Ethernet header construction and parsing — the
|
||||||
transport deals only with payloads and MAC addresses.
|
transport deals only with payloads and MAC addresses; the macOS BPF backend
|
||||||
|
presents the same API and handles the 14-byte Ethernet header itself.
|
||||||
|
|
||||||
Data frames use a 3-byte header: a 1-byte frame type (`0x00`) followed by
|
Data frames use a 3-byte header: a 1-byte frame type (`0x00`) followed by
|
||||||
a 2-byte little-endian payload length. The length field allows the receiver
|
a 2-byte little-endian payload length. The length field allows the receiver
|
||||||
@@ -291,7 +292,7 @@ socket.
|
|||||||
| Beacon frame header | `[type:1][payload]` (fixed 34 bytes) |
|
| Beacon frame header | `[type:1][payload]` (fixed 34 bytes) |
|
||||||
| Effective MTU | Interface MTU - 3 (typically 1497) |
|
| Effective MTU | Interface MTU - 3 (typically 1497) |
|
||||||
| Addressing | 6-byte MAC address |
|
| Addressing | 6-byte MAC address |
|
||||||
| Platform | Linux only (`CAP_NET_RAW` required) |
|
| Platform | Linux (AF_PACKET, `CAP_NET_RAW` required) and macOS (BPF `/dev/bpf*`) |
|
||||||
|
|
||||||
### Neighbor Beacons
|
### Neighbor Beacons
|
||||||
|
|
||||||
@@ -322,7 +323,7 @@ access points commonly isolate clients from each other's broadcast traffic.
|
|||||||
Startup logging:
|
Startup logging:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Ethernet transport started name=eth0 interface=eth0 mac=aa:bb:cc:dd:ee:ff mtu=1499 if_mtu=1500
|
Ethernet transport started name=eth0 interface=eth0 mac=aa:bb:cc:dd:ee:ff mtu=1497 if_mtu=1500
|
||||||
```
|
```
|
||||||
|
|
||||||
## TCP/IP: Transport for UDP-Filtered Networks
|
## TCP/IP: Transport for UDP-Filtered Networks
|
||||||
@@ -715,6 +716,123 @@ The Nym transport exposes per-instance counters covering successful
|
|||||||
send/receive, send/receive errors, connection establishment, SOCKS5-level
|
send/receive, send/receive errors, connection establishment, SOCKS5-level
|
||||||
errors, connect timeouts, and MTU rejections.
|
errors, connect timeouts, and MTU rejections.
|
||||||
|
|
||||||
|
## BLE: The Local Radio Transport
|
||||||
|
|
||||||
|
The BLE transport peers two nodes over Bluetooth Low Energy with no IP
|
||||||
|
network between them, using an L2CAP connection-oriented channel as the
|
||||||
|
byte pipe. It is the only transport whose reach is a radio horizon
|
||||||
|
rather than a route, which makes it the fallback when there is no
|
||||||
|
infrastructure at all: two phones in a room, a node and a handset, a
|
||||||
|
mesh with its uplink cut.
|
||||||
|
|
||||||
|
Like TCP, Tor and Nym it is connection-oriented and reliable, so the
|
||||||
|
same TCP-over-TCP considerations apply. Unlike them, its peer set is
|
||||||
|
discovered rather than configured, and the addresses it discovers are
|
||||||
|
not stable.
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
|
||||||
|
Nothing above the radio has a platform dependency. `BleTransport<I>` is
|
||||||
|
generic over a `BleIo` seam (`ble/io.rs`) that covers listening,
|
||||||
|
connecting, advertising, scanning and the stream I/O itself; the
|
||||||
|
connection pool, the PSM wire format, the stream framer and the
|
||||||
|
scan/probe loop are shared by every backend.
|
||||||
|
|
||||||
|
The backends live one per file and are selected by a three-way cascade
|
||||||
|
in `ble/mod.rs`: `BluerIo` (`io_linux.rs`) talks to BlueZ over D-Bus,
|
||||||
|
`AndroidIo` (`io_android.rs`) drives a radio the embedding application
|
||||||
|
installs, and `MockBleIo` (`io.rs`) is an in-memory double compiled only
|
||||||
|
under `cfg(test)`. A build that matches none of the three fails with a
|
||||||
|
`compile_error!` rather than silently selecting the mock.
|
||||||
|
|
||||||
|
That failure is deliberate. An earlier arrangement wrote the mock arm as
|
||||||
|
"anything that is not BlueZ", which meant a new platform got a transport
|
||||||
|
that compiled, started, reported itself Up and never peered, with no
|
||||||
|
error anywhere to find it.
|
||||||
|
|
||||||
|
### Backend Availability
|
||||||
|
|
||||||
|
`build.rs` sets `ble_available` for glibc Linux or Android, which is the
|
||||||
|
set of platforms with a concrete backend rather than the set that could
|
||||||
|
plausibly have Bluetooth. `bluer_available`, the BlueZ sub-condition, is
|
||||||
|
glibc Linux alone: musl cannot satisfy `libdbus-sys`'s pkg-config
|
||||||
|
cross-compile requirement, and musl router targets do not run BlueZ by
|
||||||
|
default. macOS, FreeBSD and Windows have no backend and so have no BLE
|
||||||
|
transport at all.
|
||||||
|
|
||||||
|
On glibc Linux the build needs `libdbus-1-dev` and `pkg-config`; the
|
||||||
|
BlueZ daemon itself is a runtime dependency. On Android the radio is
|
||||||
|
supplied by the application: scanning, advertising, L2CAP listen and
|
||||||
|
connect all sit behind Java APIs held under a permission and
|
||||||
|
foreground-service model that only the app can satisfy, so the embedder
|
||||||
|
implements `AndroidRadio` and installs it into a per-node slot which the
|
||||||
|
backend resolves per operation.
|
||||||
|
|
||||||
|
### Framing
|
||||||
|
|
||||||
|
The channel is L2CAP CoC, not GATT, so there is no ATT_MTU to negotiate.
|
||||||
|
The per-connection CoC MTU applies, defaulting to 2048, and it overrides
|
||||||
|
the transport-wide default per link.
|
||||||
|
|
||||||
|
Packet boundaries are recovered from the byte stream rather than assumed
|
||||||
|
from the socket. BlueZ's `SOCK_SEQPACKET` preserves SDU boundaries, but
|
||||||
|
that is a property of one backend's socket type and not of L2CAP:
|
||||||
|
Android's `BluetoothSocket` input stream and macOS's `CBL2CAPChannel`
|
||||||
|
may return a fragment of a packet or several packets coalesced in one
|
||||||
|
read. FIPS packets are self-delimiting through the 4-byte FMP common
|
||||||
|
prefix, so `stream_read.rs` adapts the datagram-shaped stream into the
|
||||||
|
`AsyncRead` that `transport::framing::read_fmp_packet` already expects,
|
||||||
|
shared with every other stream-oriented transport.
|
||||||
|
|
||||||
|
### Discovery and the PSM
|
||||||
|
|
||||||
|
Discovery is an LE advertisement, received passively, carrying the
|
||||||
|
128-bit FIPS service UUID plus the listener's L2CAP PSM as service data.
|
||||||
|
|
||||||
|
The PSM has to ride the advertisement because it is not knowable any
|
||||||
|
other way. BlueZ lets an application choose the PSM it binds, and BlueZ
|
||||||
|
is the exception: Android's `listenUsingInsecureL2capChannel` and
|
||||||
|
macOS's `CBPeripheralManager.publishL2CAPChannel` both return an
|
||||||
|
OS-assigned PSM the application cannot request. A dialer cannot guess
|
||||||
|
it, and before a connection exists there is no channel on which to be
|
||||||
|
told. So `BleIo::listen` reports the PSM it actually bound,
|
||||||
|
`start_advertising` takes that PSM, and the scanner yields it alongside
|
||||||
|
the address.
|
||||||
|
|
||||||
|
The wire layout is fixed by a byte budget and specified in `ble/psm.rs`.
|
||||||
|
A legacy advertising PDU carries 31 bytes of AD payload. Flags take 3
|
||||||
|
and the 128-bit service UUID list takes 18, so keying the service data
|
||||||
|
on the full 128-bit UUID would need 20 more and overrun by 10. Keying it
|
||||||
|
on the 16-bit UUID `0x9C90`, which is the leading 16 bits of the FIPS
|
||||||
|
service UUID expanded through the Bluetooth base UUID, takes 6 and fits
|
||||||
|
at 27. The budget is asserted at compile time. It leaves no room for a
|
||||||
|
local name, and it must ride the primary advertisement rather than the
|
||||||
|
scan response, because a scan response arrives only after an active-scan
|
||||||
|
round trip that drops asymmetrically across chipsets.
|
||||||
|
|
||||||
|
### Connection Establishment
|
||||||
|
|
||||||
|
A scan/probe loop dials discovered addresses, keeping the learned PSM
|
||||||
|
per address beside a probe-cooldown book and falling back to the
|
||||||
|
configured `DEFAULT_PSM` for a peer that advertises none.
|
||||||
|
|
||||||
|
Peers are identified by node address, not by link address. A device
|
||||||
|
using resolvable private addresses rotates continually, and modern
|
||||||
|
phones do so by default, so an address-keyed pool sees every rotation as
|
||||||
|
a new device and every already-connected guard fails to fire.
|
||||||
|
|
||||||
|
Failing addresses back off by powers of two up to
|
||||||
|
`MAX_PROBE_BACKOFF_SHIFT`, and the retry book is capped at
|
||||||
|
`MAX_PENDING_PROBES` so that rotating addresses cannot grow it without
|
||||||
|
bound. Both bounds matter more here than on other transports because BLE
|
||||||
|
hardware caps concurrent connections at roughly four to ten, so a
|
||||||
|
handful of unreachable addresses can starve discovery of everything
|
||||||
|
behind them.
|
||||||
|
|
||||||
|
Inbound connections are admitted off the accept loop, with
|
||||||
|
`INBOUND_HANDSHAKE_INFLIGHT` handshakes allowed at once and the oldest
|
||||||
|
aborted at the bound rather than the loop waiting for a slot.
|
||||||
|
|
||||||
## Discovery
|
## Discovery
|
||||||
|
|
||||||
Discovery determines that a FIPS-capable endpoint is reachable at a given
|
Discovery determines that a FIPS-capable endpoint is reachable at a given
|
||||||
@@ -747,7 +865,7 @@ X." FMP does not need to distinguish beacons from query responses.
|
|||||||
| UDP (LAN) | Broadcast/multicast | On local network segment |
|
| UDP (LAN) | Broadcast/multicast | On local network segment |
|
||||||
| Ethernet | Broadcast | Custom EtherType, ff:ff:ff:ff:ff:ff |
|
| Ethernet | Broadcast | Custom EtherType, ff:ff:ff:ff:ff:ff |
|
||||||
| Radio | Beacon | Shared RF channel, natural fit |
|
| Radio | Beacon | Shared RF channel, natural fit |
|
||||||
| BLE | Advertising | GATT service UUID |
|
| BLE | Advertising | LE advertisement: 128-bit FIPS service UUID plus service-data PSM |
|
||||||
|
|
||||||
### Nostr Relay Discovery
|
### Nostr Relay Discovery
|
||||||
|
|
||||||
@@ -778,16 +896,19 @@ Key properties:
|
|||||||
|
|
||||||
### Current State
|
### Current State
|
||||||
|
|
||||||
> **Implemented**: UDP, TCP, Tor, and Ethernet peers can be configured
|
> **Implemented**: UDP, TCP, Tor, Ethernet, and BLE peers can be configured
|
||||||
> statically via YAML. Ethernet peers can also be discovered via beacon
|
> statically via YAML. Ethernet peers can also be discovered via beacon
|
||||||
> broadcast — the `discover()` trait method returns newly seen endpoints,
|
> broadcast and BLE peers via LE scanning — the `discover()` trait method
|
||||||
> and per-transport `auto_connect()` / `accept_connections()` policies
|
> returns newly seen endpoints, and per-transport `auto_connect()` /
|
||||||
> control whether discovered peers are connected automatically or require
|
> `accept_connections()` policies control whether discovered peers are
|
||||||
> explicit configuration. TCP and Tor have no built-in discovery mechanism.
|
> connected automatically or require explicit configuration. TCP and Tor
|
||||||
|
> have no built-in discovery mechanism.
|
||||||
> Nostr relay discovery and STUN-assisted UDP hole punching are
|
> Nostr relay discovery and STUN-assisted UDP hole punching are
|
||||||
> implemented and toggled via configuration; see
|
> implemented and toggled via configuration; see
|
||||||
> [../reference/configuration.md](../reference/configuration.md) for the
|
> [../reference/configuration.md](../reference/configuration.md) for the
|
||||||
> `node.discovery.nostr.*` configuration tree.
|
> `node.rendezvous.nostr.*` configuration tree. LAN/mDNS peer rendezvous
|
||||||
|
> is implemented as a separate subsystem and documented in
|
||||||
|
> [fips-nostr-discovery.md](fips-nostr-discovery.md).
|
||||||
|
|
||||||
## Transport Interface
|
## Transport Interface
|
||||||
|
|
||||||
@@ -895,11 +1016,11 @@ transitions through `Starting` to `Up` (operational). `stop()` moves to
|
|||||||
| --------- | ------ | ----- |
|
| --------- | ------ | ----- |
|
||||||
| UDP/IP | **Implemented** | Primary transport, AsyncFd/recvmsg, SO_RXQ_OVFL kernel drop detection |
|
| UDP/IP | **Implemented** | Primary transport, AsyncFd/recvmsg, SO_RXQ_OVFL kernel drop detection |
|
||||||
| TCP/IP | **Implemented** | FMP header-based framing, non-blocking connect, per-connection MSS MTU |
|
| TCP/IP | **Implemented** | FMP header-based framing, non-blocking connect, per-connection MSS MTU |
|
||||||
| Ethernet | **Implemented** | AF_PACKET SOCK_DGRAM, EtherType 0x2121, neighbor beacons, Linux only |
|
| Ethernet | **Implemented** | AF_PACKET SOCK_DGRAM, EtherType 0x2121, neighbor beacons; Linux (AF_PACKET) and macOS (BPF) |
|
||||||
| WiFi | **Implemented** (via Ethernet transport, infrastructure mode) | mac80211 translates 802.11↔802.3; broadcast beacons unreliable through APs |
|
| WiFi | **Implemented** (via Ethernet transport, infrastructure mode) | mac80211 translates 802.11↔802.3; broadcast beacons unreliable through APs |
|
||||||
| Tor | **Implemented** | Outbound SOCKS5, inbound via onion service, .onion and clearnet addressing |
|
| Tor | **Implemented** | Outbound SOCKS5, inbound via onion service, .onion and clearnet addressing |
|
||||||
| Nym | **Implemented** | Outbound-only SOCKS5 through nym-socks5-client, mixnet anonymity, IP/hostname addressing |
|
| Nym | **Implemented** | Outbound-only SOCKS5 through nym-socks5-client, mixnet anonymity, IP/hostname addressing |
|
||||||
| BLE | **Implemented** (Linux/glibc only; experimental) | L2CAP CoC, ATT_MTU negotiation, per-link MTU; musl/macOS/Windows skip |
|
| BLE | **Implemented** (glibc Linux and Android; experimental) | L2CAP CoC, per-connection MTU (2048 default), per-link MTU; musl, macOS, FreeBSD and Windows have no backend |
|
||||||
| Radio | Future direction | Constrained MTU (51–222 bytes) |
|
| Radio | Future direction | Constrained MTU (51–222 bytes) |
|
||||||
| Serial | Future direction | SLIP/COBS framing, point-to-point |
|
| Serial | Future direction | SLIP/COBS framing, point-to-point |
|
||||||
|
|
||||||
@@ -937,8 +1058,9 @@ quality. The spanning tree parent selection factors in link quality through
|
|||||||
cost-based effective depth (`effective_depth = depth + link_cost`), where
|
cost-based effective depth (`effective_depth = depth + link_cost`), where
|
||||||
`link_cost` is derived from locally measured MMP metrics (ETX and SRTT).
|
`link_cost` is derived from locally measured MMP metrics (ETX and SRTT).
|
||||||
This allows the tree to prefer lower-latency, lower-loss links when the
|
This allows the tree to prefer lower-latency, lower-loss links when the
|
||||||
quality difference is significant. Link cost is not yet used in
|
quality difference is significant. Link cost is also the primary key in
|
||||||
`find_next_hop()` candidate ranking for data forwarding.
|
`find_next_hop()` candidate ranking for data forwarding, which orders
|
||||||
|
candidates by `(link_cost, distance_to_dest, node_addr)`.
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
|
|||||||
@@ -146,15 +146,18 @@ separate `protocol` tag, scoping adverts within a relay set
|
|||||||
without splitting them across multiple `d`-tag streams.
|
without splitting them across multiple `d`-tag streams.
|
||||||
|
|
||||||
The advert content is a JSON document carrying a list of endpoint
|
The advert content is a JSON document carrying a list of endpoint
|
||||||
entries, each shaped as `{transport, addr}`. The transport string
|
entries, each shaped as `{transport, addr}`. The `transport` field
|
||||||
takes one of:
|
takes one of `udp`, `tcp` or `tor`, and `addr` carries the rest:
|
||||||
|
|
||||||
- `udp:host:port` — direct public UDP endpoint.
|
- `{"transport": "udp", "addr": "host:port"}` — direct public UDP
|
||||||
- `udp:nat` — NAT-mapped UDP endpoint; reach via Part 2 traversal.
|
endpoint.
|
||||||
- `tcp:host:port` — direct public TCP endpoint, for peers whose
|
- `{"transport": "udp", "addr": "nat"}` — NAT-mapped UDP endpoint;
|
||||||
networks filter outbound UDP. Public-only; there is no
|
reach via Part 2 traversal.
|
||||||
`tcp:nat` analogue.
|
- `{"transport": "tcp", "addr": "host:port"}` — direct public TCP
|
||||||
- `tor:<onion>:<port>` — Tor onion-service endpoint.
|
endpoint, for peers whose networks filter outbound UDP. Public-only;
|
||||||
|
there is no NAT analogue.
|
||||||
|
- `{"transport": "tor", "addr": "<onion>:<port>"}` — Tor onion-service
|
||||||
|
endpoint.
|
||||||
|
|
||||||
FIPS publishes the advert with `expiration` set to `now +
|
FIPS publishes the advert with `expiration` set to `now +
|
||||||
advert_ttl_secs` (default 1 hour) and refreshes it every
|
advert_ttl_secs` (default 1 hour) and refreshes it every
|
||||||
@@ -575,7 +578,7 @@ loop:
|
|||||||
|
|
||||||
These knobs are documented in
|
These knobs are documented in
|
||||||
[FIPS configuration reference](https://github.com/jmcorgan/fips/blob/master/docs/reference/configuration.md)
|
[FIPS configuration reference](https://github.com/jmcorgan/fips/blob/master/docs/reference/configuration.md)
|
||||||
under `node.discovery.nostr`.
|
under `node.rendezvous.nostr`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+82
-23
@@ -27,8 +27,9 @@ running nodes can mesh.
|
|||||||
|
|
||||||
## What you'll need
|
## What you'll need
|
||||||
|
|
||||||
- A Linux, macOS, or Windows host. Linux is the most exercised
|
- A Linux, macOS, FreeBSD, or Windows host. Linux is the most
|
||||||
platform; macOS and Windows installers are available.
|
exercised platform; macOS, FreeBSD, and Windows installers are
|
||||||
|
available. The FreeBSD package is built for **x86_64 only**.
|
||||||
- The pre-built installer for your platform (see the project
|
- The pre-built installer for your platform (see the project
|
||||||
README's [Quick start](../README.md#quick-start) section for
|
README's [Quick start](../README.md#quick-start) section for
|
||||||
download links), **or** a source checkout if you want to build
|
download links), **or** a source checkout if you want to build
|
||||||
@@ -42,10 +43,10 @@ running nodes can mesh.
|
|||||||
|
|
||||||
FIPS is installed by running a binary installer for your
|
FIPS is installed by running a binary installer for your
|
||||||
platform. The installer drops the daemon and CLI tools into
|
platform. The installer drops the daemon and CLI tools into
|
||||||
system locations, installs systemd / launchd / Windows-service
|
system locations, installs systemd / launchd / rc.d /
|
||||||
unit files, places a default `fips.yaml`, and creates the `fips`
|
Windows-service unit files, places a default `fips.yaml`, and
|
||||||
system group. There is no `cargo install` path: the daemon needs
|
creates the `fips` system group. There is no `cargo install`
|
||||||
more than just binaries copied into place.
|
path: the daemon needs more than just binaries copied into place.
|
||||||
|
|
||||||
You can either build the installer yourself from source, or
|
You can either build the installer yourself from source, or
|
||||||
download a pre-built one from the release distribution. Both
|
download a pre-built one from the release distribution. Both
|
||||||
@@ -57,16 +58,62 @@ post-install state.
|
|||||||
The most direct path. The release distribution carries a
|
The most direct path. The release distribution carries a
|
||||||
per-platform installer:
|
per-platform installer:
|
||||||
|
|
||||||
- Debian/Ubuntu — `.deb` package
|
- Debian/Ubuntu: `.deb` package
|
||||||
- Arch Linux — `fips` AUR package
|
- Arch Linux: `fips` AUR package
|
||||||
- OpenWrt — `.ipk` package
|
- OpenWrt: `.ipk` and `.apk` packages
|
||||||
- macOS — `.pkg` installer
|
- macOS: `.pkg` installer
|
||||||
- Windows — `.zip` with service-install scripts
|
- FreeBSD: native `.pkg` (x86_64 only)
|
||||||
- Generic systemd Linux — `.tar.gz` with an `install.sh` script
|
- Windows: `.zip` with service-install scripts
|
||||||
|
- Generic systemd Linux: `.tar.gz` with an `install.sh` script
|
||||||
|
|
||||||
See the [project README's Quick start section](../README.md#quick-start)
|
See the [project README's Quick start section](../README.md#quick-start)
|
||||||
for download links and per-platform invocations.
|
for download links and per-platform invocations.
|
||||||
|
|
||||||
|
### FreeBSD
|
||||||
|
|
||||||
|
FreeBSD gets a native package built from `packaging/freebsd/`. It
|
||||||
|
ships `fips`, `fipsctl`, `fipstop`, the `fips` and `fips_dns` rc.d
|
||||||
|
services, and `.fips` DNS integration. `fips-gateway` is **not**
|
||||||
|
included: its NAT backend is nftables, which is Linux-only. The
|
||||||
|
Ethernet and BLE transports are unavailable on FreeBSD; UDP, TCP,
|
||||||
|
Tor, and Nym are.
|
||||||
|
|
||||||
|
**One architecture.** The published artifact is
|
||||||
|
`fips-<version>-freebsd-amd64.pkg`. There is no aarch64 FreeBSD
|
||||||
|
build, so on any other architecture use the from-source path below.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pkg add ./fips-<version>-freebsd-amd64.pkg
|
||||||
|
cp /usr/local/etc/fips/fips.yaml.sample /usr/local/etc/fips/fips.yaml
|
||||||
|
sysrc fips_enable=YES fips_dns_enable=YES
|
||||||
|
service fips start
|
||||||
|
service fips_dns start
|
||||||
|
fipsctl show status
|
||||||
|
```
|
||||||
|
|
||||||
|
FreeBSD differs from the Linux layout in three places worth knowing
|
||||||
|
before you go looking for files:
|
||||||
|
|
||||||
|
- Config lives at `/usr/local/etc/fips/fips.yaml`, not `/etc/fips/`.
|
||||||
|
It installs with sample semantics and mode `0600`, so an edited
|
||||||
|
file survives `pkg upgrade` and `pkg delete`, and a `nsec:` in it
|
||||||
|
is not world-readable.
|
||||||
|
- The daemon runs under `daemon(8)` with pidfile
|
||||||
|
`/var/run/fips/fips.pid` and logs to `/var/log/fips.log`. The
|
||||||
|
rc.conf knobs are `fips_config`, `fips_flags`, and
|
||||||
|
`fips_logfile`.
|
||||||
|
- The control socket resolves to `/var/run/fips/control.sock`. As on
|
||||||
|
Linux, a `fips` group is created and its members can run `fipsctl`
|
||||||
|
and `fipstop` without root (`pw groupmod fips -m <user>`, then
|
||||||
|
re-login).
|
||||||
|
|
||||||
|
Making the local resolver the *system* resolver is a one-time
|
||||||
|
operator step the package deliberately does not take, and there are
|
||||||
|
field-tested caveats around unbound upstreams and `/etc/resolv.conf`.
|
||||||
|
Both are covered in the FreeBSD section of
|
||||||
|
[packaging/README.md](../packaging/README.md) and in
|
||||||
|
`packaging/freebsd/README.md`.
|
||||||
|
|
||||||
### From source
|
### From source
|
||||||
|
|
||||||
For development, custom builds, or unsupported architectures.
|
For development, custom builds, or unsupported architectures.
|
||||||
@@ -77,7 +124,7 @@ downloaded one.
|
|||||||
```sh
|
```sh
|
||||||
git clone https://github.com/jmcorgan/fips.git
|
git clone https://github.com/jmcorgan/fips.git
|
||||||
cd fips/packaging
|
cd fips/packaging
|
||||||
make deb # or: tarball, ipk, aur, pkg, zip, all
|
make deb # or: tarball, ipk, apk, aur, pkg, freebsd, zip, all
|
||||||
```
|
```
|
||||||
|
|
||||||
The resulting installer lands in `deploy/` at the project root.
|
The resulting installer lands in `deploy/` at the project root.
|
||||||
@@ -102,8 +149,10 @@ nix develop # dev shell with the toolchain + build deps
|
|||||||
This path produces binaries only — it does not run the installer, so
|
This path produces binaries only — it does not run the installer, so
|
||||||
there are no systemd units, no `fips` group, and no default `fips.yaml`.
|
there are no systemd units, no `fips` group, and no default `fips.yaml`.
|
||||||
On NixOS, wire the daemon in through your system configuration using the
|
On NixOS, wire the daemon in through your system configuration using the
|
||||||
flake's `packages.<system>.fips` output instead. See the Nix / NixOS
|
flake's `nixosModules.default` output instead: import it and set
|
||||||
section of [packaging/README.md](../packaging/README.md).
|
`services.fips.enable = true`. See
|
||||||
|
[packaging/nixos/README.md](../packaging/nixos/README.md) and the Nix /
|
||||||
|
NixOS section of [packaging/README.md](../packaging/README.md).
|
||||||
|
|
||||||
## What's installed and running
|
## What's installed and running
|
||||||
|
|
||||||
@@ -115,18 +164,20 @@ running, and what you'll need to set up yourself.
|
|||||||
- `fips` (daemon)
|
- `fips` (daemon)
|
||||||
- `fipsctl` (control-socket client)
|
- `fipsctl` (control-socket client)
|
||||||
- `fipstop` (live-status TUI)
|
- `fipstop` (live-status TUI)
|
||||||
- `fips-gateway`
|
- `fips-gateway` (Linux only)
|
||||||
|
|
||||||
**Files placed on disk:**
|
**Files placed on disk:**
|
||||||
|
|
||||||
- `/etc/fips/fips.yaml` — default daemon config (preserved on
|
- `/etc/fips/fips.yaml` — default daemon config (preserved on
|
||||||
upgrade).
|
upgrade). On macOS and FreeBSD this is
|
||||||
|
`/usr/local/etc/fips/fips.yaml`.
|
||||||
- `/etc/fips/fips.nft` — mesh-interface nftables baseline (used
|
- `/etc/fips/fips.nft` — mesh-interface nftables baseline (used
|
||||||
only when the firewall service is enabled).
|
only when the firewall service is enabled). Linux only.
|
||||||
- `/etc/fips/fips.d/` — empty drop-in directory for operator
|
- `/etc/fips/fips.d/` — empty drop-in directory for operator
|
||||||
nftables additions.
|
nftables additions. Linux only.
|
||||||
- Systemd, launchd, or Windows-service unit files for the four
|
- Systemd, launchd, rc.d, or Windows-service unit files for the
|
||||||
fips services.
|
fips services. FreeBSD installs `fips` and `fips_dns` only, since
|
||||||
|
`fips-gateway` and the nftables firewall service are Linux-only.
|
||||||
|
|
||||||
**System changes:**
|
**System changes:**
|
||||||
|
|
||||||
@@ -136,7 +187,7 @@ running, and what you'll need to set up yourself.
|
|||||||
- The runtime directory `/run/fips/` exists with mode
|
- The runtime directory `/run/fips/` exists with mode
|
||||||
`0750 root:fips`.
|
`0750 root:fips`.
|
||||||
|
|
||||||
**Services enabled and started on boot:**
|
**Services enabled at install, and started on the next boot:**
|
||||||
|
|
||||||
- `fips.service` — the daemon. Brings up the `fips0` TUN
|
- `fips.service` — the daemon. Brings up the `fips0` TUN
|
||||||
adapter, listens on the configured transports, and exposes
|
adapter, listens on the configured transports, and exposes
|
||||||
@@ -145,13 +196,21 @@ running, and what you'll need to set up yourself.
|
|||||||
the host resolver (a `/etc/systemd/resolved.conf.d/` drop-in
|
the host resolver (a `/etc/systemd/resolved.conf.d/` drop-in
|
||||||
pointing at `[::1]:5354` on systemd hosts).
|
pointing at `[::1]:5354` on systemd hosts).
|
||||||
|
|
||||||
|
The Debian package enables both and starts neither, so a fresh install
|
||||||
|
leaves them stopped. Start them yourself rather than waiting for a
|
||||||
|
reboot:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl start fips fips-dns
|
||||||
|
```
|
||||||
|
|
||||||
**Services installed but not enabled** (operator opt-in):
|
**Services installed but not enabled** (operator opt-in):
|
||||||
|
|
||||||
- `fips-firewall.service` — applies `/etc/fips/fips.nft` to
|
- `fips-firewall.service` — applies `/etc/fips/fips.nft` to
|
||||||
the mesh interface. See
|
the mesh interface. See
|
||||||
[how-to/enable-mesh-firewall.md](how-to/enable-mesh-firewall.md).
|
[how-to/enable-mesh-firewall.md](how-to/enable-mesh-firewall.md).
|
||||||
|
|
||||||
**What's working out of the box:**
|
**What's working once both services are running:**
|
||||||
|
|
||||||
- The daemon is running with a fresh **ephemeral** identity —
|
- The daemon is running with a fresh **ephemeral** identity —
|
||||||
a new Nostr keypair is generated on every start.
|
a new Nostr keypair is generated on every start.
|
||||||
|
|||||||
@@ -138,8 +138,9 @@ gateway:
|
|||||||
|
|
||||||
Pick a pool CIDR that does **not** overlap with any address space in
|
Pick a pool CIDR that does **not** overlap with any address space in
|
||||||
use on the LAN or in the mesh (the FIPS mesh occupies `fd00::/8`;
|
use on the LAN or in the mesh (the FIPS mesh occupies `fd00::/8`;
|
||||||
pick a different `fdXX::/N`). The `/112` size yields 65 536 virtual
|
pick a different `fdXX::/N`). The `/112` size yields 65 535 usable
|
||||||
IPs, which is the gateway's hard cap regardless of CIDR width.
|
virtual IPs, which is the gateway's hard cap regardless of CIDR
|
||||||
|
width.
|
||||||
|
|
||||||
This minimum config is enough to start the gateway. The `dns.*` block
|
This minimum config is enough to start the gateway. The `dns.*` block
|
||||||
is optional and defaults to `listen: "[::1]:5353"` and
|
is optional and defaults to `listen: "[::1]:5353"` and
|
||||||
@@ -186,7 +187,7 @@ Constraints:
|
|||||||
- Must not overlap with `fd00::/8` (the FIPS mesh address space).
|
- Must not overlap with `fd00::/8` (the FIPS mesh address space).
|
||||||
- Must not overlap with any LAN-side IPv6 prefix already in use.
|
- Must not overlap with any LAN-side IPv6 prefix already in use.
|
||||||
- `/112` is the practical width — wider just wastes address space
|
- `/112` is the practical width — wider just wastes address space
|
||||||
because the pool is hard-capped at 65 536 entries. Narrower is
|
because the pool is hard-capped at 65 535 usable entries. Narrower is
|
||||||
fine if you want a smaller pool, but you'll reject DNS lookups
|
fine if you want a smaller pool, but you'll reject DNS lookups
|
||||||
faster under churn.
|
faster under churn.
|
||||||
|
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ shows the new peer with `transport=tor`.
|
|||||||
|
|
||||||
## Optional: advertise the onion endpoint via Nostr discovery
|
## Optional: advertise the onion endpoint via Nostr discovery
|
||||||
|
|
||||||
If `node.discovery.nostr.enabled: true`, set
|
If `node.rendezvous.nostr.enabled: true`, set
|
||||||
`transports.tor.advertise_on_nostr: true` so the onion endpoint
|
`transports.tor.advertise_on_nostr: true` so the onion endpoint
|
||||||
appears in this node's published advert. See
|
appears in this node's published advert. See
|
||||||
[enable-nostr-discovery.md](enable-nostr-discovery.md) Scenario 2.
|
[enable-nostr-discovery.md](enable-nostr-discovery.md) Scenario 2.
|
||||||
|
|||||||
@@ -78,7 +78,8 @@ ip6 saddr fd97:1234:5678:9abc:def0:1234:5678:9abc tcp dport 22 accept
|
|||||||
|
|
||||||
The source filter is the node's mesh address. To find a node's mesh
|
The source filter is the node's mesh address. To find a node's mesh
|
||||||
address, look in their `fips.pub` (which contains the npub) and derive
|
address, look in their `fips.pub` (which contains the npub) and derive
|
||||||
the `fd97:...` address from it, or query the running daemon:
|
the address from it (only the leading `fd` byte is fixed; the other 15
|
||||||
|
come from the node's key), or query the running daemon:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
fipsctl show identity-cache
|
fipsctl show identity-cache
|
||||||
|
|||||||
@@ -4,13 +4,18 @@ Nostr-mediated discovery lets FIPS nodes find each other (and punch
|
|||||||
through UDP NAT) using public Nostr relays as the signaling channel.
|
through UDP NAT) using public Nostr relays as the signaling channel.
|
||||||
The feature ships in every stock packaging artifact but is **off by
|
The feature ships in every stock packaging artifact but is **off by
|
||||||
default** — it activates when an operator sets
|
default** — it activates when an operator sets
|
||||||
`node.discovery.nostr.enabled: true`. Default relay and STUN-server
|
`node.rendezvous.nostr.enabled: true`. Default relay and STUN-server
|
||||||
lists ship in the config; both are optional overrides. See
|
lists ship in the config; both are optional overrides. See
|
||||||
[../design/fips-nostr-discovery.md](../design/fips-nostr-discovery.md)
|
[../design/fips-nostr-discovery.md](../design/fips-nostr-discovery.md)
|
||||||
for the design and rationale; see
|
for the design and rationale; see
|
||||||
[../reference/configuration.md](../reference/configuration.md) for the
|
[../reference/configuration.md](../reference/configuration.md) for the
|
||||||
full knob inventory.
|
full knob inventory.
|
||||||
|
|
||||||
|
The table was called `node.discovery` before v0.5.0; that spelling still
|
||||||
|
parses and logs one deprecation warning naming the move, so an existing
|
||||||
|
config keeps working (see
|
||||||
|
[../reference/configuration.md](../reference/configuration.md)).
|
||||||
|
|
||||||
Nostr discovery provides three independent capabilities. They can be
|
Nostr discovery provides three independent capabilities. They can be
|
||||||
enabled separately; most deployments end up using two or three of
|
enabled separately; most deployments end up using two or three of
|
||||||
them together.
|
them together.
|
||||||
@@ -57,7 +62,7 @@ resolution without becoming a rendezvous target itself.
|
|||||||
node:
|
node:
|
||||||
identity:
|
identity:
|
||||||
persistent: true
|
persistent: true
|
||||||
discovery:
|
rendezvous:
|
||||||
nostr:
|
nostr:
|
||||||
enabled: true
|
enabled: true
|
||||||
advertise: false
|
advertise: false
|
||||||
@@ -107,7 +112,7 @@ fits the deployment.
|
|||||||
node:
|
node:
|
||||||
identity:
|
identity:
|
||||||
persistent: true
|
persistent: true
|
||||||
discovery:
|
rendezvous:
|
||||||
nostr:
|
nostr:
|
||||||
enabled: true
|
enabled: true
|
||||||
advertise: true
|
advertise: true
|
||||||
@@ -166,7 +171,7 @@ side has a NAT type compatible with hole-punching.
|
|||||||
node:
|
node:
|
||||||
identity:
|
identity:
|
||||||
persistent: true
|
persistent: true
|
||||||
discovery:
|
rendezvous:
|
||||||
nostr:
|
nostr:
|
||||||
enabled: true
|
enabled: true
|
||||||
advertise: true
|
advertise: true
|
||||||
@@ -235,7 +240,7 @@ the advertised port.
|
|||||||
node:
|
node:
|
||||||
identity:
|
identity:
|
||||||
persistent: true
|
persistent: true
|
||||||
discovery:
|
rendezvous:
|
||||||
nostr:
|
nostr:
|
||||||
enabled: true
|
enabled: true
|
||||||
advertise: true
|
advertise: true
|
||||||
@@ -275,7 +280,7 @@ trade-offs and the `torrc` directives each requires, see
|
|||||||
node:
|
node:
|
||||||
identity:
|
identity:
|
||||||
persistent: true
|
persistent: true
|
||||||
discovery:
|
rendezvous:
|
||||||
nostr:
|
nostr:
|
||||||
enabled: true
|
enabled: true
|
||||||
advertise: true
|
advertise: true
|
||||||
@@ -309,7 +314,7 @@ for connection attempts subject to `open_discovery_max_pending`.
|
|||||||
node:
|
node:
|
||||||
identity:
|
identity:
|
||||||
persistent: true
|
persistent: true
|
||||||
discovery:
|
rendezvous:
|
||||||
nostr:
|
nostr:
|
||||||
enabled: true
|
enabled: true
|
||||||
advertise: true
|
advertise: true
|
||||||
@@ -353,7 +358,7 @@ to unrelated overlays that happen to share the default namespace.
|
|||||||
- [../design/fips-nostr-discovery.md](../design/fips-nostr-discovery.md)
|
- [../design/fips-nostr-discovery.md](../design/fips-nostr-discovery.md)
|
||||||
— discovery runtime design, security model
|
— discovery runtime design, security model
|
||||||
- [../reference/configuration.md](../reference/configuration.md) —
|
- [../reference/configuration.md](../reference/configuration.md) —
|
||||||
full `node.discovery.nostr.*` and per-transport
|
full `node.rendezvous.nostr.*` and per-transport
|
||||||
`advertise_on_nostr`/`public` table
|
`advertise_on_nostr`/`public` table
|
||||||
- [../reference/nostr-events.md](../reference/nostr-events.md) — Kind
|
- [../reference/nostr-events.md](../reference/nostr-events.md) — Kind
|
||||||
37195 advert format, Kind 21059 traversal signaling, Kind 10050
|
37195 advert format, Kind 21059 traversal signaling, Kind 10050
|
||||||
|
|||||||
@@ -145,18 +145,22 @@ transports:
|
|||||||
ethernet:
|
ethernet:
|
||||||
mesh0:
|
mesh0:
|
||||||
interface: "fips-mesh0"
|
interface: "fips-mesh0"
|
||||||
discovery: true
|
listen: true
|
||||||
announce: true
|
announce: true
|
||||||
auto_connect: true
|
auto_connect: true
|
||||||
accept_connections: true
|
accept_connections: true
|
||||||
mesh1:
|
mesh1:
|
||||||
interface: "fips-mesh1"
|
interface: "fips-mesh1"
|
||||||
discovery: true
|
listen: true
|
||||||
announce: true
|
announce: true
|
||||||
auto_connect: true
|
auto_connect: true
|
||||||
accept_connections: true
|
accept_connections: true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`listen:` was called `discovery:` before v0.5.0; the old spelling still
|
||||||
|
parses as an alias, so an existing config keeps working (see
|
||||||
|
[../reference/configuration.md](../reference/configuration.md)).
|
||||||
|
|
||||||
## Step 3 — restart the daemon (order matters)
|
## Step 3 — restart the daemon (order matters)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
@@ -246,8 +246,8 @@ debugging FIPS. The FIPS daemon will log a warning if it cannot
|
|||||||
acquire the adapter.
|
acquire the adapter.
|
||||||
|
|
||||||
If the FIPS log contains `bluer` D-Bus errors, the daemon usually
|
If the FIPS log contains `bluer` D-Bus errors, the daemon usually
|
||||||
lacks permission. Run as root or grant `CAP_NET_ADMIN` and add the
|
lacks permission. Run as root, add the fips user to the
|
||||||
fips user to the `bluetooth` group.
|
`bluetooth` group, or grant the binary `CAP_NET_RAW`.
|
||||||
|
|
||||||
### Peers see each other but never connect
|
### Peers see each other but never connect
|
||||||
|
|
||||||
@@ -263,11 +263,12 @@ fails.
|
|||||||
|
|
||||||
Practical L2CAP CoC throughput in good conditions reaches
|
Practical L2CAP CoC throughput in good conditions reaches
|
||||||
1-2 Mbps, but interference, range, and controller capability all
|
1-2 Mbps, but interference, range, and controller capability all
|
||||||
push it lower. If throughput is well below that range, check the
|
push it lower. FIPS carries BLE over an L2CAP connection-oriented
|
||||||
negotiated ATT_MTU — a small ATT_MTU (default 23 bytes when
|
channel rather than GATT, so there is no ATT_MTU to negotiate: the
|
||||||
extended ATT MTU is not negotiated) caps per-PDU payload
|
per-connection L2CAP CoC MTU applies, defaulting to 2048. The
|
||||||
regardless of radio conditions. The per-link MTU reported in
|
measured path MTU is the `path_mtu` field of `fipsctl show mmp`,
|
||||||
`fipsctl show transports` reveals what was negotiated.
|
under the session layer; `show peers` omits it and `show
|
||||||
|
transports` carries only the transport-wide default.
|
||||||
|
|
||||||
If MTU is unexpectedly low, both endpoints must support and have
|
If MTU is unexpectedly low, both endpoints must support and have
|
||||||
negotiated the BlueZ L2CAP `cocmode=2` extension. Older Bluetooth
|
negotiated the BlueZ L2CAP `cocmode=2` extension. Older Bluetooth
|
||||||
|
|||||||
@@ -183,18 +183,22 @@ transports:
|
|||||||
ethernet:
|
ethernet:
|
||||||
ap0:
|
ap0:
|
||||||
interface: "fips-ap0"
|
interface: "fips-ap0"
|
||||||
discovery: true
|
listen: true
|
||||||
announce: true
|
announce: true
|
||||||
auto_connect: true
|
auto_connect: true
|
||||||
accept_connections: true
|
accept_connections: true
|
||||||
ap1:
|
ap1:
|
||||||
interface: "fips-ap1"
|
interface: "fips-ap1"
|
||||||
discovery: true
|
listen: true
|
||||||
announce: true
|
announce: true
|
||||||
auto_connect: true
|
auto_connect: true
|
||||||
accept_connections: true
|
accept_connections: true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`listen:` was called `discovery:` before v0.5.0; the old spelling still
|
||||||
|
parses as an alias, so an existing config keeps working (see
|
||||||
|
[../reference/configuration.md](../reference/configuration.md)).
|
||||||
|
|
||||||
## Step 3 — restart the daemon (order matters)
|
## Step 3 — restart the daemon (order matters)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ Compare the live descriptor count against the established peer count:
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
ls /proc/$(pidof fips)/fd | wc -l
|
ls /proc/$(pidof fips)/fd | wc -l
|
||||||
fipsctl show peers | wc -l
|
fipsctl show peers | jq '.peers | length'
|
||||||
```
|
```
|
||||||
|
|
||||||
At steady state, expect a stable ratio of about **3 descriptors per
|
At steady state, expect a stable ratio of about **3 descriptors per
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ on.
|
|||||||
|
|
||||||
## Step 1: Enable it on the daemon
|
## Step 1: Enable it on the daemon
|
||||||
|
|
||||||
Add to `/etc/fips/fips.yaml` (or a drop-in under `/etc/fips/fips.d/`):
|
Add to `/etc/fips/fips.yaml`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
node:
|
node:
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ controlled through the standard service control manager.
|
|||||||
| Flag | Argument | Description |
|
| Flag | Argument | Description |
|
||||||
| ---- | -------- | ----------- |
|
| ---- | -------- | ----------- |
|
||||||
| `-c`, `--config` | `FILE` | Use `FILE` as the configuration. Skips the default search paths. |
|
| `-c`, `--config` | `FILE` | Use `FILE` as the configuration. Skips the default search paths. |
|
||||||
| `-V` | — | Print the short version (e.g. `0.4.0 (rev abcdef1)`). |
|
| `-V` | — | Print the short version, `<version> (rev <git-hash>)`. |
|
||||||
| `--version` | — | Print the long version: short version plus build target triple. |
|
| `--version` | — | Print the long version: short version plus build target triple. |
|
||||||
| `-h`, `--help` | — | Print usage and exit. |
|
| `-h`, `--help` | — | Print usage and exit. |
|
||||||
| `--install-service` | — | (Windows only) Install `fips` as a Windows service. Requires Administrator. |
|
| `--install-service` | — | (Windows only) Install `fips` as a Windows service. Requires Administrator. |
|
||||||
@@ -66,11 +66,15 @@ highest-priority value wins.
|
|||||||
|
|
||||||
| Priority | Path | Purpose |
|
| Priority | Path | Purpose |
|
||||||
| -------- | ---- | ------- |
|
| -------- | ---- | ------- |
|
||||||
| 1 | `/usr/local/etc/fips/fips.yaml` (macOS), `/etc/fips/fips.yaml` (other Unix) | System-wide defaults |
|
| 1 | `/usr/local/etc/fips/fips.yaml` (macOS, FreeBSD), `/etc/fips/fips.yaml` (other Unix) | System-wide defaults |
|
||||||
| 2 | `~/.config/fips/fips.yaml` | User preferences |
|
| 2 | `~/.config/fips/fips.yaml` | User preferences |
|
||||||
| 3 | `~/.fips.yaml` | Legacy user config |
|
| 3 | `~/.fips.yaml` | Legacy user config |
|
||||||
| 4 | `./fips.yaml` | Deployment-specific overrides |
|
| 4 | `./fips.yaml` | Deployment-specific overrides |
|
||||||
|
|
||||||
|
On macOS and FreeBSD both system directories are probed: `/etc/fips`
|
||||||
|
first, then `/usr/local/etc/fips`, so the packaged file wins over a
|
||||||
|
leftover `/etc/fips` copy from an earlier install.
|
||||||
|
|
||||||
Adjacent to the highest-priority config file the daemon reads (or
|
Adjacent to the highest-priority config file the daemon reads (or
|
||||||
writes, on first start) the identity files:
|
writes, on first start) the identity files:
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ that defines the socket location, see
|
|||||||
| Flag | Argument | Description |
|
| Flag | Argument | Description |
|
||||||
| ---- | -------- | ----------- |
|
| ---- | -------- | ----------- |
|
||||||
| `-s`, `--socket` | `PATH` | Override the control-socket path (Linux/macOS) or TCP port (Windows). |
|
| `-s`, `--socket` | `PATH` | Override the control-socket path (Linux/macOS) or TCP port (Windows). |
|
||||||
| `-V`, `--version` | — | Print the short version. |
|
| `-V` | — | Print the short version, `<version> (rev <git-hash>)`. |
|
||||||
| `--version` | — | Print the long version. |
|
| `--version` | — | Print the long version: short version plus build target triple. |
|
||||||
| `-h`, `--help` | — | Print usage and exit. Per-subcommand help via `fipsctl <subcommand> --help`. |
|
| `-h`, `--help` | — | Print usage and exit. Per-subcommand help via `fipsctl <subcommand> --help`. |
|
||||||
|
|
||||||
## Subcommands
|
## Subcommands
|
||||||
@@ -90,7 +90,7 @@ daemon.
|
|||||||
|
|
||||||
| Flag | Argument | Default | Description |
|
| Flag | Argument | Default | Description |
|
||||||
| ---- | -------- | ------- | ----------- |
|
| ---- | -------- | ------- | ----------- |
|
||||||
| `-d`, `--dir` | `DIR` | `/usr/local/etc/fips` (macOS), `/etc/fips` (other Unix), `%APPDATA%\fips` (Windows) | Output directory for `fips.key` and `fips.pub`. Matches the directory the platform's packaging installs config into, which is where the daemon derives the key paths from. |
|
| `-d`, `--dir` | `DIR` | `/usr/local/etc/fips` (macOS, FreeBSD), `/etc/fips` (other Unix), `%APPDATA%\fips` (Windows) | Output directory for `fips.key` and `fips.pub`. Matches the directory the platform's packaging installs config into, which is where the daemon derives the key paths from. |
|
||||||
| `-f`, `--force` | — | off | Overwrite an existing `fips.key`. |
|
| `-f`, `--force` | — | off | Overwrite an existing `fips.key`. |
|
||||||
| `-s`, `--stdout` | — | off | Print `nsec` then `npub` to stdout instead of writing files. |
|
| `-s`, `--stdout` | — | off | Print `nsec` then `npub` to stdout instead of writing files. |
|
||||||
|
|
||||||
@@ -177,6 +177,9 @@ The stages are:
|
|||||||
5. **rtt** — one MMP sender/receiver report exchange, for a real
|
5. **rtt** — one MMP sender/receiver report exchange, for a real
|
||||||
round-trip time.
|
round-trip time.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
Exit status is 0 only for an overall verdict of `ok`; `partial`,
|
Exit status is 0 only for an overall verdict of `ok`; `partial`,
|
||||||
`failed` and `cancelled` all exit 1.
|
`failed` and `cancelled` all exit 1.
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ a confirmation prompt — see [Keybindings](#keybindings)). For
|
|||||||
| `-s`, `--socket` | `PATH` | (auto) | Daemon control-socket path / port. Same default as `fipsctl`. |
|
| `-s`, `--socket` | `PATH` | (auto) | Daemon control-socket path / port. Same default as `fipsctl`. |
|
||||||
| `--gateway-socket` | `PATH` | (auto) | `fips-gateway` control-socket path / port. Default: `/run/fips/gateway.sock` (Unix), TCP port `21211` (Windows). |
|
| `--gateway-socket` | `PATH` | (auto) | `fips-gateway` control-socket path / port. Default: `/run/fips/gateway.sock` (Unix), TCP port `21211` (Windows). |
|
||||||
| `-r`, `--refresh` | `SECONDS` | `2` | Poll interval. |
|
| `-r`, `--refresh` | `SECONDS` | `2` | Poll interval. |
|
||||||
| `-V`, `--version` | — | — | Print short version. |
|
| `-V` | — | — | Print the short version, `<version> (rev <git-hash>)`. |
|
||||||
| `--version` | — | — | Print long version. |
|
| `--version` | — | — | Print the long version: short version plus build target triple. |
|
||||||
| `-h`, `--help` | — | — | Print usage and exit. |
|
| `-h`, `--help` | — | — | Print usage and exit. |
|
||||||
|
|
||||||
## Tabs
|
## Tabs
|
||||||
|
|||||||
+154
-64
@@ -13,7 +13,7 @@ locations, lowest to highest priority:
|
|||||||
|
|
||||||
| Priority | Path | Purpose |
|
| Priority | Path | Purpose |
|
||||||
|----------|------|---------|
|
|----------|------|---------|
|
||||||
| 1 (lowest) | `/usr/local/etc/fips/fips.yaml` (macOS), `/etc/fips/fips.yaml` (other Unix) | System-wide defaults |
|
| 1 (lowest) | `/usr/local/etc/fips/fips.yaml` (macOS, FreeBSD), `/etc/fips/fips.yaml` (other Unix) | System-wide defaults |
|
||||||
| 2 | `~/.config/fips/fips.yaml` | User preferences |
|
| 2 | `~/.config/fips/fips.yaml` | User preferences |
|
||||||
| 3 | `~/.fips.yaml` | Legacy user config |
|
| 3 | `~/.fips.yaml` | Legacy user config |
|
||||||
| 4 (highest) | `./fips.yaml` | Deployment-specific overrides |
|
| 4 (highest) | `./fips.yaml` | Deployment-specific overrides |
|
||||||
@@ -21,9 +21,13 @@ locations, lowest to highest priority:
|
|||||||
All found files are loaded and merged in priority order. Values from higher
|
All found files are loaded and merged in priority order. Values from higher
|
||||||
priority files override those from lower priority files. This allows a system
|
priority files override those from lower priority files. This allows a system
|
||||||
administrator to set site-wide defaults in the priority 1 path above,
|
administrator to set site-wide defaults in the priority 1 path above,
|
||||||
`/usr/local/etc/fips/fips.yaml` on macOS and `/etc/fips/fips.yaml` on other
|
`/usr/local/etc/fips/fips.yaml` on macOS and FreeBSD and
|
||||||
Unix systems, while individual deployments override specific values in
|
`/etc/fips/fips.yaml` on other Unix systems, while individual
|
||||||
`./fips.yaml`.
|
deployments override specific values in `./fips.yaml`.
|
||||||
|
|
||||||
|
On macOS and FreeBSD both directories are probed: `/etc/fips` first,
|
||||||
|
then `/usr/local/etc/fips`, so the packaged file wins over a leftover
|
||||||
|
`/etc/fips` copy from an earlier install.
|
||||||
|
|
||||||
### CLI Option
|
### CLI Option
|
||||||
|
|
||||||
@@ -41,7 +45,8 @@ only the identity and peer list, inheriting all other defaults.
|
|||||||
|
|
||||||
## YAML Structure
|
## YAML Structure
|
||||||
|
|
||||||
The configuration is organized into five top-level sections:
|
The configuration is organized into six top-level sections (`gateway:`
|
||||||
|
is Linux only):
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
node: # Node behavior, protocol parameters, and tuning
|
node: # Node behavior, protocol parameters, and tuning
|
||||||
@@ -49,6 +54,7 @@ tun: # TUN virtual interface
|
|||||||
dns: # DNS responder for .fips domain
|
dns: # DNS responder for .fips domain
|
||||||
transports: # Network transports (UDP, Ethernet, Bluetooth, Tor, ...)
|
transports: # Network transports (UDP, Ethernet, Bluetooth, Tor, ...)
|
||||||
peers: # Static peer list
|
peers: # Static peer list
|
||||||
|
gateway: # LAN gateway service (Linux only)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Control Socket (`node.control.*`)
|
### Control Socket (`node.control.*`)
|
||||||
@@ -111,6 +117,7 @@ to the highest-priority config file for operator visibility, even in ephemeral m
|
|||||||
| `node.base_rtt_ms` | u64 | `100` | Initial RTT estimate for new links before measurements converge |
|
| `node.base_rtt_ms` | u64 | `100` | Initial RTT estimate for new links before measurements converge |
|
||||||
| `node.heartbeat_interval_secs` | u64 | `10` | Heartbeat send interval per peer for liveness detection |
|
| `node.heartbeat_interval_secs` | u64 | `10` | Heartbeat send interval per peer for liveness detection |
|
||||||
| `node.link_dead_timeout_secs` | u64 | `30` | No-traffic timeout before a peer is declared dead and removed |
|
| `node.link_dead_timeout_secs` | u64 | `30` | No-traffic timeout before a peer is declared dead and removed |
|
||||||
|
| `node.drain_timeout_secs` | u64 | `2` | Upper bound in seconds on the `Draining` shutdown phase. On shutdown the node broadcasts Disconnect to its peers and then waits up to this long for the links to clear, exiting as soon as the last peer is gone. `0` skips the wait. The key is absent from a default config file rather than written with its default value, so an unset key and the 2-second default are the same thing |
|
||||||
| `node.log_level` | string | `"info"` | Tracing filter default. Case-insensitive; one of `trace`, `debug`, `info`, `warn`, `error`. Overridden by the `RUST_LOG` environment variable when set |
|
| `node.log_level` | string | `"info"` | Tracing filter default. Case-insensitive; one of `trace`, `debug`, `info`, `warn`, `error`. Overridden by the `RUST_LOG` environment variable when set |
|
||||||
|
|
||||||
### Resource Limits (`node.limits.*`)
|
### Resource Limits (`node.limits.*`)
|
||||||
@@ -197,22 +204,38 @@ Controls caching of tree coordinates and identity mappings.
|
|||||||
| `node.cache.coord_ttl_secs` | u64 | `300` | Coordinate cache entry TTL (5 minutes) |
|
| `node.cache.coord_ttl_secs` | u64 | `300` | Coordinate cache entry TTL (5 minutes) |
|
||||||
| `node.cache.identity_size` | usize | `10000` | Max entries in identity cache (LRU, no TTL) |
|
| `node.cache.identity_size` | usize | `10000` | Max entries in identity cache (LRU, no TTL) |
|
||||||
|
|
||||||
### Discovery Protocol (`node.discovery.*`)
|
### Mesh Lookup (`node.lookup.*`)
|
||||||
|
|
||||||
Controls bloom-guided node discovery (LookupRequest/LookupResponse).
|
Controls bloom-guided mesh lookup (LookupRequest/LookupResponse): finding
|
||||||
|
the current coordinates of a mesh address the node already knows.
|
||||||
|
|
||||||
|
> **Renamed in v0.5.0.** These six keys were `node.discovery.*`. See
|
||||||
|
> [Deprecated keys](#deprecated-keys) for the full mapping. A deployed
|
||||||
|
> `node.discovery:` block still loads and still applies, with a one-time
|
||||||
|
> deprecation warning at startup.
|
||||||
|
|
||||||
| Parameter | Type | Default | Description |
|
| Parameter | Type | Default | Description |
|
||||||
|-----------|------|---------|-------------|
|
|-----------|------|---------|-------------|
|
||||||
| `node.discovery.ttl` | u8 | `64` | Hop limit for LookupRequest forwarding |
|
| `node.lookup.ttl` | u8 | `64` | Hop limit for LookupRequest forwarding |
|
||||||
| `node.discovery.attempt_timeouts_secs` | array<u64> | `[1, 2, 4, 8]` | Per-attempt timeouts. Each entry is the deadline for one `LookupRequest` before sending the next attempt with a fresh `request_id`. Length determines total attempt count; default gives 4 attempts and a 15s total budget |
|
| `node.lookup.attempt_timeouts_secs` | array<u64> | `[1, 2, 4, 8]` | Per-attempt timeouts. Each entry is the deadline for one `LookupRequest` before sending the next attempt with a fresh `request_id`. Length determines total attempt count; default gives 4 attempts and a 15s total budget |
|
||||||
| `node.discovery.recent_expiry_secs` | u64 | `10` | Dedup cache expiry for recent request IDs |
|
| `node.lookup.recent_expiry_secs` | u64 | `10` | Dedup cache expiry for recent request IDs |
|
||||||
| `node.discovery.backoff_base_secs` | u64 | `0` | Optional post-failure suppression base in seconds; doubles per consecutive failure. `0` disables (default) — the per-attempt sequence is the only retry pacing |
|
| `node.lookup.backoff_base_secs` | u64 | `0` | Optional post-failure suppression base in seconds; doubles per consecutive failure. `0` disables (default); the per-attempt sequence is the only retry pacing |
|
||||||
| `node.discovery.backoff_max_secs` | u64 | `0` | Cap on optional post-failure backoff |
|
| `node.lookup.backoff_max_secs` | u64 | `0` | Cap on optional post-failure backoff |
|
||||||
| `node.discovery.forward_min_interval_secs` | u64 | `2` | Transit-side rate limiting: minimum interval between forwarded lookups for the same target |
|
| `node.lookup.forward_min_interval_secs` | u64 | `2` | Transit-side rate limiting: minimum interval between forwarded lookups for the same target |
|
||||||
|
|
||||||
#### Nostr Overlay Discovery (`node.discovery.nostr.*`)
|
### Peer Rendezvous (`node.rendezvous.*`)
|
||||||
|
|
||||||
Optional Nostr-mediated overlay discovery. This layer publishes replaceable
|
How the node finds peers to connect to at all, over the Nostr overlay and
|
||||||
|
on the local link. Distinct from mesh lookup above, which resolves
|
||||||
|
coordinates for a mesh address that is already known.
|
||||||
|
|
||||||
|
> **Renamed in v0.5.0.** `node.discovery.nostr.*` is now
|
||||||
|
> `node.rendezvous.nostr.*`, and `node.discovery.lan.*` is now
|
||||||
|
> `node.rendezvous.lan.*`. See [Deprecated keys](#deprecated-keys).
|
||||||
|
|
||||||
|
#### Nostr Rendezvous (`node.rendezvous.nostr.*`)
|
||||||
|
|
||||||
|
Optional Nostr-mediated overlay rendezvous. This layer publishes replaceable
|
||||||
endpoint adverts (`fips-overlay-v1`), consumes advert-derived endpoint
|
endpoint adverts (`fips-overlay-v1`), consumes advert-derived endpoint
|
||||||
fallbacks for configured peers, and can optionally discover non-configured
|
fallbacks for configured peers, and can optionally discover non-configured
|
||||||
peers (`policy: open`). `udp:nat` remains the trigger for NAT traversal
|
peers (`policy: open`). `udp:nat` remains the trigger for NAT traversal
|
||||||
@@ -221,39 +244,39 @@ into the normal FIPS transport/session stack.
|
|||||||
Inbox-relay discovery falls back to the local DM relay list if remote relay
|
Inbox-relay discovery falls back to the local DM relay list if remote relay
|
||||||
metadata cannot be fetched.
|
metadata cannot be fetched.
|
||||||
The Nostr discovery runtime is compiled into every build of the crate; it
|
The Nostr discovery runtime is compiled into every build of the crate; it
|
||||||
is enabled at runtime via `node.discovery.nostr.enabled: true` and stays
|
is enabled at runtime via `node.rendezvous.nostr.enabled: true` and stays
|
||||||
inert otherwise.
|
inert otherwise.
|
||||||
|
|
||||||
| Parameter | Type | Default | Description |
|
| Parameter | Type | Default | Description |
|
||||||
|-----------|------|---------|-------------|
|
|-----------|------|---------|-------------|
|
||||||
| `node.discovery.nostr.enabled` | bool | `false` | Enable Nostr-mediated overlay discovery |
|
| `node.rendezvous.nostr.enabled` | bool | `false` | Enable Nostr-mediated overlay discovery |
|
||||||
| `node.discovery.nostr.policy` | string | `"configured_only"` | Advert discovery policy: `disabled`, `configured_only`, `open` |
|
| `node.rendezvous.nostr.policy` | string | `"configured_only"` | Advert discovery policy: `disabled`, `configured_only`, `open` |
|
||||||
| `node.discovery.nostr.open_discovery_max_pending` | usize | `64` | Max open-discovery peers queued in outbound retry/connection state at once |
|
| `node.rendezvous.nostr.open_discovery_max_pending` | usize | `64` | Max open-discovery peers queued in outbound retry/connection state at once |
|
||||||
| `node.discovery.nostr.max_concurrent_incoming_offers` | usize | `16` | Max concurrent inbound traversal offers processed at once (rate limit against offer spam) |
|
| `node.rendezvous.nostr.max_concurrent_incoming_offers` | usize | `16` | Max concurrent inbound traversal offers processed at once (rate limit against offer spam) |
|
||||||
| `node.discovery.nostr.max_concurrent_offers_per_npub` | usize | `4` | Max concurrent inbound traversal offers accepted from any one sender npub, so a single identity cannot hold the whole pool. Sits inside `max_concurrent_incoming_offers`, which stays the outer bound; a larger value is inert. Zero is rejected, since it refuses every inbound offer rather than disabling the limit |
|
| `node.rendezvous.nostr.max_concurrent_offers_per_npub` | usize | `4` | Max concurrent inbound traversal offers accepted from any one sender npub, so a single identity cannot hold the whole pool. Sits inside `max_concurrent_incoming_offers`, which stays the outer bound; a larger value is inert. Zero is rejected, since it refuses every inbound offer rather than disabling the limit |
|
||||||
| `node.discovery.nostr.advert_cache_max_entries` | usize | `2048` | Max cached overlay adverts retained from relay traffic |
|
| `node.rendezvous.nostr.advert_cache_max_entries` | usize | `2048` | Max cached overlay adverts retained from relay traffic |
|
||||||
| `node.discovery.nostr.seen_sessions_max_entries` | usize | `2048` | Max seen-session IDs retained for replay detection |
|
| `node.rendezvous.nostr.seen_sessions_max_entries` | usize | `2048` | Max seen-session IDs retained for replay detection |
|
||||||
| `node.discovery.nostr.advertise` | bool | `true` | Publish local endpoint adverts |
|
| `node.rendezvous.nostr.advertise` | bool | `true` | Publish local endpoint adverts |
|
||||||
| `node.discovery.nostr.advert_relays` | list[string] | `["wss://relay.damus.io", "wss://nos.lol", "wss://offchain.pub"]` | Relays used for service adverts |
|
| `node.rendezvous.nostr.advert_relays` | list[string] | `["wss://relay.damus.io", "wss://nos.lol", "wss://offchain.pub"]` | Relays used for service adverts |
|
||||||
| `node.discovery.nostr.dm_relays` | list[string] | `["wss://relay.damus.io", "wss://nos.lol", "wss://offchain.pub"]` | Relays used for encrypted signaling events |
|
| `node.rendezvous.nostr.dm_relays` | list[string] | `["wss://relay.damus.io", "wss://nos.lol", "wss://offchain.pub"]` | Relays used for encrypted signaling events |
|
||||||
| `node.discovery.nostr.stun_servers` | list[string] | `["stun:stun.l.google.com:19302", "stun:stun.cloudflare.com:3478", "stun:global.stun.twilio.com:3478"]` | STUN servers used for local reflexive address discovery |
|
| `node.rendezvous.nostr.stun_servers` | list[string] | `["stun:stun.l.google.com:19302", "stun:stun.cloudflare.com:3478", "stun:global.stun.twilio.com:3478"]` | STUN servers used for local reflexive address discovery |
|
||||||
| `node.discovery.nostr.share_local_candidates` | bool | `false` | Whether to advertise local (RFC 1918 / ULA) interface addresses as host candidates in the traversal offer. Off by default: in most deployments peers aren't on the same broadcast domain, and sharing private host candidates causes misleading punch successes when an asymmetric L3 path (VPN, Tailscale subnet route, overlapping address space) makes a peer's private IP one-way reachable. Enable only when peers are on the same physical LAN |
|
| `node.rendezvous.nostr.share_local_candidates` | bool | `false` | Whether to advertise local (RFC 1918 / ULA) interface addresses as host candidates in the traversal offer. Off by default: in most deployments peers aren't on the same broadcast domain, and sharing private host candidates causes misleading punch successes when an asymmetric L3 path (VPN, Tailscale subnet route, overlapping address space) makes a peer's private IP one-way reachable. Enable only when peers are on the same physical LAN |
|
||||||
| `node.discovery.nostr.app` | string | `"fips-overlay-v1"` | Traversal application namespace, published in the advert's `protocol` tag (the `d` tag itself is hardcoded to `fips-overlay-v1`) |
|
| `node.rendezvous.nostr.app` | string | `"fips-overlay-v1"` | Traversal application namespace, published in the advert's `protocol` tag (the `d` tag itself is hardcoded to `fips-overlay-v1`) |
|
||||||
| `node.discovery.nostr.signal_ttl_secs` | u64 | `120` | Signaling TTL in seconds |
|
| `node.rendezvous.nostr.signal_ttl_secs` | u64 | `120` | Signaling TTL in seconds |
|
||||||
| `node.discovery.nostr.attempt_timeout_secs` | u64 | `10` | Overall traversal attempt timeout in seconds |
|
| `node.rendezvous.nostr.attempt_timeout_secs` | u64 | `10` | Overall traversal attempt timeout in seconds |
|
||||||
| `node.discovery.nostr.replay_window_secs` | u64 | `300` | Replay tracking retention window in seconds |
|
| `node.rendezvous.nostr.replay_window_secs` | u64 | `300` | Replay tracking retention window in seconds |
|
||||||
| `node.discovery.nostr.punch_start_delay_ms` | u64 | `2000` | Delay before punch traffic starts |
|
| `node.rendezvous.nostr.punch_start_delay_ms` | u64 | `2000` | Delay before punch traffic starts |
|
||||||
| `node.discovery.nostr.punch_interval_ms` | u64 | `200` | Interval between punch packets |
|
| `node.rendezvous.nostr.punch_interval_ms` | u64 | `200` | Interval between punch packets |
|
||||||
| `node.discovery.nostr.punch_duration_ms` | u64 | `10000` | How long to keep punching before failure |
|
| `node.rendezvous.nostr.punch_duration_ms` | u64 | `10000` | How long to keep punching before failure |
|
||||||
| `node.discovery.nostr.advert_ttl_secs` | u64 | `3600` | Advert TTL in seconds |
|
| `node.rendezvous.nostr.advert_ttl_secs` | u64 | `3600` | Advert TTL in seconds |
|
||||||
| `node.discovery.nostr.advert_refresh_secs` | u64 | `1800` | How often adverts are refreshed in seconds |
|
| `node.rendezvous.nostr.advert_refresh_secs` | u64 | `1800` | How often adverts are refreshed in seconds |
|
||||||
| `node.discovery.nostr.startup_sweep_delay_secs` | u64 | `5` | Settle delay after Nostr discovery starts before the one-shot startup advert sweep runs (only used under `policy: open`). Allows the relay subscription backlog to populate the in-memory advert cache before the sweep fires |
|
| `node.rendezvous.nostr.startup_sweep_delay_secs` | u64 | `5` | Settle delay after Nostr discovery starts before the one-shot startup advert sweep runs (only used under `policy: open`). Allows the relay subscription backlog to populate the in-memory advert cache before the sweep fires |
|
||||||
| `node.discovery.nostr.startup_sweep_max_age_secs` | u64 | `3600` | Maximum advert age (`now - created_at`) considered by the one-shot startup sweep (only used under `policy: open`). Adverts older than this are skipped on startup; the per-tick sweep still considers them up to `valid_until_ms` |
|
| `node.rendezvous.nostr.startup_sweep_max_age_secs` | u64 | `3600` | Maximum advert age (`now - created_at`) considered by the one-shot startup sweep (only used under `policy: open`). Adverts older than this are skipped on startup; the per-tick sweep still considers them up to `valid_until_ms` |
|
||||||
| `node.discovery.nostr.failure_streak_threshold` | u32 | `5` | Consecutive NAT-traversal failures against a peer before an extended cooldown is applied. At this threshold the daemon also actively re-fetches the peer's advert from `advert_relays` to evict cache entries for peers that have gone away |
|
| `node.rendezvous.nostr.failure_streak_threshold` | u32 | `5` | Consecutive NAT-traversal failures against a peer before an extended cooldown is applied. At this threshold the daemon also actively re-fetches the peer's advert from `advert_relays` to evict cache entries for peers that have gone away |
|
||||||
| `node.discovery.nostr.extended_cooldown_secs` | u64 | `1800` | Cooldown applied to a peer once `failure_streak_threshold` is hit. Suppresses both open-discovery sweep enqueues and per-attempt retry firings until elapsed (30 minutes default) |
|
| `node.rendezvous.nostr.extended_cooldown_secs` | u64 | `1800` | Cooldown applied to a peer once `failure_streak_threshold` is hit. Suppresses both open-discovery sweep enqueues and per-attempt retry firings until elapsed (30 minutes default) |
|
||||||
| `node.discovery.nostr.warn_log_interval_secs` | u64 | `300` | Minimum interval between `NAT traversal failed` WARN log lines for the same peer. Subsequent failures inside the window log at DEBUG to reduce log spam on public-test nodes with many cache-learned peers |
|
| `node.rendezvous.nostr.warn_log_interval_secs` | u64 | `300` | Minimum interval between `NAT traversal failed` WARN log lines for the same peer. Subsequent failures inside the window log at DEBUG to reduce log spam on public-test nodes with many cache-learned peers |
|
||||||
| `node.discovery.nostr.failure_state_max_entries` | usize | `4096` | Maximum entries retained in the per-npub failure-state map. Bounds memory under high cache turnover; oldest entries (by last failure time) are evicted when the cap is exceeded |
|
| `node.rendezvous.nostr.failure_state_max_entries` | usize | `4096` | Maximum entries retained in the per-npub failure-state map. Bounds memory under high cache turnover; oldest entries (by last failure time) are evicted when the cap is exceeded |
|
||||||
| `node.discovery.nostr.protocol_mismatch_cooldown_secs` | u64 | `86400` | Cooldown applied after observing a fatal protocol mismatch on a Nostr-adopted bootstrap transport (e.g. `Unknown FMP version` from a peer running a different FMP-protocol version). Independent of `extended_cooldown_secs` and much longer (24 hours default) because the mismatch is structural — re-traversing is wasted effort until one side upgrades |
|
| `node.rendezvous.nostr.protocol_mismatch_cooldown_secs` | u64 | `86400` | Cooldown applied after observing a fatal protocol mismatch on a Nostr-adopted bootstrap transport (e.g. `Unknown FMP version` from a peer running a different FMP-protocol version). Independent of `extended_cooldown_secs` and much longer (24 hours default) because the mismatch is structural; re-traversing is wasted effort until one side upgrades |
|
||||||
|
|
||||||
If `stun_servers` is omitted, the built-in default list above is used. If it is
|
If `stun_servers` is omitted, the built-in default list above is used. If it is
|
||||||
specified in YAML, the configured list fully overrides the defaults.
|
specified in YAML, the configured list fully overrides the defaults.
|
||||||
@@ -274,9 +297,9 @@ addresses for the punch socket port.
|
|||||||
During punching, compatible private-subnet candidates and reflexive candidates
|
During punching, compatible private-subnet candidates and reflexive candidates
|
||||||
are attempted in parallel; the first successful path wins.
|
are attempted in parallel; the first successful path wins.
|
||||||
|
|
||||||
#### LAN Discovery (`node.discovery.lan.*`)
|
#### LAN Rendezvous (`node.rendezvous.lan.*`)
|
||||||
|
|
||||||
Peer discovery on the local link via mDNS / DNS-SD (RFC 6762 / RFC
|
Peer rendezvous on the local link via mDNS / DNS-SD (RFC 6762 / RFC
|
||||||
6763). When enabled, the node publishes a `_fips._udp.local.` service
|
6763). When enabled, the node publishes a `_fips._udp.local.` service
|
||||||
advert carrying its `npub` (and optional scope) and concurrently
|
advert carrying its `npub` (and optional scope) and concurrently
|
||||||
browses for the same service type to learn same-broadcast-domain peers.
|
browses for the same service type to learn same-broadcast-domain peers.
|
||||||
@@ -285,7 +308,7 @@ STUN observation, or NAT traversal: the observed endpoint is by
|
|||||||
construction routable from the consumer's LAN.
|
construction routable from the consumer's LAN.
|
||||||
|
|
||||||
mDNS adverts are unauthenticated, so a LAN advert is treated only as a
|
mDNS adverts are unauthenticated, so a LAN advert is treated only as a
|
||||||
routing hint. Identity is still proven end-to-end by the Noise XX
|
routing hint. Identity is still proven end-to-end by the Noise IK
|
||||||
handshake the node initiates against the observed endpoint; a spoofed
|
handshake the node initiates against the observed endpoint; a spoofed
|
||||||
advert carrying another peer's npub fails the handshake and is dropped.
|
advert carrying another peer's npub fails the handshake and is dropped.
|
||||||
LAN discovery requires an active UDP transport (peers dial the
|
LAN discovery requires an active UDP transport (peers dial the
|
||||||
@@ -293,9 +316,9 @@ advertised UDP port to begin the handshake).
|
|||||||
|
|
||||||
| Parameter | Type | Default | Description |
|
| Parameter | Type | Default | Description |
|
||||||
|-----------|------|---------|-------------|
|
|-----------|------|---------|-------------|
|
||||||
| `node.discovery.lan.enabled` | bool | `false` | Master switch. Opt-in: enable for sub-second same-LAN pairing. Default-off avoids reintroducing a per-LAN identity broadcast on nodes that have deliberately disabled other discovery channels |
|
| `node.rendezvous.lan.enabled` | bool | `false` | Master switch. Opt-in: enable for sub-second same-LAN pairing. Default-off avoids reintroducing a per-LAN identity broadcast on nodes that have deliberately disabled other discovery channels |
|
||||||
| `node.discovery.lan.service_type` | string | `"_fips._udp.local."` | DNS-SD service type. Primarily an override for integration tests running multiple isolated services on one loopback interface; leave at the default in production |
|
| `node.rendezvous.lan.service_type` | string | `"_fips._udp.local."` | DNS-SD service type. Primarily an override for integration tests running multiple isolated services on one loopback interface; leave at the default in production |
|
||||||
| `node.discovery.lan.scope` | string | *(none)* | Optional application/network scope carried in a `scope=<name>` TXT entry. Browsers with a scope set only surface peers advertising the same scope, so nodes on the same physical LAN configured for different mesh networks do not cross-feed. Intentionally separate from `node.discovery.nostr.app` so relay-visible adverts can stay generic while LAN discovery is isolated per private network |
|
| `node.rendezvous.lan.scope` | string | *(none)* | Optional application/network scope carried in a `scope=<name>` TXT entry. Browsers with a scope set only surface peers advertising the same scope, so nodes on the same physical LAN configured for different mesh networks do not cross-feed. Intentionally separate from `node.rendezvous.nostr.app` so relay-visible adverts can stay generic while LAN discovery is isolated per private network |
|
||||||
|
|
||||||
### Spanning Tree (`node.tree.*`)
|
### Spanning Tree (`node.tree.*`)
|
||||||
|
|
||||||
@@ -526,8 +549,10 @@ adding entries and the precedence rules:
|
|||||||
|
|
||||||
### Ethernet (`transports.ethernet.*`)
|
### Ethernet (`transports.ethernet.*`)
|
||||||
|
|
||||||
Ethernet transport sends raw frames via AF_PACKET SOCK_DGRAM sockets.
|
Ethernet transport sends raw frames over the platform's raw-frame
|
||||||
Requires `CAP_NET_RAW` or running as root. Linux only.
|
socket: AF_PACKET SOCK_DGRAM on Linux, BPF (`/dev/bpf*`) on macOS.
|
||||||
|
Linux and macOS only. On Linux it requires `CAP_NET_RAW` or running as
|
||||||
|
root; on macOS it requires read/write access to a `/dev/bpf*` device.
|
||||||
|
|
||||||
| Parameter | Type | Default | Description |
|
| Parameter | Type | Default | Description |
|
||||||
|-----------|------|---------|-------------|
|
|-----------|------|---------|-------------|
|
||||||
@@ -536,7 +561,7 @@ Requires `CAP_NET_RAW` or running as root. Linux only.
|
|||||||
| `mtu` | u16 | *(auto)* | Override MTU. Default: interface MTU minus 3 (for frame type + length prefix) |
|
| `mtu` | u16 | *(auto)* | Override MTU. Default: interface MTU minus 3 (for frame type + length prefix) |
|
||||||
| `recv_buf_size` | usize | `2097152` | Socket receive buffer size in bytes (2 MB) |
|
| `recv_buf_size` | usize | `2097152` | Socket receive buffer size in bytes (2 MB) |
|
||||||
| `send_buf_size` | usize | `2097152` | Socket send buffer size in bytes (2 MB) |
|
| `send_buf_size` | usize | `2097152` | Socket send buffer size in bytes (2 MB) |
|
||||||
| `listen` | bool | `true` | Listen for neighbor beacons from other nodes |
|
| `listen` | bool | `true` | Listen for neighbor beacons from other nodes. Renamed from `discovery` in v0.5.0; the old key is still accepted as an alias, so a deployed config loads unchanged |
|
||||||
| `announce` | bool | `false` | Broadcast announcement beacons on the LAN |
|
| `announce` | bool | `false` | Broadcast announcement beacons on the LAN |
|
||||||
| `auto_connect` | bool | `false` | Auto-connect to discovered peers |
|
| `auto_connect` | bool | `false` | Auto-connect to discovered peers |
|
||||||
| `accept_connections` | bool | `false` | Accept incoming connection attempts from discovered peers |
|
| `accept_connections` | bool | `false` | Accept incoming connection attempts from discovered peers |
|
||||||
@@ -612,6 +637,7 @@ Requires an external Tor daemon providing a SOCKS5 proxy. Three modes:
|
|||||||
| `transports.tor.max_inbound_connections` | usize | `64` | Maximum inbound connections via onion service. |
|
| `transports.tor.max_inbound_connections` | usize | `64` | Maximum inbound connections via onion service. |
|
||||||
| `transports.tor.directory_service.hostname_file` | string | `"/var/lib/tor/fips_onion_service/hostname"` | Path to Tor-managed hostname file containing the `.onion` address. |
|
| `transports.tor.directory_service.hostname_file` | string | `"/var/lib/tor/fips_onion_service/hostname"` | Path to Tor-managed hostname file containing the `.onion` address. |
|
||||||
| `transports.tor.directory_service.bind_addr` | string | `"127.0.0.1:8443"` | Local bind address for the listener that Tor forwards inbound connections to. Must match `HiddenServicePort` target in `torrc`. |
|
| `transports.tor.directory_service.bind_addr` | string | `"127.0.0.1:8443"` | Local bind address for the listener that Tor forwards inbound connections to. Must match `HiddenServicePort` target in `torrc`. |
|
||||||
|
| `transports.tor.advertise_on_nostr` | bool | `false` | Include this Tor transport in Nostr endpoint adverts. Requires `node.rendezvous.nostr.enabled: true`; setting it while Nostr rendezvous is disabled is a config-load error. `advertised_port` has no effect unless this is `true`. |
|
||||||
| `transports.tor.advertised_port` | u16 | `443` | Public-facing onion port published in Nostr overlay adverts. Must match the virtual port in torrc's `HiddenServicePort <port> 127.0.0.1:<bind_port>` directive — that is the port other peers will use to reach this onion. |
|
| `transports.tor.advertised_port` | u16 | `443` | Public-facing onion port published in Nostr overlay adverts. Must match the virtual port in torrc's `HiddenServicePort <port> 127.0.0.1:<bind_port>` directive — that is the port other peers will use to reach this onion. |
|
||||||
|
|
||||||
**Named instances.** Like other transports, multiple Tor instances can
|
**Named instances.** Like other transports, multiple Tor instances can
|
||||||
@@ -721,13 +747,15 @@ be configured with named sub-keys for different SOCKS5 proxy endpoints.
|
|||||||
### BLE (`transports.ble.*`)
|
### BLE (`transports.ble.*`)
|
||||||
|
|
||||||
Bluetooth Low Energy transport using L2CAP Connection-Oriented Channels.
|
Bluetooth Low Energy transport using L2CAP Connection-Oriented Channels.
|
||||||
Linux + glibc only — at build time, `build.rs` probes for the BlueZ /
|
Compiled on glibc Linux and on Android. At build time, `build.rs` sets
|
||||||
`bluer` crate dependencies and sets the `bluer_available` `cfg`; the BLE
|
`bluer_available` from the target triple (Linux and not musl) and sets
|
||||||
runtime is gated behind `#[cfg(bluer_available)]`. There is no Cargo
|
`ble_available` for that or Android; the BLE runtime is gated behind
|
||||||
feature flag to toggle. On non-glibc Linux (musl) or non-Linux platforms,
|
`#[cfg(ble_available)]`, with `bluer_available` gating only the BlueZ
|
||||||
BLE config still parses but the transport runtime is absent and config
|
backend inside it. There is no Cargo feature flag to toggle. On musl
|
||||||
entries become no-ops. Communicates with BlueZ via D-Bus through the
|
Linux or any other platform, BLE config still parses but the transport
|
||||||
`bluer` crate.
|
runtime is absent and config entries become no-ops. On glibc Linux the
|
||||||
|
transport communicates with BlueZ via D-Bus through the `bluer` crate;
|
||||||
|
on Android the radio is supplied by the embedding application.
|
||||||
|
|
||||||
| Parameter | Type | Default | Description |
|
| Parameter | Type | Default | Description |
|
||||||
|-----------|------|---------|-------------|
|
|-----------|------|---------|-------------|
|
||||||
@@ -810,13 +838,21 @@ Static peer list. Each entry defines a peer to connect to.
|
|||||||
| `peers[].npub` | string | *(required)* | Peer's Nostr public key (npub-encoded) |
|
| `peers[].npub` | string | *(required)* | Peer's Nostr public key (npub-encoded) |
|
||||||
| `peers[].alias` | string | *(none)* | Human-readable name for logging |
|
| `peers[].alias` | string | *(none)* | Human-readable name for logging |
|
||||||
| `peers[].addresses` | list | `[]` | Transport addresses for the peer. May be left empty (or omitted) when `via_nostr: true`, in which case the daemon resolves endpoints from the peer's Nostr advert at dial time. |
|
| `peers[].addresses` | list | `[]` | Transport addresses for the peer. May be left empty (or omitted) when `via_nostr: true`, in which case the daemon resolves endpoints from the peer's Nostr advert at dial time. |
|
||||||
| `peers[].addresses[].transport` | string | *(required)* | Transport type: `udp`, `tcp`, `ethernet`, `tor`, or `ble` |
|
| `peers[].addresses[].transport` | string | *(required)* | Transport type: `udp`, `tcp`, `ethernet`, `tor`, `nym`, or `ble`. A `udp` entry may be qualified with a named instance as `udp/<instance>` (see below). |
|
||||||
| `peers[].addresses[].addr` | string | *(required)* | Transport address. UDP/TCP: `"host:port"` (IP or DNS hostname). Ethernet: `"interface/mac"` (e.g., `"eth0/aa:bb:cc:dd:ee:ff"`). BLE: `"adapter/device_address"` (e.g., `"hci0/AA:BB:CC:DD:EE:FF"`). Tor: `".onion:port"` or `"host:port"` |
|
| `peers[].addresses[].addr` | string | *(required)* | Transport address. UDP/TCP: `"host:port"` (IP or DNS hostname). Ethernet: `"interface/mac"` (e.g., `"eth0/aa:bb:cc:dd:ee:ff"`). BLE: `"adapter/device_address"` (e.g., `"hci0/AA:BB:CC:DD:EE:FF"`). Tor: `".onion:port"` or `"host:port"` |
|
||||||
| `peers[].addresses[].priority` | u8 | `100` | Address priority (lower = preferred) |
|
| `peers[].addresses[].priority` | u8 | `100` | Address priority (lower = preferred) |
|
||||||
| `peers[].connect_policy` | string | `"auto_connect"` | Connection policy: `auto_connect`, `on_demand`, or `manual`. Note: `on_demand` and `manual` are reserved for future use; the only policy currently honored at runtime is `auto_connect`. |
|
| `peers[].connect_policy` | string | `"auto_connect"` | Connection policy: `auto_connect`, `on_demand`, or `manual`. Note: `on_demand` and `manual` are reserved for future use; the only policy currently honored at runtime is `auto_connect`. |
|
||||||
| `peers[].auto_reconnect` | bool | `true` | Automatically reconnect after MMP link-dead removal (exponential backoff, unlimited retries) |
|
| `peers[].auto_reconnect` | bool | `true` | Automatically reconnect after MMP link-dead removal (exponential backoff, unlimited retries) |
|
||||||
| `peers[].via_nostr` | bool | `false` | Append Nostr advert-derived endpoints after static addresses for this peer |
|
| `peers[].via_nostr` | bool | `false` | Append Nostr advert-derived endpoints after static addresses for this peer |
|
||||||
|
|
||||||
|
**Named UDP instances.** Where several UDP transports are configured
|
||||||
|
under named sub-keys, a peer address can name the one it belongs to by
|
||||||
|
writing the transport field as `udp/<instance>`, for example
|
||||||
|
`udp/aware`. A bare `udp` matches any instance. The qualifier resolves
|
||||||
|
only for `udp`: writing it on any other transport type, or naming a UDP
|
||||||
|
instance that is not configured, fails config load with a validation
|
||||||
|
error rather than falling back to another instance.
|
||||||
|
|
||||||
## Gateway (`gateway.*`)
|
## Gateway (`gateway.*`)
|
||||||
|
|
||||||
The `gateway.*` block configures the optional `fips-gateway`
|
The `gateway.*` block configures the optional `fips-gateway`
|
||||||
@@ -834,7 +870,7 @@ end-to-end design, see
|
|||||||
| Parameter | Type | Default | Description |
|
| Parameter | Type | Default | Description |
|
||||||
|-----------|------|---------|-------------|
|
|-----------|------|---------|-------------|
|
||||||
| `gateway.enabled` | bool | `false` | Enable the gateway. Must be `true` for `fips-gateway` to start. |
|
| `gateway.enabled` | bool | `false` | Enable the gateway. Must be `true` for `fips-gateway` to start. |
|
||||||
| `gateway.pool` | string | *(required)* | Virtual IPv6 pool CIDR (e.g., `"fd01::/112"`). Must not overlap with the FIPS mesh address space (`fd00::/8`) or any address space already in use on the LAN. The `/112` size yields 65 536 virtual IPs, which is the gateway's hard cap regardless of CIDR width. |
|
| `gateway.pool` | string | *(required)* | Virtual IPv6 pool CIDR (e.g., `"fd01::/112"`). Must not overlap with the FIPS mesh address space (`fd00::/8`) or any address space already in use on the LAN. The `/112` size yields 65 535 usable virtual IPs (address 0 in the pool is skipped), which is the gateway's hard cap regardless of CIDR width. |
|
||||||
| `gateway.lan_interface` | string | *(required)* | LAN-facing network interface name (e.g., `"enp3s0"`). Used for proxy-NDP entry installation so LAN clients can resolve the link-layer address of allocated virtual IPs. |
|
| `gateway.lan_interface` | string | *(required)* | LAN-facing network interface name (e.g., `"enp3s0"`). Used for proxy-NDP entry installation so LAN clients can resolve the link-layer address of allocated virtual IPs. |
|
||||||
| `gateway.pool_grace_period` | u64 | `60` | Seconds a virtual-IP allocation is retained after its last referencing session ends, before the address is returned to the free pool. Larger values reduce churn for short-lived flows; smaller values reclaim addresses faster. |
|
| `gateway.pool_grace_period` | u64 | `60` | Seconds a virtual-IP allocation is retained after its last referencing session ends, before the address is returned to the free pool. Larger values reduce churn for short-lived flows; smaller values reclaim addresses faster. |
|
||||||
|
|
||||||
@@ -997,6 +1033,53 @@ node:
|
|||||||
coord_size: 100000
|
coord_size: 100000
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Deprecated Keys
|
||||||
|
|
||||||
|
Every key below still loads. Nothing has been removed, so a config file
|
||||||
|
written against v0.4.x keeps working after an upgrade. The old spellings
|
||||||
|
are scheduled for removal at the next wire-protocol cutover, so migrate
|
||||||
|
when convenient rather than urgently.
|
||||||
|
|
||||||
|
### `node.discovery.*` split into `node.lookup.*` and `node.rendezvous.*`
|
||||||
|
|
||||||
|
The single `node.discovery` table mixed two unrelated jobs: resolving
|
||||||
|
coordinates for a mesh address already known (lookup), and finding peers
|
||||||
|
to connect to in the first place (rendezvous). It is now two tables.
|
||||||
|
|
||||||
|
| Deprecated key | Replacement |
|
||||||
|
|----------------|-------------|
|
||||||
|
| `node.discovery.ttl` | `node.lookup.ttl` |
|
||||||
|
| `node.discovery.attempt_timeouts_secs` | `node.lookup.attempt_timeouts_secs` |
|
||||||
|
| `node.discovery.recent_expiry_secs` | `node.lookup.recent_expiry_secs` |
|
||||||
|
| `node.discovery.backoff_base_secs` | `node.lookup.backoff_base_secs` |
|
||||||
|
| `node.discovery.backoff_max_secs` | `node.lookup.backoff_max_secs` |
|
||||||
|
| `node.discovery.forward_min_interval_secs` | `node.lookup.forward_min_interval_secs` |
|
||||||
|
| `node.discovery.nostr.*` (whole sub-table) | `node.rendezvous.nostr.*` |
|
||||||
|
| `node.discovery.lan.*` (whole sub-table) | `node.rendezvous.lan.*` |
|
||||||
|
|
||||||
|
Behaviour of a deployed `node.discovery:` block: each config file is
|
||||||
|
folded as it is parsed, before the cross-file merge, and a single warning
|
||||||
|
is logged on the `fips::config` target naming the move. Only the keys
|
||||||
|
actually present in the old block are applied; the rest keep their
|
||||||
|
defaults. The compat block is never written back out, so anything that
|
||||||
|
re-serializes the configuration emits the new spelling only.
|
||||||
|
|
||||||
|
**Mixing the two spellings inside one file is not a merge.** The fold
|
||||||
|
runs after that file is parsed, so a value under `node.discovery`
|
||||||
|
overwrites whatever the corresponding `node.lookup` or `node.rendezvous`
|
||||||
|
key held in the same file. Use one spelling per file.
|
||||||
|
|
||||||
|
### `transports.ethernet.discovery` renamed to `transports.ethernet.listen`
|
||||||
|
|
||||||
|
| Deprecated key | Replacement |
|
||||||
|
|----------------|-------------|
|
||||||
|
| `transports.ethernet.discovery` | `transports.ethernet.listen` |
|
||||||
|
|
||||||
|
This one is a plain alias rather than a compat fold, so both spellings
|
||||||
|
parse into the same field and no warning is logged. The name changed
|
||||||
|
because the key never controlled discovery in the `node.discovery`
|
||||||
|
sense: it decides whether the interface listens for neighbour beacons.
|
||||||
|
|
||||||
## Complete Reference
|
## Complete Reference
|
||||||
|
|
||||||
The full YAML structure with all defaults:
|
The full YAML structure with all defaults:
|
||||||
@@ -1011,6 +1094,7 @@ node:
|
|||||||
base_rtt_ms: 100
|
base_rtt_ms: 100
|
||||||
heartbeat_interval_secs: 10
|
heartbeat_interval_secs: 10
|
||||||
link_dead_timeout_secs: 30
|
link_dead_timeout_secs: 30
|
||||||
|
# drain_timeout_secs: 2 # bounded Draining phase; absent = 2s
|
||||||
limits:
|
limits:
|
||||||
max_connections: 256
|
max_connections: 256
|
||||||
max_peers: 128
|
max_peers: 128
|
||||||
@@ -1033,14 +1117,18 @@ node:
|
|||||||
coord_size: 50000
|
coord_size: 50000
|
||||||
coord_ttl_secs: 300
|
coord_ttl_secs: 300
|
||||||
identity_size: 10000
|
identity_size: 10000
|
||||||
discovery:
|
lookup:
|
||||||
ttl: 64
|
ttl: 64
|
||||||
attempt_timeouts_secs: [1, 2, 4, 8]
|
attempt_timeouts_secs: [1, 2, 4, 8]
|
||||||
recent_expiry_secs: 10
|
recent_expiry_secs: 10
|
||||||
backoff_base_secs: 0
|
backoff_base_secs: 0
|
||||||
backoff_max_secs: 0
|
backoff_max_secs: 0
|
||||||
forward_min_interval_secs: 2
|
forward_min_interval_secs: 2
|
||||||
# lan: # uncomment to enable mDNS LAN discovery
|
rendezvous:
|
||||||
|
# nostr: # uncomment to enable Nostr rendezvous
|
||||||
|
# enabled: true # opt-in, default false
|
||||||
|
# policy: configured_only # disabled | configured_only | open
|
||||||
|
# lan: # uncomment to enable mDNS LAN rendezvous
|
||||||
# enabled: true # opt-in, default false
|
# enabled: true # opt-in, default false
|
||||||
# scope: "my-mesh" # optional per-network scope filter
|
# scope: "my-mesh" # optional per-network scope filter
|
||||||
tree:
|
tree:
|
||||||
@@ -1143,6 +1231,8 @@ transports:
|
|||||||
# # hostname_file: "/var/lib/tor/fips_onion_service/hostname"
|
# # hostname_file: "/var/lib/tor/fips_onion_service/hostname"
|
||||||
# # bind_addr: "127.0.0.1:8443"
|
# # bind_addr: "127.0.0.1:8443"
|
||||||
# # max_inbound_connections: 64
|
# # max_inbound_connections: 64
|
||||||
|
# # advertise_on_nostr: false # publish this onion in Nostr adverts
|
||||||
|
# # # (requires node.rendezvous.nostr.enabled)
|
||||||
# # advertised_port: 443 # public-facing onion port for Nostr adverts
|
# # advertised_port: 443 # public-facing onion port for Nostr adverts
|
||||||
# nym: # uncomment to enable Nym mixnet transport (outbound-only)
|
# nym: # uncomment to enable Nym mixnet transport (outbound-only)
|
||||||
# socks5_addr: "127.0.0.1:1080" # nym-socks5-client SOCKS5 proxy address
|
# socks5_addr: "127.0.0.1:1080" # nym-socks5-client SOCKS5 proxy address
|
||||||
|
|||||||
@@ -80,8 +80,10 @@ One JSON object per line.
|
|||||||
### I/O timeouts
|
### I/O timeouts
|
||||||
|
|
||||||
The daemon enforces a 5-second timeout for both the request read and
|
The daemon enforces a 5-second timeout for both the request read and
|
||||||
the response write. If the connection idles longer than that, the
|
the response write. A request that does not arrive within the read
|
||||||
daemon closes it with no response.
|
timeout is answered with a `read timeout` error response and the
|
||||||
|
connection is then closed; a response that cannot be written within the
|
||||||
|
write timeout is dropped and the connection closed with no response.
|
||||||
|
|
||||||
### Common error messages
|
### Common error messages
|
||||||
|
|
||||||
@@ -89,7 +91,7 @@ daemon closes it with no response.
|
|||||||
| ------- | ----- |
|
| ------- | ----- |
|
||||||
| `empty request` | Connection closed before a newline was received. |
|
| `empty request` | Connection closed before a newline was received. |
|
||||||
| `invalid request: <serde error>` | Malformed JSON or missing `command`. |
|
| `invalid request: <serde error>` | Malformed JSON or missing `command`. |
|
||||||
| `request too large` | Request exceeded 4096 bytes. |
|
| `read error: request too large` | Request exceeded 4096 bytes. |
|
||||||
| `read timeout` / `read error: ...` | Slow client or transport failure. |
|
| `read timeout` / `read error: ...` | Slow client or transport failure. |
|
||||||
| `unknown command: <name>` | Command not registered with this daemon. |
|
| `unknown command: <name>` | Command not registered with this daemon. |
|
||||||
| `missing params for <name>` | Command requires `params` but none were provided. |
|
| `missing params for <name>` | Command requires `params` but none were provided. |
|
||||||
|
|||||||
@@ -30,13 +30,13 @@ under its identity.
|
|||||||
- `d` — fixed to the literal `fips-overlay-v1` (the application
|
- `d` — fixed to the literal `fips-overlay-v1` (the application
|
||||||
identifier baked into the binary). Together with `pubkey`, this
|
identifier baked into the binary). Together with `pubkey`, this
|
||||||
identifies the unique replaceable event slot.
|
identifies the unique replaceable event slot.
|
||||||
- `protocol` — the configured `node.discovery.nostr.app` value
|
- `protocol`: the configured `node.rendezvous.nostr.app` value
|
||||||
(default `fips-overlay-v1`). Distinct from the `d` tag so the
|
(default `fips-overlay-v1`). Distinct from the `d` tag so the
|
||||||
application string can evolve without breaking the replaceable
|
application string can evolve without breaking the replaceable
|
||||||
event slot.
|
event slot.
|
||||||
- `version` — protocol version string (currently `"1"`).
|
- `version` — protocol version string (currently `"1"`).
|
||||||
- `expiration` — NIP-40 expiration timestamp set to now +
|
- `expiration` — NIP-40 expiration timestamp set to now +
|
||||||
`node.discovery.nostr.advert_ttl_secs` (default 3600 seconds).
|
`node.rendezvous.nostr.advert_ttl_secs` (default 3600 seconds).
|
||||||
Conforming relays stop serving the event after this time.
|
Conforming relays stop serving the event after this time.
|
||||||
|
|
||||||
### Content
|
### Content
|
||||||
@@ -143,7 +143,7 @@ The `TraversalAnswer` echoes `sessionId` and carries:
|
|||||||
|
|
||||||
Dialer publishes offers to the recipient's NIP-17 inbox relays (kind
|
Dialer publishes offers to the recipient's NIP-17 inbox relays (kind
|
||||||
10050) when available; otherwise to the local
|
10050) when available; otherwise to the local
|
||||||
`node.discovery.nostr.dm_relays` list. The responder publishes the
|
`node.rendezvous.nostr.dm_relays` list. The responder publishes the
|
||||||
answer back through the same relay channel.
|
answer back through the same relay channel.
|
||||||
|
|
||||||
## Kind 10050 — NIP-17 Inbox Relay List
|
## Kind 10050 — NIP-17 Inbox Relay List
|
||||||
|
|||||||
@@ -294,7 +294,7 @@ for the metadata-privacy model and the rejection of onion routing.
|
|||||||
| Ethernet | Listens on configured interface (raw `AF_PACKET`) | EtherType 0x2121 on selected interface | Per-flag `listen`, `announce`, `auto_connect`, `accept_connections` |
|
| Ethernet | Listens on configured interface (raw `AF_PACKET`) | EtherType 0x2121 on selected interface | Per-flag `listen`, `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` |
|
| 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.*` |
|
| 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` |
|
| Nostr discovery | Off by default | n/a (relay client, not a listener) | Operator sets `node.rendezvous.nostr.enabled: true` |
|
||||||
|
|
||||||
The mesh-layer `fips0` interface is reachable from any mesh node that
|
The mesh-layer `fips0` interface is reachable from any mesh node that
|
||||||
can route to you, not only direct peers — your direct peers forward
|
can route to you, not only direct peers — your direct peers forward
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ module.
|
|||||||
| `bytes_sent` / `bytes_recv` | Byte counters |
|
| `bytes_sent` / `bytes_recv` | Byte counters |
|
||||||
| `send_errors` / `recv_errors` | Send/receive failures |
|
| `send_errors` / `recv_errors` | Send/receive failures |
|
||||||
| `beacons_sent` / `beacons_recv` | Peer-discovery beacon traffic |
|
| `beacons_sent` / `beacons_recv` | Peer-discovery beacon traffic |
|
||||||
|
| `beacons_dropped` | Received beacons the discovery buffer had no room for |
|
||||||
| `frames_too_short` | Frames below minimum length, dropped |
|
| `frames_too_short` | Frames below minimum length, dropped |
|
||||||
| `frames_too_long` | Frames above transport MTU, dropped |
|
| `frames_too_long` | Frames above transport MTU, dropped |
|
||||||
|
|
||||||
@@ -92,10 +93,16 @@ Nym is outbound-only (no inbound listener), so there are no
|
|||||||
| `connections_established` | Successful outbound L2CAP connections |
|
| `connections_established` | Successful outbound L2CAP connections |
|
||||||
| `connections_accepted` | Accepted inbound L2CAP connections |
|
| `connections_accepted` | Accepted inbound L2CAP connections |
|
||||||
| `connections_rejected` | Rejected inbound (limit exceeded) |
|
| `connections_rejected` | Rejected inbound (limit exceeded) |
|
||||||
|
| `handshakes_aborted` | Inbound handshakes aborted to free an in-flight slot |
|
||||||
| `connect_timeouts` | Connection timeout count |
|
| `connect_timeouts` | Connection timeout count |
|
||||||
|
| `connect_errors` | Outbound connects that failed with an error rather than timing out |
|
||||||
|
| `pubkey_exchange_failures` | Connections dropped because the pre-handshake pubkey exchange failed |
|
||||||
|
| `tiebreaker_yields` | Outbound connections stood down by the cross-probe tie-breaker |
|
||||||
|
| `tiebreaker_drops` | Inbound connections stood down by the cross-probe tie-breaker |
|
||||||
| `pool_evictions` | Connection-pool entries evicted |
|
| `pool_evictions` | Connection-pool entries evicted |
|
||||||
| `advertisements_sent` | BLE advertisements emitted |
|
| `advertisements_sent` | BLE advertisements emitted |
|
||||||
| `scan_results` | BLE scan results observed |
|
| `scan_results` | BLE scan results observed |
|
||||||
|
| `duplicate_node_declines` | Connections declined because the peer was already linked on another link address |
|
||||||
|
|
||||||
## See also
|
## See also
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,787 @@
|
|||||||
|
# FIPS v0.5.0
|
||||||
|
|
||||||
|
**Released**: 2026-08-23 (provisional: the date is confirmed when the
|
||||||
|
release is tagged)
|
||||||
|
|
||||||
|
v0.5.0 is a feature release, the first since v0.4.0. It adds two new
|
||||||
|
supported platforms, a new way for applications to use the mesh, and a set
|
||||||
|
of smaller additions across transports, diagnostics and packaging. It also
|
||||||
|
renames part of the configuration surface, carries four fixes for bugs that
|
||||||
|
shipped in earlier releases, and adds four security fixes specific to this
|
||||||
|
line.
|
||||||
|
|
||||||
|
**Upgrading.** Nodes upgrade in any order. A v0.5.0 node and a v0.4.x node
|
||||||
|
peer, rekey and route normally, so there is no flag day and no coordination.
|
||||||
|
If you are coming from v0.4.1 or earlier, read the v0.4.2 notes too: v0.5.0
|
||||||
|
contains that release, and most of this cycle's security work is in it.
|
||||||
|
|
||||||
|
## Platform support
|
||||||
|
|
||||||
|
A port to FreeBSD, on x86_64, has been made, with a native package. The
|
||||||
|
daemon, `fipsctl` and `fipstop` build and run there; `fips-gateway` stays
|
||||||
|
Linux-only. One thing this paves the way for is to create FIPS native
|
||||||
|
support in FreeBSD-based firewall appliances, such as OPNsense and pfSense.
|
||||||
|
|
||||||
|
Android support is officially provided in this release, but for use as an
|
||||||
|
embedded crate in other applications rather than as a standalone daemon.
|
||||||
|
Android applications own the TUN adapter, and the FIPS crate provides a Rust
|
||||||
|
API to send and receive packets over the mesh. The Bluetooth LE transport is
|
||||||
|
now supported on Android, and is interoperable with Linux.
|
||||||
|
|
||||||
|
## Native API for datagrams
|
||||||
|
|
||||||
|
The FIPS mesh sends datagrams between cryptographic endpoints, and the main
|
||||||
|
way this has been used up until now has been to emulate an IPv6 network
|
||||||
|
adapter and tunnel IPv6 packets over it. This has allowed existing IPv6
|
||||||
|
applications to use the mesh without any changes.
|
||||||
|
|
||||||
|
The native datagram API lets an application written to it communicate
|
||||||
|
directly over the mesh, addressing a peer by public key and exchanging
|
||||||
|
datagrams on a file descriptor. It bypasses the local IP stack and the
|
||||||
|
emulated IPv6 adapter entirely. How nodes peer with each other does not
|
||||||
|
change, and nothing on the wire changes.
|
||||||
|
|
||||||
|
## Other changes
|
||||||
|
|
||||||
|
OpenWrt gains an 802.11s mesh between routers and the open `!FIPS` client
|
||||||
|
SSID, both opt-in. The Bluetooth LE transport is refactored so Linux and
|
||||||
|
Android share one implementation with a backend for each. A peer address may
|
||||||
|
name which instance of a transport it belongs to, so a node running several
|
||||||
|
listeners of one type can be dialled on the right one.
|
||||||
|
|
||||||
|
`fipsctl` gains two subcommands: `probe`, which reports in five stages
|
||||||
|
whether one target is reachable and where the attempt stopped, and
|
||||||
|
`address`, which derives a node's mesh address from a key file with no
|
||||||
|
daemon running. Shutdown now drains before it closes, and an optional
|
||||||
|
profiler measures where the maintenance tick spends its time.
|
||||||
|
|
||||||
|
The Nix flake gains a NixOS module and an overlay, so a flake consumer
|
||||||
|
enables the daemon with one line rather than hand-rolling a systemd unit.
|
||||||
|
|
||||||
|
## FreeBSD details
|
||||||
|
|
||||||
|
The `.pkg` is published on the release page. No aarch64 artifact is produced
|
||||||
|
and that combination is not verified here.
|
||||||
|
|
||||||
|
FreeBSD follows the macOS install layout: `/usr/local/etc/fips` for config,
|
||||||
|
`hosts`, `peers.allow`, `peers.deny` and `fipsctl keygen` output, and
|
||||||
|
`/var/run/fips` for the control socket. Packaging under `packaging/freebsd/`
|
||||||
|
builds through `make freebsd` and ships rc.d services, a `fips`
|
||||||
|
control-socket group, service stop and restart across `pkg upgrade`, and
|
||||||
|
`.fips` DNS integration for `local_unbound`, `unbound` and `dnsmasq`. mDNS
|
||||||
|
LAN discovery works, by way of an `mdns-sd` bump to 0.20 that picks up the
|
||||||
|
first `socket-pktinfo` release building on FreeBSD.
|
||||||
|
|
||||||
|
Two changes here reach every platform. That `mdns-sd` bump applies to all
|
||||||
|
targets, and the daemon now disables ANSI color in its logs when stdout is
|
||||||
|
not a terminal.
|
||||||
|
|
||||||
|
Contributed by [@fr34aky](https://github.com/fr34aky)
|
||||||
|
([#129](https://github.com/jmcorgan/fips/pull/129)).
|
||||||
|
|
||||||
|
## The native datagram API
|
||||||
|
|
||||||
|
The API is off by default and its surface may still change, so it ships for
|
||||||
|
client authors to build against and report back on.
|
||||||
|
|
||||||
|
The wire needs no change and gets none. Every FSP data packet has carried a
|
||||||
|
port pair inside its AEAD envelope since v0.2.0, and port 256 is simply the
|
||||||
|
IPv6 shim, so what was missing was a way for a program to ask for a port of
|
||||||
|
its own and be handed the traffic.
|
||||||
|
|
||||||
|
The x-only public key is the address, and an npub is that key written in
|
||||||
|
bech32, so converting between them is a local encoding rather than a lookup
|
||||||
|
or a name service. The 16-byte node address that travels on the wire is a
|
||||||
|
truncated hash of the key, does not invert, and appears nowhere a client can
|
||||||
|
see.
|
||||||
|
|
||||||
|
The API is a direct, best-effort interface to FSP, the session protocol the
|
||||||
|
mesh already speaks. FSP authenticates and encrypts end to end, and it
|
||||||
|
delivers datagrams on a best-effort basis: no acknowledgement, no
|
||||||
|
retransmission, no ordering guarantee and no flow control between the two
|
||||||
|
ends. A program that needs any of those builds them itself, or runs an
|
||||||
|
ordinary reliable protocol over the `fips0` adapter instead.
|
||||||
|
|
||||||
|
The interface style is deliberately close to Berkeley sockets. A program
|
||||||
|
calls `connect` for a flow to a public key and a port, or `bind` for a port
|
||||||
|
to receive flows on, and from there uses ordinary socket calls on the
|
||||||
|
descriptor it holds.
|
||||||
|
|
||||||
|
A listener is a descriptor. The daemon writes one message per arrival to it,
|
||||||
|
carrying the new flow's descriptor and the peer's address, so `poll`,
|
||||||
|
`select` and `epoll` work on a listener and accepting is a `recvmsg`. There
|
||||||
|
is no accept command and no reject command: refusing a flow is closing the
|
||||||
|
descriptor you were handed.
|
||||||
|
|
||||||
|
The Rust surface mirrors `std::net`, with `FipsStream::connect`,
|
||||||
|
`FipsListener::bind`, `incoming`, `accept`, `io::Result` and an errno
|
||||||
|
mapping rather than a bespoke error type, plus `set_nonblocking`, `AsFd` and
|
||||||
|
four deadline methods under the names and signatures `std::net` uses for the
|
||||||
|
same jobs.
|
||||||
|
|
||||||
|
**One rule has no counterpart in Berkeley sockets and a client author must
|
||||||
|
know it: the v1 wire carries no half-close.** Nothing peer-driven ever
|
||||||
|
closes a flow, so a server written to read until the flow ends waits for a
|
||||||
|
signal that cannot arrive.
|
||||||
|
|
||||||
|
The listener uses `SOCK_SEQPACKET` on Linux and `SOCK_DGRAM` on macOS and
|
||||||
|
FreeBSD; both keep the message boundaries the API's contract with its
|
||||||
|
clients rests on. macOS does not implement `SOCK_SEQPACKET` for `AF_UNIX` at
|
||||||
|
all. FreeBSD accepts the constant and returns a socket that is not an
|
||||||
|
atomic-record socket, so consecutive messages coalesce and a zero-length
|
||||||
|
message is dropped rather than delivered; both were measured on the FreeBSD
|
||||||
|
15.1 image rather than reasoned about. The three kernels signal a closed
|
||||||
|
peer differently and were measured too, so the receive path treats a Darwin
|
||||||
|
or FreeBSD `ECONNRESET` as end of file alongside the `POLLHUP` and zero-byte
|
||||||
|
read that Linux gives.
|
||||||
|
|
||||||
|
Contributed by [@jmcorgan](https://github.com/jmcorgan) (Johnathan
|
||||||
|
Corgan).
|
||||||
|
|
||||||
|
Start with [the walkthrough](../tutorials/native-api-walkthrough.md), then
|
||||||
|
[how to write a client](../how-to/write-a-native-api-client.md); [the
|
||||||
|
reference](../reference/native-api.md) carries the full surface.
|
||||||
|
|
||||||
|
## OpenWrt
|
||||||
|
|
||||||
|
Two capabilities land together here, and the second is stacked on the first.
|
||||||
|
Both arrive as opt-in helpers: a package must not commandeer a router's
|
||||||
|
radios on install, so neither runs unless you run it.
|
||||||
|
|
||||||
|
### 802.11s mesh backhaul
|
||||||
|
|
||||||
|
FIPS can now be the encryption and routing layer over router-to-router radio
|
||||||
|
links. The mesh runs **open**, with `mesh_fwding 0`: SAE would duplicate the
|
||||||
|
Noise layer and force ath10k into raw mode, and the FIPS spanning tree is
|
||||||
|
the routing layer, so the 802.11s link is deliberately left as a bare L2
|
||||||
|
neighbor link and FIPS provides all encryption, authentication and routing
|
||||||
|
over it.
|
||||||
|
|
||||||
|
`fips-mesh-setup` is a UCI helper that creates a mesh point per radio:
|
||||||
|
`radio0` becomes `fips-mesh0`, `radio1` becomes `fips-mesh1`, with a
|
||||||
|
free-index fallback and a collision guard. A dual-band router gets one
|
||||||
|
instance per radio, and FIPS treats the two paths as **failover rather than
|
||||||
|
multipath**: cross-connection resolution keeps one active link per peer and
|
||||||
|
the second band stands by, re-establishing after keepalive timeout.
|
||||||
|
|
||||||
|
The shipped `fips.yaml` carries the matching `mesh0` and `mesh1`
|
||||||
|
Ethernet-transport entries commented out, so a stock install that never
|
||||||
|
creates the interfaces logs no per-boot interface-missing warning. The
|
||||||
|
helper uncomments the block when it creates the interface and re-comments it
|
||||||
|
on remove. Two silent non-peering causes found in the field are surfaced by
|
||||||
|
the helper's warnings: a radio left on `auto` channel, and a `sta` interface
|
||||||
|
dragging the radio to its upstream access point's channel.
|
||||||
|
|
||||||
|
Contributed by [@Origami74](https://github.com/Origami74)
|
||||||
|
([#123](https://github.com/jmcorgan/fips/pull/123)). The full procedure
|
||||||
|
is in
|
||||||
|
[the 802.11s backhaul how-to](../how-to/set-up-80211s-mesh-backhaul.md).
|
||||||
|
|
||||||
|
### The open `!FIPS` access SSID
|
||||||
|
|
||||||
|
Stacked on that backhaul, every FIPS router can broadcast the same open
|
||||||
|
`!FIPS` SSID, forming one standard ESS that phones and laptops save once and
|
||||||
|
roam between natively, with the Noise IK handshake as the only security
|
||||||
|
layer. The leading `!` sorts it to the top of alphabetically ordered network
|
||||||
|
pickers. The encryption type must be uniform across routers or clients treat
|
||||||
|
the ESS as separate saved networks.
|
||||||
|
|
||||||
|
`fips-ap-setup` creates the `fips-ap0` open access point on an isolated
|
||||||
|
network. IPv6 is a static ULA /64 announced by router advertisement, so
|
||||||
|
addressing is stateless SLAAC with DHCPv6 off; IPv4 is a DHCPv4 lease out of
|
||||||
|
a fixed `10.21.<N>.0/24`, deliberately identical on every router so a
|
||||||
|
roaming client's lease stays valid. Both sit behind a locked-down `fips_ap`
|
||||||
|
firewall zone with no path to `br-lan` or the WAN, passing only DHCPv4,
|
||||||
|
ICMPv6, mDNS and the FIPS transports. There is no internet by design, so
|
||||||
|
phones keep cellular as their default route, and the addressing is what an
|
||||||
|
Android client's connectivity check needs to stay associated. The helper
|
||||||
|
also uncomments the `node.rendezvous.lan` block, since a phone app cannot
|
||||||
|
open raw Ethernet sockets and DNS-SD is how it finds the daemon.
|
||||||
|
|
||||||
|
Contributed by [@Origami74](https://github.com/Origami74)
|
||||||
|
([#126](https://github.com/jmcorgan/fips/pull/126)). The full procedure
|
||||||
|
is in
|
||||||
|
[the open access SSID how-to](../how-to/set-up-open-access-ssid.md).
|
||||||
|
|
||||||
|
## Bluetooth LE peering
|
||||||
|
|
||||||
|
The BLE transport has been refactored so the code common to Linux and
|
||||||
|
Android is implemented once, with a separate backend for each platform. Most
|
||||||
|
of the work is contributed. The transport compiles on every glibc Linux
|
||||||
|
target and on Android, and is excluded on musl; the gate is `ble_available`,
|
||||||
|
and a platform with no concrete backend now fails the build rather than
|
||||||
|
compiling a transport that starts, reports itself up and never peers.
|
||||||
|
|
||||||
|
A peer is recognised by node identity rather than by its link address.
|
||||||
|
Resolvable private addresses rotate continually and modern phones use them
|
||||||
|
by default, so every rotation presented as a brand-new device and none of
|
||||||
|
the already-connected guards could tell.
|
||||||
|
|
||||||
|
The L2CAP PSM is now decided by the backend. BlueZ is the exception in
|
||||||
|
letting an application choose the PSM it binds: Android and macOS both
|
||||||
|
return an OS-assigned one that cannot be requested, and before a connection
|
||||||
|
exists there is no channel on which to be told it. So `listen` reports the
|
||||||
|
PSM it actually bound, the advertisement carries it alongside the 128-bit
|
||||||
|
FIPS service UUID, and a dialer takes it from the scan, falling back to the
|
||||||
|
configured value for a peer that advertises none.
|
||||||
|
|
||||||
|
Probe retry is bounded. A discovered address that failed to connect was
|
||||||
|
re-dialled every cooldown for the life of the process, and because BLE
|
||||||
|
hardware caps concurrent connections at roughly four to ten, a handful of
|
||||||
|
unreachable peers starved discovery of everything behind them. Failing
|
||||||
|
addresses now back off by powers of two and the retry book itself is capped,
|
||||||
|
so rotating private addresses cannot grow it without bound. Each connect
|
||||||
|
outcome has its own counter and structured log line carrying the role, the
|
||||||
|
outcome, the PSM dialled and how long the peer took to conclude, which is
|
||||||
|
what distinguishes a peer out of range from one being dialled at the wrong
|
||||||
|
PSM.
|
||||||
|
|
||||||
|
Inbound handshakes run off the accept loop, eight in flight, aborting the
|
||||||
|
oldest at the bound. The exchange previously ran inline, so a peer that
|
||||||
|
connected and then said nothing held the loop for the full 5-second deadline
|
||||||
|
and the effective inbound concurrency was one.
|
||||||
|
|
||||||
|
## Node and transport control
|
||||||
|
|
||||||
|
### Shutdown now drains before it closes
|
||||||
|
|
||||||
|
On the shutdown signal the node broadcasts Disconnect to all peers and then
|
||||||
|
keeps serving for a bounded window, exiting early once all peers are gone.
|
||||||
|
The window is the new `node.drain_timeout_secs`, default 2 seconds. Teardown
|
||||||
|
was previously immediate.
|
||||||
|
|
||||||
|
Under systemd or launchd this shows up as a stop taking up to two seconds
|
||||||
|
longer than it used to. If your service manager has a short stop timeout, or
|
||||||
|
you have tooling that expects the process to be gone immediately, that is
|
||||||
|
the thing to check. Setting `node.drain_timeout_secs: 0` restores the old
|
||||||
|
behaviour. The immediate stop path used by non-daemon callers is unchanged.
|
||||||
|
|
||||||
|
### A peer address may name a transport instance
|
||||||
|
|
||||||
|
A peer address may name which instance of a transport it belongs to, as
|
||||||
|
`transport: "udp/aware"`, where the part after the slash is the key the
|
||||||
|
transport was configured under. A node running several instances of one type
|
||||||
|
could not be told them apart by a dialer: both bind wildcard sockets, so the
|
||||||
|
address-family test matched either and selection fell through to the lowest
|
||||||
|
transport id. One socket carried every dial and the other never carried
|
||||||
|
traffic. A bare type is unqualified and matches any instance, which is what
|
||||||
|
every existing configuration and caller produces, so nothing changes for a
|
||||||
|
node that does not use the syntax. A qualified name is never substituted
|
||||||
|
with a different instance, since that is the wrong-lane dial the syntax
|
||||||
|
exists to prevent, and the configuration validator rejects a name that no
|
||||||
|
configured transport answers to rather than letting the address be skipped
|
||||||
|
invisibly at every dial.
|
||||||
|
|
||||||
|
## Diagnostics
|
||||||
|
|
||||||
|
### `fipsctl probe`
|
||||||
|
|
||||||
|
For one target, where it sits in the spanning tree relative to this node and
|
||||||
|
whether this node can actually reach it. Five stages report separately,
|
||||||
|
`bloom`, `discovery`, `path`, `session` and `rtt`, because one verdict
|
||||||
|
covering several findings is what sends an operator to the source: "no
|
||||||
|
peer's filter claims this address" says the mesh has never heard of the
|
||||||
|
target, while "a filter claimed it and nothing answered" says the opposite.
|
||||||
|
The probe opens an FSP session, waits for one MMP receiver report to yield a
|
||||||
|
round-trip time, and tears down only what it opened; a session that existed
|
||||||
|
before the probe started is left alone. The path it prints is the
|
||||||
|
least-common-ancestor walk computed from the two sets of coordinates. That
|
||||||
|
is the worst-case fallback route, not necessarily the route a packet takes:
|
||||||
|
a cut-through between peers can deliver in fewer hops, so the tree distance
|
||||||
|
is an upper bound. Nothing here changes the wire format. `--json` emits
|
||||||
|
exactly one document at the end, so a script parsing the report does not
|
||||||
|
have to skip past progress output.
|
||||||
|
|
||||||
|
### `fipsctl address`
|
||||||
|
|
||||||
|
`fipsctl address [npub|hostname]` prints a node's `fd00::/8` mesh
|
||||||
|
address and nothing else, without contacting the daemon. With no argument it
|
||||||
|
derives the local node's address from `fips.key` in the default key
|
||||||
|
directory, falling back to the world-readable `fips.pub` beside it; `--key
|
||||||
|
PATH` names a key or public key file elsewhere. This lets an installer or an
|
||||||
|
image build write a mesh address into a config file at a point where no node
|
||||||
|
is running and none can be, and keeps the derivation in one place rather
|
||||||
|
than reimplemented by whatever needs it.
|
||||||
|
|
||||||
|
### Maintenance tick profiling
|
||||||
|
|
||||||
|
The rx-loop tick arm runs twenty-five unconditional housekeeping steps on
|
||||||
|
one runtime thread and is polled last, so anything slow in it holds inbound
|
||||||
|
packets, TUN traffic and control commands behind it. A new tick-body
|
||||||
|
profiler measures that, on a live node, with no restart.
|
||||||
|
|
||||||
|
It lives behind the new `profiling` Cargo feature and is **off by default**.
|
||||||
|
With the feature off, the instrumentation macro is a pure pass-through, so a
|
||||||
|
default build carries no timing code on the tick path. With it on, `fipsctl
|
||||||
|
profile tick on [--dir PATH]`, `off` and `status` start and stop a capture
|
||||||
|
at runtime. Each capture writes one tab-separated file, by default under
|
||||||
|
`/var/log/fips` and capped at 32 MB, carrying per ten-second interval the
|
||||||
|
exact count, max and total for every step, the whole-tick span, and gauges
|
||||||
|
for ticks, peer count, the gap between successive tick-arm entries and the
|
||||||
|
resulting arm-starvation delay.
|
||||||
|
|
||||||
|
Getting an instrumented build installed is supported directly:
|
||||||
|
`packaging/debian/build-deb.sh --features <list>` builds the `.deb` with a
|
||||||
|
Cargo feature list, and the auto-derived dev Version gains a matching
|
||||||
|
`+<features>` marker so a feature build and a default build of the same
|
||||||
|
commit are no longer indistinguishable. The marker sorts above the unmarked
|
||||||
|
build, so installing a feature build is an upgrade and reverting to the
|
||||||
|
default build is a downgrade: **revert with `dpkg -i`, not `apt install`.**
|
||||||
|
The packaged systemd units gained `LogsDirectory=fips` so the capture
|
||||||
|
directory is created and cleaned up declaratively.
|
||||||
|
|
||||||
|
## Packaging and deployment
|
||||||
|
|
||||||
|
### The NixOS module and overlay
|
||||||
|
|
||||||
|
The flake now exposes a NixOS module and an overlay, so a flake consumer
|
||||||
|
enables the daemon with one line instead of hand-rolling a systemd unit.
|
||||||
|
`overlays.default` adds `pkgs.fips`; `nixosModules.default` provides
|
||||||
|
`services.fips.*` with `enable`, `package`, `configFile`, `openFirewall`
|
||||||
|
(UDP 2121 and TCP 8443) and `dns.enable`, which routes `.fips` to
|
||||||
|
`[::1]:5354` through systemd-resolved declaratively rather than with setup
|
||||||
|
and teardown scripts. `packaging/nixos/README.md` carries a full consumer
|
||||||
|
`flake.nix`. Contributed by [@Origami74](https://github.com/Origami74)
|
||||||
|
(Arjen).
|
||||||
|
|
||||||
|
## For app and embedding developers
|
||||||
|
|
||||||
|
FIPS can now be embedded in an application, and that is how Android is
|
||||||
|
supported: as an embedded crate rather than as a standalone daemon. There is
|
||||||
|
no Android daemon artifact and no host-app integration guide. What ships is
|
||||||
|
a library surface that compiles for Android and a pair of entry points for
|
||||||
|
an app that owns its own tunnel.
|
||||||
|
|
||||||
|
The daemon's desktop transports and TUN operations are now gated by
|
||||||
|
`target_os` rather than by Cargo features, so a plain `cargo build` compiles
|
||||||
|
for every target with no flags, and Android self-excludes the raw Ethernet
|
||||||
|
transport exactly as Windows already did. No Cargo features are introduced
|
||||||
|
and desktop builds are unchanged.
|
||||||
|
|
||||||
|
`Node::enable_app_owned_tun()` gives an embedder that owns the TUN file
|
||||||
|
descriptor, an Android `VpnService` for instance, a channel pair for
|
||||||
|
exchanging IPv6 packet bytes with FIPS, instead of FIPS creating a system
|
||||||
|
TUN device. `start()` then performs no system-TUN and no `CAP_NET_ADMIN`
|
||||||
|
operations. Packets entering this way bypass `handle_tun_packet`, so **the
|
||||||
|
embedder must push only `fd00::/8` destined packets and must clamp TCP MSS
|
||||||
|
on outbound SYNs**.
|
||||||
|
|
||||||
|
`Node::dns_local_addr()` is the DNS companion. An embedder whose resolver is
|
||||||
|
pointed into the tunnel has no system socket aimed at the built-in `.fips`
|
||||||
|
responder, so the accessor reports the address read back off the bound
|
||||||
|
socket: `dns.port = 0` therefore yields the kernel-assigned port. It returns
|
||||||
|
`Some` only while the responder is up. Read it once, after `start()` returns
|
||||||
|
and before the node is moved into a background task; it is not a liveness
|
||||||
|
feed.
|
||||||
|
|
||||||
|
Both contributed by [@Origami74](https://github.com/Origami74)
|
||||||
|
([#127](https://github.com/jmcorgan/fips/pull/127),
|
||||||
|
[#136](https://github.com/jmcorgan/fips/pull/136)). CI cross-compiles the
|
||||||
|
library for `aarch64-linux-android` and runs clippy against it, which is a
|
||||||
|
compile gate. Nothing executes on Android in CI.
|
||||||
|
|
||||||
|
`Node::enable_app_owned_udp_fd()` is a third such entry point. Some hosts
|
||||||
|
associate a socket with one interface or network and steer inbound traffic
|
||||||
|
by that association rather than by destination address, and the socket
|
||||||
|
option that corrects it depends on host state FIPS has no basis to reason
|
||||||
|
about, so the descriptor goes to whoever does. One descriptor arrives per
|
||||||
|
UDP transport that binds, labelled with the instance name it was configured
|
||||||
|
under, so an embedder running several listeners can tell them apart. FIPS
|
||||||
|
keeps owning the socket. Unix only, since the Windows UDP backend has no
|
||||||
|
descriptor.
|
||||||
|
|
||||||
|
## Upgrade notes
|
||||||
|
|
||||||
|
This section is the operator-actionable list. Everything in it applies
|
||||||
|
to every platform.
|
||||||
|
|
||||||
|
### A node with no working transport now fails to start
|
||||||
|
|
||||||
|
Node health is determined once startup completes, instead of every node
|
||||||
|
unconditionally reaching a single running state. **Zero transports up is
|
||||||
|
now fatal**: the node tears down cleanly and the daemon exits with an
|
||||||
|
error. Previously such a node came up, reported itself running, and
|
||||||
|
served nothing.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
fipsctl show transports
|
||||||
|
```
|
||||||
|
|
||||||
|
On the running v0.4.x node, that lists every transport instance with its
|
||||||
|
state. If it lists none, or lists none in an up state, that node will
|
||||||
|
fail to start on v0.5.0 and the fix is a working transport, not a
|
||||||
|
rollback. The common causes are a `transports:` block where every entry
|
||||||
|
is commented out, and an Ethernet transport naming an interface that
|
||||||
|
does not exist on the box, which logs an interface-missing warning and
|
||||||
|
does not come up.
|
||||||
|
|
||||||
|
A node with at least one transport up, and some other configured child
|
||||||
|
that failed, comes up **degraded and serving**, with a warning naming
|
||||||
|
what failed. That covers a second or later transport, Nostr, mDNS, TUN,
|
||||||
|
DNS, and the worker pools. A child you never asked the node to run does
|
||||||
|
not count against it.
|
||||||
|
|
||||||
|
### Three new node states are visible through the control socket
|
||||||
|
|
||||||
|
`Degraded`, `Failed` and `Draining` join the published node state and
|
||||||
|
show up in control queries. `Degraded` is operational, `Failed` is not.
|
||||||
|
If you have a monitor that matches the node state string exactly, teach
|
||||||
|
it the three new values before you upgrade.
|
||||||
|
|
||||||
|
Exit detection also re-evaluates health at runtime for the DNS task, the
|
||||||
|
two TUN threads and mDNS, so a child that dies after a healthy start now
|
||||||
|
shows as degraded rather than staying green.
|
||||||
|
|
||||||
|
### The `node.discovery.*` config table is split
|
||||||
|
|
||||||
|
`node.discovery.*` carried two unrelated things: the scalars that govern
|
||||||
|
mesh lookup, and the settings that govern peer rendezvous. They are now
|
||||||
|
separate tables.
|
||||||
|
|
||||||
|
- `node.lookup.*` takes the mesh-lookup scalars: `ttl`,
|
||||||
|
`attempt_timeouts_secs`, `recent_expiry_secs`, `backoff_base_secs`,
|
||||||
|
`backoff_max_secs`, `forward_min_interval_secs`.
|
||||||
|
- `node.rendezvous.*` takes peer rendezvous: `nostr.*` and `lan.*`.
|
||||||
|
|
||||||
|
**A deployed `node.discovery:` block still loads.** It is folded into
|
||||||
|
the new tables at startup and behaves identically, with a one-time
|
||||||
|
deprecation warning on the `fips::config` target naming the moves. The
|
||||||
|
legacy block will be removed at the v2 cutover, so migrate your
|
||||||
|
`fips.yaml` rather than leaving it.
|
||||||
|
|
||||||
|
Two of these keys are ones you may have adopted only one release ago. If
|
||||||
|
you are coming from v0.4.2, then
|
||||||
|
`node.discovery.nostr.max_concurrent_offers_per_npub` and
|
||||||
|
`node.discovery.nostr.signal_ttl_secs` are now
|
||||||
|
`node.rendezvous.nostr.max_concurrent_offers_per_npub` and
|
||||||
|
`node.rendezvous.nostr.signal_ttl_secs`.
|
||||||
|
|
||||||
|
One further rename, in the same vocabulary: the Ethernet transport's
|
||||||
|
per-interface `discovery` flag is now `listen`, pairing with the
|
||||||
|
existing `announce` flag as receive and transmit. The old `discovery:`
|
||||||
|
key is still accepted through a serde alias, so deployed configs load
|
||||||
|
unchanged, but a config the daemon re-emits will carry `listen:`.
|
||||||
|
|
||||||
|
Every shipped sample, guide and reference now teaches the new spelling.
|
||||||
|
**One exception is worth knowing about on OpenWrt**: `/etc/fips/fips.yaml`
|
||||||
|
is an opkg conffile there, so upgrading a router keeps its existing copy
|
||||||
|
and the new sample is never installed. A router upgraded from an earlier
|
||||||
|
release will still show the old commented `discovery:` examples in its
|
||||||
|
config file. Nothing breaks, since the old key parses, but the file on
|
||||||
|
the router is not the file in the package.
|
||||||
|
|
||||||
|
### Tracing targets moved, so `RUST_LOG` filters go blind rather than error
|
||||||
|
|
||||||
|
The internal restructuring moved modules, and tracing targets follow
|
||||||
|
module paths, so the targets moved with them:
|
||||||
|
|
||||||
|
- `fips::discovery::nostr::*` is now `fips::nostr::*`
|
||||||
|
- mDNS is now `fips::mdns::*`
|
||||||
|
- `fips::tree` is now `fips::proto::stp`
|
||||||
|
- `fips::bloom` is now `fips::proto::bloom`
|
||||||
|
- `fips::protocol` is now `fips::proto::*`
|
||||||
|
- the mesh-lookup subsystem moves from `fips::discovery` to
|
||||||
|
`fips::proto::lookup`
|
||||||
|
|
||||||
|
An existing `RUST_LOG` filter naming an old target still parses. It
|
||||||
|
simply stops matching. The symptom is missing log lines rather than an
|
||||||
|
error, and a filter that has gone blind looks exactly like a subsystem
|
||||||
|
that has gone quiet, so update `RUST_LOG` settings, journal-watch
|
||||||
|
recipes and log-scraping alerts as part of the upgrade. Four targets are
|
||||||
|
named explicitly in the source rather than derived from a module path
|
||||||
|
and are unaffected: `fips::config`, `fips::instr`,
|
||||||
|
`fips::node::handlers::handshake` and `fips::node::handlers::rekey`.
|
||||||
|
|
||||||
|
### The `discovery` metric family is now `lookup`
|
||||||
|
|
||||||
|
The mesh-lookup control-metrics family is emitted under the key `lookup`
|
||||||
|
in `fipsctl stats metrics` and `show routing`. The former `discovery`
|
||||||
|
key is still emitted as a deprecated alias carrying identical counters
|
||||||
|
during the migration window, and will be removed. Point dashboards and
|
||||||
|
alerts at `lookup.*`.
|
||||||
|
|
||||||
|
`fipstop`'s Routing State pane follows: its `Discovery Requests` and
|
||||||
|
`Discovery Responses` sections are now `Lookup Requests` and `Lookup
|
||||||
|
Responses`. The counters are unchanged, so an operator who knows the
|
||||||
|
pane by its old labels is reading the same numbers under new names.
|
||||||
|
|
||||||
|
### The first handshake resend no longer follows its config key
|
||||||
|
|
||||||
|
`node.rate_limit.handshake_resend_interval_ms` no longer governs the
|
||||||
|
**first** outbound handshake resend, which is now armed from a hardcoded
|
||||||
|
1000 ms constant in the peer state machine. The key still governs the
|
||||||
|
second and later resends, alongside
|
||||||
|
`node.rate_limit.handshake_resend_backoff` and
|
||||||
|
`node.rate_limit.handshake_max_resends`. The constant equals the shipped
|
||||||
|
default of 1000, so a deployment that never overrode the key sees no
|
||||||
|
change. A deployment that raised or lowered it will find the first
|
||||||
|
resend still firing at 1000 ms.
|
||||||
|
|
||||||
|
## For library consumers
|
||||||
|
|
||||||
|
**These changes are source-breaking for code that depends on the `fips`
|
||||||
|
crate.** Nothing about the behaviour of the shipped binaries changes,
|
||||||
|
nothing on the wire changes, and an operator who runs the packaged
|
||||||
|
daemon and tools is unaffected. If you do not build against the crate,
|
||||||
|
skip this section.
|
||||||
|
|
||||||
|
The protocol layers were restructured into sans-IO cores with the I/O
|
||||||
|
kept in a thin shell. The consequence for the public surface:
|
||||||
|
|
||||||
|
- The crate-root modules `bloom`, `discovery`, `mmp`, `protocol` and
|
||||||
|
`tree` are gone. The protocol cores moved into an internal `proto`
|
||||||
|
module and are reached through crate-root re-exports: tree types
|
||||||
|
through `proto::stp`, bloom types through `proto::bloom`, and the FSP,
|
||||||
|
STP, lookup, routing and FMP wire types through their matching
|
||||||
|
`proto::*` submodules. `PromotionResult` and `cross_connection_winner`
|
||||||
|
come from `proto::fmp` rather than from `peer`.
|
||||||
|
- The crate-root `HandshakeState`, `PeerConnection`, `PeerSlot` and
|
||||||
|
`ProtocolError` re-exports are removed. **The `HandshakeState` removed
|
||||||
|
here is the peer connection-phase enum, not the Noise handshake type
|
||||||
|
of the same name**, which is untouched and still lives at
|
||||||
|
`fips::noise::HandshakeState`.
|
||||||
|
- `ProtocolError` is replaced by `fips::Error`. Its `Malformed` variant
|
||||||
|
now carries a `&'static str` rather than a `String`, and it gained
|
||||||
|
`BadSizeClass`, `BadCoord` and `BadBloom` variants, so the diagnostic
|
||||||
|
text changed with it.
|
||||||
|
- `PeerSlot` and the `PeerConnection` resend API were unused and are
|
||||||
|
deleted.
|
||||||
|
- `Node::connections()` is now `pub(crate)` and yields the internal peer
|
||||||
|
machine rather than a `PeerConnection`. A consumer that walked links
|
||||||
|
through it should use `Node::peers()`, `Node::get_peer()` and
|
||||||
|
`Node::peer_count()` over `ActivePeer`, all of which remain public.
|
||||||
|
|
||||||
|
Two new crate-root modules, `nostr` and `mdns`, own peer rendezvous and
|
||||||
|
LAN discovery, and the crate root gains the `is_punch_packet` helper and
|
||||||
|
the `CoordError`, `MtuExceeded`, `COORDS_REQUIRED_SIZE` and
|
||||||
|
`MTU_EXCEEDED_SIZE` exports.
|
||||||
|
|
||||||
|
## Data plane and diagnostics
|
||||||
|
|
||||||
|
- **Batched macOS receives on connected UDP peer drains.** The connected
|
||||||
|
UDP path now uses `recvmsg_x(2)`, matching the wildcard UDP receive
|
||||||
|
path instead of issuing one `recv(2)` syscall per queued datagram.
|
||||||
|
Contributed by Martti Malmi ([@mmalmi](https://github.com/mmalmi),
|
||||||
|
[#135](https://github.com/jmcorgan/fips/pull/135)).
|
||||||
|
- **Allocation-free next-hop selection.** Routing next-hop selection
|
||||||
|
visits borrowed peers and coordinates instead of allocating candidate
|
||||||
|
snapshots for each forwarded packet. Contributed by Martti Malmi
|
||||||
|
([@mmalmi](https://github.com/mmalmi),
|
||||||
|
[#134](https://github.com/jmcorgan/fips/pull/134)).
|
||||||
|
- **A connected UDP socket that cannot open now names the syscall and
|
||||||
|
the address.** The local address for `bind`, the peer address for
|
||||||
|
`connect`. Both paths previously returned a bare OS error that the
|
||||||
|
caller wrapped identically, so a field report of `Address already in
|
||||||
|
use` could not be attributed to either, and the two have entirely
|
||||||
|
different causes. A node at roughly 245 peers was emitting this three
|
||||||
|
times a second across nine peers with no way to diagnose it.
|
||||||
|
- **The sub-floor path-MTU refusal warning carries its correlator.** The
|
||||||
|
warning raised when a lookup response carries a path MTU below the
|
||||||
|
actionable floor now names the request it refused, as a `request_id`
|
||||||
|
field on the log line. Only the log line changes: the response is
|
||||||
|
still accepted, the coordinates are still cached, the sub-floor value
|
||||||
|
is still discarded, and the same counter is still charged.
|
||||||
|
|
||||||
|
## Notable bug fixes
|
||||||
|
|
||||||
|
This release carries four fixes for bugs that shipped in previous
|
||||||
|
releases. Every other fix in the range either shipped in v0.4.2
|
||||||
|
or repairs something that was introduced and corrected within this
|
||||||
|
development cycle, and never reached a released version. The CHANGELOG
|
||||||
|
has the complete list.
|
||||||
|
|
||||||
|
- **The macOS control socket lands in `/var/run/fips`, not `/tmp`.** The
|
||||||
|
packaged macOS daemon now recreates and binds its control socket at
|
||||||
|
`/var/run/fips/control.sock`. A privileged macOS process selects that
|
||||||
|
private runtime path before its leaf exists, so bind creates it, and
|
||||||
|
clients follow once it is there. Socket setup now changes ownership
|
||||||
|
and mode only for a private parent directory it creates or recognizes
|
||||||
|
as a canonical FIPS runtime directory. Previously the packaged daemon
|
||||||
|
fell through to the shared `/tmp/fips-control.sock` path after every
|
||||||
|
boot, and because socket setup changed the parent directory
|
||||||
|
unconditionally, the root daemon also took group ownership of `/tmp`
|
||||||
|
itself. Contributed by
|
||||||
|
[@erskingardner](https://github.com/erskingardner)
|
||||||
|
([#138](https://github.com/jmcorgan/fips/pull/138)).
|
||||||
|
|
||||||
|
- **`fipsctl disconnect` now closes the transport connection, not only
|
||||||
|
the peer.** It notified the peer and freed every node-side structure,
|
||||||
|
sessions, indices, links, address mapping, tree and bloom state, and
|
||||||
|
never touched the transport, so on a connection-oriented transport
|
||||||
|
(TCP, Tor, Nym, BLE) the pool entry, the socket and its inbound-slot
|
||||||
|
accounting outlived the peer the node had just forgotten, until the
|
||||||
|
far end closed or the receive loop errored. An operator who
|
||||||
|
disconnected a peer to free a slot did not free the slot. UDP,
|
||||||
|
Ethernet and loopback are unaffected, their `close_connection` being
|
||||||
|
the connectionless no-op. Still not addressed: `disconnect` reports
|
||||||
|
`peer not found` for an identity that is only mid-handshake.
|
||||||
|
- **`fipsctl connect` now tries the address it was given for a peer the
|
||||||
|
node is already connected to**, instead of reporting success without
|
||||||
|
doing anything. The command built an ephemeral peer configuration and
|
||||||
|
handed it to the ordinary dial path, which returns success the moment
|
||||||
|
the peer is already held, so an operator moving a peer onto a freshly
|
||||||
|
provisioned link had no way to make the node use it: the peer stayed
|
||||||
|
where it first authenticated until that path died. The address is now
|
||||||
|
tried as an alternate path alongside the live one, so promotion
|
||||||
|
happens only after the alternate handshake authenticates and a wrong
|
||||||
|
address cannot displace a healthy link. The response gains an additive
|
||||||
|
`refreshed` field. `connect` stays ephemeral: the peer is not written
|
||||||
|
to configuration and gets no auto-reconnect.
|
||||||
|
- **A path MTU measured on one link no longer clamps a peer that has
|
||||||
|
moved to another.** Every writer of the per-destination path-MTU cache
|
||||||
|
keeps the smaller of the existing and incoming value, which is right
|
||||||
|
while a peer stays put, but the entry was keyed by destination alone.
|
||||||
|
A peer first reached over a narrow link stayed clamped to that link's
|
||||||
|
ceiling for the lifetime of the process: when it later became
|
||||||
|
reachable over a wider transport, the re-seed saw a tighter existing
|
||||||
|
value and declined, and traffic kept running at the old ceiling with
|
||||||
|
nothing reporting it, because the clamp was doing exactly what it was
|
||||||
|
told. The node now records which transport last seeded each
|
||||||
|
destination and treats a seed from a different one as authoritative.
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
**Most of this cycle's security content shipped in v0.4.2, which v0.5.0
|
||||||
|
contains**: session and handshake authentication hardening, path MTU
|
||||||
|
bounding, routing-signal gating, private key material protection and
|
||||||
|
clearing, gateway DNS answer validation, the supply-chain work, and the
|
||||||
|
nineteen further fixes from two security reports received during the release
|
||||||
|
cycle. If you are upgrading from v0.4.1 or earlier, all of that arrives with
|
||||||
|
this release, and the `[0.4.2]` section of the CHANGELOG is where it is
|
||||||
|
enumerated.
|
||||||
|
|
||||||
|
**Four items are specific to this line**, because the code they touch
|
||||||
|
exists only here.
|
||||||
|
|
||||||
|
An inbound onion connection no longer leaks its inbound slot. The Tor
|
||||||
|
accept loop spawned the per-connection receive task before inserting the
|
||||||
|
pool entry and bumping the counter, so a remote that reset immediately
|
||||||
|
let the receive task reach its cleanup first: the removal found nothing,
|
||||||
|
the decrement never fired, and the increment landed with nothing left to
|
||||||
|
undo it. Enough of those and `max_inbound` rejected every further onion
|
||||||
|
connection while the pool was visibly empty. The readiness barrier the
|
||||||
|
TCP accept loop already used is now applied here too.
|
||||||
|
|
||||||
|
The `--dir` given to `profile tick on` is confined to `/var/log/fips`
|
||||||
|
when the daemon runs as root. The control socket is reachable by the
|
||||||
|
`fips` group, which the security model treats as strictly weaker than
|
||||||
|
root, and the directory travelled from the socket into a root
|
||||||
|
`create_dir_all` with no validation. This affects only a
|
||||||
|
`--features profiling` build; the subcommand is absent from a stock
|
||||||
|
package. The capture sink also no longer writes over whatever is already
|
||||||
|
at its path, and capture files are created private to their owner.
|
||||||
|
|
||||||
|
Two further defects were found while merging the v0.4.2 security work up
|
||||||
|
into this line, and they were already present here in a different shape
|
||||||
|
than on the maintenance line: the socket-bind policy, which this line had
|
||||||
|
centralized across three sockets rather than one, and a shared
|
||||||
|
per-address rate limiter that swept its whole map on every admission with
|
||||||
|
no ceiling. Fixing them here reaches further than the original fixes did.
|
||||||
|
|
||||||
|
One piece of supply-chain hygiene does belong to this release. The
|
||||||
|
workflow files and composite actions that exist only on this line are
|
||||||
|
now pinned to full commit SHAs, so the whole `.github` tree is pinned or
|
||||||
|
explicitly justified: 75 action references, 71 pinned to a
|
||||||
|
40-character commit SHA with the mandatory version comment, and 4 left
|
||||||
|
on mutable tags by explicit allowance. Nine of those were pinned here,
|
||||||
|
in files that arrived through the merge on mutable tags because the
|
||||||
|
original pinning sweep was authored on a branch that never carried them.
|
||||||
|
|
||||||
|
Security reports have a private channel; see
|
||||||
|
[`SECURITY.md`](../../SECURITY.md).
|
||||||
|
|
||||||
|
## Known limitations
|
||||||
|
|
||||||
|
### A zero-length datagram before a close is reported as the close
|
||||||
|
|
||||||
|
This affects the experimental native datagram API only.
|
||||||
|
|
||||||
|
A peer that closes its half of a flow leaves `POLLHUP` latched, and the flag
|
||||||
|
stays set while its messages are still queued. The receive path therefore asks
|
||||||
|
`FIONREAD` as well: bytes still queued prove a further message is waiting, so a
|
||||||
|
client that sends an empty datagram, then a message, then closes has both
|
||||||
|
delivered.
|
||||||
|
|
||||||
|
**One case has no answer.** A zero-length datagram that is the last message
|
||||||
|
before a close is indistinguishable from the close itself. Reading it drains the
|
||||||
|
queue, and a zero-length message contributes no bytes for `FIONREAD` to report.
|
||||||
|
Measured on Linux 6.8: a socket in that state is identical to a drained one in
|
||||||
|
`revents`, in `FIONREAD`, under `MSG_PEEK` and in the `recvmsg` return.
|
||||||
|
|
||||||
|
Do not give a zero-length payload a meaning of its own on this API. Carry a
|
||||||
|
one-byte discriminator, and let the zero-byte read mean end of file. Separating
|
||||||
|
the two needs a payload that is never zero bytes on the wire, which is a
|
||||||
|
protocol change and is not in this release.
|
||||||
|
|
||||||
|
## Getting v0.5.0
|
||||||
|
|
||||||
|
- **Linux x86_64 / aarch64**: `.deb` and tarball at the
|
||||||
|
[v0.5.0 release page](https://github.com/jmcorgan/fips/releases/tag/v0.5.0).
|
||||||
|
- **Arch Linux**: `fips` from the AUR.
|
||||||
|
- **macOS**: `.pkg` at the v0.5.0 release page.
|
||||||
|
- **Windows**: ZIP at the v0.5.0 release page.
|
||||||
|
- **FreeBSD (x86_64)**: `.pkg` at the v0.5.0 release page. New this
|
||||||
|
release; see the FreeBSD section of `packaging/README.md`.
|
||||||
|
- **OpenWrt**: `.ipk` (OpenWrt 24.x and earlier) or `.apk` (OpenWrt 25+)
|
||||||
|
at the v0.5.0 release page. Both carry the `fips-mesh-setup` and
|
||||||
|
`fips-ap-setup` helpers.
|
||||||
|
- **From source**: `cargo build --release` from a checkout of the v0.5.0
|
||||||
|
tag (Rust 1.94.1 per `rust-toolchain.toml`; `libclang-dev` is a
|
||||||
|
required Linux build prerequisite).
|
||||||
|
- **Nix / NixOS**: `nix build .#fips` from a checkout of the v0.5.0 tag
|
||||||
|
builds the binaries from source with the pinned toolchain and no
|
||||||
|
manual prerequisites (see the Nix section of `packaging/README.md`).
|
||||||
|
|
||||||
|
There is no Android daemon artifact. Android is supported as an
|
||||||
|
embedded crate, described above.
|
||||||
|
|
||||||
|
The full per-commit changelog lives in
|
||||||
|
[`CHANGELOG.md`](../../CHANGELOG.md). Issues and discussion at
|
||||||
|
[github.com/jmcorgan/fips](https://github.com/jmcorgan/fips).
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
|
||||||
|
Thanks to everyone who contributed code, packaging work, bug reports, or
|
||||||
|
reviews to this release. Twenty of this release's commits came from
|
||||||
|
outside the project, and they carry several of the capabilities an
|
||||||
|
operator meets first.
|
||||||
|
|
||||||
|
- [@Origami74](https://github.com/Origami74) (Arjen): the OpenWrt
|
||||||
|
802.11s mesh backhaul
|
||||||
|
([#123](https://github.com/jmcorgan/fips/pull/123)), the open `!FIPS`
|
||||||
|
access SSID ([#126](https://github.com/jmcorgan/fips/pull/126)), the
|
||||||
|
Android-ready core with the app-owned TUN interface
|
||||||
|
([#127](https://github.com/jmcorgan/fips/pull/127)), and
|
||||||
|
`dns_local_addr()` for embedders
|
||||||
|
([#136](https://github.com/jmcorgan/fips/pull/136)). Also the
|
||||||
|
per-instance transport addressing that lets a peer address name which
|
||||||
|
listener it belongs to, the app-owned UDP socket interface beside it, and
|
||||||
|
the `connect`, `disconnect` and path-MTU fixes, all carried in through
|
||||||
|
the platform integration branch rather than a numbered pull request.
|
||||||
|
Also the NixOS flake module and overlay, the UDP `sin6_scope_id`
|
||||||
|
receive fix, and most of the Bluetooth LE rework: packet-boundary
|
||||||
|
recovery, identity-based peer recognition, the bounded probe retry and
|
||||||
|
the embedder-supplied Android radio backend, with the build gate that
|
||||||
|
decides where the transport exists. Fifteen commits, and the two
|
||||||
|
largest new operator capabilities in the release.
|
||||||
|
- Martti Malmi ([@mmalmi](https://github.com/mmalmi)): allocation-free
|
||||||
|
routing next-hop selection
|
||||||
|
([#134](https://github.com/jmcorgan/fips/pull/134)) and batched macOS
|
||||||
|
connected-UDP receives
|
||||||
|
([#135](https://github.com/jmcorgan/fips/pull/135)). Two commits.
|
||||||
|
- [@fr34aky](https://github.com/fr34aky): FreeBSD support, covering the
|
||||||
|
daemon, the TUN datapath, `.fips` DNS integration and native pkg
|
||||||
|
packaging ([#129](https://github.com/jmcorgan/fips/pull/129)), and the
|
||||||
|
L2CAP PSM interface for Bluetooth LE with its BlueZ implementation. Two
|
||||||
|
commits, and a new supported platform.
|
||||||
|
- [@erskingardner](https://github.com/erskingardner) (Jeff Gardner): the
|
||||||
|
control-socket runtime directory fix
|
||||||
|
([#138](https://github.com/jmcorgan/fips/pull/138)). One commit, and a
|
||||||
|
first contribution to FIPS.
|
||||||
|
- [@jmcorgan](https://github.com/jmcorgan) (Johnathan Corgan): release
|
||||||
|
shepherd; the sans-IO protocol restructuring, the per-peer control
|
||||||
|
machine, the peering reconciler, node lifecycle, health and drain, the
|
||||||
|
tick profiler, the lookup and rendezvous naming split, and the
|
||||||
|
integration and review of the contributed work above, plus the
|
||||||
|
native datagram API and the `fipsctl probe` diagnostic.
|
||||||
|
171 commits.
|
||||||
@@ -39,7 +39,7 @@ for the cases where direct UDP advertising isn't an option.
|
|||||||
|
|
||||||
You will change two things in `/etc/fips/fips.yaml`:
|
You will change two things in `/etc/fips/fips.yaml`:
|
||||||
|
|
||||||
- Flip `discovery.nostr.advertise` from `false` to `true`.
|
- Flip `node.rendezvous.nostr.advertise` from `false` to `true`.
|
||||||
- Add `advertise_on_nostr: true` and `public: true` under
|
- Add `advertise_on_nostr: true` and `public: true` under
|
||||||
`transports.udp`.
|
`transports.udp`.
|
||||||
|
|
||||||
@@ -109,8 +109,8 @@ You should be coming out of
|
|||||||
|
|
||||||
- A persistent npub (`fipsctl show status | grep '"npub"'`).
|
- A persistent npub (`fipsctl show status | grep '"npub"'`).
|
||||||
- Nostr discovery in consume-only mode
|
- Nostr discovery in consume-only mode
|
||||||
(`discovery.nostr.enabled: true`,
|
(`node.rendezvous.nostr.enabled: true`,
|
||||||
`discovery.nostr.advertise: false`).
|
`node.rendezvous.nostr.advertise: false`).
|
||||||
- A peer entry for `test-us01` with `via_nostr: true` and no
|
- A peer entry for `test-us01` with `via_nostr: true` and no
|
||||||
static address. `fipsctl show peers` shows the link
|
static address. `fipsctl show peers` shows the link
|
||||||
established.
|
established.
|
||||||
@@ -132,13 +132,13 @@ Copy the value.
|
|||||||
Open `/etc/fips/fips.yaml` and change two things.
|
Open `/etc/fips/fips.yaml` and change two things.
|
||||||
|
|
||||||
**Change 1: flip `advertise` to `true`.** Find the
|
**Change 1: flip `advertise` to `true`.** Find the
|
||||||
`discovery.nostr` block under `node:` and set:
|
`rendezvous.nostr` block under `node:` and set:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
node:
|
node:
|
||||||
identity:
|
identity:
|
||||||
persistent: true
|
persistent: true
|
||||||
discovery:
|
rendezvous:
|
||||||
nostr:
|
nostr:
|
||||||
enabled: true
|
enabled: true
|
||||||
advertise: true
|
advertise: true
|
||||||
@@ -147,6 +147,11 @@ node:
|
|||||||
(The previous tutorial set `advertise: false`; you're flipping
|
(The previous tutorial set `advertise: false`; you're flipping
|
||||||
that bit now.)
|
that bit now.)
|
||||||
|
|
||||||
|
This table was `node.discovery` before v0.5.0; that spelling still parses
|
||||||
|
and logs one deprecation warning naming the new table, so an existing
|
||||||
|
config keeps working (see
|
||||||
|
[../reference/configuration.md](../reference/configuration.md)).
|
||||||
|
|
||||||
**Change 2: add the UDP advert flags.** Find the `udp:` block
|
**Change 2: add the UDP advert flags.** Find the `udp:` block
|
||||||
under `transports:`. The wildcard-bind default
|
under `transports:`. The wildcard-bind default
|
||||||
(`0.0.0.0:2121`) means the daemon needs help knowing what to
|
(`0.0.0.0:2121`) means the daemon needs help knowing what to
|
||||||
@@ -337,19 +342,20 @@ transports:
|
|||||||
public: false # ← was true; change to false
|
public: false # ← was true; change to false
|
||||||
```
|
```
|
||||||
|
|
||||||
And add the signaling/STUN block under `discovery.nostr`:
|
And add the signaling/STUN block under `node.rendezvous.nostr`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
discovery:
|
node:
|
||||||
nostr:
|
rendezvous:
|
||||||
enabled: true
|
nostr:
|
||||||
advertise: true
|
enabled: true
|
||||||
dm_relays:
|
advertise: true
|
||||||
- "wss://relay.damus.io"
|
dm_relays:
|
||||||
- "wss://nos.lol"
|
- "wss://relay.damus.io"
|
||||||
stun_servers:
|
- "wss://nos.lol"
|
||||||
- "stun:stun.l.google.com:19302"
|
stun_servers:
|
||||||
- "stun:stun.cloudflare.com:3478"
|
- "stun:stun.l.google.com:19302"
|
||||||
|
- "stun:stun.cloudflare.com:3478"
|
||||||
```
|
```
|
||||||
|
|
||||||
For the full setup including peer-side config and the punch-
|
For the full setup including peer-side config and the punch-
|
||||||
@@ -404,7 +410,7 @@ If your advert doesn't appear on the relays:
|
|||||||
If the daemon is running but `nak` returns no advert, the
|
If the daemon is running but `nak` returns no advert, the
|
||||||
field was accepted but something else is wrong; double-check
|
field was accepted but something else is wrong; double-check
|
||||||
the spelling on the UDP block and that
|
the spelling on the UDP block and that
|
||||||
`discovery.nostr.advertise: true` is also set.
|
`node.rendezvous.nostr.advertise: true` is also set.
|
||||||
|
|
||||||
## What's next
|
## What's next
|
||||||
|
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ gateway:
|
|||||||
pool: "fd01::/112" # virtual IP range (up to 65535 addresses)
|
pool: "fd01::/112" # virtual IP range (up to 65535 addresses)
|
||||||
lan_interface: "br-lan" # LAN-facing interface for proxy NDP
|
lan_interface: "br-lan" # LAN-facing interface for proxy NDP
|
||||||
dns:
|
dns:
|
||||||
|
listen: "[::1]:5353" # gateway DNS bind (IPv6 loopback only)
|
||||||
upstream: "[::1]:5354" # FIPS daemon DNS resolver (matches daemon default)
|
upstream: "[::1]:5354" # FIPS daemon DNS resolver (matches daemon default)
|
||||||
ttl: 60 # DNS TTL and mapping lifetime (seconds)
|
ttl: 60 # DNS TTL and mapping lifetime (seconds)
|
||||||
pool_grace_period: 60 # seconds after last session before reclaiming
|
pool_grace_period: 60 # seconds after last session before reclaiming
|
||||||
@@ -146,11 +147,10 @@ Three things to notice:
|
|||||||
- `lan_interface: "br-lan"` — the OpenWrt LAN bridge. The gateway
|
- `lan_interface: "br-lan"` — the OpenWrt LAN bridge. The gateway
|
||||||
installs proxy-NDP entries on this interface so LAN clients can
|
installs proxy-NDP entries on this interface so LAN clients can
|
||||||
ARP-equivalent for pool addresses.
|
ARP-equivalent for pool addresses.
|
||||||
- No `dns.listen` line — the source default `[::1]:5353` is exactly
|
- `dns.listen: "[::1]:5353"` — the gateway's DNS bind, pinned to
|
||||||
what OpenWrt wants. The gateway listens on IPv6 loopback only;
|
IPv6 loopback only. dnsmasq, which owns LAN port 53, forwards
|
||||||
dnsmasq, which owns LAN port 53, forwards `.fips` queries to it.
|
`.fips` queries to it. The init script wires up that forwarding;
|
||||||
The init script wires up that forwarding; you don't bind to a LAN
|
you don't bind to a LAN address yourself.
|
||||||
address yourself.
|
|
||||||
|
|
||||||
For the full reference, see
|
For the full reference, see
|
||||||
[../reference/configuration.md § Gateway](../reference/configuration.md#gateway-gateway).
|
[../reference/configuration.md § Gateway](../reference/configuration.md#gateway-gateway).
|
||||||
|
|||||||
@@ -177,8 +177,8 @@ different interface names — that is normal.
|
|||||||
|
|
||||||
Edit `/etc/fips/fips.yaml` on **both** nodes. Under
|
Edit `/etc/fips/fips.yaml` on **both** nodes. Under
|
||||||
`transports:`, add an `ethernet:` block. The key settings are
|
`transports:`, add an `ethernet:` block. The key settings are
|
||||||
the four neighbor flags — both nodes must opt in to all four,
|
the four neighbor flags — both nodes must opt in to all four.
|
||||||
and they default to off:
|
`listen` defaults on; the other three default to off:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
transports:
|
transports:
|
||||||
@@ -302,7 +302,7 @@ sudo fipsctl show peers
|
|||||||
```
|
```
|
||||||
|
|
||||||
Expect one entry whose `npub` matches the **other** node and
|
Expect one entry whose `npub` matches the **other** node and
|
||||||
whose `addresses` line shows `transport: ethernet`. Your
|
whose `transport_type` reads `ethernet`. Your
|
||||||
existing overlay peers (if any from earlier tutorials) appear
|
existing overlay peers (if any from earlier tutorials) appear
|
||||||
alongside it. Each peer has its own row, and the link status
|
alongside it. Each peer has its own row, and the link status
|
||||||
columns show whether the Noise session is up.
|
columns show whether the Noise session is up.
|
||||||
@@ -313,7 +313,7 @@ sudo fipsctl show transports
|
|||||||
|
|
||||||
Confirms that the Ethernet transport is running and shows the
|
Confirms that the Ethernet transport is running and shows the
|
||||||
beacon counters incrementing. Both `beacons_sent` and
|
beacon counters incrementing. Both `beacons_sent` and
|
||||||
`beacons_received` should be non-zero if the link is healthy.
|
`beacons_recv` should be non-zero if the link is healthy.
|
||||||
|
|
||||||
## Step 6: Reach the other node by name
|
## Step 6: Reach the other node by name
|
||||||
|
|
||||||
@@ -443,7 +443,7 @@ Windows builds skip it.
|
|||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
- **No beacons received.** On either node, `sudo fipsctl show
|
- **No beacons received.** On either node, `sudo fipsctl show
|
||||||
transports` should show `beacons_received` incrementing
|
transports` should show `beacons_recv` incrementing
|
||||||
every `beacon_interval_secs` once the other node is also
|
every `beacon_interval_secs` once the other node is also
|
||||||
running. If it stays at zero:
|
running. If it stays at zero:
|
||||||
- Confirm the chosen interface is `LOWER_UP` (carrier
|
- Confirm the chosen interface is `LOWER_UP` (carrier
|
||||||
|
|||||||
@@ -92,8 +92,10 @@ adapter. Two equivalent ways to get it:
|
|||||||
ip -6 addr show fips0
|
ip -6 addr show fips0
|
||||||
```
|
```
|
||||||
|
|
||||||
Look for the `inet6 fd97:...` line. The address up to the `/`
|
Look for the `inet6 fd...` line: every mesh address begins with
|
||||||
is what you want.
|
`fd`, and the rest is derived from your key, so it will not be
|
||||||
|
`fd97` unless you are lucky. The address up to the `/` is what
|
||||||
|
you want.
|
||||||
|
|
||||||
Or via the daemon:
|
Or via the daemon:
|
||||||
|
|
||||||
@@ -109,7 +111,7 @@ when you run the commands. Save it to a shell variable for
|
|||||||
convenience:
|
convenience:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
FIPS0_ADDR=$(ip -6 addr show fips0 | awk '/inet6 fd97:/ {print $2}' | cut -d/ -f1)
|
FIPS0_ADDR=$(ip -6 addr show fips0 | awk '/inet6 fd/ {print $2}' | cut -d/ -f1)
|
||||||
echo "$FIPS0_ADDR"
|
echo "$FIPS0_ADDR"
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -510,7 +512,7 @@ an existing HTTP server on a regular LAN box — and expose it to
|
|||||||
mesh peers through a `fips-gateway`, that's the inbound
|
mesh peers through a `fips-gateway`, that's the inbound
|
||||||
port-forward mode: the gateway runs a mesh-side listener on `fips0`
|
port-forward mode: the gateway runs a mesh-side listener on `fips0`
|
||||||
and forwards to a LAN target. The operator recipe is at
|
and forwards to a LAN target. The operator recipe is at
|
||||||
[../how-to/deploy-gateway.md#inbound-port-forwarding](../how-to/deploy-gateway.md#inbound-port-forwarding);
|
[../how-to/deploy-gateway.md#configure-the-inbound-half](../how-to/deploy-gateway.md#configure-the-inbound-half);
|
||||||
a hand-held walk-through on an OpenWrt AP is at
|
a hand-held walk-through on an OpenWrt AP is at
|
||||||
[deploy-fips-gateway.md](deploy-fips-gateway.md) under "Advanced"
|
[deploy-fips-gateway.md](deploy-fips-gateway.md) under "Advanced"
|
||||||
in [README.md](README.md).
|
in [README.md](README.md).
|
||||||
|
|||||||
@@ -87,8 +87,8 @@ prefix, and returns the result. There is no shared registry; the
|
|||||||
address space is self-allocating from the public-key namespace.
|
address space is self-allocating from the public-key namespace.
|
||||||
|
|
||||||
If you ask for any non-`.fips` suffix, the responder returns
|
If you ask for any non-`.fips` suffix, the responder returns
|
||||||
`REFUSED` — it is intentionally a stub for this single zone, not a
|
`NXDOMAIN` — it is intentionally a stub for this single zone, not a
|
||||||
recursive resolver. An unknown `.fips` name returns `NXDOMAIN`.
|
recursive resolver. An unknown `.fips` name returns `NXDOMAIN` too.
|
||||||
|
|
||||||
The full DNS integration is documented in
|
The full DNS integration is documented in
|
||||||
[../design/fips-ipv6-adapter.md](../design/fips-ipv6-adapter.md).
|
[../design/fips-ipv6-adapter.md](../design/fips-ipv6-adapter.md).
|
||||||
|
|||||||
@@ -124,9 +124,10 @@ to [Troubleshooting](#troubleshooting) below.
|
|||||||
sudo fipsctl show peers
|
sudo fipsctl show peers
|
||||||
```
|
```
|
||||||
|
|
||||||
Expect one entry whose `alias` is `test-us01`. Useful fields:
|
Expect one entry whose `display_name` is `test-us01`. Useful
|
||||||
|
fields:
|
||||||
|
|
||||||
- `connectivity` — should be active / authenticated.
|
- `connectivity` — should be `connected`.
|
||||||
- `transport_addr` — the resolved UDP endpoint your daemon is
|
- `transport_addr` — the resolved UDP endpoint your daemon is
|
||||||
using to reach `test-us01`.
|
using to reach `test-us01`.
|
||||||
- `transport_type` — `udp`.
|
- `transport_type` — `udp`.
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ fips --config ~/napi-lab/b/fips.yaml
|
|||||||
Each should log that it bound its API socket:
|
Each should log that it bound its API socket:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Native API socket listening on /home/YOU/napi-lab/a/api.sock
|
Native API socket listening path=/home/YOU/napi-lab/a/api.sock
|
||||||
```
|
```
|
||||||
|
|
||||||
In a third terminal, confirm the two found each other:
|
In a third terminal, confirm the two found each other:
|
||||||
@@ -138,7 +138,9 @@ the datagram would simply be held and then dropped.
|
|||||||
|
|
||||||
## Step 5: Write the listening program
|
## Step 5: Write the listening program
|
||||||
|
|
||||||
Make a crate next to the lab directory:
|
Make a crate inside the lab directory (the shell is still in
|
||||||
|
`~/napi-lab` from Step 2, so both crates land there and Step 8's
|
||||||
|
`rm -rf ~/napi-lab` removes them along with everything else):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo new --bin napi-listen
|
cargo new --bin napi-listen
|
||||||
@@ -334,7 +336,7 @@ relay.
|
|||||||
|
|
||||||
- [../how-to/use-the-native-datagram-api.md](../how-to/use-the-native-datagram-api.md)
|
- [../how-to/use-the-native-datagram-api.md](../how-to/use-the-native-datagram-api.md)
|
||||||
— the same ground as a recipe, including enabling the API on a real node and
|
— the same ground as a recipe, including enabling the API on a real node and
|
||||||
the security posture that grants
|
the security posture that granting a program access to the socket implies
|
||||||
- [../reference/native-api.md](../reference/native-api.md)
|
- [../reference/native-api.md](../reference/native-api.md)
|
||||||
— every type and method, the errno table, the ceilings, and what happens to
|
— every type and method, the errno table, the ceilings, and what happens to
|
||||||
data that disappears
|
data that disappears
|
||||||
|
|||||||
@@ -37,14 +37,14 @@ you'll be a full participant in the ambient
|
|||||||
```
|
```
|
||||||
|
|
||||||
You will change one thing in `/etc/fips/fips.yaml`: under
|
You will change one thing in `/etc/fips/fips.yaml`: under
|
||||||
`discovery.nostr`, set `policy: open` (the default is
|
`node.rendezvous.nostr`, set `policy: open` (the default is
|
||||||
`configured_only`). After restart, the daemon subscribes to
|
`configured_only`). After restart, the daemon subscribes to
|
||||||
every Kind 37195 advert in the `fips-overlay-v1` namespace and
|
every Kind 37195 advert in the `fips-overlay-v1` namespace and
|
||||||
queues the publishers for outbound connection attempts.
|
queues the publishers for outbound connection attempts.
|
||||||
|
|
||||||
## How open discovery works
|
## How open discovery works
|
||||||
|
|
||||||
> **Discovery policy.** `discovery.nostr.policy` decides what
|
> **Discovery policy.** `node.rendezvous.nostr.policy` decides what
|
||||||
> the daemon does with incoming advert data. Two values:
|
> the daemon does with incoming advert data. Two values:
|
||||||
>
|
>
|
||||||
> - `configured_only` (the default): the daemon only consumes
|
> - `configured_only` (the default): the daemon only consumes
|
||||||
@@ -71,7 +71,7 @@ working:
|
|||||||
|
|
||||||
The namespace is what scopes who's visible to whom:
|
The namespace is what scopes who's visible to whom:
|
||||||
|
|
||||||
> **The namespace is the scope.** `discovery.nostr.app`
|
> **The namespace is the scope.** `node.rendezvous.nostr.app`
|
||||||
> defaults to `fips-overlay-v1` — the namespace the public
|
> defaults to `fips-overlay-v1` — the namespace the public
|
||||||
> test mesh uses. Setting a different value (e.g.,
|
> test mesh uses. Setting a different value (e.g.,
|
||||||
> `app: "my-experiment.v1"`) carves out a private discovery
|
> `app: "my-experiment.v1"`) carves out a private discovery
|
||||||
@@ -110,7 +110,7 @@ You should be coming out of
|
|||||||
[advertise-your-node](advertise-your-node.md) with:
|
[advertise-your-node](advertise-your-node.md) with:
|
||||||
|
|
||||||
- Persistent identity, advertising enabled
|
- Persistent identity, advertising enabled
|
||||||
(`discovery.nostr.advertise: true`), and either the
|
(`node.rendezvous.nostr.advertise: true`), and either the
|
||||||
direct-UDP path
|
direct-UDP path
|
||||||
(`transports.udp.advertise_on_nostr: true`,
|
(`transports.udp.advertise_on_nostr: true`,
|
||||||
`transports.udp.public: true`) or the `udp:nat` path
|
`transports.udp.public: true`) or the `udp:nat` path
|
||||||
@@ -133,14 +133,14 @@ you.
|
|||||||
|
|
||||||
## Step 2: Switch the discovery policy to `open`
|
## Step 2: Switch the discovery policy to `open`
|
||||||
|
|
||||||
Open `/etc/fips/fips.yaml` and find the `discovery.nostr`
|
Open `/etc/fips/fips.yaml` and find the `rendezvous.nostr`
|
||||||
block. Add (or change) the `policy` line:
|
block. Add (or change) the `policy` line:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
node:
|
node:
|
||||||
identity:
|
identity:
|
||||||
persistent: true
|
persistent: true
|
||||||
discovery:
|
rendezvous:
|
||||||
nostr:
|
nostr:
|
||||||
enabled: true
|
enabled: true
|
||||||
advertise: true
|
advertise: true
|
||||||
@@ -159,7 +159,12 @@ That's the only change. Notes on what you don't have to touch:
|
|||||||
- **You don't have to set `open_discovery_max_pending`.** The
|
- **You don't have to set `open_discovery_max_pending`.** The
|
||||||
default of 64 is plenty for a tutorial; only tune it if you
|
default of 64 is plenty for a tutorial; only tune it if you
|
||||||
see the daemon log
|
see the daemon log
|
||||||
`open-discovery: max-pending reached, deferring`.
|
`open-discovery sweep: enqueue budget is 0, skipping`
|
||||||
|
(a debug-level line).
|
||||||
|
- **You don't have to rewrite an older `node.discovery` block.**
|
||||||
|
That spelling still parses and logs one deprecation warning
|
||||||
|
naming the new table (see
|
||||||
|
[../reference/configuration.md](../reference/configuration.md)).
|
||||||
|
|
||||||
Save the file.
|
Save the file.
|
||||||
|
|
||||||
@@ -192,16 +197,20 @@ You should see considerably more entries than before:
|
|||||||
- Plus any other operator publishing on `fips-overlay-v1`
|
- Plus any other operator publishing on `fips-overlay-v1`
|
||||||
(community nodes, other operators' experiments).
|
(community nodes, other operators' experiments).
|
||||||
|
|
||||||
Each entry has its own `connectivity` state. Some will be
|
Each entry has its own `connectivity` state, and every entry
|
||||||
`active` (handshake completed). Some will show as
|
that appears here completed a handshake at least once: a peer
|
||||||
`connecting` and may transition to `failed` shortly after —
|
whose advert was stale, or that NAT traversal never reached,
|
||||||
that's normal; the publisher might be offline, the advert
|
produces no entry at all rather than a failed one. Healthy links
|
||||||
might be stale, or NAT traversal failed for that pair.
|
read `connected`. A link not heard from recently reads `stale`
|
||||||
|
and still carries traffic; one that dropped and is being retried
|
||||||
|
reads `reconnecting`, and one explicitly torn down reads
|
||||||
|
`disconnected`. Neither of the last two can send.
|
||||||
|
|
||||||
To get a list of just the active links:
|
To get a list of just the connected links:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo fipsctl show peers | jq '.peers[] | select(.connectivity == "active") | .npub'
|
sudo fipsctl show peers \
|
||||||
|
| jq '.peers[] | select(.connectivity == "connected") | .npub'
|
||||||
```
|
```
|
||||||
|
|
||||||
The peer count will continue to drift over time as adverts
|
The peer count will continue to drift over time as adverts
|
||||||
@@ -259,12 +268,13 @@ nodes — colleagues, a workshop cohort, a specific deployment
|
|||||||
— set a custom `app` value:
|
— set a custom `app` value:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
discovery:
|
node:
|
||||||
nostr:
|
rendezvous:
|
||||||
enabled: true
|
nostr:
|
||||||
advertise: true
|
enabled: true
|
||||||
policy: open
|
advertise: true
|
||||||
app: "my-team.experiment-1"
|
policy: open
|
||||||
|
app: "my-team.experiment-1"
|
||||||
```
|
```
|
||||||
|
|
||||||
All nodes participating in the experiment use the same
|
All nodes participating in the experiment use the same
|
||||||
|
|||||||
@@ -43,12 +43,11 @@ After this tutorial your node will have:
|
|||||||
|
|
||||||
## Where these files live
|
## Where these files live
|
||||||
|
|
||||||
Every path in this tutorial is written in its Linux form. The macOS
|
Every path in this tutorial is written in its Linux form. macOS and
|
||||||
package (`.pkg`) installs config and keys under
|
FreeBSD install config and keys under `/usr/local/etc/fips/` instead
|
||||||
`/usr/local/etc/fips/` instead of `/etc/fips/`, so on macOS
|
of `/etc/fips/`, so on those platforms substitute as you go:
|
||||||
substitute as you go:
|
|
||||||
|
|
||||||
| Linux / other Unix | macOS |
|
| Linux / other Unix | macOS / FreeBSD |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `/etc/fips/fips.yaml` | `/usr/local/etc/fips/fips.yaml` |
|
| `/etc/fips/fips.yaml` | `/usr/local/etc/fips/fips.yaml` |
|
||||||
| `/etc/fips/fips.key` | `/usr/local/etc/fips/fips.key` |
|
| `/etc/fips/fips.key` | `/usr/local/etc/fips/fips.key` |
|
||||||
|
|||||||
@@ -56,15 +56,15 @@ hostname on the public internet. There is no separate
|
|||||||
the tool takes a hostname, it accepts a `.fips` hostname.
|
the tool takes a hostname, it accepts a `.fips` hostname.
|
||||||
|
|
||||||
> **Where the address comes from.** Every FIPS node's mesh
|
> **Where the address comes from.** Every FIPS node's mesh
|
||||||
> address is the first 16 bytes of SHA-256 of its public key,
|
> address is `0xfd` (the `fd00::/8` ULA prefix) followed by the
|
||||||
> with the leading byte replaced by `0xfd` (the `fd00::/8` ULA
|
> first 15 bytes of its node address, which is itself the first
|
||||||
> prefix). The remaining bytes are hash output, so an address
|
> 16 bytes of SHA-256 of its public key. The remaining bytes
|
||||||
> like `fd97:...` is per-node — the `97` is part of the hash,
|
> are hash output, so an address like `fd97:...` is per-node —
|
||||||
> not a fixed prefix shared across nodes. Names of the form
|
> the `97` is part of the hash, not a fixed prefix shared across
|
||||||
> `<npub>.fips` and any shortname mapped in `/etc/fips/hosts`
|
> nodes. Names of the form `<npub>.fips` and any shortname
|
||||||
> are aliases for that address. The daemon's local DNS
|
> mapped in `/etc/fips/hosts` are aliases for that address. The
|
||||||
> responder hands the answer back to your kernel without ever
|
> daemon's local DNS responder hands the answer back to your
|
||||||
> talking to a remote DNS server.
|
> kernel without ever talking to a remote DNS server.
|
||||||
|
|
||||||
## Step 1: Ping a mesh node (recap)
|
## Step 1: Ping a mesh node (recap)
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ The whole exercise should take about ten minutes.
|
|||||||
|
|
||||||
You'll change two things in `/etc/fips/fips.yaml`:
|
You'll change two things in `/etc/fips/fips.yaml`:
|
||||||
|
|
||||||
- Add a `node.discovery.nostr` block that turns the consume-
|
- Add a `node.rendezvous.nostr` block that turns the consume-
|
||||||
side of Nostr discovery on.
|
side of Nostr discovery on.
|
||||||
- Edit the existing `test-us01` peer entry to drop its hard-
|
- Edit the existing `test-us01` peer entry to drop its hard-
|
||||||
coded `addresses:` block and add `via_nostr: true`.
|
coded `addresses:` block and add `via_nostr: true`.
|
||||||
@@ -51,7 +51,7 @@ peer normally.
|
|||||||
|
|
||||||
## How Nostr discovery resolves an address
|
## How Nostr discovery resolves an address
|
||||||
|
|
||||||
Every FIPS daemon with `node.discovery.nostr.advertise: true`
|
Every FIPS daemon with `node.rendezvous.nostr.advertise: true`
|
||||||
publishes a signed Nostr event (Kind 37195) listing the
|
publishes a signed Nostr event (Kind 37195) listing the
|
||||||
transport endpoints it is willing to accept connections on.
|
transport endpoints it is willing to accept connections on.
|
||||||
The event is signed by the daemon's secret key, so anyone
|
The event is signed by the daemon's secret key, so anyone
|
||||||
@@ -95,8 +95,9 @@ You should currently have:
|
|||||||
sudo fipsctl show peers
|
sudo fipsctl show peers
|
||||||
```
|
```
|
||||||
|
|
||||||
Expect `test-us01` listed with `connectivity` active and a
|
Expect `test-us01` listed with `connectivity` `connected`
|
||||||
`transport_addr` of roughly `test-us01.fips.network:2121`.
|
and a `transport_addr` of roughly
|
||||||
|
`test-us01.fips.network:2121`.
|
||||||
|
|
||||||
If either of those isn't true, finish the previous two
|
If either of those isn't true, finish the previous two
|
||||||
tutorials first; the Nostr discovery layer is built on top of
|
tutorials first; the Nostr discovery layer is built on top of
|
||||||
@@ -104,14 +105,14 @@ that working state.
|
|||||||
|
|
||||||
## Step 2: Enable the consume side of Nostr discovery
|
## Step 2: Enable the consume side of Nostr discovery
|
||||||
|
|
||||||
Open `/etc/fips/fips.yaml` and add a `discovery` block under
|
Open `/etc/fips/fips.yaml` and add a `rendezvous` block under
|
||||||
`node:`:
|
`node:`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
node:
|
node:
|
||||||
identity:
|
identity:
|
||||||
persistent: true
|
persistent: true
|
||||||
discovery:
|
rendezvous:
|
||||||
nostr:
|
nostr:
|
||||||
enabled: true
|
enabled: true
|
||||||
advertise: false
|
advertise: false
|
||||||
@@ -130,6 +131,11 @@ Two knobs, one job each:
|
|||||||
disable advertising for this consume-only tutorial. The next
|
disable advertising for this consume-only tutorial. The next
|
||||||
tutorial flips it back on.
|
tutorial flips it back on.
|
||||||
|
|
||||||
|
If you already have a `node.discovery` block from an older release it
|
||||||
|
still parses and logs one deprecation warning naming the new table, so
|
||||||
|
nothing is broken (see
|
||||||
|
[../reference/configuration.md](../reference/configuration.md)).
|
||||||
|
|
||||||
## Step 3: Switch the peer entry to `via_nostr`
|
## Step 3: Switch the peer entry to `via_nostr`
|
||||||
|
|
||||||
Find the `peers:` block you added during
|
Find the `peers:` block you added during
|
||||||
@@ -187,11 +193,12 @@ necessary for this tutorial.)
|
|||||||
sudo fipsctl show peers
|
sudo fipsctl show peers
|
||||||
```
|
```
|
||||||
|
|
||||||
`test-us01` should appear with `connectivity` active and a
|
`test-us01` should appear with `connectivity` `connected` and
|
||||||
`transport_addr` reflecting the address that was resolved from
|
a `transport_addr` reflecting the endpoint that was resolved
|
||||||
the advert — `test-us01.fips.network:2121` at time of writing. That field
|
from the advert — `test-us01.fips.network:2121` at time of
|
||||||
is the strong signal: nothing in your config gave the daemon
|
writing. That is the same endpoint you just removed from the
|
||||||
that IP, yet there it is.
|
config, so the field on its own does not show where the daemon
|
||||||
|
got it; the `nak` query below is what settles that.
|
||||||
|
|
||||||
You can confirm independently that the address came from the
|
You can confirm independently that the address came from the
|
||||||
advert. The advert is a public Nostr event — anyone can fetch
|
advert. The advert is a public Nostr event — anyone can fetch
|
||||||
@@ -250,11 +257,11 @@ If the link does not come up:
|
|||||||
query returns nothing, that is the problem and it is on the
|
query returns nothing, that is the problem and it is on the
|
||||||
peer's side. Re-add the static `addresses:` entry as a
|
peer's side. Re-add the static `addresses:` entry as a
|
||||||
fallback while you wait for the peer to come back up.
|
fallback while you wait for the peer to come back up.
|
||||||
- **Relay reachability.** `Connected to relay` lines should
|
- **Relay reachability.** A `Connected to 'wss://...'` line
|
||||||
appear for at least one of the three default relays. If
|
should appear for at least one of the three default
|
||||||
none do, your network may be filtering outbound WebSocket
|
relays, naming the relay URL. If none do, your network may
|
||||||
traffic or DNS for those hostnames. Check the journal for
|
be filtering outbound WebSocket traffic or DNS for those
|
||||||
TLS/DNS errors.
|
hostnames. Check the journal for TLS/DNS errors.
|
||||||
- **Stale cache.** The daemon caches resolved endpoints
|
- **Stale cache.** The daemon caches resolved endpoints
|
||||||
briefly. If a peer's advert changes mid-session and you
|
briefly. If a peer's advert changes mid-session and you
|
||||||
hit a stale entry, restart the daemon to force a fresh
|
hit a stale entry, restart the daemon to force a fresh
|
||||||
|
|||||||
@@ -45,7 +45,8 @@ How the pieces interlock:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Generate a node identity (any machine with fipsctl, or reuse one):
|
# 1. Generate a node identity (any machine with fipsctl, or reuse one):
|
||||||
fipsctl keygen
|
# --stdout prints the nsec and npub instead of writing key files.
|
||||||
|
fipsctl keygen --stdout
|
||||||
|
|
||||||
# 2. Put the nsec into the environment:
|
# 2. Put the nsec into the environment:
|
||||||
export FIPS_NSEC=<your-nsec>
|
export FIPS_NSEC=<your-nsec>
|
||||||
@@ -132,7 +133,7 @@ docker compose exec fips curl -s -H "Accept: application/nostr+json" http://127.
|
|||||||
|
|
||||||
| Variable | Default | Meaning |
|
| Variable | Default | Meaning |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `FIPS_NSEC` | *(required)* | Node identity, `fipsctl keygen` |
|
| `FIPS_NSEC` | *(required)* | Node identity, `fipsctl keygen --stdout` |
|
||||||
| `FIPS_PEER_NPUB` | test-us03's npub | Peer to dial; empty = standalone |
|
| `FIPS_PEER_NPUB` | test-us03's npub | Peer to dial; empty = standalone |
|
||||||
| `FIPS_PEER_ADDR` | `54.183.70.180:443` | **TCP** endpoint in nym/tcp mode (use `:2121` for udp) |
|
| `FIPS_PEER_ADDR` | `54.183.70.180:443` | **TCP** endpoint in nym/tcp mode (use `:2121` for udp) |
|
||||||
| `FIPS_PEER_TRANSPORT` | `nym` | `nym` \| `tcp` \| `udp` — see "Switching transport" above |
|
| `FIPS_PEER_TRANSPORT` | `nym` | `nym` \| `tcp` \| `udp` — see "Switching transport" above |
|
||||||
|
|||||||
@@ -12,9 +12,12 @@ iptables — it can only be reached via the node's `.fips` name.
|
|||||||
The relay needs a unique FIPS identity. Generate one with:
|
The relay needs a unique FIPS identity. Generate one with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fipsctl keygen
|
fipsctl keygen --stdout
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`--stdout` prints the nsec and npub instead of writing `fips.key` and
|
||||||
|
`fips.pub`.
|
||||||
|
|
||||||
Then set it in `.env`:
|
Then set it in `.env`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -229,9 +232,10 @@ cannot be applied and the entrypoint will fail.
|
|||||||
|
|
||||||
## Production Considerations
|
## Production Considerations
|
||||||
|
|
||||||
**Secrets management**: The default `.env` contains a hardcoded nsec for
|
**Secrets management**: The shipped `.env` leaves `FIPS_NSEC=` empty, so the
|
||||||
development. In production, use Docker secrets, a vault, or inject the key
|
operator supplies the key. In production, use Docker secrets, a vault, or
|
||||||
via a secure CI/CD pipeline. Never commit production keys to version control.
|
inject the key via a secure CI/CD pipeline. Never commit production keys to
|
||||||
|
version control.
|
||||||
|
|
||||||
**Logging**: Set `RUST_LOG` to control log verbosity (`debug`, `info`,
|
**Logging**: Set `RUST_LOG` to control log verbosity (`debug`, `info`,
|
||||||
`warn`, `error`). For production, configure the Docker logging driver with
|
`warn`, `error`). For production, configure the Docker logging driver with
|
||||||
|
|||||||
@@ -11,6 +11,14 @@ This example lets macOS reach the FIPS mesh through a local Docker container.
|
|||||||
- `identity/fips.key` and `identity/fips.pub`: generated persistent sidecar identity
|
- `identity/fips.key` and `identity/fips.pub`: generated persistent sidecar identity
|
||||||
- `fips.yaml`: FIPS node config used inside the container
|
- `fips.yaml`: FIPS node config used inside the container
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
`fips-on.sh` generates the sidecar identity with `fipsctl` from the `fips-test:latest` image, which this example's `docker-compose.yml` does not build. Build it once from the repo root before the first run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./testing/scripts/build.sh
|
||||||
|
```
|
||||||
|
|
||||||
## Configure Peers
|
## Configure Peers
|
||||||
|
|
||||||
Before first use, replace the placeholder bootstrap peer in `fips.yaml` with a real peer for the mesh you want to join.
|
Before first use, replace the placeholder bootstrap peer in `fips.yaml` with a real peer for the mesh you want to join.
|
||||||
|
|||||||
+13
-7
@@ -1,7 +1,8 @@
|
|||||||
# FIPS Packaging
|
# FIPS Packaging
|
||||||
|
|
||||||
This directory contains packaging for all supported target platforms.
|
This directory contains packaging for all supported target platforms.
|
||||||
All build outputs go to `deploy/` at the project root.
|
Most build outputs go to `deploy/` at the project root; `make ipk`
|
||||||
|
and `make apk` write to `dist/` instead.
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
@@ -35,9 +36,13 @@ sudo apt install libclang-dev # Debian / Ubuntu
|
|||||||
This is a build-time prerequisite only — it is not a runtime
|
This is a build-time prerequisite only — it is not a runtime
|
||||||
dependency, so hosts installing a pre-built `.deb` do not need it.
|
dependency, so hosts installing a pre-built `.deb` do not need it.
|
||||||
|
|
||||||
BLE support is optional and, when building with it, additionally needs
|
BLE is not optional, and it is not universal either. `build.rs` sets
|
||||||
`bluez`, `libdbus-1-dev`, and `pkg-config`; the build picks up BLE if
|
`ble_available` for glibc Linux or Android, which is the set of
|
||||||
those are present and skips it cleanly if not.
|
platforms with a concrete backend: the transport is absent from musl
|
||||||
|
Linux, macOS, FreeBSD and Windows builds entirely. On glibc Linux
|
||||||
|
`libdbus-1-dev` and `pkg-config` are hard build prerequisites: there is
|
||||||
|
no probe that skips BLE when they are missing. The BlueZ daemon is a
|
||||||
|
runtime dependency and is not needed to build.
|
||||||
|
|
||||||
## Directory Structure
|
## Directory Structure
|
||||||
|
|
||||||
@@ -60,8 +65,9 @@ packaging/
|
|||||||
### Debian/Ubuntu (`.deb`)
|
### Debian/Ubuntu (`.deb`)
|
||||||
|
|
||||||
Built with [cargo-deb](https://github.com/kornelski/cargo-deb). Installs
|
Built with [cargo-deb](https://github.com/kornelski/cargo-deb). Installs
|
||||||
`fips`, `fipsctl`, and `fipstop` to `/usr/bin/`, and enables the systemd
|
`fips`, `fipsctl`, `fipstop`, and `fips-gateway` to `/usr/bin/`, ships
|
||||||
service.
|
the `fips`, `fips-dns`, `fips-firewall`, and `fips-gateway` systemd
|
||||||
|
units, and enables the `fips` and `fips-dns` services.
|
||||||
|
|
||||||
The default configuration ships as an example at
|
The default configuration ships as an example at
|
||||||
`/usr/share/fips/fips.yaml.example` and is **not** a dpkg conf-file.
|
`/usr/share/fips/fips.yaml.example` and is **not** a dpkg conf-file.
|
||||||
@@ -240,7 +246,7 @@ flakes enabled.
|
|||||||
```sh
|
```sh
|
||||||
nix build .#fips # build the package (all four binaries)
|
nix build .#fips # build the package (all four binaries)
|
||||||
nix run .#fips -- --help # run a binary directly
|
nix run .#fips -- --help # run a binary directly
|
||||||
nix run .#fipsctl -- status
|
nix run .#fipsctl -- show status
|
||||||
nix develop # dev shell with the pinned toolchain + cargo-edit
|
nix develop # dev shell with the pinned toolchain + cargo-edit
|
||||||
nix flake check # build + validate the flake
|
nix flake check # build + validate the flake
|
||||||
```
|
```
|
||||||
|
|||||||
+13
-7
@@ -18,6 +18,8 @@ This directory contains Arch Linux packaging files for two AUR packages:
|
|||||||
| `fips.tmpfiles` | tmpfiles.d fragment (creates `/run/fips/`) |
|
| `fips.tmpfiles` | tmpfiles.d fragment (creates `/run/fips/`) |
|
||||||
| `fips.service` | Symlink to `../debian/fips.service` |
|
| `fips.service` | Symlink to `../debian/fips.service` |
|
||||||
| `fips-dns.service` | Symlink to `../debian/fips-dns.service` |
|
| `fips-dns.service` | Symlink to `../debian/fips-dns.service` |
|
||||||
|
| `build-aur.sh` | Local `fips-git` build plus namcap validation (run by `make aur`) |
|
||||||
|
| `patch-pkgbuild.sh` | Rewrites `pkgver`, `pkgrel`, `conflicts`, `options`, and `b2sums` in the PKGBUILD at publish time |
|
||||||
|
|
||||||
Both PKGBUILDs reference files from `packaging/debian/` (service files) and
|
Both PKGBUILDs reference files from `packaging/debian/` (service files) and
|
||||||
`packaging/common/` (config files) at build time. These are pulled from the
|
`packaging/common/` (config files) at build time. These are pulled from the
|
||||||
@@ -194,10 +196,11 @@ cp packaging/aur/fips.tmpfiles /tmp/aur-fips/
|
|||||||
|
|
||||||
Before pushing, ensure the PKGBUILD is correct for the current release:
|
Before pushing, ensure the PKGBUILD is correct for the current release:
|
||||||
|
|
||||||
1. Verify `pkgver` matches the latest tagged release (currently `0.1.0`)
|
1. Verify `pkgver` matches the latest tagged release. The checked-in value is a
|
||||||
|
placeholder; `patch-pkgbuild.sh` rewrites it at publish time
|
||||||
2. If the tarball b2sum is a placeholder, download the tarball and compute:
|
2. If the tarball b2sum is a placeholder, download the tarball and compute:
|
||||||
```sh
|
```sh
|
||||||
curl -sL https://github.com/jmcorgan/fips/archive/v0.1.0.tar.gz | b2sum | cut -d' ' -f1
|
curl -sL https://github.com/jmcorgan/fips/archive/v<VERSION>.tar.gz | b2sum | cut -d' ' -f1
|
||||||
```
|
```
|
||||||
3. Update the first entry in `b2sums=()` in the PKGBUILD with the real hash
|
3. Update the first entry in `b2sums=()` in the PKGBUILD with the real hash
|
||||||
|
|
||||||
@@ -213,7 +216,7 @@ makepkg --printsrcinfo > .SRCINFO
|
|||||||
```sh
|
```sh
|
||||||
cd /tmp/aur-fips
|
cd /tmp/aur-fips
|
||||||
git add PKGBUILD .SRCINFO fips.install fips.sysusers fips.tmpfiles
|
git add PKGBUILD .SRCINFO fips.install fips.sysusers fips.tmpfiles
|
||||||
git commit -m "Initial import of fips 0.1.0"
|
git commit -m "Initial import of fips <VERSION>"
|
||||||
git push
|
git push
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -261,9 +264,12 @@ yay -S fips
|
|||||||
|
|
||||||
Then run the same verification commands above.
|
Then run the same verification commands above.
|
||||||
|
|
||||||
## GitHub Secrets for CI (Phase 4 Preparation)
|
## GitHub Secrets for CI
|
||||||
|
|
||||||
For automated AUR updates via GitHub Actions, a separate SSH key is needed.
|
AUR publication is automated: `.github/workflows/aur-publish.yml` pushes the
|
||||||
|
release package, and `aur-publish-git.yml` pushes `fips-git`. The manual steps
|
||||||
|
above are the fallback for when the workflow cannot be used. The automation
|
||||||
|
needs a separate SSH key.
|
||||||
|
|
||||||
### Step 1: Generate a CI-Specific Key
|
### Step 1: Generate a CI-Specific Key
|
||||||
|
|
||||||
@@ -326,8 +332,8 @@ Push an update when a new version is tagged. The steps are:
|
|||||||
```
|
```
|
||||||
6. Commit and push both `PKGBUILD` and `.SRCINFO`
|
6. Commit and push both `PKGBUILD` and `.SRCINFO`
|
||||||
|
|
||||||
Phase 4 CI automation will handle this workflow automatically on new GitHub
|
The AUR Publish workflow performs these steps automatically on a new GitHub
|
||||||
releases.
|
release; run them by hand only as a fallback.
|
||||||
|
|
||||||
For a packaging-only republish of an existing release tag, run the AUR Publish
|
For a packaging-only republish of an existing release tag, run the AUR Publish
|
||||||
workflow manually with the existing tag and incremented `pkgrel` (for example,
|
workflow manually with the existing tag and incremented `pkgrel` (for example,
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ Output: `deploy/fips-<version>-freebsd-<arch>.pkg` (e.g.
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
pkg add ./deploy/fips-0.5.0.dev-freebsd-amd64.pkg
|
pkg add ./deploy/fips-0.5.0.dev-freebsd-amd64.pkg
|
||||||
cp /usr/local/etc/fips/fips.yaml.sample /usr/local/etc/fips/fips.yaml # then edit
|
# post-install seeds this from the sample if absent, at mode 0600
|
||||||
|
vi /usr/local/etc/fips/fips.yaml
|
||||||
sysrc fips_enable=YES fips_dns_enable=YES
|
sysrc fips_enable=YES fips_dns_enable=YES
|
||||||
service fips start
|
service fips start
|
||||||
service fips_dns start
|
service fips_dns start
|
||||||
|
|||||||
@@ -95,10 +95,11 @@ script that only runs when the target file is absent.
|
|||||||
|
|
||||||
### Why `--config /var/lib/fips/fips.yaml`?
|
### Why `--config /var/lib/fips/fips.yaml`?
|
||||||
|
|
||||||
fips has a config search path (`./fips.yaml`, `~/.config/fips/fips.yaml`,
|
fips has a config search path (`./fips.yaml`, `~/.fips.yaml`,
|
||||||
`/etc/fips/fips.yaml`). Passing `--config` explicitly bypasses that search
|
`~/.config/fips/fips.yaml`, `/etc/fips/fips.yaml`). Passing `--config`
|
||||||
path entirely, so fips loads **only** the user-managed file and never
|
explicitly bypasses that search path entirely, so fips loads **only** the
|
||||||
accidentally picks up a stale `/etc/fips/fips.yaml`.
|
user-managed file and never accidentally picks up a stale
|
||||||
|
`/etc/fips/fips.yaml`.
|
||||||
|
|
||||||
## Usage after install
|
## Usage after install
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ calls. The only extra requirement over the `.ipk` build is the `apk` binary.
|
|||||||
The installed-filesystem payload (init scripts, `fips.yaml`, sysctl drop-ins,
|
The installed-filesystem payload (init scripts, `fips.yaml`, sysctl drop-ins,
|
||||||
hotplug, uci-defaults, …) is **shared** with the `.ipk` package — there is one
|
hotplug, uci-defaults, …) is **shared** with the `.ipk` package — there is one
|
||||||
canonical copy in [`../openwrt-ipk/files/`](../openwrt-ipk/files/). `build-apk.sh`
|
canonical copy in [`../openwrt-ipk/files/`](../openwrt-ipk/files/). `build-apk.sh`
|
||||||
stages from there, so the two packages always ship the same files. Keep the
|
stages from there, so the two packages ship the same files apart from one
|
||||||
|
staged rewrite: `build-apk.sh` changes `ethernet.wan.interface` in the staged
|
||||||
|
`fips.yaml` from `eth0` to `wan`, the OpenWrt 25 DSA port name. Keep the
|
||||||
staging block in `build-apk.sh` in sync with `../openwrt-ipk/build-ipk.sh`.
|
staging block in `build-apk.sh` in sync with `../openwrt-ipk/build-ipk.sh`.
|
||||||
|
|
||||||
## Versioning
|
## Versioning
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ else ifeq ($(ARCH),arm)
|
|||||||
# Override RUST_TARGET in your build if your target uses softfloat.
|
# Override RUST_TARGET in your build if your target uses softfloat.
|
||||||
RUST_TARGET:=arm-unknown-linux-musleabihf
|
RUST_TARGET:=arm-unknown-linux-musleabihf
|
||||||
else
|
else
|
||||||
$(error Unsupported architecture: $(ARCH). Add a RUST_TARGET mapping in packaging/openwrt/Makefile.)
|
$(error Unsupported architecture: $(ARCH). Add a RUST_TARGET mapping in packaging/openwrt-ipk/Makefile.)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
RUST_RELEASE_DIR:=$(PKG_BUILD_DIR)/target/$(RUST_TARGET)/release
|
RUST_RELEASE_DIR:=$(PKG_BUILD_DIR)/target/$(RUST_TARGET)/release
|
||||||
|
|||||||
@@ -3,9 +3,6 @@
|
|||||||
This directory is an OpenWrt feed package that builds and installs FIPS on any
|
This directory is an OpenWrt feed package that builds and installs FIPS on any
|
||||||
OpenWrt 22.03+ router via the standard `opkg` package system.
|
OpenWrt 22.03+ router via the standard `opkg` package system.
|
||||||
|
|
||||||
For ad-hoc deployment without the build system, see
|
|
||||||
[`deploy/native/`](../../deploy/native/README.md) instead.
|
|
||||||
|
|
||||||
## Package contents
|
## Package contents
|
||||||
|
|
||||||
| Installed path | Purpose |
|
| Installed path | Purpose |
|
||||||
@@ -15,6 +12,7 @@ For ad-hoc deployment without the build system, see
|
|||||||
| `/usr/bin/fipstop` | Live TUI dashboard |
|
| `/usr/bin/fipstop` | Live TUI dashboard |
|
||||||
| `/usr/bin/fips-gateway` | Outbound LAN gateway service (not started by default) |
|
| `/usr/bin/fips-gateway` | Outbound LAN gateway service (not started by default) |
|
||||||
| `/usr/bin/fips-mesh-setup` | Opt-in helper — creates an open 802.11s mesh interface for router↔router backhaul |
|
| `/usr/bin/fips-mesh-setup` | Opt-in helper — creates an open 802.11s mesh interface for router↔router backhaul |
|
||||||
|
| `/usr/bin/fips-ap-setup` | Opt-in helper — creates the open `!FIPS` access SSID for client devices |
|
||||||
| `/etc/init.d/fips` | procd service for the daemon (auto-start, crash respawn) |
|
| `/etc/init.d/fips` | procd service for the daemon (auto-start, crash respawn) |
|
||||||
| `/etc/init.d/fips-gateway` | procd service for the gateway (disabled by default) |
|
| `/etc/init.d/fips-gateway` | procd service for the gateway (disabled by default) |
|
||||||
| `/etc/fips/fips.yaml` | Node configuration (edit before first start) |
|
| `/etc/fips/fips.yaml` | Node configuration (edit before first start) |
|
||||||
@@ -74,7 +72,7 @@ Copy or symlink this directory into the SDK's `package/` tree:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# From inside the SDK root:
|
# From inside the SDK root:
|
||||||
ln -s /path/to/fips/packaging/openwrt package/fips
|
ln -s /path/to/fips/packaging/openwrt-ipk package/fips
|
||||||
```
|
```
|
||||||
|
|
||||||
Or add the FIPS repository as a feed in `feeds.conf`:
|
Or add the FIPS repository as a feed in `feeds.conf`:
|
||||||
@@ -136,8 +134,7 @@ physical interface names for your router. **Always use physical port names
|
|||||||
(`eth0`, `eth1`, or DSA port names like `wan`/`lan1`), never bridge names
|
(`eth0`, `eth1`, or DSA port names like `wan`/`lan1`), never bridge names
|
||||||
(`br-lan`).** The shipped default WAN port is `eth0` (OpenWrt 24); on OpenWrt
|
(`br-lan`).** The shipped default WAN port is `eth0` (OpenWrt 24); on OpenWrt
|
||||||
25 (DSA) boards the WAN port is named `wan` — the `.apk` package ships that
|
25 (DSA) boards the WAN port is named `wan` — the `.apk` package ships that
|
||||||
default. Run `ip link show` to confirm the names on your board. See
|
default. Run `ip link show` to confirm the names on your board.
|
||||||
[`deploy/native/README.md`](../../deploy/native/README.md) for details.
|
|
||||||
|
|
||||||
## Service management
|
## Service management
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
# An .ipk is just an ar archive containing two tarballs — no SDK required.
|
# An .ipk is just an ar archive containing two tarballs — no SDK required.
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# ./packaging/openwrt/build-ipk.sh [--arch <name>]
|
# ./packaging/openwrt-ipk/build-ipk.sh [--arch <name>]
|
||||||
#
|
#
|
||||||
# Architectures (--arch):
|
# Architectures (--arch):
|
||||||
# aarch64 GL.iNet MT3000/MT6000, RPi 3/4/5, most modern routers [default]
|
# aarch64 GL.iNet MT3000/MT6000, RPi 3/4/5, most modern routers [default]
|
||||||
|
|||||||
@@ -127,13 +127,13 @@ transports:
|
|||||||
# docs/how-to/set-up-80211s-mesh-backhaul.md.
|
# docs/how-to/set-up-80211s-mesh-backhaul.md.
|
||||||
# mesh0:
|
# mesh0:
|
||||||
# interface: "fips-mesh0"
|
# interface: "fips-mesh0"
|
||||||
# discovery: true
|
# listen: true
|
||||||
# announce: true
|
# announce: true
|
||||||
# auto_connect: true
|
# auto_connect: true
|
||||||
# accept_connections: true
|
# accept_connections: true
|
||||||
# mesh1:
|
# mesh1:
|
||||||
# interface: "fips-mesh1"
|
# interface: "fips-mesh1"
|
||||||
# discovery: true
|
# listen: true
|
||||||
# announce: true
|
# announce: true
|
||||||
# auto_connect: true
|
# auto_connect: true
|
||||||
# accept_connections: true
|
# accept_connections: true
|
||||||
@@ -151,13 +151,13 @@ transports:
|
|||||||
# docs/how-to/set-up-open-access-ssid.md.
|
# docs/how-to/set-up-open-access-ssid.md.
|
||||||
# ap0:
|
# ap0:
|
||||||
# interface: "fips-ap0"
|
# interface: "fips-ap0"
|
||||||
# discovery: true
|
# listen: true
|
||||||
# announce: true
|
# announce: true
|
||||||
# auto_connect: true
|
# auto_connect: true
|
||||||
# accept_connections: true
|
# accept_connections: true
|
||||||
# ap1:
|
# ap1:
|
||||||
# interface: "fips-ap1"
|
# interface: "fips-ap1"
|
||||||
# discovery: true
|
# listen: true
|
||||||
# announce: true
|
# announce: true
|
||||||
# auto_connect: true
|
# auto_connect: true
|
||||||
# accept_connections: true
|
# accept_connections: true
|
||||||
|
|||||||
@@ -76,8 +76,8 @@ transports:
|
|||||||
|
|
||||||
### 3. Bluetooth Transport
|
### 3. Bluetooth Transport
|
||||||
|
|
||||||
If using BLE for local mesh discovery, the FIPS binary must be built with
|
BLE is compiled automatically on glibc Linux and Android; there is no
|
||||||
the `ble` feature (enabled by default). BlueZ must be installed and running:
|
cargo feature to enable. BlueZ must be installed and running:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt install bluez
|
sudo apt install bluez
|
||||||
|
|||||||
@@ -71,6 +71,56 @@ mechanism-match summary across the run. Used for statistical reliability
|
|||||||
characterization of known flake classes under calibrated stress, not as
|
characterization of known flake classes under calibrated stress, not as
|
||||||
a per-commit gate; not part of `ci-local.sh`.
|
a per-commit gate; not part of `ci-local.sh`.
|
||||||
|
|
||||||
|
### [sidecar/](sidecar/) -- Network Sidecar Isolation
|
||||||
|
|
||||||
|
FIPS running as a sidecar container that owns the network namespace of
|
||||||
|
a companion application container, with iptables/ip6tables rules
|
||||||
|
confining the app to the mesh. `scripts/test-sidecar.sh` boots a
|
||||||
|
three-node chain of such pairs and asserts both connectivity and
|
||||||
|
isolation.
|
||||||
|
|
||||||
|
### [firewall/](firewall/) -- nftables Baseline
|
||||||
|
|
||||||
|
End-to-end exercise of the production `fips0` nftables baseline at
|
||||||
|
`packaging/common/fips.nft`, covering the default-deny, conntrack and
|
||||||
|
drop-in semantics.
|
||||||
|
|
||||||
|
### [acl-allowlist/](acl-allowlist/) -- Peer ACL Enforcement
|
||||||
|
|
||||||
|
Six nodes with per-node allowlist files mounted at the runtime ACL
|
||||||
|
paths, exercising insiders, outsiders and allowed remotes at once to
|
||||||
|
check which peer pairs are admitted and which are rejected.
|
||||||
|
|
||||||
|
### [native-api/](native-api/) -- Native Datagram API
|
||||||
|
|
||||||
|
Checks the experimental native datagram API: a client process opens a
|
||||||
|
flow to a remote pubkey over a Unix socket, receives a file descriptor,
|
||||||
|
and exchanges datagrams on it with no TUN device and no IPv6 emulation.
|
||||||
|
|
||||||
|
### [dns-resolver/](dns-resolver/) -- `fips-dns-setup` Backends
|
||||||
|
|
||||||
|
Runs `fips-dns-setup` against each supported Linux resolver backend in
|
||||||
|
systemd containers, verifying backend detection, generated config and
|
||||||
|
teardown, plus an end-to-end scenario that resolves a `.fips` name
|
||||||
|
through the configured backend.
|
||||||
|
|
||||||
|
### [deb-install/](deb-install/) -- Debian Package Install
|
||||||
|
|
||||||
|
Installs the built `.deb` in privileged systemd containers for each
|
||||||
|
target distro and verifies unit enablement, conffile placement and
|
||||||
|
end-to-end `.fips` resolution as a user would meet it.
|
||||||
|
|
||||||
|
### [boringtun/](boringtun/) -- WireGuard Throughput Baseline
|
||||||
|
|
||||||
|
Two userspace WireGuard peers running Cloudflare BoringTun, measured
|
||||||
|
with `iperf3`, as a comparison baseline for FIPS tunnel throughput.
|
||||||
|
|
||||||
|
### [ble/](ble/) -- BLE L2CAP Spike
|
||||||
|
|
||||||
|
Standalone cargo project (`ble_spike`) that validates the `bluer` API
|
||||||
|
assumptions behind the `BleIo` trait against real adapters on two
|
||||||
|
machines. Not a Docker harness.
|
||||||
|
|
||||||
## Running CI locally (`ci-local.sh`)
|
## Running CI locally (`ci-local.sh`)
|
||||||
|
|
||||||
[`ci-local.sh`](ci-local.sh) runs the full local CI pipeline — build,
|
[`ci-local.sh`](ci-local.sh) runs the full local CI pipeline — build,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ automatically.
|
|||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./testing/chaos/scripts/build.sh
|
./testing/scripts/build.sh
|
||||||
./testing/chaos/scripts/chaos.sh churn-mixed
|
./testing/chaos/scripts/chaos.sh churn-mixed
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -88,9 +88,9 @@ timing all confound the outcome (a deterministic `link_swap` attempt still
|
|||||||
produced zero periodic switches in a full run).
|
produced zero periodic switches in a full run).
|
||||||
|
|
||||||
That logic is now covered by deterministic sans-IO unit tests in
|
That logic is now covered by deterministic sans-IO unit tests in
|
||||||
`src/tree/tests.rs` (`test_evaluate_parent_cost_*`, `..._hysteresis_*`,
|
`src/proto/stp/tests/state.rs` (`test_effective_depth_*`, `test_hysteresis_*`,
|
||||||
`..._effective_depth_*`), which run in the cargo quartet on every commit and
|
`test_cost_*`), which run in the cargo quartet on every commit and can each be
|
||||||
can each be shown to fail by breaking the cost or hysteresis logic.
|
shown to fail by breaking the cost or hysteresis logic.
|
||||||
|
|
||||||
### Transport-specific
|
### Transport-specific
|
||||||
|
|
||||||
@@ -168,6 +168,8 @@ scenario runs.
|
|||||||
| `-v`, `--verbose` | Enable debug logging |
|
| `-v`, `--verbose` | Enable debug logging |
|
||||||
| `--seed N` | Override the scenario's random seed |
|
| `--seed N` | Override the scenario's random seed |
|
||||||
| `--duration secs` | Override the scenario's duration |
|
| `--duration secs` | Override the scenario's duration |
|
||||||
|
| `--nodes N` | Override the scenario's node count |
|
||||||
|
| `--subnet CIDR` | Override the simulation's subnet |
|
||||||
| `--list` | List available scenarios |
|
| `--list` | List available scenarios |
|
||||||
|
|
||||||
The scenario argument accepts either a name (`churn-mixed`) or a file
|
The scenario argument accepts either a name (`churn-mixed`) or a file
|
||||||
|
|||||||
@@ -54,14 +54,14 @@ layer doesn't amplify it.
|
|||||||
assertions:
|
assertions:
|
||||||
bloom_send_rate:
|
bloom_send_rate:
|
||||||
window_secs: 30
|
window_secs: 30
|
||||||
max_per_node: 30
|
max_per_node: 40
|
||||||
min_parent_switches:
|
min_parent_switches:
|
||||||
min_total: 10
|
min_total: 10
|
||||||
```
|
```
|
||||||
|
|
||||||
`bloom_send_rate` is the load-bearing assertion: per-node delta of
|
`bloom_send_rate` is the load-bearing assertion: per-node delta of
|
||||||
`stats.bloom.sent` over the trailing 30s of the run must be at most
|
`stats.bloom.sent` over the trailing 30s of the run must be at most
|
||||||
30. Per-node deltas and the offending node IDs are written to
|
40. Per-node deltas and the offending node IDs are written to
|
||||||
`assertions.txt` and the runner exits 3 on failure.
|
`assertions.txt` and the runner exits 3 on failure.
|
||||||
|
|
||||||
`min_parent_switches` is a sanity guard. It fails if the run did not
|
`min_parent_switches` is a sanity guard. It fails if the run did not
|
||||||
@@ -146,7 +146,7 @@ plausible jitter band).
|
|||||||
(or the `backup-broadcast-gate-bloom-storm` branch if still
|
(or the `backup-broadcast-gate-bloom-storm` branch if still
|
||||||
retained), build, copy binaries into `testing/docker/`, and rerun
|
retained), build, copy binaries into `testing/docker/`, and rerun
|
||||||
this scenario; the bloom-rate assertion is expected to fail loud
|
this scenario; the bloom-rate assertion is expected to fail loud
|
||||||
with n05/n06 deltas well above 30.
|
with n05/n06 deltas well above 40.
|
||||||
|
|
||||||
- Root-election outcome is sensitive to the seed (smallest
|
- Root-election outcome is sensitive to the seed (smallest
|
||||||
`NodeAddr` wins, where `NodeAddr = SHA-256(pubkey)[..16]`). The
|
`NodeAddr` wins, where `NodeAddr = SHA-256(pubkey)[..16]`). The
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
# --seed <N> Override scenario seed
|
# --seed <N> Override scenario seed
|
||||||
# --duration <secs> Override scenario duration
|
# --duration <secs> Override scenario duration
|
||||||
# --nodes <N> Override topology.num_nodes
|
# --nodes <N> Override topology.num_nodes
|
||||||
|
# --subnet <CIDR> Override the simulation subnet
|
||||||
# --list List available scenarios
|
# --list List available scenarios
|
||||||
#
|
#
|
||||||
# Examples:
|
# Examples:
|
||||||
@@ -35,6 +36,7 @@ usage() {
|
|||||||
echo " --seed <N> Override scenario seed"
|
echo " --seed <N> Override scenario seed"
|
||||||
echo " --duration <secs> Override scenario duration"
|
echo " --duration <secs> Override scenario duration"
|
||||||
echo " --nodes <N> Override topology.num_nodes"
|
echo " --nodes <N> Override topology.num_nodes"
|
||||||
|
echo " --subnet <CIDR> Override the simulation subnet"
|
||||||
echo " --list List available scenarios"
|
echo " --list List available scenarios"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ more than once. The slots resolve to the three images built by
|
|||||||
| ---- | ------------------- | -------------------------------- |
|
| ---- | ------------------- | -------------------------------- |
|
||||||
| `a` | version under test | the branch tip / commit to vet |
|
| `a` | version under test | the branch tip / commit to vet |
|
||||||
| `b` | parent / comparison | parent commit on the same branch |
|
| `b` | parent / comparison | parent commit on the same branch |
|
||||||
| `c` | release baseline | latest release tag (`v0.3.0`) |
|
| `c` | release baseline | latest release tag |
|
||||||
|
|
||||||
`build-images.sh` is **unchanged** — it always builds exactly three images
|
`build-images.sh` is **unchanged** — it always builds exactly three images
|
||||||
from three refs. A node-spec like `a a b c` resolves to the *same* three
|
from three refs. A node-spec like `a a b c` resolves to the *same* three
|
||||||
@@ -237,7 +237,7 @@ working copy at all.
|
|||||||
|
|
||||||
## How to read the output
|
## How to read the output
|
||||||
|
|
||||||
The driver runs six phases:
|
The driver runs seven phases:
|
||||||
|
|
||||||
| Phase | Check |
|
| Phase | Check |
|
||||||
| ----- | ---------------------------------------------------------------- |
|
| ----- | ---------------------------------------------------------------- |
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ Defined in [pressure-profiles.sh](pressure-profiles.sh):
|
|||||||
|
|
||||||
## Environment-variable knobs
|
## Environment-variable knobs
|
||||||
|
|
||||||
The harness reads three optional environment variables that shape what
|
The harness reads optional environment variables that shape what each
|
||||||
each rep does, set them in the invoking shell:
|
rep does, set them in the invoking shell:
|
||||||
|
|
||||||
- **`FIPS_MESH_LAB_NETEM`** — netem argument string (e.g.
|
- **`FIPS_MESH_LAB_NETEM`** — netem argument string (e.g.
|
||||||
`"delay 10ms 5ms 25% loss 1%"`). When set, the harness runs
|
`"delay 10ms 5ms 25% loss 1%"`). When set, the harness runs
|
||||||
@@ -101,7 +101,7 @@ each rep does, set them in the invoking shell:
|
|||||||
- rekey / rekey-accept-off / rekey-outbound-only — `rekey`,
|
- rekey / rekey-accept-off / rekey-outbound-only — `rekey`,
|
||||||
`handshake`, `forwarding`, `session`, `encrypted`, `mmp`
|
`handshake`, `forwarding`, `session`, `encrypted`, `mmp`
|
||||||
(via `compose-trace.yml`).
|
(via `compose-trace.yml`).
|
||||||
- nat-lan — `discovery::nostr`, `transport::udp`,
|
- nat-lan — `fips::nostr`, `transport::udp`,
|
||||||
`node::lifecycle`, `handlers::handshake`, `dataplane::forwarding`
|
`node::lifecycle`, `handlers::handshake`, `dataplane::forwarding`
|
||||||
(via `compose-trace-nat.yml`, picked up by
|
(via `compose-trace-nat.yml`, picked up by
|
||||||
`testing/nat/scripts/nat-test.sh` through the
|
`testing/nat/scripts/nat-test.sh` through the
|
||||||
|
|||||||
@@ -80,6 +80,12 @@ Run one scenario:
|
|||||||
- injects and configures the NAT LAN `veth` pairs in the container namespaces
|
- injects and configures the NAT LAN `veth` pairs in the container namespaces
|
||||||
- `scripts/nat-test.sh`
|
- `scripts/nat-test.sh`
|
||||||
- boots the lab, waits for convergence, and asserts the resulting path
|
- boots the lab, waits for convergence, and asserts the resulting path
|
||||||
|
- `scripts/nostr-relay-test.sh`
|
||||||
|
- exercises the Nostr overlay advert publish/consume round-trip, including
|
||||||
|
rejection of a malformed advert event
|
||||||
|
- `scripts/stun-faults-test.sh`
|
||||||
|
- cycles the daemon through STUN drop, delay and outage faults and asserts
|
||||||
|
graceful behavior at each step
|
||||||
|
|
||||||
## Assertions
|
## Assertions
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ node:
|
|||||||
max_retries: 3
|
max_retries: 3
|
||||||
base_interval_secs: 2
|
base_interval_secs: 2
|
||||||
max_backoff_secs: 8
|
max_backoff_secs: 8
|
||||||
discovery:
|
rendezvous:
|
||||||
nostr:
|
nostr:
|
||||||
enabled: true
|
enabled: true
|
||||||
advertise: true
|
advertise: true
|
||||||
|
|||||||
+22
-21
@@ -13,15 +13,17 @@ non-mesh peers.
|
|||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# From the repo root:
|
||||||
|
./testing/scripts/build.sh
|
||||||
|
|
||||||
cd testing/sidecar
|
cd testing/sidecar
|
||||||
./scripts/build.sh
|
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
|
|
||||||
# Verify the sidecar is running:
|
# Verify the sidecar is running:
|
||||||
docker exec fips-sidecar fipsctl show status
|
docker compose exec fips fipsctl show status
|
||||||
|
|
||||||
# Verify the app container can see the FIPS interface:
|
# Verify the app container can see the FIPS interface:
|
||||||
docker exec fips-app ip addr show fips0
|
docker compose exec app ip addr show fips0
|
||||||
```
|
```
|
||||||
|
|
||||||
With the default `.env`, FIPS starts with no peers. See
|
With the default `.env`, FIPS starts with no peers. See
|
||||||
@@ -37,7 +39,7 @@ The sidecar pattern enforces strict network isolation on the app container:
|
|||||||
- **No IPv6 on eth0**: ip6tables blocks all IPv6 traffic on eth0. The app
|
- **No IPv6 on eth0**: ip6tables blocks all IPv6 traffic on eth0. The app
|
||||||
container cannot use link-local or any Docker-assigned IPv6 addresses.
|
container cannot use link-local or any Docker-assigned IPv6 addresses.
|
||||||
- **FIPS mesh only**: The only routable network path is through `fips0`
|
- **FIPS mesh only**: The only routable network path is through `fips0`
|
||||||
(`fd::/8`). All application traffic traverses the FIPS mesh with
|
(`fd00::/8`). All application traffic traverses the FIPS mesh with
|
||||||
end-to-end encryption.
|
end-to-end encryption.
|
||||||
- **Loopback allowed**: `lo` is unrestricted for inter-process communication
|
- **Loopback allowed**: `lo` is unrestricted for inter-process communication
|
||||||
within the shared namespace.
|
within the shared namespace.
|
||||||
@@ -63,7 +65,7 @@ with the transport layer directly.
|
|||||||
│ Interfaces: │
|
│ Interfaces: │
|
||||||
│ lo — loopback (unrestricted) │
|
│ lo — loopback (unrestricted) │
|
||||||
│ eth0 — Docker bridge (iptables: FIPS only) │
|
│ eth0 — Docker bridge (iptables: FIPS only) │
|
||||||
│ fips0 — FIPS TUN (fd::/8, unrestricted) │
|
│ fips0 — FIPS TUN (fd00::/8, unrestricted) │
|
||||||
└───────────────────────────────────────────────────┘
|
└───────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -89,7 +91,7 @@ before launching the FIPS daemon:
|
|||||||
DNS inside the container is handled by dnsmasq (127.0.0.1:53):
|
DNS inside the container is handled by dnsmasq (127.0.0.1:53):
|
||||||
|
|
||||||
- `.fips` queries are forwarded to the FIPS daemon's built-in DNS resolver
|
- `.fips` queries are forwarded to the FIPS daemon's built-in DNS resolver
|
||||||
(127.0.0.1:5354), which resolves npub-based names to `fd::/8` addresses
|
(127.0.0.1:5354), which resolves npub-based names to `fd00::/8` addresses
|
||||||
- All other queries are forwarded to Docker's embedded DNS (127.0.0.11)
|
- All other queries are forwarded to Docker's embedded DNS (127.0.0.11)
|
||||||
|
|
||||||
The `resolv.conf` mount points the container's resolver at 127.0.0.1,
|
The `resolv.conf` mount points the container's resolver at 127.0.0.1,
|
||||||
@@ -98,13 +100,12 @@ where dnsmasq handles the routing.
|
|||||||
## Build
|
## Build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd testing/sidecar
|
./testing/scripts/build.sh
|
||||||
./scripts/build.sh
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This compiles FIPS for Linux, copies the binaries into the Docker context,
|
Run it from the repo root. It compiles FIPS for Linux, copies the binaries
|
||||||
and builds the sidecar and app images. Cross-compilation from macOS is
|
into the Docker context, and builds the sidecar and app images.
|
||||||
supported via `cargo-zigbuild`.
|
Cross-compilation from macOS is supported via `cargo-zigbuild`.
|
||||||
|
|
||||||
## Run with Peers
|
## Run with Peers
|
||||||
|
|
||||||
@@ -121,8 +122,8 @@ docker compose up -d
|
|||||||
Verify the peer link:
|
Verify the peer link:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker exec fips-sidecar fipsctl show peers
|
docker compose exec fips fipsctl show peers
|
||||||
docker exec fips-sidecar fipsctl show links
|
docker compose exec fips fipsctl show links
|
||||||
```
|
```
|
||||||
|
|
||||||
## Verify Connectivity and Isolation
|
## Verify Connectivity and Isolation
|
||||||
@@ -131,16 +132,16 @@ From the app container:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Ping a mesh node by npub (resolves via .fips DNS):
|
# Ping a mesh node by npub (resolves via .fips DNS):
|
||||||
docker exec fips-app ping6 -c3 npub1sjlh2c3x9w7kjsqg2ay080n2lff2uvt325vpan33ke34rn8l5jcqawh57m.fips
|
docker compose exec app ping6 -c3 npub1sjlh2c3x9w7kjsqg2ay080n2lff2uvt325vpan33ke34rn8l5jcqawh57m.fips
|
||||||
|
|
||||||
# Fetch a web page from a mesh node over FIPS:
|
# Fetch a web page from a mesh node over FIPS:
|
||||||
docker exec fips-app curl -6 "http://[fd69:e08d:65cc:3a6b:9c2c:2ac4:bd40:5e4b]:8000/"
|
docker compose exec app curl -6 "http://[fd69:e08d:65cc:3a6b:9c2c:2ac4:bd40:5e4b]:8000/"
|
||||||
|
|
||||||
# Docker bridge is blocked — this should fail:
|
# Docker bridge is blocked — this should fail:
|
||||||
docker exec fips-app ping -c1 -W2 172.20.0.13
|
docker compose exec app ping -c1 -W2 172.20.0.13
|
||||||
|
|
||||||
# Loopback is allowed:
|
# Loopback is allowed:
|
||||||
docker exec fips-app ping -c1 127.0.0.1
|
docker compose exec app ping -c1 127.0.0.1
|
||||||
```
|
```
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
@@ -175,15 +176,15 @@ devices:
|
|||||||
```
|
```
|
||||||
|
|
||||||
**No peer connection established** — Verify the peer address is reachable
|
**No peer connection established** — Verify the peer address is reachable
|
||||||
from the sidecar container (`docker exec fips-sidecar ping -c1 <peer-ip>`).
|
from the sidecar container (`docker compose exec fips ping -c1 <peer-ip>`).
|
||||||
If joining an external Docker network, ensure `FIPS_NETWORK`, `FIPS_SUBNET`,
|
If joining an external Docker network, ensure `FIPS_NETWORK`, `FIPS_SUBNET`,
|
||||||
and `FIPS_IPV4` match the target network. Check logs with
|
and `FIPS_IPV4` match the target network. Check logs with
|
||||||
`docker logs fips-sidecar`.
|
`docker compose logs fips`.
|
||||||
|
|
||||||
**DNS not resolving `.fips` names** — Verify dnsmasq is running:
|
**DNS not resolving `.fips` names** — Verify dnsmasq is running:
|
||||||
`docker exec fips-sidecar pgrep dnsmasq`. Check that `resolv.conf` is
|
`docker compose exec fips pgrep dnsmasq`. Check that `resolv.conf` is
|
||||||
mounted (should contain `nameserver 127.0.0.1`). Verify the FIPS DNS
|
mounted (should contain `nameserver 127.0.0.1`). Verify the FIPS DNS
|
||||||
resolver is listening: `docker exec fips-sidecar dig @127.0.0.1 -p 5354 <npub>.fips AAAA`.
|
resolver is listening: `docker compose exec fips dig @127.0.0.1 -p 5354 <npub>.fips AAAA`.
|
||||||
|
|
||||||
**iptables errors in entrypoint** — The sidecar container requires
|
**iptables errors in entrypoint** — The sidecar container requires
|
||||||
`NET_ADMIN` capability for iptables. Without it, the isolation rules
|
`NET_ADMIN` capability for iptables. Without it, the isolation rules
|
||||||
|
|||||||
+54
-41
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
Multi-node integration test for FIPS using Docker containers with fixed
|
Multi-node integration test for FIPS using Docker containers with fixed
|
||||||
topologies. Multiple topologies are provided: a sparse mesh (5 nodes, 6
|
topologies. Multiple topologies are provided: a sparse mesh (5 nodes, 6
|
||||||
links), a linear chain (5 nodes, 4 links), a mesh with a public external
|
links), a linear chain (5 nodes, 4 links), a gateway topology (3 nodes plus a
|
||||||
node, and a TCP chain (3 nodes). All exercise the full FIPS stack including
|
non-FIPS LAN client), and three rekey variants. All exercise the full FIPS
|
||||||
TUN devices, DNS resolution, peer link encryption, spanning tree
|
stack including TUN devices, DNS resolution, peer link encryption, spanning
|
||||||
construction, and discovery-driven multi-hop routing.
|
tree construction, and discovery-driven multi-hop routing.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
@@ -15,12 +15,16 @@ construction, and discovery-driven multi-hop routing.
|
|||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
Build the binary and generate configs:
|
Build the binaries and images, then generate the node configs:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./testing/static/scripts/build.sh
|
./testing/scripts/build.sh
|
||||||
|
./testing/static/scripts/generate-configs.sh mesh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`build.sh` is the shared harness builder and is run from the repo root; it
|
||||||
|
does not generate configs.
|
||||||
|
|
||||||
Start the mesh (default topology):
|
Start the mesh (default topology):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -79,24 +83,14 @@ The ping test covers:
|
|||||||
- Multi-hop: A->C (2 hops), A->D (3 hops), A->E (4 hops)
|
- Multi-hop: A->C (2 hops), A->D (3 hops), A->E (4 hops)
|
||||||
- Reverse: E->A (4 hops)
|
- Reverse: E->A (4 hops)
|
||||||
|
|
||||||
### Mesh-Public
|
### Gateway
|
||||||
|
|
||||||
Same five Docker nodes as the mesh topology, plus an external public node
|
Three FIPS nodes: a gateway (`a`) with a LAN interface, and two mesh
|
||||||
(`pub`) at a remote IP. Nodes A, B, and C peer with the public node. This
|
destinations (`b`, `c`) directly peered with it. A non-FIPS client container
|
||||||
topology is for testing mixed local/remote mesh operation.
|
attaches to the gateway's LAN interface. Two destinations are required so the
|
||||||
|
multi-client phase of `gateway-test.sh` can allocate distinct virtual-IP
|
||||||
External nodes are not managed by Docker -- only their identity and address
|
mappings, one per LAN client. Identities are derived deterministically from
|
||||||
appear in the topology file so that Docker nodes can peer with them.
|
the mesh name `gateway-test`.
|
||||||
|
|
||||||
### TCP Chain
|
|
||||||
|
|
||||||
Three nodes in a linear chain using TCP transport (port 8443) instead of
|
|
||||||
UDP: A -- B -- C. Each node peers only with its immediate neighbors.
|
|
||||||
Tests basic TCP transport connectivity and multi-hop routing over TCP.
|
|
||||||
|
|
||||||
The topology file sets `default_transport: tcp`, which causes config
|
|
||||||
generation to use TCP peer addresses (port 8443), inject the TCP transport
|
|
||||||
section, and remove the UDP transport section.
|
|
||||||
|
|
||||||
### Rekey
|
### Rekey
|
||||||
|
|
||||||
@@ -104,22 +98,39 @@ Same sparse mesh as the mesh topology (5 nodes, 6 links). Configs are
|
|||||||
post-processed to use aggressive rekey timers (35s) for CI testing. The
|
post-processed to use aggressive rekey timers (35s) for CI testing. The
|
||||||
`rekey-test.sh` script handles config injection and multi-phase verification.
|
`rekey-test.sh` script handles config injection and multi-phase verification.
|
||||||
|
|
||||||
|
### Rekey-Accept-Off
|
||||||
|
|
||||||
|
The rekey topology with `transports.udp.accept_connections: false` set on
|
||||||
|
node B, the single-peer node auto-connected to C. Pins the regression where a
|
||||||
|
rekey `msg1` arriving at an auto-connect initiator with accept off was dropped
|
||||||
|
by the Node-level admission gate.
|
||||||
|
|
||||||
|
### Rekey-Outbound-Only
|
||||||
|
|
||||||
|
The rekey topology with `transports.udp.outbound_only: true` on node B, whose
|
||||||
|
peer-C address is also rewritten to the Docker hostname (`node-c:2121`). Pins
|
||||||
|
the regression where the hostname-versus-numeric mismatch made the
|
||||||
|
`addr_to_link` lookup miss and the admission carve-out fall through.
|
||||||
|
|
||||||
## Configuration Management
|
## Configuration Management
|
||||||
|
|
||||||
### File Structure
|
### File Structure
|
||||||
|
|
||||||
```text
|
```text
|
||||||
testing/static/
|
testing/static/
|
||||||
├── Dockerfile # Container image definition
|
|
||||||
├── docker-compose.yml # Service definitions for all topologies
|
├── docker-compose.yml # Service definitions for all topologies
|
||||||
├── resolv.conf # DNS config pointing to FIPS resolver
|
├── docker-compose.gateway-external-net.yml # Gateway on an external network
|
||||||
├── .env # Default compose profile
|
├── .env # Default compose profile
|
||||||
├── configs/
|
├── configs/
|
||||||
│ ├── node.template.yaml # Template for all node configs
|
│ ├── node.template.yaml # Template for all node configs
|
||||||
|
│ ├── gateway-resolv.conf # LAN client resolver config
|
||||||
│ └── topologies/
|
│ └── topologies/
|
||||||
│ ├── mesh.yaml # Mesh topology definition
|
│ ├── mesh.yaml # Mesh topology definition
|
||||||
│ ├── chain.yaml # Chain topology definition
|
│ ├── chain.yaml # Chain topology definition
|
||||||
│ └── rekey.yaml # Rekey integration test (5 nodes)
|
│ ├── gateway.yaml # Gateway integration test (3 nodes)
|
||||||
|
│ ├── rekey.yaml # Rekey integration test (5 nodes)
|
||||||
|
│ ├── rekey-accept-off.yaml # Rekey with accept_connections off
|
||||||
|
│ └── rekey-outbound-only.yaml # Rekey with outbound_only
|
||||||
├── generated-configs/ # Auto-generated, run-scoped (gitignored)
|
├── generated-configs/ # Auto-generated, run-scoped (gitignored)
|
||||||
│ ├── npubs.env # NPUB_A=..., NPUB_B=..., etc.
|
│ ├── npubs.env # NPUB_A=..., NPUB_B=..., etc.
|
||||||
│ ├── mesh/
|
│ ├── mesh/
|
||||||
@@ -127,16 +138,24 @@ testing/static/
|
|||||||
│ └── chain/
|
│ └── chain/
|
||||||
│ ├── node-a.yaml ... node-e.yaml
|
│ ├── node-a.yaml ... node-e.yaml
|
||||||
├── scripts/
|
├── scripts/
|
||||||
│ ├── build.sh # Build binary + generate configs
|
|
||||||
│ ├── generate-configs.sh # Generate node configs from topology
|
│ ├── generate-configs.sh # Generate node configs from topology
|
||||||
│ ├── derive-keys.py # Deterministic nsec/npub derivation
|
|
||||||
│ ├── ping-test.sh # Connectivity test
|
│ ├── ping-test.sh # Connectivity test
|
||||||
│ ├── iperf-test.sh # Bandwidth test
|
│ ├── iperf-test.sh # Bandwidth test
|
||||||
|
│ ├── iperf-compare-refs.sh # Bandwidth comparison across refs
|
||||||
|
│ ├── bench-multirun.sh # Repeated benchmark runs
|
||||||
|
│ ├── gateway-test.sh # Gateway integration test
|
||||||
|
│ ├── rekey-test.sh # Rekey integration test
|
||||||
|
│ ├── admission-cap-test.sh # Peer admission cap test
|
||||||
│ └── netem.sh # Network impairment
|
│ └── netem.sh # Network impairment
|
||||||
├── docker-mesh-topology.svg # Mesh topology diagram
|
├── docker-mesh-topology.svg # Mesh topology diagram
|
||||||
└── docker-chain-topology.svg # Chain topology diagram
|
└── docker-chain-topology.svg # Chain topology diagram
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The container image definition (`Dockerfile`), its entrypoint and the
|
||||||
|
`resolv.conf` that points at the FIPS resolver are shared with the other
|
||||||
|
harnesses and live in `testing/docker/`. The identity-derivation helper is
|
||||||
|
`testing/lib/derive_keys.py`.
|
||||||
|
|
||||||
### Topology Files
|
### Topology Files
|
||||||
|
|
||||||
Each topology file in `configs/topologies/` defines:
|
Each topology file in `configs/topologies/` defines:
|
||||||
@@ -190,8 +209,8 @@ bare invocation leaves the suffix unset and writes the plain path.
|
|||||||
The `npubs.env` file is sourced by the test scripts and injected into
|
The `npubs.env` file is sourced by the test scripts and injected into
|
||||||
Docker containers via `env_file` in `docker-compose.yml`.
|
Docker containers via `env_file` in `docker-compose.yml`.
|
||||||
|
|
||||||
The build script (`scripts/build.sh`) calls `generate-configs.sh`
|
`testing/scripts/build.sh` compiles the binaries and builds the images; run
|
||||||
automatically after compiling.
|
`generate-configs.sh` separately afterwards.
|
||||||
|
|
||||||
### Adding a New Topology
|
### Adding a New Topology
|
||||||
|
|
||||||
@@ -208,10 +227,6 @@ each mesh needs unique node identities to avoid key conflicts. The optional
|
|||||||
`mesh-name` parameter generates deterministic per-mesh identities:
|
`mesh-name` parameter generates deterministic per-mesh identities:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build with derived identities
|
|
||||||
./testing/static/scripts/build.sh mesh my-mesh-1
|
|
||||||
|
|
||||||
# Or generate configs directly
|
|
||||||
./testing/static/scripts/generate-configs.sh mesh my-mesh-1
|
./testing/static/scripts/generate-configs.sh mesh my-mesh-1
|
||||||
./testing/static/scripts/generate-configs.sh chain my-mesh-1
|
./testing/static/scripts/generate-configs.sh chain my-mesh-1
|
||||||
```
|
```
|
||||||
@@ -232,14 +247,14 @@ the test environment.
|
|||||||
Without a mesh name, the identities from the topology YAML are used as-is
|
Without a mesh name, the identities from the topology YAML are used as-is
|
||||||
(the original behavior).
|
(the original behavior).
|
||||||
|
|
||||||
### The derive-keys.py Script
|
### The derive_keys.py Script
|
||||||
|
|
||||||
The derivation is performed by `scripts/derive-keys.py`, a standalone tool
|
The derivation is performed by `testing/lib/derive_keys.py`, a standalone tool
|
||||||
with no external dependencies (pure Python stdlib: hashlib for SHA-256,
|
with no external dependencies (pure Python stdlib: hashlib for SHA-256,
|
||||||
manual secp256k1 scalar multiplication, and BIP-173 bech32 encoding):
|
manual secp256k1 scalar multiplication, and BIP-173 bech32 encoding):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ ./testing/static/scripts/derive-keys.py my-mesh-1 a
|
$ python3 testing/lib/derive_keys.py my-mesh-1 a
|
||||||
nsec=<64-char-hex>
|
nsec=<64-char-hex>
|
||||||
npub=npub1...
|
npub=npub1...
|
||||||
```
|
```
|
||||||
@@ -255,7 +270,6 @@ NPUB_B=npub1...
|
|||||||
NPUB_C=npub1...
|
NPUB_C=npub1...
|
||||||
NPUB_D=npub1...
|
NPUB_D=npub1...
|
||||||
NPUB_E=npub1...
|
NPUB_E=npub1...
|
||||||
NPUB_PUB=npub1... # only present for topologies with a pub node
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This file is:
|
This file is:
|
||||||
@@ -404,7 +418,7 @@ docker exec fips-node-a dig AAAA <npub>.fips @127.0.0.1
|
|||||||
the binary inside the container:
|
the binary inside the container:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
md5sum testing/static/fips
|
md5sum testing/docker/fips
|
||||||
docker exec fips-node-a md5sum /usr/local/bin/fips
|
docker exec fips-node-a md5sum /usr/local/bin/fips
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -413,5 +427,4 @@ convergence wait in `ping-test.sh` may be insufficient. Edit the `sleep`
|
|||||||
value at the top of the script.
|
value at the top of the script.
|
||||||
|
|
||||||
**Missing npubs.env**: If test scripts fail with "npubs.env not found", run
|
**Missing npubs.env**: If test scripts fail with "npubs.env not found", run
|
||||||
`./testing/static/scripts/generate-configs.sh mesh` (or your topology) first,
|
`./testing/static/scripts/generate-configs.sh mesh` (or your topology) first.
|
||||||
or use `./testing/static/scripts/build.sh` which generates configs automatically.
|
|
||||||
|
|||||||
Reference in New Issue
Block a user