1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
//! The UDP STUN client (`SPEC.md` §4) — one Binding transaction against one server, over one
//! socket. Extracted byte-for-byte from `dig-nat 0.21.1` `src/stun.rs:309-347`; the only change is
//! that the address-usability guard is now expressed via [`crate::scope::scope_of`] rather than a
//! private per-crate predicate.
//!
//! This is the crate's only `async fn` and its only I/O (`SPEC.md` §8.1) — every other public item
//! is a pure function.
use SocketAddr;
use Duration;
use UdpSocket;
use crate;
use crate;
use cratenew_transaction_id;
/// Perform a single STUN Binding transaction against `server` over `socket`, returning the
/// discovered reflexive (public) [`SocketAddr`] of `socket`.
///
/// **What the result means, and only this:** the address:port at which `socket`'s datagrams
/// arrived at `server`. The port is the NAT mapping of THAT socket, so the result is a genuinely
/// dialable candidate only when `socket` is the very socket whose external mapping the caller
/// wants — `socket` should be the caller's real listen socket, not a throwaway one, if the caller
/// needs a dialable port rather than merely its public IP. A caller MUST NOT infer inbound
/// reachability from a successful transaction (`SPEC.md` §4, §10).
///
/// # Anti-spoof: two independent defenses
///
/// A UDP reply's source address is easy to check and hard for an off-path attacker to spoof
/// (spoofing the source AND getting the reply routed back requires being on-path or the same
/// network). This function therefore accepts a datagram only when it actually originates from
/// `server`; anything else (a stray reply, a scan, an attacker racing a forged response) is
/// discarded and the receive loop keeps waiting within the overall `timeout` deadline — one
/// mismatched-source datagram must not fail the whole transaction, since the genuine reply may
/// still be in flight. This is independent, defense-in-depth hygiene alongside the transaction-id
/// check ([`new_transaction_id`]); neither replaces the other.
///
/// # The scope guard
///
/// The parsed address is rejected as [`StunError::NoMappedAddress`] when
/// `scope_of(addr) == Scope::NeverDialable` — a malicious or misconfigured STUN server fully
/// controls the bytes it returns, and this stops it from handing back a bogus reflexive address
/// (loopback, multicast, a documentation range, port `0`, …) that the caller would otherwise
/// advertise (`SPEC.md` §5, §10).
pub async