use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
#[inline]
#[must_use]
pub fn is_private(ip: IpAddr) -> bool {
match ip {
IpAddr::V4(ip) => is_private_v4(ip),
IpAddr::V6(ip) => is_private_v6(ip),
}
}
#[inline]
fn is_private_v4(ip: Ipv4Addr) -> bool {
ip.is_private()
|| ip.is_loopback()
|| ip.is_link_local()
|| ip.is_broadcast()
|| ip.is_multicast()
|| ip.is_unspecified()
|| ip.is_documentation()
|| is_shared_v4(ip)
|| is_reserved_v4(ip)
}
#[inline]
fn is_shared_v4(ip: Ipv4Addr) -> bool {
let octets = ip.octets();
octets[0] == 100 && (octets[1] & 0b1100_0000) == 0b0100_0000
}
#[inline]
fn is_reserved_v4(ip: Ipv4Addr) -> bool {
(ip.octets()[0] & 0b1111_0000) == 0b1111_0000
}
#[inline]
fn is_private_v6(ip: Ipv6Addr) -> bool {
if let Some(ip) = ip.to_ipv4_mapped() {
return is_private_v4(ip);
}
ip.is_loopback()
|| ip.is_unspecified()
|| ip.is_multicast()
|| ip.is_unique_local()
|| ip.is_unicast_link_local()
|| is_documentation_v6(ip)
}
#[inline]
fn is_documentation_v6(ip: Ipv6Addr) -> bool {
let segments = ip.segments();
segments[0] == 0x2001 && segments[1] == 0x0db8
}
#[cfg(test)]
mod tests {
use super::*;
fn ip(literal: &str) -> IpAddr {
literal.parse().unwrap()
}
#[test]
fn rfc1918_space_is_private() {
assert!(is_private(ip("10.0.0.1")));
assert!(is_private(ip("172.16.5.4")));
assert!(is_private(ip("172.31.255.255")));
assert!(is_private(ip("192.168.1.1")));
}
#[test]
fn loopback_link_local_and_unspecified_are_private() {
assert!(is_private(ip("127.0.0.1")));
assert!(is_private(ip("169.254.10.20")));
assert!(is_private(ip("0.0.0.0")));
assert!(is_private(ip("::1")));
assert!(is_private(ip("::")));
assert!(is_private(ip("fe80::1")));
}
#[test]
fn carrier_grade_nat_space_is_private() {
assert!(is_private(ip("100.64.0.1")));
assert!(is_private(ip("100.100.50.1")));
assert!(is_private(ip("100.127.255.255")));
assert!(!is_private(ip("100.63.255.255")));
assert!(!is_private(ip("100.128.0.0")));
}
#[test]
fn reserved_space_is_private() {
assert!(is_private(ip("240.0.0.1")));
assert!(is_private(ip("255.255.255.254")));
assert!(is_private(ip("255.255.255.255")));
assert!(!is_private(ip("223.255.255.255")));
}
#[test]
fn documentation_space_is_private() {
assert!(is_private(ip("192.0.2.1")));
assert!(is_private(ip("198.51.100.1")));
assert!(is_private(ip("203.0.113.1")));
}
#[test]
fn multicast_is_private_in_both_families() {
assert!(is_private(ip("224.0.0.1")));
assert!(is_private(ip("239.1.2.3")));
assert!(is_private(ip("ff02::1")));
}
#[test]
fn unique_local_ipv6_is_private() {
assert!(is_private(ip("fc00::1")));
assert!(is_private(ip("fd12:3456:789a::1")));
}
#[test]
fn documentation_space_is_private_in_both_families() {
assert!(is_private(ip("192.0.2.1")));
assert!(is_private(ip("2001:db8::1")));
assert!(is_private(ip("2001:db8:ffff:ffff::1")));
assert!(!is_private(ip("2001:db9::1")));
assert!(!is_private(ip("2001:db7::1")));
}
#[test]
fn an_ipv4_mapped_address_answers_by_its_ipv4_rules() {
assert!(is_private(ip("::ffff:127.0.0.1")));
assert!(is_private(ip("::ffff:10.0.0.1")));
assert!(!is_private(ip("::ffff:8.8.8.8")));
}
#[test]
fn routable_space_is_not_private() {
assert!(!is_private(ip("8.8.8.8")));
assert!(!is_private(ip("1.0.0.1")));
assert!(!is_private(ip("2.125.160.216")));
assert!(!is_private(ip("89.160.20.112")));
assert!(!is_private(ip("172.15.0.1")));
assert!(!is_private(ip("172.32.0.1")));
assert!(!is_private(ip("2606:4700:4700::1111")));
}
}