1use std::net::SocketAddr;
36use std::time::{Duration, Instant};
37
38use crate::udp_sockets;
39
40pub const STUN_MAGIC_COOKIE: u32 = 0x2112_A442;
50pub fn build_binding_request(tx_id: &[u8; 12]) -> [u8; 20] {
52 let mut pkt = [0u8; 20];
53 pkt[0..2].copy_from_slice(&0x0001u16.to_be_bytes()); pkt[2..4].copy_from_slice(&0u16.to_be_bytes()); pkt[4..8].copy_from_slice(&STUN_MAGIC_COOKIE.to_be_bytes());
56 pkt[8..20].copy_from_slice(tx_id);
57 pkt
58}
59
60pub fn parse_binding_response(pkt: &[u8]) -> Option<(std::net::IpAddr, u16)> {
73 if pkt.len() < 20 {
74 return None;
75 }
76 let msg_type = u16::from_be_bytes([pkt[0], pkt[1]]);
77 if msg_type != 0x0101 {
78 return None;
79 }
80 let msg_len = u16::from_be_bytes([pkt[2], pkt[3]]) as usize;
81 let body_end = 20 + msg_len;
82 if pkt.len() < body_end {
83 return None;
84 }
85 let magic_cookie = u32::from_be_bytes([pkt[4], pkt[5], pkt[6], pkt[7]]);
86 if magic_cookie != STUN_MAGIC_COOKIE {
87 return None;
88 }
89 let mut tx_id = [0u8; 12];
90 tx_id.copy_from_slice(&pkt[8..20]);
91
92 let mut off = 20;
95 while off + 4 <= body_end {
96 let attr_type = u16::from_be_bytes([pkt[off], pkt[off + 1]]);
97 let attr_len = u16::from_be_bytes([pkt[off + 2], pkt[off + 3]]) as usize;
98 let val_start = off + 4;
99 let val_end = val_start + attr_len;
100 if val_end > body_end {
101 return None;
102 }
103 match attr_type {
104 0x0020 if attr_len >= 8 => {
108 let family = pkt[val_start + 1];
109 let xor_port = u16::from_be_bytes([pkt[val_start + 2], pkt[val_start + 3]]);
110 let port = xor_port ^ ((STUN_MAGIC_COOKIE >> 16) as u16);
111 match family {
112 0x01 => {
113 let xor_addr = u32::from_be_bytes([
114 pkt[val_start + 4],
115 pkt[val_start + 5],
116 pkt[val_start + 6],
117 pkt[val_start + 7],
118 ]);
119 let addr = xor_addr ^ STUN_MAGIC_COOKIE;
120 let ip = std::net::Ipv4Addr::from(addr.to_be_bytes());
121 return Some((std::net::IpAddr::V4(ip), port));
122 }
123 0x02 if attr_len >= 20 => {
124 let mut octets = [0u8; 16];
128 octets.copy_from_slice(&pkt[val_start + 4..val_start + 20]);
129 let cookie_be = STUN_MAGIC_COOKIE.to_be_bytes();
130 for i in 0..4 {
131 octets[i] ^= cookie_be[i];
132 }
133 for i in 0..12 {
134 octets[4 + i] ^= tx_id[i];
135 }
136 let ip = std::net::Ipv6Addr::from(octets);
137 return Some((std::net::IpAddr::V6(ip), port));
138 }
139 _ => {}
140 }
141 }
142 0x0001 if attr_len >= 8 => {
144 let family = pkt[val_start + 1];
145 let port = u16::from_be_bytes([pkt[val_start + 2], pkt[val_start + 3]]);
146 match family {
147 0x01 => {
148 let ip = std::net::Ipv4Addr::new(
149 pkt[val_start + 4],
150 pkt[val_start + 5],
151 pkt[val_start + 6],
152 pkt[val_start + 7],
153 );
154 return Some((std::net::IpAddr::V4(ip), port));
155 }
156 0x02 if attr_len >= 20 => {
157 let mut octets = [0u8; 16];
158 octets.copy_from_slice(&pkt[val_start + 4..val_start + 20]);
159 let ip = std::net::Ipv6Addr::from(octets);
160 return Some((std::net::IpAddr::V6(ip), port));
161 }
162 _ => {}
163 }
164 }
165 _ => {}
166 }
167 off = val_end;
169 if off % 4 != 0 {
170 off += 4 - (off % 4);
171 }
172 }
173 None
174}
175
176fn fresh_tx_id() -> [u8; 12] {
187 use std::sync::atomic::{AtomicU64, Ordering};
188 static COUNTER: AtomicU64 = AtomicU64::new(0);
189
190 let mut id = [0u8; 12];
191 let nanos = std::time::SystemTime::now()
192 .duration_since(std::time::UNIX_EPOCH)
193 .map(|d| d.as_nanos())
194 .unwrap_or(0);
195 let counter = COUNTER.fetch_add(1, Ordering::Relaxed);
196 id[0..8].copy_from_slice(&(nanos as u64).to_le_bytes());
197 id[8..12].copy_from_slice(&(counter as u32).to_le_bytes());
198 let pid = std::process::id();
199 let pid_be = pid.to_le_bytes();
200 for i in 0..4 {
201 id[i] ^= pid_be[i];
202 }
203 id
204}
205
206pub fn stun_query(
211 socket_id: u64,
212 stun_host: &str,
213 stun_port: u16,
214 timeout: Duration,
215) -> Option<(std::net::IpAddr, u16)> {
216 let socket = udp_sockets::get(socket_id)?;
217 let addr = udp_sockets::resolve_one(stun_host, stun_port)?;
218 let tx_id = fresh_tx_id();
219 let pkt = build_binding_request(&tx_id);
220 socket.send_to(&pkt, addr).ok()?;
221 socket.set_read_timeout(Some(timeout)).ok()?;
222 let mut buf = [0u8; 1024];
223 loop {
224 let (n, src) = socket.recv_from(&mut buf).ok()?;
225 if src != addr {
229 continue;
230 }
231 if n < 20 || buf[8..20] != tx_id {
232 continue;
233 }
234 return parse_binding_response(&buf[..n]);
235 }
236}
237
238pub const DEFAULT_STUN_SERVERS: &[(&str, u16)] = &[
245 ("stun.l.google.com", 19302),
246 ("stun.cloudflare.com", 3478),
247 ("stun.nextcloud.com", 443),
248];
249
250#[derive(Debug, Clone)]
252pub struct StunObservation {
253 pub server: String,
255 pub ok: bool,
257 pub public_ip: Option<std::net::IpAddr>,
259 pub public_port: Option<u16>,
261}
262
263#[derive(Debug, Clone)]
279pub struct NatClassification {
280 pub nat_type: &'static str,
282 pub public_ip: Option<std::net::IpAddr>,
284 pub observations: Vec<StunObservation>,
286 pub queried: u32,
288 pub succeeded: u32,
290}
291
292pub fn classify_nat(
297 socket_id: u64,
298 servers: &[(&str, u16)],
299 timeout: Duration,
300) -> NatClassification {
301 let mut observations: Vec<StunObservation> = Vec::with_capacity(servers.len());
302 let mut ports_seen: Vec<u16> = Vec::with_capacity(servers.len());
303 let mut ip_seen: Option<std::net::IpAddr> = None;
304
305 for (host, port) in servers {
306 let result = stun_query(socket_id, host, *port, timeout);
307 let observation = match result {
308 Some((ip, p)) => {
309 ports_seen.push(p);
310 if ip_seen.is_none() {
315 ip_seen = Some(ip);
316 }
317 StunObservation {
318 server: format!("{}:{}", host, port),
319 ok: true,
320 public_ip: Some(ip),
321 public_port: Some(p),
322 }
323 }
324 None => StunObservation {
325 server: format!("{}:{}", host, port),
326 ok: false,
327 public_ip: None,
328 public_port: None,
329 },
330 };
331 observations.push(observation);
332 }
333
334 let queried = servers.len() as u32;
335 let succeeded = ports_seen.len() as u32;
336 let nat_type = if succeeded < 2 {
337 "unknown"
338 } else if ports_seen.iter().all(|p| *p == ports_seen[0]) {
339 "cone"
340 } else {
341 "symmetric"
342 };
343
344 NatClassification {
345 nat_type,
346 public_ip: ip_seen,
347 observations,
348 queried,
349 succeeded,
350 }
351}
352
353#[derive(Debug, Clone)]
355pub struct PunchResult {
356 pub established: bool,
358 pub latency_ms: u64,
360 pub bombards_sent: u32,
362 pub peer_msg: Option<Vec<u8>>,
364 pub peer_addr: Option<SocketAddr>,
366}
367
368pub fn hole_punch(
377 socket_id: u64,
378 peer_host: &str,
379 peer_port: u16,
380 timeout: Duration,
381 interval: Duration,
382 probe_payload: &[u8],
383) -> PunchResult {
384 let mut result = PunchResult {
385 established: false,
386 latency_ms: 0,
387 bombards_sent: 0,
388 peer_msg: None,
389 peer_addr: None,
390 };
391 let Some(socket) = udp_sockets::get(socket_id) else {
392 return result;
393 };
394 let Some(peer_addr) = udp_sockets::resolve_one(peer_host, peer_port) else {
395 return result;
396 };
397 let start = Instant::now();
398 let deadline = start + timeout;
399 let recv_timeout = std::cmp::min(interval, Duration::from_millis(50));
401 if socket.set_read_timeout(Some(recv_timeout)).is_err() {
402 return result;
403 }
404 let mut buf = [0u8; 65_535];
405 while Instant::now() < deadline {
406 if socket.send_to(probe_payload, peer_addr).is_ok() {
407 result.bombards_sent += 1;
408 }
409 match socket.recv_from(&mut buf) {
410 Ok((n, src)) => {
411 result.established = true;
416 result.latency_ms = start.elapsed().as_millis() as u64;
417 result.peer_msg = Some(buf[..n].to_vec());
418 result.peer_addr = Some(src);
419 return result;
420 }
421 Err(_) => {
422 }
424 }
425 std::thread::sleep(interval);
426 }
427 result.latency_ms = start.elapsed().as_millis() as u64;
428 result
429}
430
431#[cfg(test)]
432mod tests {
433 use super::*;
434
435 #[test]
436 fn binding_request_packet_shape() {
437 let tx = [0u8; 12];
438 let pkt = build_binding_request(&tx);
439 assert_eq!(pkt.len(), 20);
440 assert_eq!(&pkt[0..2], &[0x00, 0x01], "msg type = Binding Request");
441 assert_eq!(&pkt[2..4], &[0x00, 0x00], "msg length = 0");
442 assert_eq!(&pkt[4..8], &STUN_MAGIC_COOKIE.to_be_bytes(), "magic cookie");
443 assert_eq!(&pkt[8..20], &tx, "transaction ID");
444 }
445
446 #[test]
447 fn parse_xor_mapped_address_round_trip() {
448 let real_ip = std::net::Ipv4Addr::new(203, 0, 113, 45);
451 let real_port: u16 = 51234;
452 let xor_port = real_port ^ ((STUN_MAGIC_COOKIE >> 16) as u16);
453 let real_ip_u32 = u32::from_be_bytes(real_ip.octets());
454 let xor_addr = real_ip_u32 ^ STUN_MAGIC_COOKIE;
455
456 let mut pkt = vec![0u8; 32];
457 pkt[0..2].copy_from_slice(&0x0101u16.to_be_bytes()); pkt[2..4].copy_from_slice(&12u16.to_be_bytes()); pkt[4..8].copy_from_slice(&STUN_MAGIC_COOKIE.to_be_bytes());
460 pkt[20..22].copy_from_slice(&0x0020u16.to_be_bytes());
463 pkt[22..24].copy_from_slice(&8u16.to_be_bytes());
464 pkt[24] = 0x00; pkt[25] = 0x01; pkt[26..28].copy_from_slice(&xor_port.to_be_bytes());
467 pkt[28..32].copy_from_slice(&xor_addr.to_be_bytes());
468
469 let parsed = parse_binding_response(&pkt).expect("must parse");
470 assert_eq!(parsed.0, std::net::IpAddr::V4(real_ip));
471 assert_eq!(parsed.1, real_port);
472 }
473
474 #[test]
478 fn parse_xor_mapped_address_ipv6_round_trip() {
479 let real_ip = std::net::Ipv6Addr::new(
480 0x2001, 0x0db8, 0xdead, 0xbeef, 0xcafe, 0xface, 0x1234, 0x5678,
481 );
482 let real_port: u16 = 51234;
483 let tx_id: [u8; 12] = [
484 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
485 ];
486 let xor_port = real_port ^ ((STUN_MAGIC_COOKIE >> 16) as u16);
487 let mut xor_addr = real_ip.octets();
488 let cookie_be = STUN_MAGIC_COOKIE.to_be_bytes();
489 for i in 0..4 {
490 xor_addr[i] ^= cookie_be[i];
491 }
492 for i in 0..12 {
493 xor_addr[4 + i] ^= tx_id[i];
494 }
495
496 let mut pkt = vec![0u8; 44]; pkt[0..2].copy_from_slice(&0x0101u16.to_be_bytes());
498 pkt[2..4].copy_from_slice(&24u16.to_be_bytes()); pkt[4..8].copy_from_slice(&STUN_MAGIC_COOKIE.to_be_bytes());
500 pkt[8..20].copy_from_slice(&tx_id);
501 pkt[20..22].copy_from_slice(&0x0020u16.to_be_bytes());
502 pkt[22..24].copy_from_slice(&20u16.to_be_bytes()); pkt[24] = 0x00; pkt[25] = 0x02; pkt[26..28].copy_from_slice(&xor_port.to_be_bytes());
506 pkt[28..44].copy_from_slice(&xor_addr);
507
508 let parsed = parse_binding_response(&pkt).expect("must parse IPv6 XOR-MAPPED");
509 assert_eq!(parsed.0, std::net::IpAddr::V6(real_ip));
510 assert_eq!(parsed.1, real_port);
511 }
512
513 #[test]
523 fn parse_ignores_unknown_optional_attributes() {
524 let real_ip = std::net::Ipv4Addr::new(192, 0, 2, 99);
525 let real_port: u16 = 12345;
526 let xor_port = real_port ^ ((STUN_MAGIC_COOKIE >> 16) as u16);
527 let xor_addr = u32::from_be_bytes(real_ip.octets()) ^ STUN_MAGIC_COOKIE;
528
529 let mut pkt = vec![0u8; 40];
533 pkt[0..2].copy_from_slice(&0x0101u16.to_be_bytes());
534 pkt[2..4].copy_from_slice(&20u16.to_be_bytes()); pkt[4..8].copy_from_slice(&STUN_MAGIC_COOKIE.to_be_bytes());
536
537 pkt[20..22].copy_from_slice(&0x9001u16.to_be_bytes());
539 pkt[22..24].copy_from_slice(&4u16.to_be_bytes());
540 pkt[24..28].copy_from_slice(b"JUNK");
541
542 pkt[28..30].copy_from_slice(&0x0020u16.to_be_bytes());
544 pkt[30..32].copy_from_slice(&8u16.to_be_bytes());
545 pkt[32] = 0x00; pkt[33] = 0x01; pkt[34..36].copy_from_slice(&xor_port.to_be_bytes());
548 pkt[36..40].copy_from_slice(&xor_addr.to_be_bytes());
549
550 let parsed = parse_binding_response(&pkt).expect("must parse despite unknown attribute");
551 assert_eq!(parsed.0, std::net::IpAddr::V4(real_ip));
552 assert_eq!(parsed.1, real_port);
553 }
554
555 #[test]
556 fn parse_rejects_wrong_message_type() {
557 let mut pkt = [0u8; 20];
558 pkt[0] = 0x00;
559 pkt[1] = 0x02; pkt[4..8].copy_from_slice(&STUN_MAGIC_COOKIE.to_be_bytes());
561 assert!(parse_binding_response(&pkt).is_none());
562 }
563
564 #[test]
565 fn parse_rejects_wrong_magic_cookie() {
566 let mut pkt = [0u8; 20];
567 pkt[0..2].copy_from_slice(&0x0101u16.to_be_bytes());
568 pkt[4..8].copy_from_slice(&[0xde, 0xad, 0xbe, 0xef]);
569 assert!(parse_binding_response(&pkt).is_none());
570 }
571
572 #[test]
573 fn parse_rejects_truncated_packet() {
574 assert!(parse_binding_response(&[0u8; 4]).is_none());
575 assert!(parse_binding_response(&[]).is_none());
576 }
577
578 #[test]
579 fn fresh_tx_id_is_12_bytes() {
580 let id = fresh_tx_id();
581 assert_eq!(id.len(), 12);
582 let id2 = fresh_tx_id();
585 assert_ne!(id, id2, "two tx ids should differ — random source broken");
586 }
587
588 #[test]
593 fn stun_query_against_local_fake_server() {
594 use std::net::UdpSocket;
595 use std::thread;
596
597 let server = UdpSocket::bind("127.0.0.1:0").expect("bind fake stun");
598 let server_addr = server.local_addr().unwrap();
599 thread::spawn(move || {
600 let mut buf = [0u8; 1024];
601 let (n, src) = server.recv_from(&mut buf).expect("recv req");
602 let tx_id = &buf[8..20];
605 let claim_ip = std::net::Ipv4Addr::new(198, 51, 100, 7);
606 let claim_port: u16 = 50001;
607 let xor_port = claim_port ^ ((STUN_MAGIC_COOKIE >> 16) as u16);
608 let claim_ip_u32 = u32::from_be_bytes(claim_ip.octets());
609 let xor_addr = claim_ip_u32 ^ STUN_MAGIC_COOKIE;
610 let mut resp = vec![0u8; 32];
611 resp[0..2].copy_from_slice(&0x0101u16.to_be_bytes());
612 resp[2..4].copy_from_slice(&12u16.to_be_bytes());
613 resp[4..8].copy_from_slice(&STUN_MAGIC_COOKIE.to_be_bytes());
614 resp[8..20].copy_from_slice(tx_id);
615 resp[20..22].copy_from_slice(&0x0020u16.to_be_bytes());
616 resp[22..24].copy_from_slice(&8u16.to_be_bytes());
617 resp[24] = 0x00;
618 resp[25] = 0x01;
619 resp[26..28].copy_from_slice(&xor_port.to_be_bytes());
620 resp[28..32].copy_from_slice(&xor_addr.to_be_bytes());
621 let _ = server.send_to(&resp, src);
622 assert!(n >= 20, "request must be ≥ 20 bytes");
623 });
624
625 let client = udp_sockets::open("127.0.0.1", 0).expect("bind client");
626 let result = stun_query(
627 client,
628 &server_addr.ip().to_string(),
629 server_addr.port(),
630 Duration::from_secs(2),
631 );
632 let (ip, port) = result.expect("STUN query must round-trip");
633 assert_eq!(
634 ip,
635 std::net::IpAddr::V4(std::net::Ipv4Addr::new(198, 51, 100, 7))
636 );
637 assert_eq!(port, 50001);
638 udp_sockets::close(client);
639 }
640
641 #[test]
647 fn classify_nat_detects_symmetric_when_servers_report_different_ports() {
648 use std::net::UdpSocket;
649 use std::thread;
650
651 fn spawn_fake_stun(claim_port: u16) -> std::net::SocketAddr {
654 let server = UdpSocket::bind("127.0.0.1:0").expect("bind fake");
655 let addr = server.local_addr().unwrap();
656 thread::spawn(move || {
657 let mut buf = [0u8; 1024];
658 let (_, src) = server.recv_from(&mut buf).expect("recv");
659 let tx_id = &buf[8..20];
660 let claim_ip = std::net::Ipv4Addr::new(198, 51, 100, 1);
661 let xor_port = claim_port ^ ((STUN_MAGIC_COOKIE >> 16) as u16);
662 let xor_addr = u32::from_be_bytes(claim_ip.octets()) ^ STUN_MAGIC_COOKIE;
663 let mut resp = vec![0u8; 32];
664 resp[0..2].copy_from_slice(&0x0101u16.to_be_bytes());
665 resp[2..4].copy_from_slice(&12u16.to_be_bytes());
666 resp[4..8].copy_from_slice(&STUN_MAGIC_COOKIE.to_be_bytes());
667 resp[8..20].copy_from_slice(tx_id);
668 resp[20..22].copy_from_slice(&0x0020u16.to_be_bytes());
669 resp[22..24].copy_from_slice(&8u16.to_be_bytes());
670 resp[24] = 0x00;
671 resp[25] = 0x01;
672 resp[26..28].copy_from_slice(&xor_port.to_be_bytes());
673 resp[28..32].copy_from_slice(&xor_addr.to_be_bytes());
674 let _ = server.send_to(&resp, src);
675 });
676 addr
677 }
678
679 let a = spawn_fake_stun(40001);
680 let b = spawn_fake_stun(40002); let client = udp_sockets::open("127.0.0.1", 0).expect("bind client");
683 let a_host = a.ip().to_string();
684 let b_host = b.ip().to_string();
685 let servers = [(a_host.as_str(), a.port()), (b_host.as_str(), b.port())];
686 let result = classify_nat(client, &servers, Duration::from_secs(2));
687 udp_sockets::close(client);
688
689 assert_eq!(result.nat_type, "symmetric");
690 assert_eq!(result.queried, 2);
691 assert_eq!(result.succeeded, 2);
692 assert_eq!(result.observations.len(), 2);
693 assert_eq!(
694 result.public_ip,
695 Some(std::net::IpAddr::V4(std::net::Ipv4Addr::new(
696 198, 51, 100, 1
697 )))
698 );
699 }
700
701 #[test]
703 fn classify_nat_detects_cone_when_servers_agree() {
704 use std::net::UdpSocket;
705 use std::thread;
706 fn spawn(claim_port: u16) -> std::net::SocketAddr {
707 let s = UdpSocket::bind("127.0.0.1:0").unwrap();
708 let a = s.local_addr().unwrap();
709 thread::spawn(move || {
710 let mut buf = [0u8; 1024];
711 let (_, src) = s.recv_from(&mut buf).unwrap();
712 let tx_id = &buf[8..20];
713 let claim_ip = std::net::Ipv4Addr::new(198, 51, 100, 9);
714 let xp = claim_port ^ ((STUN_MAGIC_COOKIE >> 16) as u16);
715 let xa = u32::from_be_bytes(claim_ip.octets()) ^ STUN_MAGIC_COOKIE;
716 let mut r = vec![0u8; 32];
717 r[0..2].copy_from_slice(&0x0101u16.to_be_bytes());
718 r[2..4].copy_from_slice(&12u16.to_be_bytes());
719 r[4..8].copy_from_slice(&STUN_MAGIC_COOKIE.to_be_bytes());
720 r[8..20].copy_from_slice(tx_id);
721 r[20..22].copy_from_slice(&0x0020u16.to_be_bytes());
722 r[22..24].copy_from_slice(&8u16.to_be_bytes());
723 r[24] = 0x00;
724 r[25] = 0x01;
725 r[26..28].copy_from_slice(&xp.to_be_bytes());
726 r[28..32].copy_from_slice(&xa.to_be_bytes());
727 let _ = s.send_to(&r, src);
728 });
729 a
730 }
731 let a = spawn(45000);
732 let b = spawn(45000); let client = udp_sockets::open("127.0.0.1", 0).unwrap();
734 let aip = a.ip().to_string();
735 let bip = b.ip().to_string();
736 let servers = [(aip.as_str(), a.port()), (bip.as_str(), b.port())];
737 let result = classify_nat(client, &servers, Duration::from_secs(2));
738 udp_sockets::close(client);
739 assert_eq!(result.nat_type, "cone");
740 assert_eq!(result.succeeded, 2);
741 }
742
743 #[test]
747 fn classify_nat_returns_unknown_when_only_one_server_responds() {
748 use std::net::UdpSocket;
749 use std::thread;
750 let s = UdpSocket::bind("127.0.0.1:0").unwrap();
752 let working = s.local_addr().unwrap();
753 thread::spawn(move || {
754 let mut buf = [0u8; 1024];
755 let (_, src) = s.recv_from(&mut buf).unwrap();
756 let tx_id = &buf[8..20];
757 let claim_ip = std::net::Ipv4Addr::new(198, 51, 100, 9);
758 let xp = 12345u16 ^ ((STUN_MAGIC_COOKIE >> 16) as u16);
759 let xa = u32::from_be_bytes(claim_ip.octets()) ^ STUN_MAGIC_COOKIE;
760 let mut r = vec![0u8; 32];
761 r[0..2].copy_from_slice(&0x0101u16.to_be_bytes());
762 r[2..4].copy_from_slice(&12u16.to_be_bytes());
763 r[4..8].copy_from_slice(&STUN_MAGIC_COOKIE.to_be_bytes());
764 r[8..20].copy_from_slice(tx_id);
765 r[20..22].copy_from_slice(&0x0020u16.to_be_bytes());
766 r[22..24].copy_from_slice(&8u16.to_be_bytes());
767 r[24] = 0x00;
768 r[25] = 0x01;
769 r[26..28].copy_from_slice(&xp.to_be_bytes());
770 r[28..32].copy_from_slice(&xa.to_be_bytes());
771 let _ = s.send_to(&r, src);
772 });
773
774 let silent_sock = UdpSocket::bind("127.0.0.1:0").unwrap();
776 let silent = silent_sock.local_addr().unwrap();
777 drop(silent_sock);
778
779 let client = udp_sockets::open("127.0.0.1", 0).unwrap();
780 let wip = working.ip().to_string();
781 let sip = silent.ip().to_string();
782 let servers = [
783 (wip.as_str(), working.port()),
784 (sip.as_str(), silent.port()),
785 ];
786 let result = classify_nat(client, &servers, Duration::from_millis(300));
787 udp_sockets::close(client);
788 assert_eq!(result.nat_type, "unknown");
789 assert_eq!(result.queried, 2);
790 assert_eq!(result.succeeded, 1, "exactly one server responded");
791 }
792
793 #[test]
796 fn hole_punch_loopback_establishes() {
797 let a = udp_sockets::open("127.0.0.1", 0).expect("bind a");
798 let b = udp_sockets::open("127.0.0.1", 0).expect("bind b");
799 let a_addr = udp_sockets::local_addr(a).unwrap();
800 let b_addr = udp_sockets::local_addr(b).unwrap();
801
802 let b_addr_str = b_addr.ip().to_string();
805 let a_addr_str = a_addr.ip().to_string();
806 let b_handle = std::thread::spawn(move || {
807 hole_punch(
808 b,
809 &a_addr_str,
810 a_addr.port(),
811 Duration::from_secs(2),
812 Duration::from_millis(20),
813 b"hi from b",
814 )
815 });
816 let a_result = hole_punch(
817 a,
818 &b_addr_str,
819 b_addr.port(),
820 Duration::from_secs(2),
821 Duration::from_millis(20),
822 b"hi from a",
823 );
824 let b_result = b_handle.join().expect("thread");
825 assert!(a_result.established, "a should see b's bombard");
826 assert!(b_result.established, "b should see a's bombard");
827 assert!(a_result.bombards_sent >= 1);
828 assert!(b_result.bombards_sent >= 1);
829 assert!(a_result.peer_msg.is_some());
830 assert!(b_result.peer_msg.is_some());
831
832 udp_sockets::close(a);
833 udp_sockets::close(b);
834 }
835}