NAT Traversal
Problem
Section titled “Problem”Most nodes sit behind NATs. Two peers behind separate NATs cannot connect directly without coordination. mycellm solves this with a STUN + hole-punch + QUIC pipeline, falling back to relay when direct paths fail.
STUN discovery
Section titled “STUN discovery”On startup, the node contacts public STUN servers to classify its NAT type:
stun.l.google.com:19302stun.cloudflare.com:3478stun.stunprotocol.org:3478These are free, reliable public STUN servers. Custom servers can be configured via MYCELLM_STUN_SERVERS.
The classification determines strategy:
| NAT Type | Hole Punch | Notes |
|---|---|---|
| Full Cone | Always works | Single mapped port |
| Address-Restricted | Usually works | Same external port for same dest IP |
| Port-Restricted | Sometimes works | Requires port prediction |
| Symmetric | Rarely works | Falls back to relay |
The result is cached for 5 minutes and re-probed on network change.
UDP hole punching
Section titled “UDP hole punching”When peer A wants to connect to peer B, the bootstrap node coordinates the punch:
A ──PUNCH_REQUEST──▶ Bootstrap │ ▼ B ◀──PUNCH_INITIATE── │A ◀──────────────────── │ ──────────────────▶ B (both send UDP packets to each other's mapped address) │A ──PUNCH_RESPONSE──▶ BootstrapB ──PUNCH_RESPONSE──▶ Bootstrap │ PUNCH_RESULT (success/fail)- PUNCH_REQUEST — A asks bootstrap to coordinate a punch with B
- PUNCH_INITIATE — Bootstrap tells both peers each other’s public
ip:port - Both peers send UDP packets to the other’s mapped address simultaneously
- PUNCH_RESPONSE — Each peer reports success or timeout to bootstrap
- PUNCH_RESULT — Bootstrap confirms the outcome to both
The punch attempt times out after 5 seconds. Both peers send 10 UDP probes at 500ms intervals.
QUIC upgrade
Section titled “QUIC upgrade”Once a UDP hole is established, the initiating peer upgrades the punched socket to a QUIC connection with mutual TLS:
A ──QUIC ClientHello──▶ B (over punched UDP hole)A ◀──ServerHello + Cert── BA ──Cert + Finished──▶ B │ Authenticated QUIC streamThe TLS certificates are the same device certs used for bootstrap connections. Both peers verify the other’s cert chain back to their account key.
Relay fallback
Section titled “Relay fallback”When hole punching fails (symmetric NAT, firewall, timeout), traffic routes through the bootstrap node as a relay:
# The relay path is transparent to the application layerA ──QUIC──▶ Bootstrap ──QUIC──▶ BRelayed connections incur a 2x credit cost — the relay node earns credits for bandwidth. The PUNCH_RESULT message includes a relay: true flag so both peers know the path.
Platform differences
Section titled “Platform differences”Python (CLI node)
Section titled “Python (CLI node)”Uses aioquic for QUIC transport and raw UDP sockets for hole punching:
# Hole punch initiated by PeerManagerawait peer_manager.punch_through(target_peer_id)Uses NWConnectionGroup from Network.framework for UDP hole punching. This integrates with iOS power management and avoids raw socket restrictions:
let group = NWConnectionGroup(of: .unicast, using: .udp)group.stateUpdateHandler = { state in // Handle punch coordination}After the punch succeeds, the connection upgrades to QUIC via NWConnection with custom TLS options carrying the device certificate.
Diagnostics
Section titled “Diagnostics”# Check NAT typemycellm status --nat
# Force re-probemycellm nat-probe
# View punch historymycellm peers --punches