swactor/crates/simulation/HOLE_PUNCH_FINDINGS.md

76 lines
3.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Hole-punching never succeeded — N3 bundle re-analysis
Date: 2026-05-22. Bundles: `vastai-N3-1`, `vastai-N3-2`, `vastai-N3-stub` from
`docean:/var/lib/swactor-diag/bundles/`.
## Finding
**No node ever held a stable direct path.** Across the 430 s N3-stub run,
every directed edge was `Relay` in every snapshot. The single non-Relay
sighting in all three bundles was stage-0→orchestrator in N3-1 going
`Relay → Mixed → Relay` over a ~24 s window, then collapsing; the reverse
direction never saw Mixed, so it was likely a transient asymmetric reading,
not a real two-way direct path. **Stage↔stage was 100% Relay in every run.**
## Suspected cause: symmetric (endpoint-dependent) NAT on vast.ai
Last-snapshot iroh socket counters, N3-stub:
| node | holepunch_attempts | paths_direct | send_ipv4 | **recv_data_ipv4** |
|--------------|-------------------:|-------------:|----------:|-------------------:|
| orchestrator | 0 | 0 | 199 072 | **0** |
| stage-0 | 3 220 | 0 | 255 282 | **0** |
| stage-1 | 1 072 | 0 | 152 515 | **0** |
| stage-2 | 3 068 | 0 | 241 752 | **0** |
Cluster-wide: ~7 360 hole-punch attempts, ~850 K direct-path datagrams sent,
**zero received**. `actor_tick_direct_addr_heartbeat = 0` everywhere (the
direct-path keepalive never ticked, because no direct path ever validated).
`portmap.upnp_available = 0`, `pcp_available = 0`, `mapping_failures ≈
mapping_attempts` on every stage — no NAT control protocol reachable inside
vast.ai containers, so iroh can't request a stable external port.
Ruled out as causes:
- **Address discovery worked.** `net_report.reports_full ≥ 1` on every node;
thousands of hole-punch attempts means stages had remote candidates from
the relay's signalling path.
- **UDP isn't blocked.** Every node's `udp_echo` probes to the VPS at
146.190.110.128:9081 had 100% reply success. Outbound delivers; return on a
pre-opened mapping delivers.
- **Relay path is fine.** All 8 `DialOutcome` events in N3-stub are `Success`
in 1.2–1.9 s. iroh just never upgrades from Relay to Direct.
The signature — outbound delivers, return-on-existing-mapping delivers,
return-on-newly-punched-port never delivers — is the classic
endpoint-dependent-mapping fingerprint. vast.ai's container egress NAT
appears to pick a different external source port per destination, so the
candidate stage-A learned about stage-B (the port B used reflecting off the
VPS) is not the port B uses sending to A.
## Implication
Between vast.ai stages, Direct is structurally unreliable on this provider.
The relay is the path, not a fallback. Provision relay bandwidth/headroom
accordingly and size SWIM timeouts around relay RTT.
## Data gaps — what would convert inference to proof
1. **`Tier2Peer.direct_addresses` in snapshots.** Today the peer object
carries only `conn_type` and `relay_urls`. Adding iroh's
`direct_addresses` list (from `Endpoint::remote_info()`) would let us see
which candidates each side learned for each peer, instead of inferring
"they had some" from `holepunch_attempts > 0`.
2. **UDP-echo source-port reflection.** Have the collector's UDP echo include
the observed `srcAddr:srcPort` in its reply (currently opaque). Probe from
each node to two collector destinations and compare external ports — same
port = endpoint-independent, different = symmetric. One number per node
would answer the NAT-type question definitively rather than by signature.
3. **Per-edge bandwidth as a packaged metric.** Today reconstructed post-hoc
by summing `MessageSent.size`/`MessageReceived.size` per `(local, peer)`.
Either ship `Tier2Peer { bytes_sent_total, bytes_received_total }` (small
per-peer accumulator on the aggregator; hooks exist in `iroh_driver.rs`),
or document the post-hoc derivation so future analyses don't re-discover
it.