# ADR-0010: P2P gossip, NAT-traversal relay, and TLS

## Status: Accepted

## Context

All node-to-node and node-to-tracker communication in the prototype is plain HTTP over a LAN or direct-IP internet connection. This has three problems:

1. **NAT blocking**: Most home and cloud nodes cannot accept inbound TCP connections.
2. **No encryption**: Activations and heartbeats are in plaintext.
3. **Polling overhead**: Nodes poll the tracker for coverage changes every 30s. This is slow to react to node churn and does not scale past a few hundred nodes.

The reference implementation (Petals) solves this with libp2p — GossipSub for pub/sub and Kademlia DHT for peer discovery. We adopt the same goals but start with simpler, more stable building blocks that can be swapped for libp2p later without changing the message schema.

## Decisions

### 1. TLS everywhere

**Intent:** All HTTP between nodes, tracker, and gateway uses HTTPS (TLS 1.3). Self-signed certificates are auto-generated on first node start and stored in `~/.config/meshnet/`. The certificate fingerprint is included in every heartbeat and gossip envelope. Nodes use TOFU (trust on first use) — they accept a peer's cert on first contact and pin the fingerprint; connections from the same peer with a different fingerprint are rejected.

The relay node uses a real CA-signed certificate (Let's Encrypt) because it is the internet-facing bootstrap point.

> **Alpha reality correction (2026-07-04, ADR-0016):** As implemented, **TLS applies to the relay path** (`packages/relay`, WSS circuit proxy) and P2P packages that generate certs — **not** to plain HTTP on tracker ↔ node inference and registration in typical LAN/dev deployments. Tracker registration already requires `https://` URLs for production trackers, but node-to-tracker proxy traffic may still be plaintext on alpha. Full end-to-end TLS remains the target; alpha documents this as a **known limitation**, not a silent guarantee. Hive gossip auth (ADR-0017) is the immediate mitigation for tracker replication integrity.

### 2. mDNS for LAN peer discovery

Python `zeroconf` library. Service type: `_meshnet._tcp.local.`. A node announces itself on startup and browses for existing peers. This is zero-config discovery for home and lab networks. mDNS does not traverse routers, which is correct — LAN discovery should not bleed into the internet.

### 3. WebSocket PubSub for gossip

Each node maintains persistent WSS connections to the relay and up to 8 direct peers. Messages use a stable JSON envelope with a `topic`, `version`, `from_peer`, and `payload`. Topics: `node-join`, `node-leave`, `coverage-update`, `heartbeat`, `peer-list`, `relay-announce`.

Simple flooding with `seen_ids` dedup and TTL=3 is good enough for the prototype. The message schema is stable; the fanout mechanism can be replaced with GossipSub mesh routing without changing the schema.

### 4. Circuit relay node for NAT traversal

A team-operated public relay (`packages/relay`, CLI: `meshnet-relay`) is the internet bootstrap point. A node behind NAT:

1. Connects outbound to the relay via WSS
2. Advertises `relay_addr = wss://relay.meshnet.ai:8443/relay/{peer_id}` to the tracker
3. Other nodes proxy connections through the relay when the direct addr is not reachable

Hole-punching (STUN + simultaneous TCP open) is deferred to a future story. Circuit relay is the reliable fallback.

The relay is stateless in terms of inference — it only proxies bytes. It does not decrypt activations.

### 5. Bootstrap peer list

`packages/p2p/relay_bootstrap.json` contains the team-operated relay endpoints with their TLS fingerprints. New nodes load this file on startup to find their first peer. The list is bundled with the package and updated via pip upgrades.

### Migration path to libp2p

When the network has enough volume to justify the complexity:

1. Replace the WebSocket gossip layer with libp2p GossipSub (same topics and payload schemas, different transport)
2. Replace mDNS + relay peer list with Kademlia DHT
3. Replace circuit relay with libp2p circuit relay v2

The gossip envelope schema (`topic`, `version`, `from_peer`, `payload`) is the stable contract. As long as messages on the wire are identical, the transport layer can be swapped without touching node business logic.

## Alternatives rejected

**libp2p from the start**: `py-libp2p` is experimental and not production-ready. A Go libp2p sidecar is operationally complex. The benefits of real libp2p (mesh routing, Kademlia DHT, hole-punching) are not needed until we have hundreds of nodes.

**NATS**: Stable and fast but requires a central NATS server. Adds operational dependency and contradicts the P2P goal.

**ZeroMQ**: No NAT traversal built in. Requires manual topology management.

**No gossip (keep polling)**: Does not scale; slow to react to node churn; misses the relay/NAT requirement.
