FSP wire format revision and session-layer MMP implementation

FSP wire format revision (TASK-2026-0007):

Introduce the FIPS Session Protocol (FSP) wire format with a 4-byte
common prefix [ver_phase:1][flags:1][payload_len:2 LE] replacing the
old 1-byte msg_type dispatch. All session messages share this prefix
with phase-based dispatch (Established, Setup, Ack, Unencrypted).

- New session_wire.rs: FSP constants, header types, parse/build helpers
- SessionMessageType enum: DataPacket (0x10), SenderReport (0x11),
  ReceiverReport (0x12), PathMtuNotification (0x13)
- FspFlags (CP/K/U) and FspInnerFlags (SP) for flag management
- SessionSenderReport, SessionReceiverReport, PathMtuNotification
  message structs with encode/decode
- FSP send pipeline: 12-byte header as AAD, 6-byte inner header
  (timestamp + msg_type + inner_flags), encrypt_with_aad()
- FSP receive pipeline: parse header, extract cleartext coords (CP),
  AEAD decrypt with AAD, strip inner header, msg_type dispatch
- Forwarding: transit nodes parse cleartext coords without decryption
- Removed DataPacket struct and associated types
- SessionEntry: session_start_ms, mark_established(), session_timestamp()
- FIPS_OVERHEAD: 144 → 150 bytes (+6 for FSP inner header)
- Design docs updated for new wire format

Session-layer MMP implementation (TASK-2026-0008):

Implement complete session-layer MMP reusing the link-layer algorithm
modules (SenderState, ReceiverState, MmpMetrics, SpinBitState) with
independent configuration and higher report interval clamps.

- SessionMmpConfig: separate config section (node.session_mmp.*)
- MmpSessionState: session-specific wrapper with PathMtuState tracking
- Session-layer constants (500ms-10s report intervals, 1s cold start)
- Parameterized interval methods (new_with_cold_start,
  update_report_interval_with_bounds) on SenderState/ReceiverState
- Bidirectional From conversions between link/session report types
- SessionEntry: mmp and is_initiator fields, initialized on Established
- send_session_msg() for reports/notifications
- Per-message RX recording with spin bit state tracking
- Handlers for SenderReport, ReceiverReport, PathMtuNotification
- path_mtu threaded from SessionDatagram envelope through to handlers
- check_session_mmp_reports() tick handler with collect-then-send pattern
- Periodic and teardown operator logging for session metrics
- PathMtuState: destination observes incoming MTU on all session messages,
  source seeded from outbound transport MTU, decrease-immediate /
  increase-requires-3-consecutive rules

Link-layer MMP fix:

- Stop feeding spin bit RTT samples into SRTT estimator; inter-frame
  timing in the mesh is irregular, inflating spin-bit RTT by variable
  processing delays; timestamp-echo provides accurate RTT

