use core::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use crate::std::vec::Vec;
use super::ipnet::{IpNet, Ipv4Net, Ipv6Net};
use super::private::{
is_ipv4_benchmarking, is_ipv4_protocol_assignments, is_ipv4_reserved, is_ipv4_shared,
is_ipv4_this_network, is_ipv6_benchmarking, is_ipv6_discard_only, is_ipv6_documentation,
is_ipv6_dummy_prefix, is_ipv6_local_use_translation, is_ipv6_site_local,
};
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct IpScopes: u16 {
const LOOPBACK = 1 << 0;
const PRIVATE = 1 << 1;
const LINK_LOCAL = 1 << 2;
const SHARED = 1 << 3;
const UNSPECIFIED = 1 << 4;
const MULTICAST = 1 << 5;
const DOCUMENTATION = 1 << 6;
const BENCHMARKING = 1 << 7;
const RESERVED = 1 << 8;
const GLOBAL = 1 << 15;
}
}
impl IpScopes {
pub const NON_GLOBAL: Self = Self::all().difference(Self::GLOBAL);
pub const LOCAL: Self = Self::LOOPBACK
.union(Self::PRIVATE)
.union(Self::LINK_LOCAL)
.union(Self::SHARED);
}
#[inline]
#[must_use]
pub fn ip_scope(addr: impl Into<IpAddr>) -> IpScopes {
match addr.into() {
IpAddr::V4(addr) => ipv4_scope(addr),
IpAddr::V6(addr) => ipv6_scope(addr),
}
}
#[must_use]
pub fn ipv4_scope(addr: Ipv4Addr) -> IpScopes {
let val = u32::from(addr);
if addr.is_loopback() {
IpScopes::LOOPBACK
} else if addr.is_private() {
IpScopes::PRIVATE
} else if is_ipv4_shared(val) {
IpScopes::SHARED
} else if addr.is_link_local() {
IpScopes::LINK_LOCAL
} else if is_ipv4_this_network(val) {
IpScopes::UNSPECIFIED
} else if is_ipv4_benchmarking(val) {
IpScopes::BENCHMARKING
} else if addr.is_documentation() {
IpScopes::DOCUMENTATION
} else if addr.is_multicast() {
IpScopes::MULTICAST
} else if is_ipv4_protocol_assignments(val) || addr.is_broadcast() || is_ipv4_reserved(val) {
IpScopes::RESERVED
} else {
IpScopes::GLOBAL
}
}
#[must_use]
pub fn ipv6_scope(addr: Ipv6Addr) -> IpScopes {
let val = u128::from(addr);
if addr.is_loopback() {
IpScopes::LOOPBACK
} else if addr.is_unspecified() {
IpScopes::UNSPECIFIED
} else if addr.is_multicast() {
IpScopes::MULTICAST
} else if addr.is_unicast_link_local() {
IpScopes::LINK_LOCAL
} else if addr.is_unique_local() {
IpScopes::PRIVATE
} else if is_ipv6_benchmarking(val) {
IpScopes::BENCHMARKING
} else if is_ipv6_documentation(val) {
IpScopes::DOCUMENTATION
} else if is_ipv6_site_local(val)
|| is_ipv6_discard_only(val)
|| is_ipv6_dummy_prefix(val)
|| is_ipv6_local_use_translation(val)
{
IpScopes::RESERVED
} else {
IpScopes::GLOBAL
}
}
#[must_use]
pub fn scope_cidrs(scopes: IpScopes) -> Vec<IpNet> {
fn v4(a: [u8; 4], prefix: u8) -> IpNet {
IpNet::V4(Ipv4Net::new_assert(
Ipv4Addr::new(a[0], a[1], a[2], a[3]),
prefix,
))
}
fn v6(addr: Ipv6Addr, prefix: u8) -> IpNet {
IpNet::V6(Ipv6Net::new_assert(addr, prefix))
}
let mut out = Vec::new();
if scopes.contains(IpScopes::LOOPBACK) {
out.push(v4([127, 0, 0, 0], 8));
out.push(v6(Ipv6Addr::LOCALHOST, 128));
}
if scopes.contains(IpScopes::PRIVATE) {
out.push(v4([10, 0, 0, 0], 8));
out.push(v4([172, 16, 0, 0], 12));
out.push(v4([192, 168, 0, 0], 16));
out.push(v6(Ipv6Addr::new(0xfc00, 0, 0, 0, 0, 0, 0, 0), 7)); }
if scopes.contains(IpScopes::LINK_LOCAL) {
out.push(v4([169, 254, 0, 0], 16));
out.push(v6(Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0), 10)); }
if scopes.contains(IpScopes::SHARED) {
out.push(v4([100, 64, 0, 0], 10));
}
if scopes.contains(IpScopes::UNSPECIFIED) {
out.push(v4([0, 0, 0, 0], 8));
out.push(v6(Ipv6Addr::UNSPECIFIED, 128));
}
if scopes.contains(IpScopes::MULTICAST) {
out.push(v4([224, 0, 0, 0], 4));
out.push(v6(Ipv6Addr::new(0xff00, 0, 0, 0, 0, 0, 0, 0), 8)); }
if scopes.contains(IpScopes::DOCUMENTATION) {
out.push(v4([192, 0, 2, 0], 24));
out.push(v4([198, 51, 100, 0], 24));
out.push(v4([203, 0, 113, 0], 24));
out.push(v6(Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0), 32)); }
if scopes.contains(IpScopes::BENCHMARKING) {
out.push(v4([198, 18, 0, 0], 15));
out.push(v6(Ipv6Addr::new(0x2001, 0x2, 0, 0, 0, 0, 0, 0), 48)); }
if scopes.contains(IpScopes::RESERVED) {
out.push(v4([240, 0, 0, 0], 4));
}
out
}
#[cfg(test)]
mod tests {
use super::super::private::is_private_ip;
use super::*;
#[test]
fn scope_non_global_matches_is_private_ip() {
let v4: &[[u8; 4]] = &[
[127, 0, 0, 1],
[10, 0, 0, 1],
[172, 16, 0, 1],
[192, 168, 1, 1],
[0, 1, 2, 3],
[100, 64, 0, 1],
[100, 63, 255, 255],
[169, 254, 1, 1],
[192, 0, 0, 8],
[192, 0, 0, 9],
[192, 0, 2, 1],
[198, 18, 0, 1],
[198, 51, 100, 1],
[224, 0, 0, 1],
[240, 0, 0, 1],
[255, 255, 255, 255],
[8, 8, 8, 8],
[1, 1, 1, 1],
[192, 88, 99, 1],
];
for a in v4 {
let ip = IpAddr::from(*a);
assert_eq!(
ip_scope(ip).contains(IpScopes::GLOBAL),
!is_private_ip(ip),
"v4 {ip}: scope={:?}",
ip_scope(ip)
);
assert_eq!(
ip_scope(ip).bits().count_ones(),
1,
"v4 {ip} not single-bit"
);
}
let v6: &[&str] = &[
"::1",
"::",
"fc00::1",
"fe80::1",
"ff02::1",
"fec0::1",
"2001:db8::1",
"2001:2::1",
"100::1",
"64:ff9b:1::1",
"64:ff9b::1",
"2001:4860:4860::8888",
"2606:4700:4700::1111",
];
for s in v6 {
let ip: IpAddr = s.parse().unwrap();
assert_eq!(
ip_scope(ip).contains(IpScopes::GLOBAL),
!is_private_ip(ip),
"v6 {ip}: scope={:?}",
ip_scope(ip)
);
assert_eq!(
ip_scope(ip).bits().count_ones(),
1,
"v6 {ip} not single-bit"
);
}
}
#[test]
fn scope_assigns_expected_categories() {
assert_eq!(ip_scope([127, 0, 0, 1]), IpScopes::LOOPBACK);
assert_eq!(ip_scope([10, 1, 2, 3]), IpScopes::PRIVATE);
assert_eq!(ip_scope([100, 64, 0, 1]), IpScopes::SHARED);
assert_eq!(ip_scope([169, 254, 0, 1]), IpScopes::LINK_LOCAL);
assert_eq!(ip_scope([224, 0, 0, 1]), IpScopes::MULTICAST);
assert_eq!(ip_scope([8, 8, 8, 8]), IpScopes::GLOBAL);
assert_eq!(
ip_scope("::1".parse::<IpAddr>().unwrap()),
IpScopes::LOOPBACK
);
assert_eq!(
ip_scope("fc00::1".parse::<IpAddr>().unwrap()),
IpScopes::PRIVATE
);
assert_eq!(
ip_scope("fe80::1".parse::<IpAddr>().unwrap()),
IpScopes::LINK_LOCAL
);
}
#[test]
fn masks_compose_as_expected() {
assert!(ip_scope([10, 0, 0, 1]).contains(IpScopes::PRIVATE));
assert!(!ip_scope([10, 0, 0, 1]).contains(IpScopes::LOOPBACK));
assert!(IpScopes::LOCAL.contains(ip_scope([127, 0, 0, 1])));
assert!(IpScopes::LOCAL.contains(ip_scope([100, 64, 0, 1])));
assert!(!IpScopes::LOCAL.intersects(ip_scope([224, 0, 0, 1])));
assert!(!IpScopes::LOCAL.intersects(ip_scope([8, 8, 8, 8])));
assert!(IpScopes::NON_GLOBAL.intersects(ip_scope([10, 0, 0, 1])));
assert!(!IpScopes::NON_GLOBAL.intersects(ip_scope([8, 8, 8, 8])));
}
#[test]
fn scope_cidrs_cover_their_members() {
let nets = scope_cidrs(IpScopes::LOCAL);
for net in &nets {
assert!(
IpScopes::LOCAL.contains(ip_scope(net.network())),
"cidr {net} not LOCAL"
);
}
for ip in [
IpAddr::from([10, 1, 2, 3]),
IpAddr::from([127, 0, 0, 5]),
IpAddr::from([169, 254, 9, 9]),
IpAddr::from([100, 100, 0, 1]),
] {
assert!(
nets.iter().any(|n| n.contains(&ip)),
"no cidr contains {ip}"
);
}
assert!(scope_cidrs(IpScopes::GLOBAL).is_empty());
}
}