Relay protocol
What travels between a node and its relay, and between two federating relays: the envelope layouts, the error codes, the per-leg overheads, and the fields relays, the signed map, the snapshot and the journal carry for the relay directory and federation. The relay never sees payload plaintext — a forwarded Atlas datagram is encrypted between the two peers whatever it carries.
Frames
Three outer message types exist. A client opens a session with Noise IK against the relay's pinned static key (RelaySessionInit 0x0D, RelaySessionResp 0x0E); afterwards both directions exchange RelayFrame 0x0F: a u64 nonce and the AEAD ciphertext of one envelope. Relays answer STUN on the same UDP port, by default 3478.
The outer header carries the node's wire number. A relay server carries two wires on its own sessions, the current one and the one before it — today 3 and 2 — and answers each client on the version that client's init spoke, so the hosted relays can run ahead of every customer fleet. A datagram on wire 1 is dropped unanswered, counted protocol_refused in the heartbeat, and its sender named once in the log. The relay contract is 2: contract 1 is the pre-federation set (Forward, Deliver, Keepalive, Error), and 2 adds ForwardVia, Close, DeliverVia, the federation hello, RelayForward and RelayError. nexus-atlas-relay --version prints both: nexus-atlas-relay 1.0.0+4d54d40 · wire 3, 2 · relay 2.
Envelope layouts
The plaintext inside a RelayFrame; keys are 32-byte Curve25519 static public keys.
Forward 0x01 | dest[32] | ptype[1] | payload… client → relay
Deliver 0x02 | src[32] | ptype[1] | payload… relay → client
Keepalive 0x03 both
ForwardVia 0x04 | dest[32] | via[32] | ptype[1] | payload… client → relay
RelayForward 0x10 | src_client[32] | dst_client[32] | hop[1] | ptype[1] | payload…
relay → relay
RelayError 0x11 | code[1] | dst_client[32] | from_relay[32] | src_client[32]
relay → relay
Error 0x7F | code[1] | dest[32] relay → client
ptype | Payload |
|---|---|
0x01 | A complete Atlas datagram, injected into the receiver's normal inbound path. |
0x02 | A rendezvous message: candidate lists and punch signals. |
0x03 | A relay-election RTT advertisement. |
The via hint
A Forward may carry a via hint: the static key of the relay the destination is homed on. Without a hint it is a plain Forward (kind 0x01); with one it is kind 0x04. A relay without federation support (relay contract 1) does not know that kind and drops the frame silently, so a hinted frame is never misread as a plain Forward.
- The node's relay R1 receives
ForwardVia{dest, via}. If R1 holdsdestitself, it delivers locally. Otherwise, with federation on andvialisted in its signed relay map, it wraps the frame asRelayForwardwithhop = 0and sends it over its Noise IK session to theviarelay R2, opening that session if needed. - R2 delivers it to the destination as an ordinary
Deliver{src}— the destination sees nothing new — or answersRelayErroron the same session. - R1 turns a
RelayErrorinto the usualError{code, dest}for the sender.RelayErrornamessrc_clientso R1 can address the error without keeping per-frame state.
Exactly one relay-to-relay hop: a RelayForward is delivered or refused, never sent on, and one arriving with hop != 0 is dropped. A RelayForward from a relay key the signed map does not list is dropped as well.
Error codes
| Code | Name | Meaning to the sender |
|---|---|---|
1 | UNKNOWN_DEST | The destination is not registered on the relay that was asked — the sender's relay for a plain Forward, the via relay for a federated one. |
2 | RATE_LIMITED | The relay refused to forward: the sender's session quota, or the relay pair's pair_mbps bucket on either relay. |
3 | VIA_UNKNOWN | The via relay is not in the sender's relay's signed map, cannot be reached (handshake failed, or every inter-relay session is busy), or does not federate. |
4 | FEDERATION_OFF | The sender's own relay does not federate ([federation] enabled = false). |
On the relay-to-relay leg, a far relay that does not federate answers RelayError{FEDERATION_OFF}, which the first relay reports to its client as VIA_UNKNOWN, so FEDERATION_OFF at a client always means its own relay. atlasd answers codes 3 and 4 by sending plain Forwards on that relay for 60 s before retrying the hint.
Overheads
| Leg | Bytes per datagram | Made of |
|---|---|---|
Client ⇄ relay, Forward / Deliver | 66 | outer header 8 + nonce 8 + AEAD tag 16 + kind 1 + key 32 + ptype 1 |
Client → relay, ForwardVia | 98 | the above + the 32-byte via key |
Relay → relay, RelayForward | 99 | outer header 8 + nonce 8 + tag 16 + kind 1 + src 32 + dst 32 + hop 1 + ptype 1 |
The relay-to-relay leg is the largest of the three, so a node with federation on caps its fragment payload so that leg stays one 1500-byte IPv4 datagram:
Atlas datagram = payload + 56; + 99 relay → relay leg; + 28 UDP/IPv4
1500 − 183 = 1317-byte fragment payload
Inter-relay sessions
- Noise IK between the two relays' static keys, which come from the same Ed25519-signed relay map clients use, verified with the same pinned key.
- Opened on the first
vianaming a relay, keepalived every 20 s, declared dead after 45 s of silence (and handshaken again if still in use), closed after 5 minutes unused. - At most
max_pairsoutbound andmax_pairsinbound at once; the least recently used idle session makes room. - The sender's per-session forward quota is charged as for any
Forward; on top, each relay pair has a per-directionpair_mbpsbucket on the sending relay and on the receiving one.
Heartbeat and map additions
A relay registered with the control plane reports itself in a heartbeat; federation adds three things to it and one to the signed map.
| Field | Content |
|---|---|
session_list[].client_hash | Each client session, named by 16 hex characters: the first 8 bytes of HMAC-SHA256 under a 32-byte salt the relay draws at start and never reports. Stable for one relay process, meaningless anywhere else; a restart unlinks every hash from the previous run. No client key is sent. |
session_list[].peers[] | The sessions this session forwards to, as hashes under the same salt, so sessions pair into tunnels without a key in sight. |
peer_rtt_ms | Federation on only: base64 relay key → measured round-trip time in ms to every other relay in the map, from a STUN binding request every 60 s, smoothed. |
federation | enabled, allow_plain, max_pairs, pair_mbps; pairs (distinct relays with a live session either way), pairs_out, pairs_in; forwarded_out (client frames sent on to another relay), forwarded_in (frames from another relay delivered here), errors, dropped; map_relays and map_generated_at for the map the relay holds. |
map.relays[].peerRttMs | In the relay map, beside the signed fields: the relay-to-relay matrix, {relay_key: ms} per relay. A pair appears only when both relays report each other — so the matrix also says which pairs federate — and only between relays in the published map; the value is the lower of the two directions, rounded to 0.1 ms. It sits outside the signed bytes: advisory, it can only affect how a client ranks paths, while every relay on a path is still authenticated by the key the signed part lists. |
Snapshot additions
In GET /api/stats (see the REST API):
| Path | Content |
|---|---|
traversal.directory | Present while the directory runs: enabled, published (records the control plane accepted), publish_failures (transport errors, refusals, rate limits, 409s), lookups and hits. |
traversal.federation | Present while federation is on: enabled; plain_blocked (on, but no federated path may be used — [crypto] e2e does not seal egress and federation_allow_plain is off); paths (relay link and peer pairs going via a far relay now); frames_via; errors (VIA_UNKNOWN / FEDERATION_OFF returned). |
links[].relay.via | The far relay when frames on this relay link go via one for some peer: server (ip:port), pubkey_short, region, city. null otherwise. |
links[].relay.frames_via | ForwardVia frames sent on this link. |
links[].relay.federation_errors | VIA_UNKNOWN / FEDERATION_OFF errors from this relay. |
links[].relay.bootstrap | true while the election holds this relay active only because some peer's joint ranking does not exist yet; elected and rank are then null and the dashboard reads "ranking…". |
peers[].directory | For a static peer while the directory runs: last_lookup_unix (null = never looked up), found (the last lookup found and opened a record), relays_added, consecutive_misses (lookups stop at 10 until the peer's next absence). |
peers[].relay_presence[].via_relay | The far relay — city, else region, else ip:port — when frames to this peer on this link go via one. present then speaks for this relay only, where the peer is normally absent, and frames that came back over the federated path are in last_via_frame_unix rather than last_frame_unix. |
One relay link carrying a federated path, abbreviated:
"relay": {
"server": "203.0.113.10:3478",
"region": "eu-central",
"state": "established",
"rtt_ms": 18.4,
"elected": true,
"rank": 1,
"bootstrap": false,
"via": {
"server": "198.51.100.20:3478",
"pubkey_short": "c41e..07ba",
"region": "na-east",
"city": null
},
"frames_via": 18234,
"federation_errors": 0
}
Journal codes
Read them with GET /api/journal?code=… (see the journal endpoint).
| Code | Category · severity | When · fields |
|---|---|---|
peer.directory_hit | peer · info | A lookup found a record and relay links were added · peer, relays_added, relay_servers, link_ids |
peer.directory_miss | peer · info on the first miss of an absence, then debug | An absent peer has no record; the message says so when lookups stop after 10 · peer, consecutive_misses |
peer.directory_relay_refused | peer · warn | A relay named in a record was not added, for example because the signed map does not list it · peer, relay_server, relay, reason |
relay.directory_slot_released | link · info | A directory slot was released: idle for 10 minutes, or re-armed for a relay a lookup named · link, link_id, relay_server, relay_region, reason |
relay.federated_path_up | link · info | Frames for a peer on a link start going via a far relay · peer, link, link_id, relay_server, via_server, via_relay, via_region |
relay.federated_path_down | link · info | They stop going via it · same fields |
relay.federation_refused | link · warn | The node's relay answered VIA_UNKNOWN or FEDERATION_OFF, once per refusal episode · the fields above without via_region, plus error_code |