use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
use std::time::Duration;
use tokio::net::UdpSocket;
pub use dig_stun::{
encode_binding_request, new_transaction_id, parse_binding_response, query_reflexive_address,
StunError, ATTR_MAPPED_ADDRESS, ATTR_XOR_MAPPED_ADDRESS, BINDING_REQUEST, BINDING_SUCCESS,
MAGIC_COOKIE,
};
pub async fn discover_reflexive_address(
stun_servers: &[SocketAddr],
local: dig_ip::LocalStack,
timeout: Duration,
) -> Option<SocketAddr> {
if stun_servers.is_empty() {
return None;
}
let mut candidates = dig_ip::PeerCandidates::new();
candidates.extend(
stun_servers.iter().copied(),
dig_ip::CandidateSource::StunReflexive,
);
let config = dig_ip::DialConfig {
per_attempt_timeout: timeout,
..Default::default()
};
let winner = dig_ip::connect(&local, &candidates, config, |stun_addr| async move {
let bind: SocketAddr = if stun_addr.is_ipv6() {
(Ipv6Addr::UNSPECIFIED, 0).into()
} else {
(Ipv4Addr::UNSPECIFIED, 0).into()
};
let socket = UdpSocket::bind(bind)
.await
.map_err(|e| format!("bind {bind}: {e}"))?;
query_reflexive_address(&socket, stun_addr, timeout)
.await
.map_err(|e| e.to_string())
})
.await;
match winner {
Ok(w) => Some(w.conn),
Err(_) => None,
}
}
#[cfg(test)]
mod reflexive_guard_tests {
use dig_stun::scope::is_usable_reflexive_addr;
use std::net::SocketAddr;
fn addr(s: &str) -> SocketAddr {
s.parse().expect("valid SocketAddr literal")
}
#[test]
fn accepts_genuinely_global_addresses() {
assert!(is_usable_reflexive_addr(&addr("1.1.1.1:443")));
assert!(is_usable_reflexive_addr(&addr("8.8.8.8:53")));
assert!(is_usable_reflexive_addr(&addr(
"[2606:4700:4700::1111]:443"
)));
}
#[test]
fn accepts_private_cgnat_and_ula() {
assert!(is_usable_reflexive_addr(&addr("192.168.1.5:9000")));
assert!(is_usable_reflexive_addr(&addr("10.0.0.7:9000")));
assert!(is_usable_reflexive_addr(&addr("172.16.5.5:9000")));
assert!(is_usable_reflexive_addr(&addr("100.64.0.1:9000"))); assert!(is_usable_reflexive_addr(&addr("[fd00::1]:9000"))); }
#[test]
fn rejects_port_zero() {
assert!(!is_usable_reflexive_addr(&addr("1.1.1.1:0")));
assert!(!is_usable_reflexive_addr(&addr("[2606:4700:4700::1111]:0")));
}
#[test]
fn rejects_reserved_ipv4() {
assert!(!is_usable_reflexive_addr(&addr("0.0.0.0:1234"))); assert!(!is_usable_reflexive_addr(&addr("127.0.0.1:1234"))); assert!(!is_usable_reflexive_addr(&addr("169.254.1.1:1234"))); assert!(!is_usable_reflexive_addr(&addr("224.0.0.1:1234"))); assert!(!is_usable_reflexive_addr(&addr("255.255.255.255:1234"))); assert!(!is_usable_reflexive_addr(&addr("192.0.2.1:1234"))); assert!(!is_usable_reflexive_addr(&addr("198.51.100.1:1234"))); assert!(!is_usable_reflexive_addr(&addr("203.0.113.1:1234"))); }
#[test]
fn rejects_reserved_ipv6() {
assert!(!is_usable_reflexive_addr(&addr("[::]:1234"))); assert!(!is_usable_reflexive_addr(&addr("[::1]:1234"))); assert!(!is_usable_reflexive_addr(&addr("[fe80::1]:1234"))); assert!(!is_usable_reflexive_addr(&addr("[febf::1]:1234"))); assert!(!is_usable_reflexive_addr(&addr("[ff02::1]:1234"))); assert!(!is_usable_reflexive_addr(&addr("[2001:db8::1]:1234"))); }
#[test]
fn rejects_ipv4_mapped_and_compat_smuggling_reserved_ranges() {
assert!(!is_usable_reflexive_addr(&addr("[::ffff:127.0.0.1]:1234"))); assert!(!is_usable_reflexive_addr(&addr(
"[::ffff:169.254.1.1]:1234"
))); assert!(!is_usable_reflexive_addr(&addr("[::ffff:224.0.0.1]:1234"))); assert!(!is_usable_reflexive_addr(&addr("[::ffff:192.0.2.1]:1234"))); assert!(!is_usable_reflexive_addr(&addr(
"[::ffff:255.255.255.255]:1234"
))); assert!(!is_usable_reflexive_addr(&addr("[::ffff:0.0.0.0]:1234"))); assert!(!is_usable_reflexive_addr(&addr("[::7f00:1]:1234"))); }
#[test]
fn accepts_ipv4_mapped_private() {
assert!(is_usable_reflexive_addr(&addr("[::ffff:10.0.0.1]:9000")));
}
#[test]
fn rejects_never_dialable_ipv4_ranges() {
assert!(!is_usable_reflexive_addr(&addr("198.18.0.1:1234"))); assert!(!is_usable_reflexive_addr(&addr("198.19.0.1:1234"))); assert!(!is_usable_reflexive_addr(&addr("240.0.0.1:1234"))); assert!(!is_usable_reflexive_addr(&addr("0.1.2.3:1234"))); assert!(!is_usable_reflexive_addr(&addr("192.88.99.1:1234"))); }
#[test]
fn rejects_ranges_newly_tightened_by_the_dig_stun_reconciliation() {
assert!(!is_usable_reflexive_addr(&addr("192.0.0.1:1234")));
assert!(!is_usable_reflexive_addr(&addr("[2001:2::1]:1234")));
assert!(!is_usable_reflexive_addr(&addr("[100::1]:1234")));
}
}