Implement FSP port multiplexing and IPv6 header compression

Breaking wire format change: DataPacket payloads inside the AEAD envelope
now carry a 4-byte port header [src_port:2 LE][dst_port:2 LE] before the
service payload. The receiver dispatches by destination port.

Port multiplexing:
- send_session_data() takes src_port/dst_port params, prepends port header
- New send_ipv6_packet() compresses IPv6 header and sends on port 256
- Receive path dispatches DataPackets by port: port 256 decompresses IPv6
  header from session context and delivers to TUN, unknown ports dropped
- Port constants: FSP_PORT_HEADER_SIZE (4 bytes), FSP_PORT_IPV6_SHIM (256)

IPv6 header compression:
- New ipv6_shim module with compress_ipv6()/decompress_ipv6() pure functions
- Strips src/dst addresses (32 bytes) and payload length (2 bytes) from each
  packet, preserving traffic class, flow label, next header, and hop limit
  as 6-byte residual fields
- Addresses reconstructed from session context on receive side
- Net savings: 29 bytes per packet (overhead 106 → 77 bytes)
- FIPS_IPV6_OVERHEAD constant (77 bytes), effective_ipv6_mtu() updated
- 16 unit tests for round-trip fidelity, field preservation, error cases

Documentation:
- fips-wire-formats: DataPacket port header, port registry, IPv6 shim
  format tables, updated encapsulation walkthrough and overhead budget
- fips-ipv6-adapter: FIPS_IPV6_OVERHEAD (77 bytes), updated MTU numbers,
  TUN reader/writer flow with compression steps, impl status
- fips-session-layer: port-based service dispatch section, data transfer
  description, impl status
