Skip to content

Wire Protocol

All meshguard gossip traffic uses a binary wire protocol on a single UDP port (default 51821). Messages are serialized with a minimal type-tag-delimited codec.

Message Format

Packets are classified by their first 4 bytes (little-endian u32):

  • WireGuard: Types 1, 2, 3, 4 (followed by 3 zero bytes).
  • STUN: RFC 5389 magic cookie (0x2112A442) at bytes 4-7.
  • SWIM: Anything else (typically starts with 0x01-0x34).

SWIM messages use a 1-byte type tag:

[1B type][payload...]

SWIM / Protocol Codec Tags

The 1-byte tags below are handled after packet classification has ruled out WireGuard and STUN. SWIM and hole-punch messages use protocol/codec.zig; RelayData uses the ciphertext-only helpers in nat/relay.zig.

TagNameCategoryDirection
0x01PingSWIMA → B
0x02PingReqSWIMA → C (probe B)
0x03AckSWIMB → A
0x31RelayDataNATA → Relay → B
0x33HolepunchRequestNATA → Rendezvous
0x34HolepunchResponseNATB → Rendezvous
0x41OrgAliasAnnounceOrg TrustGossip
0x42OrgCertRevokeOrg TrustGossip
0x43OrgTrustVouchOrg TrustGossip

messages.zig reserves additional enum values for future protocol messages. WireGuard handshake, cookie, and transport packets are classified by their 4-byte WireGuard type (1-4), not by this 1-byte table. The FFI app-message path uses 0x50 outside this codec.

Ping

[0x01][32B sender_pubkey][8B seq (LE)][8B incarnation (LE)][1B gossip_count][N × gossip_entry][optional org cert extension]

Minimum size: 50 bytes (no gossip, no org cert extension).

Ack

[0x03][32B sender_pubkey][8B seq (LE)][8B incarnation (LE)][1B gossip_count][N × gossip_entry][optional org cert extension]

Same format as Ping. The seq echoes the Ping's sequence number.

PingReq

[0x02][32B sender_pubkey][32B target_pubkey][8B seq (LE)]

Fixed size: 73 bytes. Asks the recipient to probe target_pubkey on behalf of the sender.

GossipEntry

Piggybacked on Ping and Ack messages:

[32B subject_pubkey]
[1B event]               # join=0, alive=1, suspect=2, dead=3, leave=4
[8B lamport (LE)]
[1B has_endpoint][1B family][16B addr][2B port (LE)]
[1B has_wg_pubkey][32B wg_pubkey]
[1B has_public_endpoint][1B family][16B addr][2B port (LE)]
[1B nat_type]            # public=0, cone=1, symmetric=2, unknown=3

Fixed size: 115 bytes per entry. Up to 8 entries are decoded per message. A fully loaded Ping/Ack with 8 gossip entries is 970 bytes before the optional org certificate extension.

The optional org certificate extension is appended to Ping/Ack after gossip as:

[1B present=1][314B NodeCertificate max]

New encoders emit the full 314-byte v2-sized certificate slot. Decoders also accept the legacy [1B present=1][186B NodeCertificate] extension and zero-fill the v2-only fields so v1 certificates continue to work during rollout.

HandshakeInitiation (Type 1)

Standard Noise_IKpsk2 initiation message.

[4B type (1)][4B sender_index]
[32B unencrypted_ephemeral]
[48B encrypted_static + auth]
[28B encrypted_timestamp + auth]
[16B mac1]
[16B mac2]

Total: 148 bytes.

HandshakeResponse (Type 2)

Standard Noise_IKpsk2 response message.

[4B type (2)][4B sender_index][4B receiver_index]
[32B unencrypted_ephemeral]
[16B encrypted_nothing + auth]
[16B mac1]
[16B mac2]

Total: 92 bytes.

RelayData

Opaque relay frame for WireGuard/Noise packets when direct and punched paths are unavailable. The relay routes by identity metadata and forwards the payload unchanged; it is not a WireGuard peer for the relayed tunnel.

[0x31][32B sender_pubkey][32B target_pubkey][2B payload_len (BE)][N WireGuard packet bytes]

The payload must be shaped like WireGuard message type 1, 2, 3, or 4 using the standard little-endian WireGuard type field. Non-WireGuard payloads are rejected by the relay frame decoder.

HolepunchRequest

[0x33][32B sender_pubkey][32B target_pubkey][20B public_endpoint][16B token]

Total: 101 bytes.

HolepunchResponse

[0x34][32B sender_pubkey][20B public_endpoint][16B token_echo]

Total: 69 bytes.

HolepunchProbe

Raw probe packet, not decoded by protocol/codec.zig:

["MGHP"][16B punch token]

The token is the same identity/session-bound nonce from the corresponding HolepunchRequest/HolepunchResponse exchange.

OrgAliasAnnounce

Propagated via gossip to claim a human-readable *.name.mesh domain for an org.

[0x41][32B org_pubkey][32B alias (null-padded)][8B lamport (LE)][64B signature]

Total: 137 bytes. Conflicts resolved by Lamport timestamp (earliest wins).

OrgCertRevoke

Broadcast to invalidate a node certificate.

[0x42][32B org_pubkey][32B node_pubkey][1B reason][8B lamport (LE)][64B signature]

Total: 138 bytes. Reason codes: 0=unspecified, 1=key_compromised, 2=admin_removed.

OrgTrustVouch

Propagated via gossip. Org admin vouches for an external standalone node — all nodes trusting the org auto-accept the vouched peer.

[0x43][32B org_pubkey][32B vouched_pubkey][8B lamport (LE)][64B signature]

Total: 137 bytes. Signature covers vouched_pubkey ‖ lamport. Revocable via OrgCertRevoke.

FFI AppMessage

The FFI application-message path uses 0x50 for end-to-end encrypted application-level messages. This is documented here because it shares the UDP port, but it is not decoded by protocol/codec.zig.

[0x50][32B dest_pubkey][32B sender_pubkey][12B nonce][N ciphertext][16B tag]

Minimum size: 93 bytes (empty payload). Maximum payload: 1024 bytes.

  • Key derivation: X25519(sender_private, dest_wg_pubkey) → HKDF("meshguard-app-v1") → symmetric key
  • AD: sender's Ed25519 public key
  • Routing: intermediate peers forward the entire packet as-is (encrypted, opaque) to the destination by pubkey lookup
  • Delivery: when dest_pubkey matches our own, the message is decrypted and delivered via callback

Endpoint Encoding

All protocol-codec endpoints are fixed-width and can carry IPv4 or IPv6:

[1B present][1B family][16B addr][2B port (LE)]
  • present=0 = absent; remaining bytes are zero
  • family=4 = IPv4 stored in the first 4 bytes of the 16-byte address field
  • family=6 = IPv6 stored in all 16 address bytes

Byte Order

Protocol-codec integers use little-endian encoding. WireGuard packets follow the WireGuard wire format, and STUN packets follow RFC 5389.

Released under the MIT License.