mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-30 19:46:15 +00:00
Design documentation illustration pass and FLP→FMP rename
Rename FIPS Link Protocol (FLP) to FIPS Mesh Protocol (FMP)
The "Link Protocol" name understated the layer's scope — spanning tree
construction, bloom filter routing, greedy forwarding, and mesh-wide
coordination go well beyond link-level concerns. Rename fips-link-layer.md
to fips-mesh-layer.md, update FLP→FMP throughout docs and source code
(FLP_VERSION→FMP_VERSION, wire.rs, rx_loop.rs, spanning_tree.rs).
New SVG illustrations
- Protocol stack: color-coded layer diagram replacing ASCII art
- OSI mapping: side-by-side comparison with traditional networking layers
- Bloom filter propagation: 6-node tree with sender-colored filter boxes
showing split-horizon computation per link
- Routing decision flowchart: 5-step priority chain with candidate ranking
by tree distance and link performance
- Coordinate discovery: sequence diagram showing LookupRequest propagation,
response caching, and SessionSetup cache warming
Redesigned existing SVGs
- Architecture overview: uniform node layout, U-shaped encrypted link
connectors, separate end-to-end session line
- Node architecture: split Router Core into FSP and FMP layers, reorganize
transports into Overlay/Shared Medium/Point-to-Point categories
- Identity derivation: wider boxes, visible encode arrow, dashed npub line
fips-intro.md revisions
- Add inline references to prior work: Yggdrasil/Ironwood for coordinate
routing, Noise Protocol Framework for IK handshakes, WireGuard for
index-based session dispatch, Wikipedia for bloom filters, split-horizon,
and greedy embedding
- Add explanatory paragraphs after bloom filter diagram describing
split-horizon filter computation and candidate selection behavior
- Simplify transport abstraction language, remove I2P/LoRa references
- Fix LookupRequest wording ("propagates" not "floods"), note intermediate
node coordinate caching on lookup responses
- Rewrite architecture overview prose to match redesigned diagrams
This commit is contained in:
@@ -0,0 +1,522 @@
|
||||
# FIPS Mesh Protocol (FMP)
|
||||
|
||||
The FIPS Mesh Protocol is the middle layer of the FIPS protocol stack. It sits
|
||||
between the transport layer below and the FIPS Session Protocol (FSP) above.
|
||||
FMP is where anonymous transport addresses become authenticated peers, where
|
||||
the mesh self-organizes, and where forwarding decisions are made.
|
||||
|
||||
## Role
|
||||
|
||||
FMP manages direct peer connections over transports. When a transport delivers
|
||||
a datagram from an unknown address, FMP authenticates the sender through a
|
||||
Noise IK handshake, establishing a cryptographic link. Once authenticated, the
|
||||
link carries all inter-peer communication: spanning tree gossip, bloom filter
|
||||
updates, coordinate discovery, and forwarded session datagrams — all encrypted
|
||||
per-hop.
|
||||
|
||||
FMP is the boundary between opaque transport addresses and identified peers.
|
||||
Below FMP, everything is transport-specific addresses (IP:port, MAC, .onion).
|
||||
Above FMP, everything is peers identified by public keys and routable by
|
||||
node_addr. The transport layer never sees FIPS-level structure; FSP never sees
|
||||
transport addresses or routing details.
|
||||
|
||||
## Services Provided to FSP
|
||||
|
||||
From the session layer's perspective, FMP is a black box providing three
|
||||
services. FSP knows nothing about transports, links, peers, spanning trees,
|
||||
coordinates, bloom filters, hop counts, or network topology.
|
||||
|
||||
### Datagram Forwarding
|
||||
|
||||
FMP accepts a datagram addressed by source and destination node_addr and
|
||||
delivers it best-effort toward the destination. The datagram travels hop by
|
||||
hop — at each node, FMP decrypts the link layer, reads the destination
|
||||
node_addr, makes a local forwarding decision, and re-encrypts onto the
|
||||
next-hop link.
|
||||
|
||||
FSP provides: source node_addr, destination node_addr, hop limit, and an
|
||||
opaque payload (the session-layer encrypted message).
|
||||
|
||||
FMP provides: best-effort delivery. No acknowledgment, no retransmission, no
|
||||
ordering guarantee. Datagrams may be dropped, duplicated, or delivered out of
|
||||
order.
|
||||
|
||||
### Error Signaling
|
||||
|
||||
When forwarding fails, FMP signals the source endpoint asynchronously:
|
||||
|
||||
- **CoordsRequired**: A transit node lacks the destination's tree coordinates
|
||||
and cannot make a forwarding decision. The source should re-initiate
|
||||
discovery and reset its coordinate warmup strategy.
|
||||
- **PathBroken**: Greedy routing reached a dead end — no peer is closer to the
|
||||
destination than the current node. The source should re-discover the
|
||||
destination's current coordinates.
|
||||
|
||||
Both signals travel inside the SessionDatagram envelope (using the existing
|
||||
src/dest/hop_limit addressing) but are generated by transit nodes and are not
|
||||
end-to-end encrypted. They are rate-limited at 100ms per destination to prevent
|
||||
storms during topology changes.
|
||||
|
||||
### Local Delivery
|
||||
|
||||
When a datagram arrives with a destination node_addr matching the local node,
|
||||
FMP delivers it up to FSP for session-layer processing.
|
||||
|
||||
## Services Required from Transport Layer
|
||||
|
||||
FMP requires the following from each transport:
|
||||
|
||||
### Datagram Delivery
|
||||
|
||||
Send and receive raw datagrams to/from transport addresses. The transport
|
||||
handles all medium-specific details. FMP sees only "send bytes to address" and
|
||||
"bytes arrived from address."
|
||||
|
||||
### MTU Reporting
|
||||
|
||||
The maximum datagram size for a given link. FMP needs this to determine how
|
||||
much payload fits in a single packet after link encryption overhead (37 bytes
|
||||
for the encrypted frame wrapper: 16-byte outer header + 5-byte inner header +
|
||||
16-byte AEAD tag).
|
||||
|
||||
### Connection Lifecycle
|
||||
|
||||
For connection-oriented transports, the transport must establish the underlying
|
||||
connection before FMP can begin the Noise IK handshake. For connectionless
|
||||
transports, datagrams can flow immediately.
|
||||
|
||||
### Endpoint Discovery (Optional)
|
||||
|
||||
When a transport discovers a FIPS-capable endpoint (via beacon, query, or
|
||||
other transport-specific mechanism), it notifies FMP so that link setup can
|
||||
be initiated. Transports without discovery support provide peer addresses
|
||||
through configuration.
|
||||
|
||||
## Peer Authentication
|
||||
|
||||
### Noise IK Handshake
|
||||
|
||||
Every peer connection begins with a Noise IK handshake that mutually
|
||||
authenticates both parties and establishes symmetric keys for link encryption.
|
||||
|
||||
The IK pattern is chosen because:
|
||||
- The **initiator** knows the responder's static public key from configuration
|
||||
or discovery, and sends their own static key encrypted in the first message
|
||||
- The **responder** learns the initiator's identity from the first message,
|
||||
then responds with their own ephemeral key
|
||||
|
||||
After the two-message handshake completes, both parties share symmetric
|
||||
session keys derived from four DH operations (es, ss, ee, se). The handshake
|
||||
provides mutual authentication, forward secrecy, and identity hiding for the
|
||||
initiator.
|
||||
|
||||
### Identity Binding
|
||||
|
||||
The Noise handshake binds the link to the peer's cryptographic identity. After
|
||||
handshake completion:
|
||||
|
||||
- The peer's public key (FIPS identity) is confirmed
|
||||
- The node_addr is computed from the public key (SHA-256, truncated to 128 bits)
|
||||
- All subsequent traffic on the link is authenticated by the Noise session —
|
||||
successful decryption proves the sender is the authenticated peer
|
||||
- The link is registered in the dispatch table for O(1) packet routing
|
||||
|
||||
### Reconnection
|
||||
|
||||
When a Noise IK msg1 arrives from a peer that already has an authenticated
|
||||
link, FMP accepts the new handshake alongside the existing session. If the new
|
||||
handshake completes successfully, it replaces the old session. This handles
|
||||
legitimate reconnection (network change, process restart, NAT rebinding)
|
||||
without disrupting ongoing traffic until the new session is confirmed.
|
||||
|
||||
### Auto-Reconnect
|
||||
|
||||
When MMP's liveness detection removes a peer (dead timeout exceeded), FMP
|
||||
automatically re-initiates the connection if the peer is configured for it.
|
||||
The auto-reconnect path:
|
||||
|
||||
1. `check_link_heartbeats()` detects the dead peer and calls
|
||||
`remove_active_peer()`, tearing down the link and triggering tree/bloom
|
||||
reconvergence
|
||||
2. `schedule_reconnect()` checks whether the peer is in the auto-connect list
|
||||
with `auto_reconnect: true` (the default)
|
||||
3. If eligible, the peer is fed into the retry system with unlimited retries
|
||||
and exponential backoff (same base interval and max backoff as startup
|
||||
retries, configured via `node.retry.*`)
|
||||
4. On each retry tick, a fresh Noise IK handshake is initiated toward the
|
||||
peer's configured transport addresses
|
||||
|
||||
Auto-reconnect only applies to peers in the static peer list with
|
||||
`connect_policy: auto_connect`. Inbound-only peers (those not in the local
|
||||
config) are not reconnected — the owning node (the one with the outbound
|
||||
config) is responsible for re-establishing the link.
|
||||
|
||||
### Handshake Message Retry
|
||||
|
||||
Both link-layer (Noise IK msg1/msg2) and session-layer (SessionSetup/
|
||||
SessionAck) handshakes use message-level retry with exponential backoff
|
||||
within the handshake timeout window. This handles packet loss on the
|
||||
underlying transport without waiting for the full handshake timeout to
|
||||
expire.
|
||||
|
||||
Configuration (under `node.rate_limit.*`):
|
||||
|
||||
- `handshake_resend_interval_ms` (default 1000): initial resend interval
|
||||
- `handshake_resend_backoff` (default 2.0): backoff multiplier per resend
|
||||
- `handshake_max_resends` (default 5): max resends per handshake attempt
|
||||
|
||||
With defaults, resends occur at 1s, 2s, 4s, 8s, 16s — all within the 30s
|
||||
handshake timeout. Under 19% per-attempt loss (10% bidirectional), the
|
||||
probability of all 6 attempts (initial + 5 resends) failing is ~0.005%.
|
||||
|
||||
Session-layer resends wrap the stored payload in a fresh SessionDatagram so
|
||||
routing adapts to topology changes between resends. Responder idempotency:
|
||||
duplicate msg1/SessionSetup triggers resend of the stored msg2/SessionAck.
|
||||
|
||||
## Link Encryption
|
||||
|
||||
All traffic between authenticated peers is encrypted. Every packet on a link
|
||||
— gossip messages, routing queries, forwarded session datagrams, disconnect
|
||||
notifications — passes through Noise's ChaCha20-Poly1305 AEAD.
|
||||
|
||||
### Encrypted Frame Structure
|
||||
|
||||
Post-handshake packets are wrapped in an encrypted frame consisting of:
|
||||
|
||||
- A 4-byte common prefix (version, phase, flags, payload length)
|
||||
- A receiver index for O(1) session lookup
|
||||
- An explicit counter used as the AEAD nonce
|
||||
- The ciphertext with a Poly1305 authentication tag
|
||||
|
||||
The 16-byte outer header (common prefix + receiver index + counter) is used as
|
||||
AAD for the AEAD, binding the header to the ciphertext without encrypting it.
|
||||
|
||||
The plaintext inside the encrypted frame begins with a 5-byte inner header
|
||||
(4-byte session-relative timestamp followed by a message type byte), then the
|
||||
message-specific payload.
|
||||
|
||||
See [fips-wire-formats.md](fips-wire-formats.md) for the complete wire format
|
||||
specification.
|
||||
|
||||
### What Encryption Provides
|
||||
|
||||
- **Confidentiality**: An observer on the underlying transport sees only
|
||||
encrypted packets, packet timing, and packet sizes
|
||||
- **Integrity**: Any modification to a packet is detected by the AEAD tag
|
||||
- **Authentication**: Only the authenticated peer can produce valid ciphertext
|
||||
for this link's session keys
|
||||
|
||||
### What Encryption Does Not Provide
|
||||
|
||||
- **End-to-end confidentiality**: Link encryption protects traffic on a single
|
||||
hop. Each transit node decrypts, reads the routing envelope, and re-encrypts
|
||||
for the next hop. Session-layer encryption (FSP) provides end-to-end
|
||||
confidentiality.
|
||||
- **Traffic analysis protection**: Packet timing, sizes, and volume are visible
|
||||
to transport-layer observers.
|
||||
|
||||
## Index-Based Session Dispatch
|
||||
|
||||
Incoming packets are dispatched to the correct Noise session using a
|
||||
receiver index — a random 32-bit value chosen by the receiver during the
|
||||
handshake. This enables O(1) lookup without relying on source addresses.
|
||||
|
||||
Each party in a link has two indices:
|
||||
- **our_index**: Chosen by us, included by the peer in packets sent to us
|
||||
- **their_index**: Chosen by them, included by us in packets sent to them
|
||||
|
||||
The tuple `(transport_id, receiver_idx)` uniquely identifies a session.
|
||||
|
||||
### Index Properties
|
||||
|
||||
- **Random**: Cryptographically random to prevent guessing
|
||||
- **Unique per transport**: No two active sessions on the same transport share
|
||||
an index
|
||||
- **Scoped to transport**: The same index value may appear on different
|
||||
transports
|
||||
- **Rotated on rekey**: New indices allocated on rekey to prevent cross-session
|
||||
correlation by passive observers
|
||||
|
||||
### Dispatch Flow
|
||||
|
||||
1. Read the 4-byte common prefix to determine the phase (established,
|
||||
handshake msg1, msg2)
|
||||
2. For established frames (phase 0x0): look up `(transport_id, receiver_idx)`
|
||||
in the session table — O(1) hash lookup. Unknown indices are rejected
|
||||
before any cryptographic operation.
|
||||
3. For handshake msg2 (phase 0x2): look up by our sender index to match to a
|
||||
pending outbound handshake
|
||||
4. For handshake msg1 (phase 0x1): rate-limited processing, creates new state
|
||||
|
||||
This approach follows WireGuard's design: source address is informational,
|
||||
not authoritative. Only successful cryptographic verification establishes
|
||||
authenticity.
|
||||
|
||||
## Roaming
|
||||
|
||||
When an encrypted packet successfully decrypts, the sender is the
|
||||
authenticated peer regardless of what transport address the packet arrived
|
||||
from. FMP updates the peer's current address to the packet's source address,
|
||||
and subsequent outbound packets use the updated address.
|
||||
|
||||
This allows peers to change transport addresses (IP:port for UDP, connection
|
||||
handle for TCP) without session interruption. The mechanism is:
|
||||
|
||||
1. Packet arrives from a different address than expected
|
||||
2. Receiver index lookup finds the session
|
||||
3. AEAD decryption succeeds — the sender is cryptographically authenticated
|
||||
4. Peer's address is updated to the new source address
|
||||
|
||||
Roaming is most useful for UDP, where source addresses can change due to NAT
|
||||
rebinding or network changes. For connection-oriented transports, "roaming"
|
||||
manifests as reconnection rather than mid-session address change.
|
||||
|
||||
## Replay Protection
|
||||
|
||||
Each link session maintains per-direction counters:
|
||||
|
||||
- **Send counter**: Monotonically increasing, used as the AEAD nonce for each
|
||||
outbound packet
|
||||
- **Receive window**: A sliding bitmap (2048 entries) tracking which counters
|
||||
have been seen
|
||||
|
||||
The receive window handles the realities of unreliable transports: packets may
|
||||
arrive out of order, be duplicated, or be lost. The window accepts any counter
|
||||
not yet seen and within 2048 of the highest counter received. Counters older
|
||||
than the window are rejected.
|
||||
|
||||
The replay check is performed before decryption to prevent CPU exhaustion from
|
||||
replayed packets that would pass the index lookup but fail decryption.
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
Handshake initiation (msg1) is the primary attack surface for unauthenticated
|
||||
traffic. Each msg1 requires Noise DH operations (~200µs on modern CPUs),
|
||||
state allocation, and response generation.
|
||||
|
||||
FMP uses a global token bucket rate limiter:
|
||||
- **Burst capacity**: Handles legitimate connection storms (e.g., node restart
|
||||
with many configured peers)
|
||||
- **Sustained rate**: Limits steady-state new connections per second
|
||||
- **Global scope**: Rate limiting is global, not per-source, because UDP
|
||||
source addresses are trivially spoofable
|
||||
|
||||
Additional protections:
|
||||
- **Connection limit**: Maximum number of pending inbound handshakes, capping
|
||||
memory usage
|
||||
- **Handshake timeout**: Stale pending handshakes are cleaned up after a
|
||||
configurable timeout
|
||||
- **Allowlist/blocklist**: Optional peer filtering before handshake processing
|
||||
|
||||
## Disconnect
|
||||
|
||||
FMP supports orderly link teardown via a Disconnect message carrying a reason
|
||||
code (shutdown, restart, protocol error, transport failure, resource
|
||||
exhaustion, security violation, configuration change, timeout).
|
||||
|
||||
On receiving Disconnect, FMP immediately cleans up state: removes the peer
|
||||
from the peer table, frees the session index, removes the link, and cleans up
|
||||
address mappings. If the departed peer was the tree parent, FMP triggers parent
|
||||
reselection.
|
||||
|
||||
Disconnect is best-effort — if the transport is broken, the message won't
|
||||
arrive. Timeout-based detection remains the fallback for detecting failed
|
||||
links.
|
||||
|
||||
On node shutdown, Disconnect is sent to all active peers before transports are
|
||||
stopped.
|
||||
|
||||
## Liveness Detection
|
||||
|
||||
FMP detects link liveness through a combination of explicit heartbeats and
|
||||
traffic observation.
|
||||
|
||||
### Heartbeat
|
||||
|
||||
A Heartbeat message (0x51) is sent to each active peer every
|
||||
`node.heartbeat_interval_secs` (default 10s). The heartbeat is a minimal
|
||||
encrypted frame with no payload beyond the standard inner header (timestamp +
|
||||
message type). Any successfully decrypted frame — data, gossip, MMP report,
|
||||
or heartbeat — resets the peer's last-receive timestamp tracked by the MMP
|
||||
receiver.
|
||||
|
||||
### Dead Timeout
|
||||
|
||||
When no traffic (of any kind) is received from a peer for
|
||||
`node.link_dead_timeout_secs` (default 30s), the peer is declared dead and
|
||||
removed via `remove_active_peer()`. This triggers the full teardown cascade:
|
||||
spanning tree parent reselection (if the dead peer was the parent),
|
||||
TreeAnnounce propagation, coordinate cache flush, and bloom filter recompute.
|
||||
|
||||
If the dead peer is eligible for auto-reconnect (see [Auto-Reconnect]
|
||||
(#auto-reconnect)), reconnection is scheduled immediately after removal.
|
||||
|
||||
The heartbeat is independent of MMP — it is needed because idle links in
|
||||
Lightweight MMP mode have no guaranteed periodic traffic (gossip is
|
||||
event-driven, and MMP reports require at least one side running Full mode).
|
||||
|
||||
## Link Message Types
|
||||
|
||||
FMP defines eight message types carried inside encrypted frames:
|
||||
|
||||
| Type | Name | Purpose |
|
||||
| ---- | ---- | ------- |
|
||||
| 0x10 | TreeAnnounce | Spanning tree state announcements between peers |
|
||||
| 0x20 | FilterAnnounce | Bloom filter reachability updates |
|
||||
| 0x30 | LookupRequest | Coordinate discovery — flood toward destination |
|
||||
| 0x31 | LookupResponse | Coordinate discovery — response with coordinates |
|
||||
| 0x00 | SessionDatagram | Encapsulated session-layer payload for forwarding |
|
||||
| 0x01 | SenderReport | MMP sender-side metrics report |
|
||||
| 0x02 | ReceiverReport | MMP receiver-side metrics report |
|
||||
| 0x50 | Disconnect | Orderly link teardown with reason code |
|
||||
| 0x51 | Heartbeat | Link liveness probe |
|
||||
|
||||
Additionally, handshake messages (phase 0x1 msg1, phase 0x2 msg2) are sent
|
||||
unencrypted before the link session is established.
|
||||
|
||||
TreeAnnounce and FilterAnnounce are exchanged between direct peers only — they
|
||||
are not forwarded. LookupRequest and LookupResponse are forwarded through the
|
||||
mesh (flooded with deduplication). SessionDatagram is forwarded hop-by-hop
|
||||
toward the destination. Disconnect is peer-to-peer.
|
||||
|
||||
See [fips-mesh-operation.md](fips-mesh-operation.md) for how these messages
|
||||
work together to build and maintain the mesh, and
|
||||
[fips-wire-formats.md](fips-wire-formats.md) for byte-level message layouts.
|
||||
|
||||
## Metrics Measurement Protocol (MMP)
|
||||
|
||||
Each active peer link runs an instance of the Metrics Measurement Protocol,
|
||||
providing per-link quality metrics to the operator and (in future) to the
|
||||
routing layer for link-cost decisions.
|
||||
|
||||
### Metrics Tracked
|
||||
|
||||
MMP computes the following metrics from the per-frame counter and timestamp
|
||||
fields in the FMP wire format:
|
||||
|
||||
- **SRTT** — Smoothed round-trip time (Jacobson/RFC 6298, α=1/8). Derived
|
||||
from timestamp-echo in ReceiverReports with dwell-time compensation.
|
||||
- **Loss rate** — Bidirectional loss inferred from counter gaps. Tracked as
|
||||
both instantaneous (per-interval) and long-term EWMA.
|
||||
- **Jitter** — Interarrival jitter (RFC 3550 algorithm) in microseconds.
|
||||
- **Goodput** — Bytes per second of payload data (excludes MMP reports).
|
||||
- **OWD trend** — One-way delay trend (µs/s, signed). Indicates congestion
|
||||
buildup before loss occurs.
|
||||
- **ETX** — Expected Transmission Count, computed from bidirectional delivery
|
||||
ratios. Not yet wired into routing decisions (link cost is currently
|
||||
constant).
|
||||
- **Dual EWMA trends** — Short-term (α=1/4) and long-term (α=1/32) trend
|
||||
indicators for both RTT and loss, enabling change detection.
|
||||
|
||||
### Operating Modes
|
||||
|
||||
MMP supports three modes, configured via `node.mmp.mode`:
|
||||
|
||||
| Mode | Reports Exchanged | Metrics Available |
|
||||
| ---- | ----------------- | ----------------- |
|
||||
| **Full** (default) | SenderReport + ReceiverReport | All metrics including RTT, loss, jitter, goodput, OWD trend |
|
||||
| **Lightweight** | ReceiverReport only | Loss (from counter gaps), jitter, OWD trend. No RTT. |
|
||||
| **Minimal** | None | Spin bit and CE echo flags only. No computed metrics. |
|
||||
|
||||
### Report Scheduling
|
||||
|
||||
Reports are sent at RTT-adaptive intervals, clamped to [100ms, 2s]. A
|
||||
cold-start interval of 500ms is used before SRTT converges. The interval
|
||||
formula is `clamp(2 × SRTT, 100ms, 2000ms)`.
|
||||
|
||||
### Spin Bit and RTT
|
||||
|
||||
The SP (spin bit) flag in the FMP inner header follows the QUIC spin bit
|
||||
pattern: reflected on receive, toggled on send when the reflected value
|
||||
matches the last sent value. The spin bit state machine runs for TX
|
||||
reflection, but **RTT samples from the spin bit are discarded**. In a mesh
|
||||
protocol where frames are sent irregularly (tree announces, bloom filters,
|
||||
MMP reports on different timers), inter-frame processing delays inflate spin
|
||||
bit RTT measurements unpredictably. Timestamp-echo from ReceiverReports
|
||||
(with dwell-time compensation) is the sole SRTT source.
|
||||
|
||||
### CE Echo
|
||||
|
||||
The CE (Congestion Experienced) echo flag (bit 1 in the FMP flags byte) is
|
||||
reserved for ECN signaling. The transport trait does not currently expose
|
||||
ECN marking, so the CE echo flag is never set. One-way delay trend serves
|
||||
as the sole pre-loss congestion indicator.
|
||||
|
||||
### Operator Logging
|
||||
|
||||
MMP emits periodic link metrics at info level (configurable via
|
||||
`node.mmp.log_interval_secs`, default 30s):
|
||||
|
||||
```text
|
||||
MMP link metrics peer=node-b rtt=2.3ms loss=0.2% jitter=0.1ms goodput=76.0MB/s tx_pkts=1234 rx_pkts=5678
|
||||
```
|
||||
|
||||
Teardown logs include final SRTT, loss rate, jitter, ETX, goodput, and
|
||||
cumulative tx/rx packet and byte counts.
|
||||
|
||||
## Security Properties
|
||||
|
||||
### Threat Resistance
|
||||
|
||||
| Threat | Mitigation |
|
||||
| ------ | ---------- |
|
||||
| Connection exhaustion | Token bucket rate limit + connection count limit |
|
||||
| CPU exhaustion (msg1 flood) | Rate limit before crypto operations |
|
||||
| Replay attacks | Counter-based nonces with sliding window |
|
||||
| State confusion | Strict handshake state machine validation |
|
||||
| Spoofed encrypted packets | Index lookup + AEAD verification |
|
||||
| Spoofed msg2 | Index lookup + Noise ephemeral key binding |
|
||||
| Address spoofing | Cryptographic authority, not address-based |
|
||||
| Session correlation | Index rotation on rekey |
|
||||
|
||||
### Unauthenticated Attack Surface
|
||||
|
||||
Only handshake msg1 can be sent by unauthenticated parties. Encrypted frames
|
||||
require a known session index, and msg2 requires a response to a specific
|
||||
ephemeral key. The msg1 attack surface is protected by rate limiting,
|
||||
connection limits, and handshake timeouts.
|
||||
|
||||
### Authenticated Peer Misbehavior
|
||||
|
||||
Authentication establishes identity but does not grant trust. An authenticated
|
||||
peer can send malformed packets (which fail AEAD and are dropped) or
|
||||
high-frequency traffic (rate-limited by higher layers). False tree coordinate
|
||||
claims are constrained by signature verification on TreeAnnounce messages.
|
||||
|
||||
### Silent Drop Policy
|
||||
|
||||
Invalid packets are silently dropped without error responses. This prevents
|
||||
information leakage about internal state and avoids amplification attacks where
|
||||
an attacker sends invalid packets to elicit responses.
|
||||
|
||||
## Implementation Status
|
||||
|
||||
| Feature | Status |
|
||||
| ------- | ------ |
|
||||
| Noise IK handshake | **Implemented** |
|
||||
| Link encryption (ChaCha20-Poly1305) | **Implemented** |
|
||||
| Index-based session dispatch | **Implemented** |
|
||||
| Replay protection (sliding window) | **Implemented** |
|
||||
| Roaming (address-follows-crypto) | **Implemented** |
|
||||
| Rate limiting (token bucket) | **Implemented** |
|
||||
| Disconnect with reason codes | **Implemented** |
|
||||
| Heartbeat liveness detection | **Implemented** |
|
||||
| Reconnection handling | **Implemented** |
|
||||
| Auto-reconnect after link-dead removal | **Implemented** |
|
||||
| Handshake message retry (link + session layer) | **Implemented** |
|
||||
| Common prefix framing | **Implemented** |
|
||||
| AAD binding on encrypted frames | **Implemented** |
|
||||
| Inner header timestamps | **Implemented** |
|
||||
| Path MTU tracking (SessionDatagram) | **Implemented** |
|
||||
| Metrics Measurement Protocol (MMP) | **Implemented** |
|
||||
| Rekey with index rotation | Planned |
|
||||
| Allowlist/blocklist | Planned |
|
||||
|
||||
## References
|
||||
|
||||
- [fips-intro.md](fips-intro.md) — Protocol overview and architecture
|
||||
- [fips-transport-layer.md](fips-transport-layer.md) — Transport layer (below FMP)
|
||||
- [fips-session-layer.md](fips-session-layer.md) — FSP (above FMP)
|
||||
- [fips-mesh-operation.md](fips-mesh-operation.md) — How FMP's routing and
|
||||
self-organization work in practice
|
||||
- [fips-wire-formats.md](fips-wire-formats.md) — Byte-level wire format reference
|
||||
Reference in New Issue
Block a user