Files
fips/docs/design/fips-link-layer.md
T
Johnathan Corgan d46dc874ef Restructure design docs around protocol layers
Reorganize FIPS design documentation from implementation-centric
structure (routing, gossip protocol, wire protocol, transports) to
protocol-layer organization with clear service boundaries.

New documents (8):
- fips-transport-layer.md — transport layer spec
- fips-link-layer.md — FLP spec (peer auth, link encryption, forwarding)
- fips-session-layer.md — FSP spec (end-to-end encryption, sessions)
- fips-ipv6-adapter.md — IPv6 adaptation (TUN, DNS, MTU enforcement)
- fips-mesh-operation.md — routing, discovery, error recovery
- fips-wire-formats.md — consolidated wire format reference
- fips-spanning-tree.md — tree algorithm reference
- fips-bloom-filters.md — bloom filter math reference

Rewritten (2):
- fips-intro.md — breadth-first intro with layer model diagrams
- fips-software-architecture.md — slimmed to stable decisions

Updated (3):
- spanning-tree-dynamics.md — removed stale root refresh, aligned terminology
- fips-configuration.md — fixed priority type (u16 → u8)
- fips-state-machines.md — synced code examples with codebase

Deleted (6): fips-transports.md, fips-wire-protocol.md,
fips-gossip-protocol.md, fips-session-protocol.md, fips-routing.md,
fips-tun-driver.md (content absorbed into new structure)
2026-02-17 04:50:04 +00:00

380 lines
16 KiB
Markdown

# FIPS Link Protocol (FLP)
The FIPS Link Protocol is the middle layer of the FIPS protocol stack. It sits
between the transport layer below and the FIPS Session Protocol (FSP) above.
FLP is where anonymous transport addresses become authenticated peers, where
the mesh self-organizes, and where forwarding decisions are made.
## Role
FLP manages direct peer connections over transports. When a transport delivers
a datagram from an unknown address, FLP 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.
FLP is the boundary between opaque transport addresses and identified peers.
Below FLP, everything is transport-specific addresses (IP:port, MAC, .onion).
Above FLP, 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, FLP 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
FLP 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, FLP 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).
FLP 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, FLP 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,
FLP delivers it up to FSP for session-layer processing.
## Services Required from Transport Layer
FLP requires the following from each transport:
### Datagram Delivery
Send and receive raw datagrams to/from transport addresses. The transport
handles all medium-specific details. FLP sees only "send bytes to address" and
"bytes arrived from address."
### MTU Reporting
The maximum datagram size for a given link. FLP needs this to determine how
much payload fits in a single packet after link encryption overhead (29 bytes
for the encrypted frame wrapper).
### Connection Lifecycle
For connection-oriented transports, the transport must establish the underlying
connection before FLP 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 FLP 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, FLP 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.
## 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 discriminator byte identifying the frame type
- A receiver index for O(1) session lookup
- An explicit counter used as the AEAD nonce
- The ciphertext with a Poly1305 authentication tag
The plaintext inside the encrypted frame begins with a message type byte
followed by 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 discriminator byte to determine packet type
2. For encrypted frames (0x00): 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 (0x02): look up by our sender index to match to a
pending outbound handshake
4. For handshake msg1 (0x01): 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. FLP 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.
FLP 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
FLP 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, FLP 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, FLP 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
FLP detects link liveness through timeout-based mechanisms. There are no
dedicated keepalive or ping/pong messages. Liveness is inferred from:
- **Data traffic**: Any successfully decrypted packet confirms the peer is
alive
- **Gossip messages**: TreeAnnounce and FilterAnnounce messages sent
periodically as part of normal mesh operation serve as implicit heartbeats
When no traffic is received for the configured timeout period, FLP marks the
link as stale. If the stale state persists, the link is torn down.
The two-state liveness model:
- **Connected → Stale**: No traffic received for threshold duration
- **Stale → Connected**: Valid traffic received (peer is alive again)
- **Stale → Disconnected**: Timeout exceeded, link torn down
## Link Message Types
FLP defines six 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 |
| 0x40 | SessionDatagram | Encapsulated session-layer payload for forwarding |
| 0x50 | Disconnect | Orderly link teardown with reason code |
Additionally, handshake messages (0x01 msg1, 0x02 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.
## 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** |
| Liveness detection (timeout-based) | **Implemented** |
| Reconnection handling | **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 FLP)
- [fips-session-layer.md](fips-session-layer.md) — FSP (above FLP)
- [fips-mesh-operation.md](fips-mesh-operation.md) — How FLP's routing and
self-organization work in practice
- [fips-wire-formats.md](fips-wire-formats.md) — Byte-level wire format reference