STUN_NAT_TRAVERSAL.md
1 # Abzu STUN & NAT Traversal 2 3 Technical documentation for Abzu's STUN-based NAT discovery and UDP hole punching. 4 5 --- 6 7 ## Overview 8 9 Abzu uses STUN (Session Traversal Utilities for NAT) to discover public IP:port mappings and establish direct peer connections through NATs. The implementation lives in `abzu-transport/src/nat.rs`. 10 11 ## Architecture 12 13 ``` 14 ┌─────────────────┐ STUN Query ┌────────────────────┐ 15 │ Local Node │──────────────────▶│ STUN Server(s) │ 16 │ (Private IP) │◀──────────────────│ (Public Internet) │ 17 └─────────────────┘ XOR-MAPPED-ADDR └────────────────────┘ 18 │ 19 │ Mapped Address 20 ▼ 21 ┌─────────────────┐ ┌────────────────────┐ 22 │ NAT Gateway │◀──────────────────▶│ Peer Node │ 23 │ (Port Mapping) │ UDP Hole Punch │ (After Punch) │ 24 └─────────────────┘ └────────────────────┘ 25 ``` 26 27 ## STUN Servers 28 29 Default configured servers (reliable public infrastructure): 30 31 | Server | Port | Provider | 32 |--------|------|----------| 33 | `stun.cloudflare.com` | 3478 | Cloudflare | 34 | `stun.l.google.com` | 19302 | Google | 35 | `stun1.l.google.com` | 19302 | Google | 36 | `stun2.l.google.com` | 19302 | Google | 37 | `stun.stunprotocol.org` | 3478 | Community | 38 39 --- 40 41 ## NAT Type Detection 42 43 Abzu queries **3 servers** (configurable) and compares the returned mapped addresses: 44 45 ### Detection Algorithm 46 47 ```rust 48 if mappings_from_all_servers_identical() { 49 NatType::RestrictedCone // Conservative: could be FullCone 50 } else { 51 NatType::Symmetric // Different port per destination = worst case 52 } 53 ``` 54 55 ### NAT Types 56 57 | Type | Behavior | Traversability | 58 |------|----------|----------------| 59 | **None** | No NAT, public IP | ✅ Easy | 60 | **Full Cone** | Any host can send to mapped addr | ✅ Easy | 61 | **Restricted Cone** | Only hosts we've sent to can reply | ⚠️ Moderate | 62 | **Port-Restricted Cone** | Host + port must match | ⚠️ Hard | 63 | **Symmetric** | Different mapping per destination | ❌ Very Hard (relay needed) | 64 65 ```rust 66 // Helper methods 67 nat_type.is_traversable() // true for all cone types 68 nat_type.needs_relay() // true only for Symmetric 69 ``` 70 71 --- 72 73 ## Configuration 74 75 ```rust 76 pub struct NatConfig { 77 /// STUN servers to query 78 pub stun_servers: Vec<String>, 79 80 /// Timeout for each STUN request (default: 3s) 81 pub request_timeout: Duration, 82 83 /// Number of retries per server (default: 2) 84 pub retries: u32, 85 86 /// Base delay between retries (default: 500ms) 87 /// Note: Exponential backoff applied 88 pub retry_delay: Duration, 89 90 /// Servers to query for NAT detection (default: 3) 91 pub detection_server_count: usize, 92 93 /// Hole punch attempts (default: 5) 94 pub punch_attempts: u32, 95 96 /// Delay between punch attempts (default: 100ms) 97 pub punch_delay: Duration, 98 } 99 ``` 100 101 --- 102 103 ## STUN Protocol Flow 104 105 ### 1. Binding Request 106 107 ``` 108 Client STUN Server 109 │ │ 110 │─── Binding Request ───────▶│ 111 │ (Transaction ID) │ 112 │ │ 113 │◀── Binding Response ───────│ 114 │ (XOR-MAPPED-ADDRESS) │ 115 ``` 116 117 ### 2. Wire Format (RFC 5389) 118 119 ``` 120 0 1 2 3 121 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 122 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 123 |0 0| STUN Message Type | Message Length | 124 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 125 | Magic Cookie (0x2112A442) | 126 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 127 | | 128 | Transaction ID (96 bits) | 129 | | 130 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 131 ``` 132 133 ### 3. Response Parsing 134 135 Abzu extracts the **XOR-MAPPED-ADDRESS** attribute: 136 137 - Validates Transaction ID matches request 138 - Verifies MessageClass is `Success` 139 - Unmarshals the XORed IP:port with transaction ID 140 141 --- 142 143 ## UDP Hole Punching 144 145 ### Simple Punch 146 147 Sends multiple packets to create NAT bindings: 148 149 ```rust 150 resolver.punch(target_addr).await?; 151 // Sends 5 packets with exponential backoff delays 152 ``` 153 154 ### Coordinated Punch (Both Sides Simultaneous) 155 156 For reliable traversal, both peers should punch at the same time: 157 158 ```rust 159 // Packet format: "ABZU_COORD" + [32-byte peer ID] 160 resolver.punch_coordinated(&socket, peer_addr, &our_id).await?; 161 ``` 162 163 **Protocol:** 164 165 1. Both sides send `ABZU_COORD{id}` packets 166 2. Short receive timeout (200ms) between attempts 167 3. Success when receiving peer's packet 168 4. 5 attempts with configurable delay 169 170 --- 171 172 ## Integration with Node 173 174 The `abzu-core` crate exposes high-level APIs: 175 176 ```rust 177 // Discover NAT type and public address 178 let mapping = node.discover_nat().await?; 179 println!("Public: {}", mapping.mapped_addr); 180 println!("NAT Type: {:?}", mapping.nat_type); 181 182 // NAT-aware peer connection 183 let peer_key = node.connect_peer_nat(&peer_info, &psk).await?; 184 ``` 185 186 ### Connection Flow 187 188 ``` 189 1. discover_nat() → Get our public mapping 190 2. Exchange mappings → Share via signaling/announce 191 3. punch_coordinated() → Both sides punch simultaneously 192 4. TLS/TCP connection → Establish encrypted channel 193 ``` 194 195 --- 196 197 ## Announce Frame (P2P Address Exchange) 198 199 Peers broadcast their NAT mapping via `AbzuFrame::Announce`: 200 201 ```rust 202 AbzuFrame::Announce { 203 peer_key: [u8; 32], // Sender's public key 204 public_addr: Vec<u8>, // Serialized SocketAddr 205 nat_type: u8, // NatType discriminant 206 timestamp: u64, // Unix epoch seconds 207 } 208 ``` 209 210 **Validation:** 211 212 - Announcements older than **5 minutes** are rejected 213 - Prevents replay attacks and stale data 214 215 --- 216 217 ## Error Handling 218 219 | Error | Meaning | Recovery | 220 |-------|---------|----------| 221 | `NoResponse` | All STUN servers unreachable | Check connectivity | 222 | `Timeout` | Server didn't respond in time | Retry, check firewall | 223 | `SymmetricNat` | Direct connection unlikely | Use relay | 224 | `DnsError` | Can't resolve STUN server | Check DNS | 225 | `PunchFailed` | Hole punch exhausted attempts | Try relay | 226 227 --- 228 229 ## Testing 230 231 ```bash 232 # Unit tests (no network) 233 cargo test -p abzu-transport nat:: 234 235 # Live integration test (requires network) 236 cargo test -p abzu-transport --test '*' -- --ignored test_discover_public_ip 237 ``` 238 239 --- 240 241 ## Security Considerations 242 243 1. **Transaction ID Verification**: Prevents response spoofing 244 2. **Source Address Check**: Response must come from queried server 245 3. **Timestamp Validation**: 5-minute window on announcements 246 4. **No STUN Authentication**: Using public servers (no credential exchange) 247 248 --- 249 250 ## Dependencies 251 252 ```toml 253 # abzu-transport/Cargo.toml 254 stun_proto = "0.1" # RFC 5389 STUN protocol 255 rand = "0.8" # Transaction ID generation 256 ``` 257 258 --- 259 260 ## Future Work 261 262 - [ ] TURN relay fallback for symmetric NAT 263 - [ ] ICE-lite for optimal candidate selection 264 - [ ] UPnP/NAT-PMP for direct port mapping 265 - [ ] Gossip-based announcement forwarding