Lesson 9

Reading the Wire

Every preceding lesson worked at the level of boxes and arrows: a packet enters a router, the router decrypts the link layer, reads the routing envelope, and forwards. This lesson zooms in on the bytes that make those boxes real. The goal is to make you comfortable walking through a capture or staring at a hex dump and recognizing what each byte is for.

Ground rules

  • Multi-byte integers are little-endian.
  • A NodeAddr is 16 bytes (truncated SHA-256 of the x-only public key).
  • Signatures are 64 bytes (secp256k1 Schnorr).
  • Variable-length arrays use a 2-byte little-endian count prefix, then that many fixed-size items.
  • Public keys on the wire are 33 bytes (compressed secp256k1, the 0x02/0x03 prefix plus 32 bytes of X).

The 4-byte common prefix

Every FMP and FSP packet starts with the same 4-byte common prefix. It is the only piece of either protocol that a transit transport can depend on without knowing anything else about the link session.

bytes 0..3, LE
ver (4 bits) phase (4 bits) flags (1B) payload_len (2B)

Phase is how a receiver tells handshake packets apart from established traffic: 0x0 is post-handshake, 0x1 and 0x2 are Noise IK msg1 and msg2 at the link layer, 0x1, 0x2, and 0x3 are SessionSetup / SessionAck / SessionMsg3 at the session layer (FSP adds a third handshake message for XK).

Flags are per-packet signals. The interesting ones at the link layer are K (key epoch during rekey), CE (ECN echo), and SP (spin bit for RTT). At the FSP layer the CP bit signals that cleartext coordinates follow the header, and U marks the packet as unencrypted (used by the three error signals).

Link frame, encrypted

The 16-byte FMP outer header is the AEAD associated data: common prefix, receiver_idx (so the receiver can find the session in O(1)), and an 8-byte counter that doubles as the AEAD nonce and the replay-window sequence number. ChaCha20-Poly1305 covers everything after. The 16-byte Poly1305 tag sits at the end of the frame.

Inside the AEAD plaintext, the first five bytes are a link-layer inner header: a 4-byte session-relative timestamp and the 1-byte msg_type. Everything after that is whatever the message type says it is: TreeAnnounce, FilterAnnounce, SessionDatagram, LookupRequest, and so on.

SessionDatagram, the routing envelope

Multi-hop traffic rides inside a SessionDatagram, which is what transit routers actually look at. Its fixed fields are ttl (1B), path_mtu (2B LE, min'd at every hop), src_addr and dest_addr (16B each). After the header comes an opaque FSP message that the router will not, and cannot, decrypt.

FSP brings its own cleartext 12-byte header: the same common prefix plus an 8-byte AEAD counter, matching the link layer's structure. When CP is set, the next bytes are two variable-length coordinate arrays before the ciphertext. Transit routers parse them and update their caches without touching the ciphertext.

Where the overhead goes

Every IPv6 packet carried by FIPS has 77 bytes of protocol overhead at the link layer: 106 bytes of base FSP + FMP + SessionDatagram, 4 bytes for the src_port / dst_port, minus 33 bytes saved by the IPv6 shim compressing the 40-byte IPv6 header down to 7 bytes of residual fields.

Walk it yourself

Pick a message type below. Each block is a field, sized roughly by its byte count. Click a block to see what it means, and adjust the toggles to watch the totals change.

An IPv6 ping inside FSP inside SessionDatagram inside FMP. Toggle CP to watch the cleartext coordinates appear between the FSP header and the ciphertext.

FMP outer (AAD)32BFMP inner (AEAD plaintext)5BSessionDatagram35BFSP cleartext12BFSP inner (AEAD plaintext)26BApplication71BTotal: 181 bytes

Click any block above to see what the field is and what it means on the wire.

Message-type table, at a glance

Link-layer (FMP) message types

0x00SessionDatagram
0x01SenderReport
0x02ReceiverReport
0x10TreeAnnounce
0x20FilterAnnounce
0x30LookupRequest
0x31LookupResponse
0x50Disconnect
0x51Heartbeat

Session-layer (FSP) message types

0x10Data (port-multiplexed)
0x11SenderReport
0x12ReceiverReport
0x13PathMtuNotification
0x14CoordsWarmup
0x20CoordsRequired (plaintext)
0x21PathBroken (plaintext)
0x22MtuExceeded (plaintext)

A couple of details worth remembering

  • TreeAnnounce carries a Schnorr signature over the whole message. Any peer can verify it before replaying the state, so tree updates do not depend on trusting the relay.
  • LookupResponse signs (request_id || target || target_coords). The path_mtu field is excluded from the signature because every transit hop rewrites it.
  • Error signals (CoordsRequired, PathBroken, MtuExceeded) set the U flag in the FSP common prefix and carry a plaintext body. No session keys are needed to read them.
  • The Ethernet transport adds a 3-byte header (frame type + 2-byte length) on data frames so the receiver can trim Ethernet minimum-frame padding that would otherwise corrupt AEAD verification.

Lesson quiz

1. How many bytes is the FMP common prefix, and what does it contain?

2. What is the purpose of the receiver_idx field in an established FMP frame?

3. Which field is excluded from the LookupResponse's Schnorr signature, and why?

4. A transit router sees a SessionDatagram go by. What can it read, and what is it blind to?

5. IPv6 traffic through FIPS has 77 bytes of net overhead. Where does the number come from?

6. How does a receiver know whether an FSP packet carries cleartext coordinates?