29 files changed, 602 tests pass, 0 clippy warnings.
This commit is contained in:
Johnathan Corgan
2026-02-19 03:15:05 +00:00
parent d8cb4d407e
commit 04d9fd625d
29 changed files with 2675 additions and 733 deletions
+1 -1
View File
@@ -148,7 +148,7 @@ Controls end-to-end session behavior and packet queuing.
| `node.session.pending_packets_per_dest` | usize | `16` | Queue depth per destination during session establishment |
| `node.session.pending_max_destinations` | usize | `256` | Max destinations with pending packets |
| `node.session.idle_timeout_secs` | u64 | `90` | Idle session timeout; established sessions with no activity for this duration are removed |
| `node.session.coords_warmup_packets` | u8 | `5` | Number of initial DataPackets per session that include COORDS_PRESENT for transit cache warmup; also the reset count on CoordsRequired receipt |
| `node.session.coords_warmup_packets` | u8 | `5` | Number of initial data packets per session that include the CP flag for transit cache warmup; also the reset count on CoordsRequired receipt |
The anti-replay window size (2048 packets) is a compile-time constant and not
configurable.
+11 -10
View File
@@ -121,13 +121,14 @@ after all layers of wrapping.
| ----- | -------- | ------- |
| Link encryption | 37 bytes | 16-byte outer header + 5-byte inner header + 16-byte AEAD tag |
| SessionDatagram envelope | 36 bytes | type + ttl + path_mtu + src_addr + dest_addr |
| DataPacket header | 12 bytes | type + flags + counter + payload_len |
| Session encryption | 16 bytes | ChaCha20-Poly1305 AEAD tag |
| **Minimal total** | **101 bytes** | |
| FSP header | 12 bytes | 4-byte prefix + 8-byte counter (used as AEAD AAD) |
| FSP inner header | 6 bytes | 4-byte timestamp + 1-byte msg_type + 1-byte inner_flags (inside AEAD) |
| Session AEAD tag | 16 bytes | ChaCha20-Poly1305 tag on session-encrypted payload |
| **Minimal total** | **107 bytes** | |
| Coordinates (if present) | ~43 bytes | Depth-dependent, first few packets only |
| **Worst case total** | **144 bytes** | With COORDS_PRESENT for depth-3 paths |
| **Worst case total** | **150 bytes** | With CP flag set for depth-3 paths |
The `FIPS_OVERHEAD` constant (144 bytes) is used for conservative MTU
The `FIPS_OVERHEAD` constant (150 bytes) is used for conservative MTU
calculations.
### Effective IPv6 MTU
@@ -142,14 +143,14 @@ For typical deployments:
| Transport MTU | Effective IPv6 MTU | Notes |
| ------------- | ------------------ | ----- |
| 1472 (UDP/Ethernet) | 1328 | Standard deployment |
| 1280 (UDP minimum) | 1136 | Below IPv6 minimum |
| 1472 (UDP/Ethernet) | 1322 | Standard deployment |
| 1280 (UDP minimum) | 1130 | Below IPv6 minimum |
IPv6 mandates that every link support at least 1280 bytes. The minimum
transport path MTU for the IPv6 adapter is therefore:
```text
1280 + 144 = 1424 bytes
1280 + 150 = 1430 bytes
```
Transports with smaller MTUs (LoRa at ~250 bytes, serial at 256 bytes) cannot
@@ -315,13 +316,13 @@ decryption (opens attack surface).
Endpoint-only fragmentation (fragment before session encryption, reassemble
after decryption) is a future direction that avoids these objections. Each
fragment would be independently encrypted and look like a normal DataPacket
fragment would be independently encrypted and look like a normal data packet
to transit nodes.
## References
- [fips-intro.md](fips-intro.md) — Protocol overview and architecture
- [fips-session-layer.md](fips-session-layer.md) — FSP (below the adapter)
- [fips-wire-formats.md](fips-wire-formats.md) — DataPacket and SessionDatagram
- [fips-wire-formats.md](fips-wire-formats.md) — FSP and SessionDatagram wire
formats
- [fips-configuration.md](fips-configuration.md) — TUN configuration parameters
+20 -21
View File
@@ -212,8 +212,7 @@ be made.
### Unified Cache
The coordinate cache is a single unified cache. All sources — SessionSetup
transit, COORDS_PRESENT DataPackets, LookupResponse — write to the same
cache.
transit, CP-flagged data packets, LookupResponse — write to the same cache.
### Population Sources
@@ -221,7 +220,7 @@ cache.
| ------ | ---- | ---- |
| SessionSetup transit | Session establishment | Both src and dest coordinates |
| SessionAck transit | Session establishment | Responder's coordinates |
| DataPacket with COORDS_PRESENT | Warmup or recovery | Both src and dest coordinates |
| CP-flagged data packet | Warmup or recovery | Both src and dest coordinates (cleartext) |
| LookupResponse | Discovery | Target's coordinates |
### Eviction
@@ -312,7 +311,7 @@ Destination Unreachable.
## SessionSetup Self-Bootstrapping
SessionSetup is the mechanism that warms transit node coordinate caches
along a path, enabling subsequent DataPackets to route efficiently.
along a path, enabling subsequent data packets to route efficiently.
### How It Works
@@ -335,18 +334,19 @@ coordinates and warming caches in the other direction.
### Result
After the handshake completes, the entire forward and reverse paths have
cached coordinates for both endpoints. Subsequent DataPackets use minimal
cached coordinates for both endpoints. Subsequent data packets use minimal
headers (no coordinates) and route efficiently through the warmed caches.
## COORDS_PRESENT Warmup
## CP (Coords Present) Warmup
The COORDS_PRESENT flag on DataPackets provides a secondary cache-warming
The CP flag in the FSP common prefix provides a secondary cache-warming
mechanism that complements SessionSetup. See
[fips-session-layer.md](fips-session-layer.md) for the warmup-then-reactive
strategy.
Transit nodes process DataPackets with COORDS_PRESENT by extracting and
caching both source and destination coordinates the same operation
Transit nodes parse the CP flag from the FSP header and extract source and
destination coordinates from the cleartext section between the header and
ciphertext — no decryption needed. This is the same caching operation
performed for SessionSetup coordinates.
## Error Recovery
@@ -367,8 +367,7 @@ coordinates for the destination. It cannot make a forwarding decision.
**Source recovery**:
1. Initiate discovery (LookupRequest flood) for the destination
2. Reset COORDS_PRESENT warmup counter — subsequent DataPackets include
coordinates
2. Reset CP warmup counter — subsequent data packets include coordinates
3. When discovery completes, warmup counter resets again (covers timing gap)
The crypto session remains active throughout — only routing state is
@@ -386,7 +385,7 @@ source.
**Source recovery**:
1. Remove stale coordinates from cache
2. Initiate discovery for the destination
3. Reset COORDS_PRESENT warmup counter
3. Reset CP warmup counter
### Error Signal Rate Limiting
@@ -397,11 +396,11 @@ packets to the same destination hit the same routing failure simultaneously.
### Error Routing Limitation
Error signals route back to the source using `find_next_hop(src_addr)`. For
steady-state DataPackets (after the COORDS_PRESENT warmup window), the
steady-state data packets (after the CP warmup window), the
transit node may lack cached coordinates for the source. If so, the error is
silently dropped.
This blind spot is partially addressed by COORDS_PRESENT warmup: transit
This blind spot is partially addressed by CP warmup: transit
nodes receive source coordinates during the warmup phase. But after warmup
expires and transit caches for the source expire, errors may be lost. The
session idle timeout (90s) limits the window — if traffic stops long enough
@@ -423,8 +422,8 @@ sequence:
returns the destination's coordinates
4. **Session establishment**: SessionSetup carries coordinates, warming
transit caches along the path
5. **Warmup**: First N DataPackets include COORDS_PRESENT, reinforcing
transit caches
5. **Warmup**: First N data packets include CP flag, reinforcing transit
caches
The first packet to a new destination always triggers this sequence. The
packet is queued (bounded) until the session is established.
@@ -435,7 +434,7 @@ After session establishment and warmup:
- Transit nodes have cached coordinates for both endpoints
- Bloom filters have converged for the destination
- DataPackets use minimal headers (no coordinates)
- Data packets use minimal headers (no coordinates)
- Routing decisions are fast: bloom candidate selection + distance ranking
### Steady State
@@ -445,7 +444,7 @@ In steady state, the mesh is mostly self-maintaining:
- TreeAnnounce gossip keeps the spanning tree current
- FilterAnnounce gossip keeps bloom filters current
- Coordinate caches are refreshed by active routing traffic
- Occasional cache misses trigger COORDS_PRESENT or discovery, but these
- Occasional cache misses trigger CP warmup or discovery, but these
are rare when traffic is flowing
### Cache Expiry and Recovery
@@ -511,8 +510,8 @@ routing decisions but retains its own end-to-end encryption and identity.
| LookupResponse | ~400 bytes | Response to discovery | Yes (greedy routed) |
| SessionDatagram + SessionSetup | ~232402 bytes | Session establishment | Yes (routed) |
| SessionDatagram + SessionAck | ~122 bytes | Session confirmation | Yes (routed) |
| SessionDatagram + DataPacket (minimal) | 40 bytes + payload | Bulk traffic | Yes (routed) |
| SessionDatagram + DataPacket (with coords) | ~172 bytes + payload | Warmup/recovery | Yes (routed) |
| SessionDatagram + Data (minimal) | ~107 bytes + payload | Bulk traffic | Yes (routed) |
| SessionDatagram + Data (with CP) | ~150 bytes + payload | Warmup/recovery | Yes (routed) |
| SessionDatagram + CoordsRequired | 70 bytes | Cache miss error | Yes (routed) |
| SessionDatagram + PathBroken | 70+ bytes | Dead-end error | Yes (routed) |
| Disconnect | 2 bytes | Link teardown | No (peer-to-peer) |
@@ -550,7 +549,7 @@ recovery).
| Flush coord cache on parent change | **Implemented** |
| LookupRequest/LookupResponse discovery | **Implemented** |
| SessionSetup self-bootstrapping | **Implemented** |
| COORDS_PRESENT warmup-then-reactive | **Implemented** |
| CP warmup-then-reactive | **Implemented** |
| CoordsRequired recovery | **Implemented** |
| PathBroken recovery | **Implemented** |
| Error signal rate limiting | **Implemented** |
+25 -21
View File
@@ -125,7 +125,7 @@ and transmitted after the session is established.
SessionSetup is self-bootstrapping for routing. It carries the source's and
destination's tree coordinates in the clear (not inside the Noise payload).
As the message transits intermediate nodes, each node caches these coordinates,
warming the path for subsequent DataPackets that carry only addresses (no
warming the path for subsequent data packets that carry only addresses (no
coordinates).
SessionAck carries the responder's coordinates back along the reverse path,
@@ -145,12 +145,14 @@ This ensures exactly one handshake completes.
### Data Transfer
Once established, sessions carry DataPacket messages containing encrypted
application data. Each DataPacket includes:
Once established, sessions carry encrypted data using the FSP pipeline. Each
encrypted message includes:
- An explicit 8-byte counter for replay protection (used as the AEAD nonce)
- A flags byte (including COORDS_PRESENT for cache warming)
- The encrypted payload
- A 12-byte cleartext header (used as AEAD AAD) containing the counter and
flags (including the CP flag for coordinate cache warming)
- Optional cleartext coordinates when the CP flag is set
- An AEAD-encrypted payload containing a 6-byte inner header (session-relative
timestamp, message type, inner flags) followed by the application data
### Session Idle Timeout
@@ -239,9 +241,9 @@ discarded after the handshake.
## Replay Protection
FSP uses explicit 8-byte counters on the wire for replay protection. Each side
maintains a monotonically increasing send counter, transmitted with every
DataPacket. The receiver maintains a sliding window (2048-entry bitmap)
tracking which counters have been seen.
maintains a monotonically increasing send counter, included in the 12-byte
cleartext header of every encrypted message. The receiver maintains a sliding
window (2048-entry bitmap) tracking which counters have been seen.
This design is critical for operation over unreliable transports. Under UDP
packet loss or reordering, implicit nonce counters (where the receiver
@@ -253,34 +255,34 @@ regardless of what packets were lost or reordered.
The same `ReplayWindow` and `decrypt_with_replay_check()` implementation is
used at both the link and session layers.
## COORDS_PRESENT Warmup Strategy
## CP (Coords Present) Warmup Strategy
Session establishment (SessionSetup/SessionAck) warms transit node coordinate
caches along the path. But coordinate caches have a finite TTL (default 300s),
and entries may be evicted under memory pressure. When a transit node's cache
entry expires, it cannot forward DataPackets (which carry only addresses, not
entry expires, it cannot forward data packets (which carry only addresses, not
coordinates) and sends a CoordsRequired error.
FSP uses a warmup-then-reactive strategy to keep transit caches populated:
### Warmup Phase
After session establishment, the first N DataPackets (configurable, default 5)
include both source and destination coordinates via the COORDS_PRESENT flag.
Transit nodes cache these coordinates as packets pass through, reinforcing the
path established by SessionSetup.
After session establishment, the first N data packets (configurable, default 5)
include both source and destination coordinates via the CP flag in the FSP
common prefix. The coordinates appear in cleartext between the 12-byte header
and the ciphertext, allowing transit nodes to cache them without decryption.
### Steady State
After the warmup count is reached, FSP clears the COORDS_PRESENT flag and
sends minimal DataPackets (4-byte header instead of ~136 bytes with
coordinates). Transit nodes serve from their coordinate caches.
After the warmup count is reached, FSP clears the CP flag and sends minimal
data packets (12-byte header + ciphertext). Transit nodes serve from their
coordinate caches.
### Reactive Recovery
When FSP receives a CoordsRequired signal:
1. The warmup counter resets — subsequent DataPackets include coordinates again
1. The warmup counter resets — subsequent data packets include coordinates again
2. A new LookupRequest may be initiated to rediscover the destination's
current coordinates
3. When the LookupResponse arrives for an established session, the warmup
@@ -343,7 +345,7 @@ either fall back to bloom-filter-only routing or signal CoordsRequired.
The coordinate cache is a single unified cache (merged from previously
separate coord_cache and route_cache). All coordinate sources — SessionSetup
transit, COORDS_PRESENT DataPackets, LookupResponse — write to the same cache.
transit, CP-flagged data packets, LookupResponse — write to the same cache.
### Eviction Policy
@@ -380,7 +382,9 @@ node caches (still within their 300s TTL) are re-warmed.
| Session establishment (Noise IK) | **Implemented** |
| End-to-end encryption (ChaCha20-Poly1305) | **Implemented** |
| Explicit counter replay protection | **Implemented** |
| COORDS_PRESENT warmup-then-reactive | **Implemented** |
| CP warmup-then-reactive | **Implemented** |
| FSP wire format (prefix, AAD, inner header) | **Implemented** |
| Session-layer MMP report types | **Implemented** (wire format) |
| Identity cache (LRU-only) | **Implemented** |
| Coordinate cache (unified, TTL + refresh) | **Implemented** |
| Session idle timeout | **Implemented** |
+147 -80
View File
@@ -396,103 +396,166 @@ Orderly link teardown with reason code.
| 0x07 | Timeout | Keepalive or stale detection timeout |
| 0xFF | Other | Unspecified reason |
## Session-Layer Message Types
## Session-Layer Message Formats
These messages are carried as the payload of a SessionDatagram (0x00).
Session-layer messages are carried as the payload of a SessionDatagram (0x00).
All FSP messages begin with a **4-byte common prefix** that identifies the
protocol version, session lifecycle phase, per-packet flags, and payload length.
### SessionSetup (0x00)
### FSP Common Prefix (4 bytes)
| Field | Size | Description |
| ----- | ---- | ----------- |
| version | 4 bits (high) | Protocol version. Currently 0x0 |
| phase | 4 bits (low) | Session lifecycle phase (see table) |
| flags | 1 byte | Per-packet signal flags (zero during handshake) |
| payload_len | 2 bytes LE | Length of payload after phase-specific header |
### FSP Phase Table
| Phase | Type | Description |
| ----- | ---- | ----------- |
| 0x0 | Established | Post-handshake encrypted traffic or plaintext error signals |
| 0x1 | Handshake msg1 | SessionSetup (Noise IK msg1) |
| 0x2 | Handshake msg2 | SessionAck (Noise IK msg2) |
### FSP Flags (Established Phase Only)
| Bit | Name | Description |
| --- | ---- | ----------- |
| 0 | CP (coords present) | Source and destination coordinates follow the header in cleartext |
| 1 | K (key epoch) | Selects active key during rekeying |
| 2 | U (unencrypted) | Payload is plaintext (error signals) |
| 3-7 | — | Reserved (must be zero) |
Flags must be zero in handshake packets (phase 0x1 and 0x2).
### FSP Encrypted Message (phase 0x0, U flag clear)
Post-handshake encrypted data. The 12-byte cleartext header is used as AEAD
AAD. Coordinates may appear in cleartext between the header and ciphertext
when the CP flag is set.
**Cleartext header** (12 bytes, used as AEAD AAD):
| Field | Size | Description |
| ----- | ---- | ----------- |
| common prefix | 4 bytes | ver=0, phase=0, flags, payload_len |
| counter | 8 bytes LE | Monotonic nonce, used as AEAD nonce and for replay detection |
**Optional cleartext coordinates** (when CP flag is set):
| Field | Size | Description |
| ----- | ---- | ----------- |
| src_coords_count | 2 bytes LE | Number of source coordinate entries |
| src_coords | 16 x n bytes | Source's ancestry (NodeAddr, self -> root) |
| dest_coords_count | 2 bytes LE | Number of dest coordinate entries |
| dest_coords | 16 x m bytes | Destination's ancestry |
Transit nodes parse the CP flag and extract coordinates without decryption.
**Encrypted inner header** (6 bytes, first bytes of AEAD plaintext):
| Field | Size | Description |
| ----- | ---- | ----------- |
| timestamp | 4 bytes LE | Session-relative milliseconds (u32) |
| msg_type | 1 byte | Session-layer message type |
| inner_flags | 1 byte | Bit 0: SP (spin bit for RTT measurement) |
After the inner header, the remaining plaintext is the message-type-specific
body.
**Complete encrypted message**:
```text
┌─────────────────────────────────┬─────────────────┬───────────────────────────┐
│ header (12 bytes, used as AAD) │ [coords if CP] │ ciphertext + AEAD tag │
│ │ │ (inner_hdr + body) + 16 │
└─────────────────────────────────┴─────────────────┴───────────────────────────┘
```
### FSP Session Message Types
| Type | Message | Description |
| ---- | ------- | ----------- |
| 0x10 | Data | Application data (IPv6 payload via TUN) |
| 0x11 | SenderReport | MMP sender-side metrics report |
| 0x12 | ReceiverReport | MMP receiver-side metrics report |
| 0x13 | PathMtuNotification | End-to-end path MTU echo |
| 0x20 | CoordsRequired | Error: transit node lacks destination coordinates |
| 0x21 | PathBroken | Error: greedy routing reached dead end |
Message types 0x10-0x13 are carried inside the AEAD ciphertext (dispatched
by the `msg_type` field in the encrypted inner header). Types 0x20-0x21 are
plaintext error signals (U flag set, no encryption).
### SessionSetup (phase 0x1)
Establishes a session and warms transit coordinate caches.
Encoded with FSP prefix: ver=0, phase=0x1, flags=0, payload_len.
**Body** (after 4-byte FSP prefix):
| Offset | Field | Size | Description |
| ------ | ----- | ---- | ----------- |
| 0 | msg_type | 1 byte | 0x00 |
| 1 | flags | 1 byte | Bit 0: REQUEST_ACK, Bit 1: BIDIRECTIONAL |
| 2 | src_coords_count | 2 bytes LE | Number of source coordinate entries |
| 4 | src_coords | 16 x n bytes | Source's ancestry (NodeAddr, self -> root) |
| 0 | flags | 1 byte | Bit 0: REQUEST_ACK, Bit 1: BIDIRECTIONAL |
| 1 | src_coords_count | 2 bytes LE | Number of source coordinate entries |
| 3 | src_coords | 16 x n bytes | Source's ancestry (NodeAddr, self -> root) |
| ... | dest_coords_count | 2 bytes LE | Number of dest coordinate entries |
| ... | dest_coords | 16 x m bytes | Destination's ancestry |
| ... | handshake_len | 2 bytes LE | Noise payload length |
| ... | handshake_payload | variable | Noise IK msg1 (82 bytes typical) |
**Example** (depth-3 source, depth-4 destination):
```text
SessionDatagram header: 36 bytes
SessionSetup payload: 1 + 1 + 2 + 48 + 2 + 64 + 2 + 82 = 202 bytes
Total: 238 bytes
```
### SessionAck (0x01)
### SessionAck (phase 0x2)
Confirms session establishment, completes the Noise handshake.
Encoded with FSP prefix: ver=0, phase=0x2, flags=0, payload_len.
**Body** (after 4-byte FSP prefix):
| Offset | Field | Size | Description |
| ------ | ----- | ---- | ----------- |
| 0 | msg_type | 1 byte | 0x01 |
| 1 | flags | 1 byte | Reserved |
| 2 | src_coords_count | 2 bytes LE | Number of coordinate entries |
| 4 | src_coords | 16 x n bytes | Acknowledger's ancestry (for cache warming) |
| 0 | flags | 1 byte | Reserved |
| 1 | src_coords_count | 2 bytes LE | Number of coordinate entries |
| 3 | src_coords | 16 x n bytes | Acknowledger's ancestry (for cache warming) |
| ... | handshake_len | 2 bytes LE | Noise payload length |
| ... | handshake_payload | variable | Noise IK msg2 (33 bytes typical) |
### DataPacket (0x10)
### Data (0x10)
Encrypted application data with explicit replay protection counter.
**Minimal header** (COORDS_PRESENT = 0):
| Offset | Field | Size | Description |
| ------ | ----- | ---- | ----------- |
| 0 | msg_type | 1 byte | 0x10 |
| 1 | flags | 1 byte | Bit 0: COORDS_PRESENT |
| 2 | counter | 8 bytes LE | Session encryption counter / replay nonce |
| 10 | payload_length | 2 bytes LE | Length of encrypted payload |
| 12 | payload | variable | Encrypted application data + 16-byte AEAD tag |
**Header size**: 12 bytes (`DATA_HEADER_SIZE`)
**With coordinates** (COORDS_PRESENT = 1):
| Offset | Field | Size | Description |
| ------ | ----- | ---- | ----------- |
| 0 | msg_type | 1 byte | 0x10 |
| 1 | flags | 1 byte | 0x01 (COORDS_PRESENT) |
| 2 | counter | 8 bytes LE | Session encryption counter |
| 10 | payload_length | 2 bytes LE | Length of encrypted payload |
| 12 | src_coords_count | 2 bytes LE | Source coordinate entries |
| 14 | src_coords | 16 x n bytes | Source's ancestry |
| ... | dest_coords_count | 2 bytes LE | Dest coordinate entries |
| ... | dest_coords | 16 x m bytes | Destination's ancestry |
| ... | payload | variable | Encrypted application data |
Application data (typically IPv6 payload). This is the `msg_type` byte
inside the encrypted inner header — there is no separate DataPacket struct.
The body after the inner header is delivered directly to the TUN interface.
### CoordsRequired (0x20)
Link-layer error signal — transit node lacks coordinates for destination.
Plaintext (not end-to-end encrypted), generated by transit nodes.
Plaintext error signal — transit node lacks coordinates for destination.
Encoded with FSP prefix: ver=0, phase=0x0, U flag set, payload_len.
**Body** (after 4-byte FSP prefix + 1-byte msg_type):
| Offset | Field | Size | Description |
| ------ | ----- | ---- | ----------- |
| 0 | msg_type | 1 byte | 0x20 |
| 1 | flags | 1 byte | Reserved |
| 2 | dest_addr | 16 bytes | NodeAddr we couldn't route to |
| 18 | reporter | 16 bytes | NodeAddr of reporting router |
| 0 | flags | 1 byte | Reserved |
| 1 | dest_addr | 16 bytes | NodeAddr we couldn't route to |
| 17 | reporter | 16 bytes | NodeAddr of reporting router |
**Payload**: 34 bytes. Wrapped in SessionDatagram: 70 bytes total.
**Body size**: 33 bytes. Total with prefix + msg_type: 38 bytes.
### PathBroken (0x21)
Link-layer error signal — greedy routing reached a dead end. Plaintext,
generated by transit nodes.
Plaintext error signal — greedy routing reached a dead end.
Encoded with FSP prefix: ver=0, phase=0x0, U flag set, payload_len.
**Body** (after 4-byte FSP prefix + 1-byte msg_type):
| Offset | Field | Size | Description |
| ------ | ----- | ---- | ----------- |
| 0 | msg_type | 1 byte | 0x21 |
| 1 | flags | 1 byte | Reserved |
| 2 | dest_addr | 16 bytes | Unreachable NodeAddr |
| 18 | reporter | 16 bytes | NodeAddr of reporting router |
| 34 | last_coords_count | 2 bytes LE | Number of stale coordinate entries |
| 36 | last_known_coords | 16 x n bytes | Stale coordinates that failed |
| 0 | flags | 1 byte | Reserved |
| 1 | dest_addr | 16 bytes | Unreachable NodeAddr |
| 17 | reporter | 16 bytes | NodeAddr of reporting router |
| 33 | last_coords_count | 2 bytes LE | Number of stale coordinate entries |
| 35 | last_known_coords | 16 x n bytes | Stale coordinates that failed |
## Encapsulation Walkthrough
@@ -507,19 +570,19 @@ Layer 4: Application data
1024 bytes
Layer 3: Session encryption (FSP)
DataPacket header (12 bytes) + encrypted payload (1024) + AEAD tag (16)
= 1052 bytes
FSP header (12 bytes) + AEAD(inner_hdr (6) + payload (1024)) + AEAD tag (16)
= 1058 bytes
Layer 2: SessionDatagram envelope (FLP routing)
msg_type (1) + ttl (1) + path_mtu (2) + src_addr (16) + dest_addr (16) + payload (1052)
= 1088 bytes
msg_type (1) + ttl (1) + path_mtu (2) + src_addr (16) + dest_addr (16) + payload (1058)
= 1094 bytes
Layer 1: Link encryption (FLP per-hop)
outer header (16) + encrypted(inner_hdr (5) + datagram (1088)) + AEAD tag (16)
= 1125 bytes
outer header (16) + encrypted(inner_hdr (5) + datagram (1094)) + AEAD tag (16)
= 1131 bytes
Layer 0: Transport
UDP datagram containing 1125 bytes
UDP datagram containing 1131 bytes
```
### Overhead Budget
@@ -528,11 +591,12 @@ Layer 0: Transport
| ----- | -------- | --------- |
| Link encryption | 37 bytes | 16 outer header (AAD) + 5 inner header + 16 AEAD tag |
| SessionDatagram | 36 bytes | 1 type + 1 ttl + 2 path_mtu + 16 src + 16 dest |
| DataPacket header | 12 bytes | 1 type + 1 flags + 8 counter + 2 length |
| FSP header | 12 bytes | 4 prefix + 8 counter |
| FSP inner header | 6 bytes | 4 timestamp + 1 msg_type + 1 inner_flags (inside AEAD) |
| Session AEAD tag | 16 bytes | Poly1305 tag on session-encrypted payload |
| **Minimal total** | **101 bytes** | |
| **Minimal total** | **107 bytes** | |
| Coordinates (if present) | ~43 bytes | Varies with tree depth |
| **Worst case** | **144 bytes** | `FIPS_OVERHEAD` constant |
| **Worst case** | **150 bytes** | `FIPS_OVERHEAD` constant |
### At Each Transit Node
@@ -581,20 +645,23 @@ endpoint session keys).
| ------- | ------------ | ----- |
| SessionSetup | ~200 bytes | Depth-dependent |
| SessionAck | ~80 bytes | Depth-dependent |
| DataPacket (minimal) | 12 + payload bytes | Steady state |
| DataPacket (with coords) | 12 + ~130 + payload bytes | Warmup/recovery |
| CoordsRequired | 34 bytes | Fixed |
| PathBroken | 36 + 16n bytes | Includes stale coords |
| Data (minimal) | 12 + 6 + payload + 16 bytes | Steady state |
| Data (with coords) | 12 + ~130 + 6 + payload + 16 bytes | Warmup/recovery |
| SenderReport | 12 + 6 + 46 + 16 bytes | MMP metrics |
| ReceiverReport | 12 + 6 + 66 + 16 bytes | MMP metrics |
| PathMtuNotification | 12 + 6 + 2 + 16 bytes | MTU signal |
| CoordsRequired | 38 bytes | Fixed (prefix + msg_type + body) |
| PathBroken | 35 + 16n bytes | Includes stale coords |
### Complete Packet Sizes (link + session)
| Scenario | Wire Size | Notes |
| -------- | --------- | ----- |
| Encrypted frame minimum | 37 bytes | Empty body |
| SessionDatagram + DataPacket (minimal) | 37 + 36 + 12 + payload + 16 | 101 + payload |
| SessionDatagram + DataPacket (with coords) | ~144 + payload | Worst case |
| SessionDatagram + Data (minimal) | 37 + 36 + 12 + 6 + payload + 16 | 107 + payload |
| SessionDatagram + Data (with coords) | ~150 + payload | Worst case |
| SessionDatagram + SessionSetup | ~275 bytes | Depth-3, both dirs |
| SessionDatagram + CoordsRequired | 37 + 36 + 34 = 107 bytes | Including link overhead |
| SessionDatagram + CoordsRequired | 37 + 36 + 38 = 111 bytes | Including link overhead |
## References