# Node Discovery and Multi-Site
See [Architecture Overview](../architecture.md).
## Node Discovery
Standard mDNS (RFC 6762) uses UDP multicast over `224.0.0.251:5353` with DNS-SD service records. The wire protocol is well-defined and the multicast group is already in active use on most LANs. The standard service discovery stack (Avahi, Bonjour, `nss-mdns`) provides that transport but brings significant overhead: persistent daemons, D-Bus dependencies, complex configuration surface, and substantial resident memory. None of that is needed here.
The approach: **reuse the multicast transport, define our own wire format**.
Rather than DNS wire format, node announcements are encoded as binary frames using the same serialization layer (`serial`) and frame header used for video transport. A node joins the multicast group, broadcasts periodic announcements, and listens for announcements from peers.
### Announcement Frame
| Field | Size | Purpose |
|---|---|---|
| `message_type` | 2 bytes | Discovery message type (e.g. `0x0010` for node announcement) |
| `channel_id` | 2 bytes | Reserved / zero |
| `payload_length` | 4 bytes | Byte length of payload |
| Payload | variable | Encoded node identity and capabilities |
Payload fields:
| Field | Type | Purpose |
|---|---|---|
| `protocol_version` | u8 | Wire format version |
| `site_id` | u16 | Site this node belongs to (`0` = local / unassigned) |
| `tcp_port` | u16 | Port where this node accepts transport connections |
| `function_flags` | u16 | Bitfield declaring node capabilities (see below) |
| `name_len` | u8 | Length of name string |
| `name` | bytes | Node name (`namespace:instance`, e.g. `v4l2:microscope`) |
`function_flags` bits:
| Bit | Mask | Meaning |
|---|---|---|
| 0 | `0x0001` | Source — produces video |
| 1 | `0x0002` | Relay — receives and distributes streams |
| 2 | `0x0004` | Sink — consumes video (display, archiver, etc.) |
| 3 | `0x0008` | Controller — participates in control plane coordination |
A node may set multiple bits — a relay that also archives sets both `RELAY` and `SINK`.
### Behaviour
- Nodes send announcements periodically (default every 5 s) and immediately on startup via multicast
- No daemon — the node process itself sends and listens; no background service required
- On receiving an announcement the node records the peer (address, port, name, capabilities) and can initiate a transport connection if needed
- A peer that goes silent for `timeout_intervals × interval_ms` is considered offline and removed from the peer table
- Announcements are informational only — identity is validated at TCP connection time
#### Startup — new node joins the network
```mermaid
sequenceDiagram
participant N as New Node
participant MC as Multicast group
participant A as Node A
participant B as Node B
N->>MC: announce (multicast)
MC-->>A: receives announce
MC-->>B: receives announce
A->>N: announce (unicast reply)
B->>N: announce (unicast reply)
Note over N,B: All parties now know each other.
Subsequent keepalives are multicast only.
```
Each node that hears a new peer sends a **unicast reply** directly to that peer. This allows the new node to populate its peer table within one round-trip rather than waiting up to `interval_ms` for other nodes' next scheduled broadcast.
#### Steady-state keepalive
```mermaid
sequenceDiagram
participant A as Node A
participant MC as Multicast group
participant B as Node B
participant C as Node C
loop every interval_ms
A->>MC: announce (multicast)
MC-->>B: receives — updates last_seen_ms, no reply
MC-->>C: receives — updates last_seen_ms, no reply
end
```
Known peers update their `last_seen_ms` timestamp and do nothing else. No reply is sent, so there is no amplification.
#### Node loss — timeout
```mermaid
sequenceDiagram
participant A as Node A
participant B as Node B (offline)
Note over B: Node B stops sending
loop timeout_intervals × interval_ms elapses
A->>A: check_timeouts() — not yet expired
end
A->>A: check_timeouts() — expired, remove B
A->>A: on_peer_lost(B) callback
```
#### Node restart — known limitation
The current implementation attempts to detect a restart by checking whether `site_id` changed for a known `(addr, port)` entry. In practice this **does not work**: `site_id` is a static configuration value and will be the same before and after a restart. A restarted node will therefore simply be treated as a continuing keepalive and will not receive an immediate unicast reply — it will have to wait up to `interval_ms` for the next scheduled multicast broadcast from its peers.
```mermaid
sequenceDiagram
participant R as Restarted Node
participant MC as Multicast group
participant A as Node A
Note over R: Node restarts — same addr, port, site_id
R->>MC: announce (multicast)
MC-->>A: receives — site_id unchanged, treated as keepalive
Note over A: No unicast reply sent. R waits up to interval_ms
to learn about A via A's next scheduled multicast.
```
**What needs to change:** a **boot nonce** (random `u32` generated at startup, not configured) should be added to the announcement payload. A change in boot nonce for a known peer unambiguously signals a restart and triggers an immediate unicast reply. This requires a wire format version bump and updates to the peer table struct, announcement builder, and receive logic.
### No Avahi/Bonjour Dependency
The system does not link against, depend on, or interact with Avahi or Bonjour. It opens a raw UDP multicast socket directly, which requires only standard POSIX socket APIs. This keeps the runtime dependency footprint minimal and the behaviour predictable.
---
## Multi-Site (Forward Compatibility)
The immediate use case is a single LAN. A planned future use case is **site-to-site linking** — two independent networks (e.g. a lab and a remote location) connected by a tunnel (SSH port-forward, WireGuard, etc.), where nodes on both sites are reachable from either side.
### Site Identity
Every node carries a `site_id` (`u16`) in its announcement. In a single-site deployment this is always `0`. When sites are joined, each site is assigned a distinct non-zero ID; nodes retain their IDs across the join and are fully addressable by `(site_id, name)` from anywhere in the combined network.
This field is reserved from day one so that multi-site never requires a wire format change or a rename of existing identifiers.
### Site Gateway Node
A site gateway is a node that participates in both networks simultaneously — it has a connection on the local transport and a connection over the inter-site tunnel. It:
- Bridges discovery announcements between sites (rewriting `site_id` appropriately)
- Forwards encapsulated transport frames across the tunnel on behalf of cross-site edges
- Is itself a named node, so the control plane can see and reason about it
The tunnel transport is out of scope for now. The gateway is a node type, not a special infrastructure component — it uses the same wire protocol as everything else.
### Site ID Translation
Both sides of a site-to-site link will independently default to `site_id = 0`. A gateway cannot simply forward announcements across the boundary — every node on both sides would appear as site 0 and be indistinguishable.
The gateway is responsible for **site ID translation**: it assigns a distinct non-zero `site_id` to each side of the link and rewrites the `site_id` field in all announcements and any protocol messages that carry a `site_id` as they cross the boundary. From each side's perspective, remote nodes appear with the translated ID assigned by the gateway; local nodes retain their own IDs.
This means `site_id = 0` should be treated as "local / unassigned" and never forwarded across a site boundary without translation. A node that receives an announcement with `site_id = 0` on a cross-site link should treat it as a protocol error from the gateway.
### Addressing
A fully-qualified node address is `site_id:namespace:instance`. Within a single site, `site_id` is implicit and can be omitted. The control plane and discovery layer must store `site_id` alongside every peer record from the start, even if it is always `0`, so that the upgrade to multi-site addressing requires only configuration and a gateway node — not code changes.