Files
fips/docs
ArjenandJohnathan Corgan 4ecc192456 fix(control): make connect and disconnect do what they report
Two control-socket commands answered success without doing what the
caller asked. They land as one commit because they share a test file:
the connect fix creates `src/node/tests/control.rs` and the `mod
control;` line that declares it, and the disconnect fix adds to that
file without declaring it, so the disconnect fix on its own does not
build.

Each original commit message follows in full.

--- connect must refresh the path of an already-connected peer ---

`connect` on a peer the node already holds a session to is silently a
no-op. `api_connect` builds an ephemeral PeerConfig and hands it to
`initiate_peer_connection`, which returns Ok(()) as soon as
`self.peers.contains_key(&peer_node_addr)`. The control socket answers
`{"status":"ok"}` and `fipsctl connect` prints success, but the node
never tries the address it was given.

That is the wrong answer whenever the caller knows a path the node does
not. An operator moving a peer onto a freshly-provisioned link, or a
supervising process that has just observed a second, faster path come
up, has no way to make the node use it — the peer stays on whatever path
it first authenticated over until that path dies and the ordinary retry
machinery rediscovers it.

`update_peers`, the other runtime peer-mutation entry point, already
gets this right: for a peer that is currently active it calls
`try_active_peer_alternative_addresses`, which drops candidates matching
the peer's current path, keeps the rest, and starts a parallel
handshake — promotion happens only after that handshake authenticates,
so a bad or spoofed address cannot displace a healthy link. Route
`api_connect`'s already-connected case through the same helper rather
than growing a second mechanism beside it.

Behaviour for an unknown or merely-connecting peer is unchanged, and
`connect` stays ephemeral: the peer is not persisted to config and gets
no auto-reconnect, so a refresh that fails leaves no residue. The
response gains one additive field, `refreshed`, so the caller can tell
"started an alternate-path handshake" from "already on this exact path
and it is fresh", which was previously indistinguishable from a dial.
`fipsctl` pretty-prints the whole `data` object and `fipstop` reads only
`status`, so neither is disturbed.

Note that `connect` deliberately bypasses the reconciler's opportunistic
discovery budget — it is a manual command and the caller is treated as
authoritative about what it can see — so it is bounded only by
`path_candidate_attempt_budget`. A caller that re-announces the same
peer on the same fresh path every cycle now provably costs nothing:
that case is a no-op with a regression test.

--- disconnect must close the transport connection, not just the peer ---

`api_disconnect` notifies the peer and calls `remove_active_peer`, which
frees every node-side structure — session, indices, link, address
mapping, tree and bloom state. It never touches the transport. On a
connection-oriented transport (TCP, Tor, Nym, BLE) the pool entry, the
underlying socket and its inbound-slot accounting therefore survive the
peer the node has just forgotten, until the far end closes or the
receive loop errors. An operator who disconnects a peer to free a slot
does not free the slot.

This is the same hazard `cleanup_stale_connection` was fixed for, and
the reasoning there applies verbatim: closing twice is harmless, because
every `close_connection` implementation is `if let Some(conn) =
pool.remove(addr)` and the connectionless default is a no-op. Read the
peer's transport id and current address before removal and close the
connection after it, mirroring that path. `current_addr` rather than the
link's remote address, because roaming updates the former and it is the
address the pool entry is keyed by.

No effect on UDP, Ethernet or loopback, whose `close_connection` is the
connectionless no-op — the change is a real leak fix on TCP and Tor
today, and on pooled link transports generally.

Not addressed here: `disconnect` still errors with `peer not found` for
an identity that is only mid-handshake, so withdrawing a peer during its
handshake leaves that leg resending msg1 until the handshake timeout
bounds it. That is a separate, timeout-bounded case.

Tests: a TCP two-node test asserts the pool entry is gone after
`api_disconnect` (it fails on the pre-fix code with `Connected`); a
connectionless test asserts the no-op default neither errors nor panics
and that a repeat withdrawal is a clean `peer not found`.

--- changelog ---

Both fixes get an entry under Fixed. Each names the mechanism and what
stays unchanged, and the disconnect entry carries the case its commit
says it does not fix. A duplicate blank line in the Changed section,
left there by an earlier entry, is removed here as well.
2026-08-20 21:50:30 +00:00
..
2026-08-09 13:41:03 +00:00

FIPS Documentation

FIPS (Free Internetworking Peering System) is a self-organizing encrypted mesh network built on Nostr identities, capable of operating over arbitrary transports — local networks, the public internet, Tor, Bluetooth, or point-to-point links — without central infrastructure.

With FIPS, your machine becomes a node in the mesh with a self-generated cryptographic identity. There are two ways to deploy it.

As an overlay on top of existing IP networks, FIPS lets your node reach any other FIPS node wherever it sits — behind a NAT, on a different ISP, on a phone over cellular, on a laptop with only Bluetooth in range, or behind a Tor onion. The mesh forwards IPv6 traffic transparently and end-to-end encrypted, with no central VPN concentrator or coordinating server.

From the 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.

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.

New to FIPS? Start with the Getting Started guide.

Documentation Sections

Tutorials

If you are starting from scratch and want a guided path to a working mesh, go here.

How-To Guides

If you have a specific task in mind — enabling a feature, deploying a component, diagnosing a problem — go here.

Reference

If you need to look up wire formats, configuration keys, command flags, or counter inventories, go here.

Design

If you want to understand how the mesh self-organizes, why FIPS makes the choices it does, or how the pieces fit together, go here.