feat(nat): birthday attack module + HardNatBirthdayStart signal (#86, #87)
Some checks failed
Mirror to GitHub / mirror (push) Failing after 25s
Build Release Binaries / build-amd64 (push) Failing after 3m43s

Birthday attack for random symmetric NATs:
- birthday.rs: open_acceptor_ports() opens N sockets, STUN-probes
  each to learn external ports. generate_dialer_targets() builds
  hit list (known ports first, then random fill). spray_dialer()
  sprays QUIC connects with rate limiting, first success wins.
- Default: 32 acceptor ports, 128 dialer probes, 20ms interval

Signal coordination:
- HardNatBirthdayStart { acceptor_ports, external_ip } sent by
  Acceptor when peer's HardNatProbe shows random/sequential NAT
- Relay forwards it like other call signals
- Desktop recv loop handles and logs it

Hybrid waterfall integration:
- On receiving HardNatProbe with non-cone allocation, Acceptor
  auto-opens birthday ports and sends BirthdayStart
- Sockets kept alive 10s for NAT mapping persistence
- Dialer spray integration into race() pending (needs transport
  hot-swap for background upgrade)

6 new tests, 599 total, 0 regressions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-04-14 16:44:36 +04:00
parent 6c49d7436f
commit f06f9073ae
6 changed files with 422 additions and 4 deletions

View File

@@ -967,6 +967,19 @@ pub enum SignalMessage {
external_ip: String,
},
/// Birthday attack coordination — Acceptor tells Dialer which
/// ports it has open. The Dialer then sprays QUIC connects to
/// these ports (and optionally random ports) on the Acceptor's IP.
HardNatBirthdayStart {
call_id: String,
/// Number of sockets the Acceptor opened.
acceptor_port_count: u16,
/// External ports discovered via STUN (the "hit list").
acceptor_ports: Vec<u16>,
/// Acceptor's external IP.
external_ip: String,
},
// ── Phase 4: cross-relay direct-call signaling ────────────────────
/// Phase 4: relay-to-relay envelope for forwarding direct-call