use std::collections::HashMap;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use std::sync::{Mutex, OnceLock};
const CACHE_CAP: usize = 256;
fn cache() -> &'static Mutex<HashMap<(IpAddr, IpAddr), IpAddr>> {
static CACHE: OnceLock<Mutex<HashMap<(IpAddr, IpAddr), IpAddr>>> = OnceLock::new();
CACHE.get_or_init(|| Mutex::new(HashMap::new()))
}
pub fn advertise_ip(bind: IpAddr, peer: SocketAddr) -> IpAddr {
if !bind.is_unspecified() {
return bind;
}
let key = (bind, peer.ip());
if let Some(cached) = cache_lookup(&key) {
return cached;
}
let resolved = resolve_advertise_ip(bind, peer);
cache_insert(key, resolved);
resolved
}
fn cache_lookup(key: &(IpAddr, IpAddr)) -> Option<IpAddr> {
cache()
.lock()
.unwrap_or_else(|p| p.into_inner())
.get(key)
.copied()
}
fn cache_insert(key: (IpAddr, IpAddr), value: IpAddr) {
let mut guard = cache().lock().unwrap_or_else(|p| p.into_inner());
if guard.len() >= CACHE_CAP && !guard.contains_key(&key) {
return;
}
guard.insert(key, value);
}
#[cfg(test)]
fn clear_cache_for_tests() {
cache().lock().unwrap_or_else(|p| p.into_inner()).clear();
}
fn resolve_advertise_ip(bind: IpAddr, peer: SocketAddr) -> IpAddr {
let probe_bind: SocketAddr = match peer.ip() {
IpAddr::V4(_) => SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0),
IpAddr::V6(_) => SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0),
};
let fallback = if peer.is_ipv4() {
IpAddr::V4(Ipv4Addr::LOCALHOST)
} else {
IpAddr::V6(Ipv6Addr::LOCALHOST)
};
let _ = bind; match std::net::UdpSocket::bind(probe_bind) {
Ok(probe) => match probe.connect(peer) {
Ok(()) => match probe.local_addr() {
Ok(a) => a.ip(),
Err(e) => {
tracing::warn!(
%peer, %fallback, error = %e,
"advertise_ip: local_addr() failed; falling back to loopback (camera will likely fail to reach us)",
);
fallback
}
},
Err(e) => {
tracing::warn!(
%peer, %fallback, error = %e,
"advertise_ip: probe-socket connect() failed; falling back to loopback (camera will likely fail to reach us)",
);
fallback
}
},
Err(e) => {
tracing::warn!(
%peer, %fallback, error = %e,
"advertise_ip: probe-socket bind() failed; falling back to loopback (camera will likely fail to reach us)",
);
fallback
}
}
}
#[cfg(test)]
mod tests {
use super::*;
static TEST_LOCK: Mutex<()> = Mutex::new(());
fn lock_and_clear() -> std::sync::MutexGuard<'static, ()> {
let g = TEST_LOCK.lock().unwrap_or_else(|p| p.into_inner());
clear_cache_for_tests();
g
}
#[test]
fn concrete_bind_is_returned_as_is() {
let _g = lock_and_clear();
let bind: IpAddr = "192.168.1.50".parse().unwrap();
let peer: SocketAddr = "203.0.113.10:9999".parse().unwrap();
assert_eq!(advertise_ip(bind, peer), bind);
}
#[test]
fn loopback_peer_resolves_to_loopback() {
let _g = lock_and_clear();
let bind: IpAddr = Ipv4Addr::UNSPECIFIED.into();
let peer: SocketAddr = "127.0.0.1:9999".parse().unwrap();
let ip = advertise_ip(bind, peer);
assert!(!ip.is_unspecified(), "advertise_ip returned wildcard");
assert!(ip.is_loopback(), "expected loopback, got {ip}");
}
#[test]
fn ipv6_loopback_peer_resolves_to_v6_loopback() {
let _g = lock_and_clear();
let bind: IpAddr = Ipv6Addr::UNSPECIFIED.into();
let peer: SocketAddr = "[::1]:9999".parse().unwrap();
let ip = advertise_ip(bind, peer);
assert!(!ip.is_unspecified(), "advertise_ip returned ::");
assert!(ip.is_loopback(), "expected v6 loopback, got {ip}");
}
#[test]
fn cache_hit_bypasses_resolver() {
let _g = lock_and_clear();
let bind: IpAddr = Ipv4Addr::UNSPECIFIED.into();
let peer_ip: IpAddr = "127.0.0.1".parse().unwrap();
let sentinel: IpAddr = "10.99.99.99".parse().unwrap();
cache_insert((bind, peer_ip), sentinel);
let peer: SocketAddr = "127.0.0.1:9999".parse().unwrap();
assert_eq!(
advertise_ip(bind, peer),
sentinel,
"second call must hit the cache, not re-resolve"
);
}
#[test]
fn cache_soft_cap_stops_growth_under_pressure() {
let _g = lock_and_clear();
const _: () = assert!(CACHE_CAP <= 65_536, "test address spread assumes ≤ /16");
let bind: IpAddr = Ipv4Addr::UNSPECIFIED.into();
for i in 0..CACHE_CAP {
let peer_ip = IpAddr::V4(Ipv4Addr::new(
10,
((i >> 8) & 0xff) as u8,
(i & 0xff) as u8,
42,
));
cache_insert((bind, peer_ip), peer_ip);
}
let len_before = cache().lock().unwrap_or_else(|p| p.into_inner()).len();
assert_eq!(len_before, CACHE_CAP);
cache_insert(
(bind, IpAddr::V4(Ipv4Addr::new(11, 0, 0, 1))),
IpAddr::V4(Ipv4Addr::new(11, 0, 0, 1)),
);
let len_after = cache().lock().unwrap_or_else(|p| p.into_inner()).len();
assert_eq!(len_after, CACHE_CAP, "soft cap must hold");
}
}