You are reading the documentation for the 1.0.0 release line. The current line is 3.0.0. Read this page in the current line

Relay protocol

Documents the atlasd 1.0.0 release lineView as Markdown

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
ptypePayload
0x01A complete Atlas datagram, injected into the receiver's normal inbound path.
0x02A rendezvous message: candidate lists and punch signals.
0x03A 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.

  1. The node's relay R1 receives ForwardVia{dest, via}. If R1 holds dest itself, it delivers locally. Otherwise, with federation on and via listed in its signed relay map, it wraps the frame as RelayForward with hop = 0 and sends it over its Noise IK session to the via relay R2, opening that session if needed.
  2. R2 delivers it to the destination as an ordinary Deliver{src} — the destination sees nothing new — or answers RelayError on the same session.
  3. R1 turns a RelayError into the usual Error{code, dest} for the sender. RelayError names src_client so 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

CodeNameMeaning to the sender
1UNKNOWN_DESTThe 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.
2RATE_LIMITEDThe relay refused to forward: the sender's session quota, or the relay pair's pair_mbps bucket on either relay.
3VIA_UNKNOWNThe 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.
4FEDERATION_OFFThe 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

LegBytes per datagramMade of
Client ⇄ relay, Forward / Deliver66outer header 8 + nonce 8 + AEAD tag 16 + kind 1 + key 32 + ptype 1
Client → relay, ForwardVia98the above + the 32-byte via key
Relay → relay, RelayForward99outer 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 via naming 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_pairs outbound and max_pairs inbound 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-direction pair_mbps bucket 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.

FieldContent
session_list[].client_hashEach 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_msFederation 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.
federationenabled, 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[].peerRttMsIn 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):

PathContent
traversal.directoryPresent while the directory runs: enabled, published (records the control plane accepted), publish_failures (transport errors, refusals, rate limits, 409s), lookups and hits.
traversal.federationPresent 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.viaThe 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_viaForwardVia frames sent on this link.
links[].relay.federation_errorsVIA_UNKNOWN / FEDERATION_OFF errors from this relay.
links[].relay.bootstraptrue 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[].directoryFor 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_relayThe 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).

CodeCategory · severityWhen · fields
peer.directory_hitpeer · infoA lookup found a record and relay links were added · peer, relays_added, relay_servers, link_ids
peer.directory_misspeer · info on the first miss of an absence, then debugAn absent peer has no record; the message says so when lookups stop after 10 · peer, consecutive_misses
peer.directory_relay_refusedpeer · warnA 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_releasedlink · infoA 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_uplink · infoFrames 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_downlink · infoThey stop going via it · same fields
relay.federation_refusedlink · warnThe node's relay answered VIA_UNKNOWN or FEDERATION_OFF, once per refusal episode · the fields above without via_region, plus error_code
Esc
↑ ↓ navigateEnter — openEsc — close