use core::net::{IpAddr, Ipv4Addr, Ipv6Addr};
#[inline]
#[must_use]
pub fn is_private_ip(addr: impl Into<IpAddr>) -> bool {
match addr.into() {
IpAddr::V4(addr) => is_private_ipv4(addr),
IpAddr::V6(addr) => is_private_ipv6(addr),
}
}
#[inline]
#[must_use]
pub fn is_private_ipv4(addr: Ipv4Addr) -> bool {
let val = u32::from(addr);
addr.is_loopback() || addr.is_private() || addr.is_link_local() || addr.is_multicast() || addr.is_broadcast() || addr.is_documentation()
|| is_ipv4_this_network(val)
|| is_ipv4_shared(val)
|| is_ipv4_protocol_assignments(val)
|| is_ipv4_benchmarking(val)
|| is_ipv4_reserved(val)
}
#[inline]
#[must_use]
pub fn is_private_ipv6(addr: Ipv6Addr) -> bool {
let val = u128::from(addr);
addr.is_loopback()
|| addr.is_multicast()
|| addr.is_unicast_link_local()
|| addr.is_unique_local()
|| addr.is_unspecified()
|| is_ipv6_site_local(val)
|| is_ipv6_documentation(val)
|| is_ipv6_benchmarking(val)
|| is_ipv6_discard_only(val)
|| is_ipv6_dummy_prefix(val)
|| is_ipv6_local_use_translation(val)
}
#[inline(always)]
pub(super) fn is_ipv4_this_network(val: u32) -> bool {
val & 0xFF00_0000 == 0x0000_0000
}
#[inline(always)]
pub(super) fn is_ipv4_shared(val: u32) -> bool {
val & 0xFFC0_0000 == 0x6440_0000
}
#[inline(always)]
pub(super) fn is_ipv4_protocol_assignments(val: u32) -> bool {
if val & 0xFFFF_FF00 == 0xC000_0000 {
let d = (val & 0xFF) as u8;
return (0..=8).contains(&d) || d == 170 || d == 171;
}
false
}
#[inline(always)]
pub(super) fn is_ipv4_benchmarking(val: u32) -> bool {
val & 0xFFFE_0000 == 0xC612_0000
}
#[inline(always)]
pub(super) fn is_ipv4_reserved(val: u32) -> bool {
val & 0xF000_0000 == 0xF000_0000
}
#[inline(always)]
pub(super) fn is_ipv6_site_local(val: u128) -> bool {
(val >> 118) == 0x03fb
}
#[inline(always)]
pub(super) fn is_ipv6_documentation(val: u128) -> bool {
(val >> 96 == 0x2001_0db8) || (val >> 108 == 0x3fff0)
}
#[inline(always)]
pub(super) fn is_ipv6_benchmarking(val: u128) -> bool {
(val >> 80) == 0x2001_0002_0000
}
#[inline(always)]
pub(super) fn is_ipv6_discard_only(val: u128) -> bool {
(val >> 64) == 0x0100_0000_0000_0000
}
#[inline(always)]
pub(super) fn is_ipv6_dummy_prefix(val: u128) -> bool {
(val >> 64) == 0x0100_0000_0000_0001
}
#[inline(always)]
pub(super) fn is_ipv6_local_use_translation(val: u128) -> bool {
(val >> 80) == 0x0064_ff9b_0001
}
#[cfg(test)]
mod tests {
use core::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use super::{is_private_ip, is_private_ipv4, is_private_ipv6};
#[test]
fn passthrough_ipv4_includes_non_public_ranges() {
let cases = [
(Ipv4Addr::new(127, 0, 0, 1), true),
(Ipv4Addr::new(10, 0, 0, 1), true),
(Ipv4Addr::new(172, 16, 0, 1), true),
(Ipv4Addr::new(192, 168, 1, 1), true),
(Ipv4Addr::new(0, 1, 2, 3), true),
(Ipv4Addr::new(100, 64, 0, 1), true),
(Ipv4Addr::new(100, 100, 12, 34), true),
(Ipv4Addr::new(100, 127, 255, 254), true),
(Ipv4Addr::new(192, 0, 0, 3), true),
(Ipv4Addr::new(192, 0, 0, 8), true),
(Ipv4Addr::new(192, 0, 0, 170), true),
(Ipv4Addr::new(192, 0, 0, 171), true),
(Ipv4Addr::new(198, 18, 0, 1), true),
(Ipv4Addr::new(198, 19, 255, 254), true),
(Ipv4Addr::new(224, 0, 0, 1), true),
(Ipv4Addr::new(250, 10, 20, 30), true),
(Ipv4Addr::new(100, 63, 255, 255), false),
(Ipv4Addr::new(100, 128, 0, 1), false),
(Ipv4Addr::new(192, 0, 0, 9), false),
(Ipv4Addr::new(192, 0, 0, 10), false),
(Ipv4Addr::new(198, 17, 255, 255), false),
(Ipv4Addr::new(198, 20, 0, 0), false),
(Ipv4Addr::new(8, 8, 8, 8), false),
(Ipv4Addr::new(1, 1, 1, 1), false),
(Ipv4Addr::new(192, 88, 99, 1), false), (Ipv4Addr::new(192, 31, 196, 1), false), (Ipv4Addr::new(198, 51, 100, 1), true), (Ipv4Addr::new(192, 52, 193, 1), false), ];
for (addr, expected) in cases {
assert_eq!(is_private_ipv4(addr), expected, "addr: {addr}");
}
}
#[test]
fn passthrough_ipv4_boundary_cases_are_correct() {
let cases = [
(Ipv4Addr::new(0, 0, 0, 0), true),
(Ipv4Addr::new(0, 255, 255, 255), true),
(Ipv4Addr::new(100, 64, 0, 0), true),
(Ipv4Addr::new(100, 127, 255, 255), true),
(Ipv4Addr::new(100, 63, 255, 255), false),
(Ipv4Addr::new(100, 128, 0, 0), false),
(Ipv4Addr::new(192, 0, 0, 0), true),
(Ipv4Addr::new(192, 0, 0, 8), true),
(Ipv4Addr::new(192, 0, 0, 9), false),
(Ipv4Addr::new(192, 0, 0, 10), false),
(Ipv4Addr::new(192, 0, 0, 11), false),
(Ipv4Addr::new(192, 0, 0, 170), true),
(Ipv4Addr::new(192, 0, 0, 171), true),
(Ipv4Addr::new(192, 0, 0, 172), false),
(Ipv4Addr::new(198, 18, 0, 0), true),
(Ipv4Addr::new(198, 19, 255, 255), true),
(Ipv4Addr::new(198, 17, 255, 255), false),
(Ipv4Addr::new(198, 20, 0, 0), false),
(Ipv4Addr::new(223, 255, 255, 255), false),
(Ipv4Addr::new(224, 0, 0, 0), true),
(Ipv4Addr::new(239, 255, 255, 255), true),
(Ipv4Addr::new(240, 0, 0, 0), true),
(Ipv4Addr::new(255, 255, 255, 254), true),
(Ipv4Addr::new(255, 255, 255, 255), true),
(Ipv4Addr::new(172, 15, 255, 255), false),
(Ipv4Addr::new(172, 16, 0, 0), true),
(Ipv4Addr::new(172, 31, 255, 255), true),
(Ipv4Addr::new(172, 32, 0, 0), false),
];
for (addr, expected) in cases {
assert_eq!(is_private_ipv4(addr), expected, "addr: {addr}");
}
}
#[test]
fn passthrough_ipv6_includes_non_public_ranges() {
let cases = [
(Ipv6Addr::LOCALHOST, true),
(Ipv6Addr::UNSPECIFIED, true),
("fc00::1".parse().unwrap(), true),
("fe80::1".parse().unwrap(), true),
("ff02::1".parse().unwrap(), true),
("fec0::1".parse().unwrap(), true),
("2001:db8::1".parse().unwrap(), true),
("3fff::1".parse().unwrap(), true),
("2001:2::1".parse().unwrap(), true),
("100::1".parse().unwrap(), true),
("100:0:0:1::1".parse().unwrap(), true),
("64:ff9b:1::1".parse().unwrap(), true),
("64:ff9b::1".parse().unwrap(), false), ("64:ff9b::808:808".parse().unwrap(), false),
("2001:4860:4860::8888".parse().unwrap(), false),
("2606:4700:4700::1111".parse().unwrap(), false),
("2001:0::1".parse().unwrap(), false), ("2001:20::1".parse().unwrap(), false), ];
for (addr, expected) in cases {
assert_eq!(is_private_ipv6(addr), expected, "addr: {addr}");
}
}
#[test]
fn passthrough_ipv6_boundary_cases_are_correct() {
let cases = [
("febf:ffff:ffff:ffff::1".parse().unwrap(), true),
("fec0::".parse().unwrap(), true),
("feff:ffff:ffff:ffff::1".parse().unwrap(), true),
("2001:db7:ffff::1".parse().unwrap(), false),
("2001:db8::".parse().unwrap(), true),
("2001:db8:ffff:ffff::1".parse().unwrap(), true),
("2001:db9::1".parse().unwrap(), false),
("3ffe:ffff::1".parse().unwrap(), false),
("3fff::".parse().unwrap(), true),
("3fff:0fff:ffff:ffff::1".parse().unwrap(), true),
("3fff:1000::1".parse().unwrap(), false),
("4000::1".parse().unwrap(), false),
("2001:2::".parse().unwrap(), true),
("2001:2:0:1::1".parse().unwrap(), true),
("2001:2:1::1".parse().unwrap(), false),
("100::".parse().unwrap(), true),
("100::ffff".parse().unwrap(), true),
("100:0:0:1::".parse().unwrap(), true),
("100:0:0:2::1".parse().unwrap(), false),
("64:ff9b:1::".parse().unwrap(), true),
("64:ff9b:1:ffff::1".parse().unwrap(), true),
("64:ff9b:2::1".parse().unwrap(), false),
];
for (addr, expected) in cases {
assert_eq!(is_private_ipv6(addr), expected, "addr: {addr}");
}
}
#[test]
fn passthrough_ip_dispatches_to_both_versions() {
let cases = [
(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)), true),
(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)), false),
(IpAddr::V6("fc00::1".parse().unwrap()), true),
(IpAddr::V6("2001:4860:4860::8888".parse().unwrap()), false),
];
for (addr, expected) in cases {
assert_eq!(is_private_ip(addr), expected, "addr: {addr}");
}
}
#[test]
fn defensive_anycast_and_transition_checks() {
let cases = [
(IpAddr::V4(Ipv4Addr::new(192, 88, 99, 1)), false), (IpAddr::V4(Ipv4Addr::new(192, 31, 196, 1)), false), (IpAddr::V4(Ipv4Addr::new(192, 52, 193, 1)), false), (IpAddr::V6("2001:0::1".parse().unwrap()), false), (IpAddr::V6("2001:1::1".parse().unwrap()), false), (IpAddr::V6("2620:4f:8000::1".parse().unwrap()), false), (IpAddr::V6("64:ff9b::1".parse().unwrap()), false), ];
for (addr, expected) in cases {
assert_eq!(is_private_ip(addr), expected, "addr: {addr}");
}
}
#[test]
fn test_trait_dispatch_consistency() {
assert!(is_private_ip([127, 0, 0, 1]));
assert!(!is_private_ip([8, 8, 8, 8]));
}
}