1use rand::RngCore;
13use serde::Serialize;
14use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket};
15use std::time::{Duration, Instant};
16
17pub const STUN_SERVERS: [(&str, u16); 2] =
18 [("stun.l.google.com", 19302), ("stun.cloudflare.com", 3478)];
19
20const MAGIC: u32 = 0x2112_A442;
21const BINDING_REQUEST: u16 = 0x0001;
22const BINDING_RESPONSE: u16 = 0x0101;
23const XOR_MAPPED_ADDRESS: u16 = 0x0020;
24
25const RETRANSMIT_DELAYS: [Duration; 4] = [
28 Duration::ZERO,
29 Duration::from_millis(250),
30 Duration::from_millis(500),
31 Duration::from_millis(1000),
32];
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
35#[serde(rename_all = "kebab-case")]
36pub enum NatType {
37 Cone,
38 Symmetric,
39 Unknown,
40}
41
42#[derive(Debug, Clone, Default)]
44pub struct NatDiscovery {
45 pub reflexive: Option<SocketAddr>,
47 pub observed: Vec<SocketAddr>,
48 pub symmetric: bool,
49}
50
51impl NatDiscovery {
52 pub fn nat_type(&self) -> NatType {
53 if self.symmetric {
54 NatType::Symmetric
55 } else if self.reflexive.is_some() {
56 NatType::Cone
57 } else {
58 NatType::Unknown
59 }
60 }
61}
62
63pub fn encode_binding_request(txid: &[u8; 12]) -> [u8; 20] {
65 let mut out = [0u8; 20];
66 out[0..2].copy_from_slice(&BINDING_REQUEST.to_be_bytes());
67 out[4..8].copy_from_slice(&MAGIC.to_be_bytes());
68 out[8..20].copy_from_slice(txid);
69 out
70}
71
72pub fn parse_binding_response(data: &[u8], txid: &[u8; 12]) -> Option<SocketAddr> {
74 if data.len() < 20 {
75 return None;
76 }
77 let msg_type = u16::from_be_bytes([data[0], data[1]]);
78 let magic = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
79 if magic != MAGIC || msg_type != BINDING_RESPONSE || &data[8..20] != txid {
80 return None;
81 }
82 let mut off = 20;
83 while off + 4 <= data.len() {
84 let attr_type = u16::from_be_bytes([data[off], data[off + 1]]);
85 let attr_len = u16::from_be_bytes([data[off + 2], data[off + 3]]) as usize;
86 off += 4;
87 let attr = data.get(off..off + attr_len)?;
88 off += (attr_len + 3) & !3; if attr_type != XOR_MAPPED_ADDRESS || attr.len() < 8 {
90 continue;
91 }
92 let family = attr[1];
93 let port = u16::from_be_bytes([attr[2], attr[3]]) ^ (MAGIC >> 16) as u16;
94 match family {
95 0x01 => {
96 let ip = u32::from_be_bytes([attr[4], attr[5], attr[6], attr[7]]) ^ MAGIC;
97 return Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::from(ip)), port));
98 }
99 0x02 if attr.len() >= 20 => {
100 let mut mask = [0u8; 16];
102 mask[..4].copy_from_slice(&MAGIC.to_be_bytes());
103 mask[4..].copy_from_slice(txid);
104 let mut raw = [0u8; 16];
105 for i in 0..16 {
106 raw[i] = attr[4 + i] ^ mask[i];
107 }
108 return Some(SocketAddr::new(IpAddr::V6(Ipv6Addr::from(raw)), port));
109 }
110 _ => {}
111 }
112 }
113 None
114}
115
116pub fn encode_binding_response(txid: &[u8; 12], addr: SocketAddr) -> Vec<u8> {
119 let mut attr = Vec::new();
120 let xport = addr.port() ^ (MAGIC >> 16) as u16;
121 match addr.ip() {
122 IpAddr::V4(v4) => {
123 attr.extend_from_slice(&[0, 0x01]);
124 attr.extend_from_slice(&xport.to_be_bytes());
125 attr.extend_from_slice(&(u32::from(v4) ^ MAGIC).to_be_bytes());
126 }
127 IpAddr::V6(v6) => {
128 attr.extend_from_slice(&[0, 0x02]);
129 attr.extend_from_slice(&xport.to_be_bytes());
130 let mut mask = [0u8; 16];
131 mask[..4].copy_from_slice(&MAGIC.to_be_bytes());
132 mask[4..].copy_from_slice(txid);
133 attr.extend(v6.octets().iter().zip(mask).map(|(a, b)| a ^ b));
134 }
135 }
136 let mut out = Vec::with_capacity(20 + 4 + attr.len());
137 out.extend_from_slice(&BINDING_RESPONSE.to_be_bytes());
138 out.extend_from_slice(&((4 + attr.len()) as u16).to_be_bytes());
139 out.extend_from_slice(&MAGIC.to_be_bytes());
140 out.extend_from_slice(txid);
141 out.extend_from_slice(&XOR_MAPPED_ADDRESS.to_be_bytes());
142 out.extend_from_slice(&(attr.len() as u16).to_be_bytes());
143 out.extend_from_slice(&attr);
144 out
145}
146
147async fn resolve(host: &str, port: u16, want_v6: bool) -> Option<SocketAddr> {
148 let addrs = tokio::net::lookup_host((host, port)).await.ok()?;
149 addrs.into_iter().find(|a| a.is_ipv6() == want_v6)
150}
151
152async fn binding_transaction(
154 sock: &tokio::net::UdpSocket,
155 server: (&str, u16),
156 timeout: Duration,
157) -> Option<SocketAddr> {
158 let want_v6 = sock.local_addr().ok()?.is_ipv6();
159 let server_addr = match resolve(server.0, server.1, want_v6).await {
160 Some(a) => a,
161 None => {
162 tracing::debug!("STUN DNS lookup for {} failed", server.0);
163 return None;
164 }
165 };
166 let mut txid = [0u8; 12];
167 rand::thread_rng().fill_bytes(&mut txid);
168 let request = encode_binding_request(&txid);
169 let deadline = Instant::now() + timeout;
170 let mut buf = [0u8; 1024];
171
172 for (i, delay) in RETRANSMIT_DELAYS.iter().enumerate() {
173 if let Err(e) = sock.send_to(&request, server_addr).await {
174 tracing::debug!("STUN send to {} failed: {e}", server.0);
175 return None;
176 }
177 let wait_until = if i + 1 < RETRANSMIT_DELAYS.len() {
179 (Instant::now() + RETRANSMIT_DELAYS[i + 1] - *delay).min(deadline)
180 } else {
181 deadline
182 };
183 loop {
184 let now = Instant::now();
185 if now >= wait_until {
186 break;
187 }
188 match tokio::time::timeout(wait_until - now, sock.recv_from(&mut buf)).await {
189 Ok(Ok((n, _))) => {
190 if let Some(addr) = parse_binding_response(&buf[..n], &txid) {
191 return Some(addr);
192 }
193 }
195 Ok(Err(_)) | Err(_) => break,
196 }
197 }
198 if Instant::now() >= deadline {
199 break;
200 }
201 }
202 tracing::debug!("STUN timed out against {}", server.0);
203 None
204}
205
206pub async fn discover(sock: &UdpSocket) -> NatDiscovery {
212 discover_with_timeout(sock, Duration::from_millis(2500)).await
213}
214
215pub async fn discover_with_timeout(sock: &UdpSocket, timeout: Duration) -> NatDiscovery {
216 let mut result = NatDiscovery::default();
217 let dup = match sock.try_clone().and_then(tokio::net::UdpSocket::from_std) {
218 Ok(s) => s,
219 Err(e) => {
220 tracing::warn!("STUN: cannot register socket with the runtime: {e}");
221 return result;
222 }
223 };
224 for server in STUN_SERVERS {
225 if let Some(addr) = binding_transaction(&dup, server, timeout).await {
226 tracing::debug!("STUN {} reports {addr}", server.0);
227 result.observed.push(addr);
228 }
229 }
230 if result.observed.is_empty() {
231 tracing::warn!(
232 "STUN failed against all {} servers — no reflexive candidate",
233 STUN_SERVERS.len()
234 );
235 return result;
236 }
237 let first = result.observed[0];
238 if result.observed.iter().all(|a| *a == first) {
239 result.reflexive = Some(first);
240 } else if result.observed.iter().all(|a| a.ip() == first.ip()) {
241 result.symmetric = true;
243 tracing::info!(
244 "symmetric NAT detected ({:?}) — reflexive candidate is unusable",
245 result.observed
246 );
247 } else {
248 result.symmetric = true;
250 tracing::info!(
251 "inconsistent external addresses from STUN ({:?}) — treating as symmetric",
252 result.observed
253 );
254 }
255 result
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261
262 #[test]
263 fn request_layout() {
264 let txid = [7u8; 12];
265 let r = encode_binding_request(&txid);
266 assert_eq!(&r[..8], &[0x00, 0x01, 0x00, 0x00, 0x21, 0x12, 0xa4, 0x42]);
267 assert_eq!(&r[8..], &txid);
268 }
269
270 #[test]
271 fn v4_response_roundtrip_and_known_bytes() {
272 let txid = [0xaa; 12];
273 let addr: SocketAddr = "203.0.113.5:4433".parse().unwrap();
274 let resp = encode_binding_response(&txid, addr);
275 assert_eq!(&resp[20..24], &[0x00, 0x20, 0x00, 0x08]);
277 assert_eq!(resp[25], 0x01);
278 assert_eq!(u16::from_be_bytes([resp[26], resp[27]]), 4433 ^ 0x2112);
279 assert_eq!(parse_binding_response(&resp, &txid), Some(addr));
280 assert_eq!(
281 parse_binding_response(&resp, &[0xbb; 12]),
282 None,
283 "txid mismatch"
284 );
285 let mut wrong = resp.clone();
286 wrong[1] = 0x11;
287 assert_eq!(
288 parse_binding_response(&wrong, &txid),
289 None,
290 "error response"
291 );
292 }
293
294 #[test]
295 fn v6_response_roundtrip() {
296 let txid = [0x5c; 12];
297 let addr: SocketAddr = "[2606:4700::1111]:60001".parse().unwrap();
298 let resp = encode_binding_response(&txid, addr);
299 assert_eq!(parse_binding_response(&resp, &txid), Some(addr));
300 }
301
302 #[test]
303 fn skips_unknown_attributes() {
304 let txid = [1u8; 12];
305 let addr: SocketAddr = "8.8.8.8:53".parse().unwrap();
306 let mut resp = encode_binding_response(&txid, addr);
307 let extra = [0x80, 0x22, 0x00, 0x03, b'x', b'y', b'z', 0x00];
309 let mut with_extra = resp[..20].to_vec();
310 with_extra.extend_from_slice(&extra);
311 with_extra.extend_from_slice(&resp[20..]);
312 let len = (with_extra.len() - 20) as u16;
313 with_extra[2..4].copy_from_slice(&len.to_be_bytes());
314 resp = with_extra;
315 assert_eq!(parse_binding_response(&resp, &txid), Some(addr));
316 }
317
318 #[test]
319 fn classification() {
320 let a: SocketAddr = "1.2.3.4:1000".parse().unwrap();
321 let b: SocketAddr = "1.2.3.4:1001".parse().unwrap();
322 let cone = NatDiscovery {
323 reflexive: Some(a),
324 observed: vec![a, a],
325 symmetric: false,
326 };
327 assert_eq!(cone.nat_type(), NatType::Cone);
328 let sym = NatDiscovery {
329 reflexive: None,
330 observed: vec![a, b],
331 symmetric: true,
332 };
333 assert_eq!(sym.nat_type(), NatType::Symmetric);
334 assert_eq!(NatDiscovery::default().nat_type(), NatType::Unknown);
335 }
336}