use std::net::IpAddr;
use std::net::Ipv4Addr;
use std::net::Ipv6Addr;
use std::sync::LazyLock;
use ipnet::Ipv4Net;
use ipnet::Ipv6Net;
static IPV4_BLOCKED: LazyLock<Vec<Ipv4Net>> = LazyLock::new(|| {
[
"0.0.0.0/8",
"10.0.0.0/8",
"100.64.0.0/10",
"127.0.0.0/8",
"169.254.0.0/16",
"172.16.0.0/12",
"192.0.0.0/24",
"192.0.2.0/24",
"192.168.0.0/16",
"198.18.0.0/15",
"198.51.100.0/24",
"203.0.113.0/24",
"224.0.0.0/4",
"240.0.0.0/4",
]
.into_iter()
.filter_map(|net| net.parse().ok())
.collect()
});
static IPV6_BLOCKED: LazyLock<Vec<Ipv6Net>> = LazyLock::new(|| {
[
"::/128",
"::1/128",
"fc00::/7",
"fe80::/10",
"fec0::/10",
"ff00::/8",
"2001:db8::/32",
"3fff::/20",
]
.into_iter()
.filter_map(|net| net.parse().ok())
.collect()
});
#[must_use]
pub fn is_private_ip(ip: IpAddr) -> bool {
match ip {
IpAddr::V4(v4) => is_private_v4(v4),
IpAddr::V6(v6) => {
if IPV6_BLOCKED.iter().any(|net| net.contains(&v6)) {
return true;
}
embedded_v4(v6).is_some_and(is_private_v4)
}
}
}
fn is_private_v4(ip: Ipv4Addr) -> bool {
IPV4_BLOCKED.iter().any(|net| net.contains(&ip))
}
fn embedded_v4(ip: Ipv6Addr) -> Option<Ipv4Addr> {
let segments = ip.segments();
let low = |index: usize| -> Ipv4Addr {
let [a, b] = segments[index].to_be_bytes();
let [c, d] = segments[index + 1].to_be_bytes();
Ipv4Addr::new(a, b, c, d)
};
match segments {
[0, 0, 0, 0, 0, 0xffff, _, _] => Some(low(6)),
[0, 0, 0, 0, 0, 0, hi, lo] if hi != 0 || lo > 1 => Some(low(6)),
[0x64, 0xff9b, 0, 0, 0, 0, _, _] | [0x64, 0xff9b, 1, _, _, _, _, _] => Some(low(6)),
[0x2002, _, _, _, _, _, _, _] => Some(low(1)),
_ => None,
}
}
#[must_use]
pub fn is_private_hostname(host: &str) -> bool {
let host = host.trim_end_matches('.').to_ascii_lowercase();
let host = host
.strip_prefix('[')
.and_then(|rest| rest.strip_suffix(']'))
.unwrap_or(&host);
if host == "localhost" || host.ends_with(".localhost") || host.ends_with(".local") {
return true;
}
if let Ok(ip) = host.parse::<IpAddr>() {
return is_private_ip(ip);
}
false
}