- fips-intro: IPv6 adapter as port 256 service, node architecture updated
- fips-mesh-operation: packet size summary with compressed overhead
- DataPacket doc updated with port header and dispatch model
- session_wire.rs module doc: DataPacket Port Multiplexing section
This commit is contained in:
Johnathan Corgan
2026-03-11 12:53:32 +00:00
parent f37eb4b846
commit 6ab8b35755
12 changed files with 644 additions and 77 deletions
+10 -7
View File
@@ -155,18 +155,21 @@ demultiplexing. FSP provides a datagram service to applications above.
See [fips-session-layer.md](fips-session-layer.md) for the FSP specification.
**IPv6 adaptation layer**: Sits above FSP and adapts the FIPS datagram
service for unmodified IPv6 applications. Provides DNS resolution (npub →
fd00::/8 address), identity cache management, MTU enforcement, and a TUN
interface. This is the primary way existing applications use the FIPS mesh.
**IPv6 adaptation layer**: Sits above FSP as a service on port 256, adapting
the FIPS datagram service for unmodified IPv6 applications. Provides DNS
resolution (npub → fd00::/8 address), identity cache management, IPv6 header
compression, MTU enforcement, and a TUN interface. This is the primary way
existing applications use the FIPS mesh.
See [fips-ipv6-adapter.md](fips-ipv6-adapter.md) for the IPv6 adapter.
### Node Architecture
Two application interfaces sit at the top of the stack: a native datagram
API addressed by npub, and an IPv6 TUN adapter that maps npubs to `fd00::/8`
addresses so unmodified IP applications can use the network transparently.
Application services sit at the top of the stack, dispatched by FSP port
number: the IPv6 TUN adapter (port 256) maps npubs to `fd00::/8` addresses
with header compression so unmodified IP applications can use the network
transparently, while the native datagram API addresses destinations directly
by npub.
![Node Architecture](diagrams/fips-node-architecture.svg)
+22 -11
View File
@@ -146,35 +146,41 @@ wrapping.
| 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 |
| **Data path total** | **106 bytes** | `FIPS_OVERHEAD` constant |
| **Protocol envelope** | **106 bytes** | `FIPS_OVERHEAD` constant |
| Port header | 4 bytes | src_port + dst_port (DataPacket service dispatch) |
| IPv6 compression | 33 bytes | 40-byte IPv6 header → 7-byte format + residual |
| **IPv6 data path total** | **77 bytes** | `FIPS_IPV6_OVERHEAD` constant |
Coordinate piggybacking (CP flag) adds variable overhead: `2 + entries × 16`
per coordinate, with both src and dst coords sent. The send path skips the
CP flag if adding coords would exceed the transport MTU.
The `FIPS_OVERHEAD` constant (106 bytes) represents the fixed data path
overhead and is used for MTU calculations.
The `FIPS_OVERHEAD` constant (106 bytes) represents the base protocol
envelope overhead (link encryption + routing + session encryption). For IPv6
traffic, FSP port multiplexing adds 4 bytes (port header) while IPv6 header
compression saves 33 bytes (40-byte header → 7-byte format + residual),
yielding a net `FIPS_IPV6_OVERHEAD` of 77 bytes.
### Effective IPv6 MTU
The effective IPv6 MTU visible to applications is:
```text
effective_ipv6_mtu = transport_mtu - FIPS_OVERHEAD
effective_ipv6_mtu = transport_mtu - FIPS_IPV6_OVERHEAD
```
For typical deployments:
| Transport MTU | Effective IPv6 MTU | Notes |
| ------------- | ------------------ | ----- |
| 1472 (UDP/Ethernet) | 1366 | Standard deployment |
| 1280 (UDP minimum) | 1174 | Below IPv6 minimum |
| 1472 (UDP/Ethernet) | 1395 | Standard deployment |
| 1280 (UDP minimum) | 1203 | 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 + 106 = 1386 bytes
1280 + 77 = 1357 bytes
```
Transports with smaller MTUs (radio at ~250 bytes, serial at 256 bytes) cannot
@@ -249,15 +255,18 @@ The TUN reader receives raw IPv6 packets from applications and processes them:
3. Look up identity cache — miss returns ICMPv6 Destination Unreachable
4. Retrieve NodeAddr and PublicKey from cache
5. Look up or establish FSP session
6. Encrypt payload with session keys
7. Route through FMP toward destination
6. Compress IPv6 header: strip addresses and payload length, build format 0x00
payload with residual fields (traffic class, flow label, next header, hop limit)
7. Prepend port header (src_port=256, dst_port=256)
8. Encrypt with session keys
9. Route through FMP toward destination
### Writer Thread
A single writer thread services an mpsc queue of outbound packets:
- Inbound mesh traffic (decrypted session payloads destined for local
applications)
- Inbound mesh traffic on port 256 (IPv6 header reconstructed from session
context + residual fields, then delivered as complete IPv6 packets)
- ICMPv6 error responses (Packet Too Big, Destination Unreachable)
- TCP MSS-clamped SYN-ACK packets
@@ -305,6 +314,8 @@ TUN device creation requires `CAP_NET_ADMIN`. Options:
| ICMP rate limiting (per-source) | **Implemented** |
| TCP MSS clamping (SYN + SYN-ACK) | **Implemented** |
| DNS service (.fips domain) | **Implemented** |
| Port-based service multiplexing (port 256) | **Implemented** |
| IPv6 header compression (format 0x00) | **Implemented** |
| Per-destination route MTU (netlink) | Planned |
| Transit MTU error signal | **Implemented** |
| Path MTU tracking (SessionDatagram field) | **Implemented** |
+2 -2
View File
@@ -647,8 +647,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 | ~170 bytes | Session confirmation | Yes (routed) |
| SessionDatagram + Data (minimal) | 106 bytes + payload | Bulk traffic | Yes (routed) |
| SessionDatagram + Data (with CP) | 106 + coords + payload | Warmup/recovery | Yes (routed) |
| SessionDatagram + Data (minimal) | 77 bytes + IPv6 payload | Bulk IPv6 traffic (compressed) | Yes (routed) |
| SessionDatagram + Data (with CP) | 77 + coords + IPv6 payload | Warmup/recovery (compressed) | 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) |
+14 -1
View File
@@ -45,6 +45,15 @@ arriving at the TUN are translated to FIPS datagrams and routed through FSP.
See [fips-ipv6-adapter.md](fips-ipv6-adapter.md) for the IPv6 adaptation
layer.
### Port-Based Service Dispatch
FSP DataPackets carry a 4-byte port header (source and destination port) inside
the AEAD envelope, enabling multiple services to share a single session. The
IPv6 adapter runs on port 256; the native FIPS API and future services
(gateways, application protocols) register on other ports. Port dispatch is
internal to the FSP layer — services see only their payload, not the port
header.
### What Applications Get
- **Authenticated datagram delivery**: Each datagram is encrypted and
@@ -174,7 +183,9 @@ encrypted message includes:
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
timestamp, message type, inner flags) followed by DataPacket content: a 4-byte
port header (src_port, dst_port) and the service payload. The receiver
dispatches by destination port to the registered service handler.
### Session Idle Timeout
@@ -539,6 +550,8 @@ MMP session metrics session=npub1tdwa...84le rtt=4.3ms loss=0.6% jitter=0.2ms go
| Simultaneous initiation tie-breaker | **Implemented** |
| Flush coord cache on parent change | **Implemented** |
| Rekey | **Implemented** |
| Port-based service multiplexing | **Implemented** |
| IPv6 header compression (shim on port 256) | **Implemented** |
| Path MTU tracking (FMP SessionDatagram field) | **Implemented** |
| Path MTU notification (end-to-end echo) | **Implemented** |
+89 -20
View File
@@ -488,7 +488,7 @@ body.
| Type | Message | Description |
| ---- | ------- | ----------- |
| 0x10 | Data | Application data (IPv6 payload via TUN) |
| 0x10 | Data | Port-multiplexed service payload (see DataPacket below) |
| 0x11 | SenderReport | MMP sender-side metrics report |
| 0x12 | ReceiverReport | MMP receiver-side metrics report |
| 0x13 | PathMtuNotification | End-to-end path MTU echo |
@@ -580,11 +580,64 @@ Encoded with FSP prefix: ver=0, phase=0x3, flags=0, payload_len.
SessionMsg3 does not carry coordinates — both endpoints already have each
other's coordinates from SessionSetup (msg1) and SessionAck (msg2).
### Data (0x10)
### Data (0x10) — DataPacket Port Multiplexing
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.
DataPacket is the primary application data carrier. The body after the
6-byte encrypted inner header contains a 4-byte port header followed by
the service payload:
| Offset | Field | Size | Description |
| ------ | ----- | ---- | ----------- |
| 0 | src_port | 2 bytes LE | Source service port |
| 2 | dst_port | 2 bytes LE | Destination service port |
| 4 | payload | variable | Service-specific payload |
The receiver dispatches by `dst_port` to the registered service handler.
**Port registry (three tiers):**
| Range | Purpose |
| ----- | ------- |
| 0255 (0x000xFF) | Reserved, protocol use |
| 2561023 (0x1000x3FF) | Reserved, FIPS standard services |
| 102465535 (0x4000xFFFF) | Application use |
**Initial assignment**: Port 256 (0x100) = IPv6 shim.
#### IPv6 Shim Payload Format (Port 256)
The IPv6 shim defines its own payload format with a leading format byte:
| Offset | Field | Size | Description |
| ------ | ----- | ---- | ----------- |
| 0 | format | 1 byte | Compression format (0x00 = mesh-internal compressed) |
| 1 | fields | variable | Format-specific residual fields |
**Format 0x00 — mesh-internal compressed (default):**
Strips source and destination IPv6 addresses (32 bytes) and payload length
(2 bytes) from each packet. Carries residual fields that cannot be derived
from session context:
| Offset | Field | Size | Description |
| ------ | ----- | ---- | ----------- |
| 0 | format | 1 byte | 0x00 |
| 1 | traffic_class | 1 byte | IPv6 Traffic Class (DSCP + ECN) |
| 2 | flow_label | 3 bytes | IPv6 Flow Label (20 bits, big-endian, zero-padded) |
| 5 | next_header | 1 byte | IPv6 Next Header (protocol identifier) |
| 6 | hop_limit | 1 byte | IPv6 Hop Limit |
| 7 | upper_payload | variable | Upper-layer payload (TCP, UDP, ICMPv6, etc.) |
The receiver reconstructs the full 40-byte IPv6 header from session context
(source and destination addresses derived from session npubs, version = 6,
payload length from outer packet length) plus the 6 bytes of residual fields,
then delivers the complete IPv6 packet to the TUN interface.
**Format 0x01+**: Reserved for future use (e.g., full-header gateway traffic).
**Compression savings**: 29 bytes per packet (34 bytes stripped, 7 bytes
format + residual added). Net overhead for IPv6 traffic: 77 bytes
(`FIPS_IPV6_OVERHEAD`), down from 110 bytes base DataPacket overhead.
### PathMtuNotification (0x13)
@@ -695,28 +748,39 @@ A complete picture of how application data is wrapped through each layer.
### Application Data -> Wire
Starting with an application sending a 1024-byte payload to a destination:
Starting with an IPv6 application sending a 1024-byte TCP payload to a
destination (the original IPv6 packet at the TUN is 1064 bytes: 40-byte
header + 1024-byte payload):
```text
Layer 4: Application data
1024 bytes
Layer 5: Application data
1024 bytes (TCP payload inside 1064-byte IPv6 packet)
Layer 4: IPv6 shim compression (port 256)
Strip IPv6 addresses (32) + payload length (2), keep residual fields
format (1) + residual (6) + upper payload (1024) = 1031 bytes
Layer 3: Session encryption (FSP)
FSP header (12 bytes) + AEAD(inner_hdr (6) + payload (1024)) + AEAD tag (16)
= 1058 bytes
FSP header (12) + AEAD(inner_hdr (6) + port_hdr (4) + shim (1031)) + tag (16)
= 12 + 1041 + 16 = 1069 bytes
Layer 2: SessionDatagram envelope (FMP routing)
msg_type (1) + ttl (1) + path_mtu (2) + src_addr (16) + dest_addr (16) + payload (1058)
= 1094 bytes
msg_type (1) + ttl (1) + path_mtu (2) + src_addr (16) + dest_addr (16) + payload (1069)
= 1105 bytes
Layer 1: Link encryption (FMP per-hop)
outer header (16) + encrypted(inner_hdr (5) + datagram (1094)) + AEAD tag (16)
= 1131 bytes
outer header (16) + encrypted(inner_hdr (5) + datagram (1105)) + AEAD tag (16)
= 1142 bytes
Layer 0: Transport
UDP datagram containing 1131 bytes
UDP datagram containing 1142 bytes
```
Total overhead for IPv6 traffic: 1142 1064 = 78 bytes per packet. The
difference from the `FIPS_IPV6_OVERHEAD` constant (77 bytes) is the 1-byte
FMP `msg_type` counted in the link inner header rather than the
SessionDatagram body.
### Overhead Budget
| Layer | Overhead | Component |
@@ -726,7 +790,11 @@ Layer 0: Transport
| 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 |
| **Data path total** | **106 bytes** | `FIPS_OVERHEAD` constant |
| **Protocol envelope** | **106 bytes** | `FIPS_OVERHEAD` constant |
| Port header | 4 bytes | src_port + dst_port (DataPacket only) |
| **DataPacket total** | **110 bytes** | Base overhead for any port-multiplexed service |
| IPv6 compression | 33 bytes | 40-byte IPv6 header → 7-byte format + residual |
| **IPv6 data path total** | **77 bytes** | `FIPS_IPV6_OVERHEAD` constant |
### At Each Transit Node
@@ -784,8 +852,8 @@ endpoint session keys).
| SessionSetup | ~170 bytes | Depth-dependent (XK msg1 = 33 bytes) |
| SessionAck | ~190 bytes | Depth-dependent, carries both endpoints' coords (XK msg2 = 57 bytes) |
| SessionMsg3 | ~80 bytes | Fixed (XK msg3 = 73 bytes, no coords) |
| Data (minimal) | 12 + 6 + payload + 16 bytes | Steady state |
| Data (with coords) | 12 + ~130 + 6 + payload + 16 bytes | Warmup/recovery |
| Data (minimal) | 12 + 6 + 4 + payload + 16 bytes | Steady state (port header included) |
| Data (with coords) | 12 + ~130 + 6 + 4 + payload + 16 bytes | Warmup/recovery (port header included) |
| SenderReport | 12 + 6 + 46 + 16 bytes | MMP metrics |
| ReceiverReport | 12 + 6 + 66 + 16 bytes | MMP metrics |
| PathMtuNotification | 12 + 6 + 2 + 16 bytes | MTU signal |
@@ -799,8 +867,9 @@ endpoint session keys).
| Scenario | Wire Size | Notes |
| -------- | --------- | ----- |
| Encrypted frame minimum | 37 bytes | Empty body |
| SessionDatagram + Data (minimal) | 37 + 35 + 12 + 6 + payload + 16 | 106 + payload |
| SessionDatagram + Data (with coords) | 106 + coords + payload | Coords vary with tree depth |
| SessionDatagram + Data (minimal) | 37 + 35 + 12 + 6 + 4 + payload + 16 | 110 + payload (any service) |
| SessionDatagram + IPv6 Data (minimal) | 110 + 7 + upper_payload 34 | 77 + IPv6 payload (compressed) |
| SessionDatagram + Data (with coords) | 110 + coords + payload | Coords vary with tree depth |
| SessionDatagram + SessionSetup | ~275 bytes | Depth-3, both dirs |
| SessionDatagram + CoordsRequired | 37 + 36 + 38 = 111 bytes | Including link overhead |