use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket};
#[must_use]
pub fn is_publishable(ip: IpAddr) -> bool {
match ip {
IpAddr::V4(v4) => is_publishable_v4(v4),
IpAddr::V6(v6) => match v6.to_ipv4_mapped() {
Some(v4) => is_publishable_v4(v4),
None => is_publishable_v6(v6),
},
}
}
const fn is_publishable_v4(ip: Ipv4Addr) -> bool {
let [a, b, ..] = ip.octets();
!(ip.is_unspecified()
|| ip.is_loopback()
|| ip.is_private()
|| ip.is_link_local()
|| ip.is_broadcast()
|| ip.is_documentation()
|| ip.is_multicast()
|| (a == 100 && (b & 0xC0) == 64)
|| (a == 198 && (b & 0xFE) == 18)
|| a == 0
|| a >= 240)
}
fn is_publishable_v6(ip: Ipv6Addr) -> bool {
let first = ip.segments().first().copied().unwrap_or(0);
!(ip.is_unspecified()
|| ip.is_loopback()
|| ip.is_multicast()
|| (first & 0xFE00) == 0xFC00
|| (first & 0xFFC0) == 0xFE80
|| (first == 0x2001 && ip.segments().get(1).copied() == Some(0x0DB8)))
}
#[must_use]
pub fn route_public_ips() -> Vec<IpAddr> {
let probes: [(SocketAddr, SocketAddr); 2] = [
(
SocketAddr::from((Ipv4Addr::UNSPECIFIED, 0)),
SocketAddr::from((Ipv4Addr::new(192, 0, 2, 1), 9)),
),
(
SocketAddr::from((Ipv6Addr::UNSPECIFIED, 0)),
SocketAddr::from((Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 1), 9)),
),
];
probes
.into_iter()
.filter_map(|(bind, target)| route_source(bind, target))
.filter(|ip| is_publishable(*ip))
.collect()
}
fn route_source(bind: SocketAddr, target: SocketAddr) -> Option<IpAddr> {
let socket = UdpSocket::bind(bind).ok()?;
socket.connect(target).ok()?;
socket.local_addr().ok().map(|a| a.ip())
}
#[must_use]
pub fn quic_multiaddr(ip: IpAddr, port: u16) -> String {
match ip {
IpAddr::V4(v4) => format!("/ip4/{v4}/udp/{port}/quic-v1"),
IpAddr::V6(v6) => format!("/ip6/{v6}/udp/{port}/quic-v1"),
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests;