63 lines
4.4 KiB
Markdown
63 lines
4.4 KiB
Markdown
# Ultra Mesh Protocol v1
|
|
|
|
This document describes the protocol implemented by `umnd`. Multi-byte integers use network byte order unless the binary property-list encoding defines their representation.
|
|
|
|
## Framing and identities
|
|
|
|
Every peer message is a four-byte unsigned body length followed by a binary property-list `WireEnvelope`. Bodies are limited to 1,200,000 bytes. An envelope carries protocol version `1` and one `WireMessage`. Unknown versions terminate the peer session.
|
|
|
|
A node record contains:
|
|
|
|
- A 32-byte Ed25519 signing public key.
|
|
- A 32-byte X25519 agreement public key.
|
|
- A 16-byte mesh address equal to `0xfd` followed by the first 15 bytes of SHA-256 over the signing public key.
|
|
|
|
The address is rendered using normal IPv6 text notation. A receiver always recomputes it from the public key before trusting the record. Private keys are created once and stored with mode `0600`.
|
|
|
|
## Peer and routing messages
|
|
|
|
The wire message variants are:
|
|
|
|
- `hello`: announces a node record when a transport session becomes ready.
|
|
- `linkState`: carries an origin record, monotonically increasing sequence, sorted neighbor addresses, and the origin's Ed25519 signature over those fields.
|
|
- `packet`: carries a UUID, source and destination addresses, hop limit, and an end-to-end sealed payload.
|
|
- `keepalive`: detects failed transport sessions.
|
|
|
|
Invalid identities, signatures, stale link-state sequence numbers, oversized neighbor lists, duplicate packet UUIDs, and expired hop limits are discarded.
|
|
|
|
Each node floods newly accepted link-state records. Records refresh every 10 seconds, expire after 30 seconds, and neighbors are considered lost when their transport connection fails. Routes use shortest-hop Dijkstra calculation with deterministic address ordering for equal-cost paths. Packets start with a hop limit of 16. The duplicate cache retains the most recent 4,096 packet UUIDs.
|
|
|
|
## End-to-end payloads
|
|
|
|
The sender serializes an `InnerFrame`, signs its bytes with Ed25519, and encrypts the signed object to the destination record:
|
|
|
|
1. Generate an ephemeral X25519 key.
|
|
2. Perform X25519 agreement with the destination's static agreement key.
|
|
3. Derive a 32-byte key using HKDF-SHA256, salt `umn-e2e-v1`, and the destination address as shared information.
|
|
4. Seal with ChaCha20-Poly1305.
|
|
|
|
The routed payload contains only the ephemeral public key and the combined authenticated ciphertext. The receiver decrypts it, verifies the signature, validates the embedded source record, and confirms that its derived address matches the outer source address. Relays cannot decrypt the inner frame. Neighbor discovery and topology metadata are authenticated but intentionally public in v1.
|
|
|
|
Inner frame kinds are ping request/reply, text, stream open/data/ack/close/reset, and error. Logical destination ports are inside the sealed payload.
|
|
|
|
## Streams and failure handling
|
|
|
|
A stream uses a random 64-bit identifier. `streamOpen` occupies sequence zero; data starts at sequence one. Receivers acknowledge the greatest contiguous sequence delivered. Duplicate frames are acknowledged without being delivered again.
|
|
|
|
Unacknowledged frames are retransmitted through the route table's current next hop. Retry delay begins at 500 milliseconds and backs off to five seconds. No progress for 60 seconds resets the stream. This makes streams independent of any one neighbor TCP connection and allows an access-point path to be replaced by a peer-to-peer or multi-hop path.
|
|
|
|
Each endpoint enforces these v1 resource bounds:
|
|
|
|
- 1 MiB aggregate data per stream.
|
|
- 32 simultaneous streams per node.
|
|
- Eight intended concurrent streams per source; broader abuse controls remain future work.
|
|
- 32 KiB bridge read chunks and a logical 64 KiB flow-control target.
|
|
|
|
New operations are live-route-only and are not written to a durable delivery queue.
|
|
|
|
## Firewall and local IPC
|
|
|
|
Ping terminates in the daemon and is always available. All ports otherwise default to denied. A stream or text message is delivered only when an allow rule matches its authenticated source and a local process currently binds the destination port. Firewall rules affect terminating traffic, not transit forwarding.
|
|
|
|
Local tools use a mode-`0600` Unix-domain socket. IPC messages use the same four-byte framing and binary property-list encoding, carry IPC version `1`, and support control operations, service binding, and stream events. A binding is removed when its IPC connection closes.
|