use core::net::{IpAddr, Ipv4Addr, Ipv6Addr};
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum AddrScope {
Global,
Private,
UniqueLocal,
Other,
LinkLocal,
Loopback,
Unspecified,
}
impl AddrScope {
pub fn of(ip: IpAddr) -> Self {
match ip {
IpAddr::V4(v4) => v4_scope(v4),
IpAddr::V6(v6) => match v6.to_ipv4_mapped() {
Some(v4) => {
if v4.is_loopback() {
Self::Loopback
} else if v4.is_unspecified() {
Self::Unspecified
} else if v4.is_link_local() {
Self::LinkLocal
} else if v4_is_global(v4) {
Self::Global
} else {
Self::Other
}
}
None => {
if v6.is_loopback() {
Self::Loopback
} else if v6.is_unspecified() {
Self::Unspecified
} else if in_v6(v6, 0xfe80 << 112, 10) {
Self::LinkLocal
} else if in_v6(v6, 0xfc00 << 112, 7) {
Self::UniqueLocal
} else if v6_is_private(v6) {
Self::Other
} else {
Self::Global
}
}
},
}
}
pub const fn preference(self) -> u8 {
match self {
Self::Global => 0,
Self::Private | Self::UniqueLocal => 1,
Self::Other => 2,
Self::LinkLocal => 3,
Self::Loopback => 4,
Self::Unspecified => 5,
}
}
pub const fn as_str(self) -> &'static str {
match self {
Self::Global => "global",
Self::Private => "private",
Self::UniqueLocal => "unique-local",
Self::Other => "other",
Self::LinkLocal => "link-local",
Self::Loopback => "loopback",
Self::Unspecified => "unspecified",
}
}
}
fn v4_scope(v4: Ipv4Addr) -> AddrScope {
if v4.is_loopback() {
AddrScope::Loopback
} else if v4.is_unspecified() {
AddrScope::Unspecified
} else if v4.is_link_local() {
AddrScope::LinkLocal
} else if v4_is_private(v4) {
AddrScope::Private
} else if v4_is_global(v4) {
AddrScope::Global
} else {
AddrScope::Other
}
}
fn in_v4(ip: Ipv4Addr, net: u32, prefix: u32) -> bool {
let shift = 32 - prefix;
prefix == 0 || (u32::from(ip) >> shift) == (net >> shift)
}
fn in_v6(ip: Ipv6Addr, net: u128, prefix: u32) -> bool {
let shift = 128 - prefix;
prefix == 0 || (u128::from(ip) >> shift) == (net >> shift)
}
const fn v4(a: u8, b: u8, c: u8, d: u8) -> u32 {
u32::from_be_bytes([a, b, c, d])
}
fn v4_is_private(ip: Ipv4Addr) -> bool {
const NETS: [(u32, u32); 14] = [
(v4(0, 0, 0, 0), 8),
(v4(10, 0, 0, 0), 8),
(v4(127, 0, 0, 0), 8),
(v4(169, 254, 0, 0), 16),
(v4(172, 16, 0, 0), 12),
(v4(192, 0, 0, 0), 24),
(v4(192, 0, 0, 170), 31),
(v4(192, 0, 2, 0), 24),
(v4(192, 168, 0, 0), 16),
(v4(198, 18, 0, 0), 15),
(v4(198, 51, 100, 0), 24),
(v4(203, 0, 113, 0), 24),
(v4(240, 0, 0, 0), 4),
(v4(255, 255, 255, 255), 32),
];
const EXCEPT: [(u32, u32); 2] = [(v4(192, 0, 0, 9), 32), (v4(192, 0, 0, 10), 32)];
NETS.iter().any(|&(n, p)| in_v4(ip, n, p)) && !EXCEPT.iter().any(|&(n, p)| in_v4(ip, n, p))
}
fn v4_is_global(ip: Ipv4Addr) -> bool {
!in_v4(ip, v4(100, 64, 0, 0), 10) && !v4_is_private(ip)
}
fn v6_is_private(ip: Ipv6Addr) -> bool {
const NETS: [(u128, u32); 11] = [
(1, 128),
(0, 128),
(0xffff_0000_0000, 96),
(0x0064_ff9b_0001 << 80, 48),
(0x0100 << 112, 64),
(0x2001 << 112, 23),
(0x2001_0db8 << 96, 32),
(0x2002 << 112, 16),
(0x3fff << 112, 20),
(0xfc00 << 112, 7),
(0xfe80 << 112, 10),
];
const EXCEPT: [(u128, u32); 6] = [
((0x2001_0001 << 96) | 1, 128),
((0x2001_0001 << 96) | 2, 128),
(0x2001_0003 << 96, 32),
(0x2001_0004_0112 << 80, 48),
(0x2001_0020 << 96, 28),
(0x2001_0030 << 96, 28),
];
NETS.iter().any(|&(n, p)| in_v6(ip, n, p)) && !EXCEPT.iter().any(|&(n, p)| in_v6(ip, n, p))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ScopedAddr {
pub ip: IpAddr,
pub ifindex: Option<u32>,
}
impl ScopedAddr {
pub const fn new(ip: IpAddr) -> Self {
Self { ip, ifindex: None }
}
pub fn scope(&self) -> AddrScope {
AddrScope::of(self.ip)
}
pub fn is_candidate(&self) -> bool {
match self.scope() {
AddrScope::Loopback | AddrScope::Unspecified => false,
AddrScope::LinkLocal => self.ip.is_ipv4() || self.ifindex.is_some(),
_ => true,
}
}
}
impl From<IpAddr> for ScopedAddr {
fn from(ip: IpAddr) -> Self {
Self::new(ip)
}
}
#[cfg(feature = "alloc")]
pub fn candidate_addresses(addrs: &[ScopedAddr]) -> Vec<ScopedAddr> {
let mut out: Vec<ScopedAddr> = addrs
.iter()
.copied()
.filter(ScopedAddr::is_candidate)
.collect();
out.sort_by_key(|a| (a.scope().preference(), *a));
out.dedup();
out
}
#[cfg(test)]
mod tests {
use super::*;
fn scope(s: &str) -> AddrScope {
AddrScope::of(s.parse().unwrap())
}
#[test]
fn scopes() {
assert_eq!(scope("8.8.8.8"), AddrScope::Global);
assert_eq!(scope("192.168.1.2"), AddrScope::Private);
assert_eq!(scope("10.0.0.1"), AddrScope::Private);
assert_eq!(scope("192.0.0.9"), AddrScope::Global);
assert_eq!(scope("100.64.0.1"), AddrScope::Other);
assert_eq!(scope("169.254.1.1"), AddrScope::LinkLocal);
assert_eq!(scope("127.0.0.1"), AddrScope::Loopback);
assert_eq!(scope("0.0.0.0"), AddrScope::Unspecified);
assert_eq!(scope("0.1.2.3"), AddrScope::Private);
assert_eq!(scope("2606:4700::1"), AddrScope::Global);
assert_eq!(scope("fd00::1"), AddrScope::UniqueLocal);
assert_eq!(scope("fe80::1"), AddrScope::LinkLocal);
assert_eq!(scope("::1"), AddrScope::Loopback);
assert_eq!(scope("::"), AddrScope::Unspecified);
assert_eq!(scope("2001:db8::1"), AddrScope::Other);
assert_eq!(scope("2001:20::1"), AddrScope::Global);
assert_eq!(scope("::ffff:10.0.0.1"), AddrScope::Other);
assert_eq!(scope("::ffff:8.8.8.8"), AddrScope::Global);
assert_eq!(scope("::ffff:127.0.0.1"), AddrScope::Loopback);
}
#[cfg(feature = "alloc")]
#[test]
fn candidates() {
let a = |s: &str, i: Option<u32>| ScopedAddr {
ip: s.parse().unwrap(),
ifindex: i,
};
let got = candidate_addresses(&[
a("fe80::1", None),
a("fe80::2", Some(3)),
a("169.254.0.5", None),
a("127.0.0.1", None),
a("fd00::1", None),
a("192.168.1.9", None),
a("192.168.1.2", None),
a("8.8.8.8", None),
a("100.64.0.1", None),
a("192.168.1.2", None),
]);
assert_eq!(
got,
[
a("8.8.8.8", None),
a("192.168.1.2", None),
a("192.168.1.9", None),
a("fd00::1", None),
a("100.64.0.1", None),
a("169.254.0.5", None),
a("fe80::2", Some(3)),
]
);
}
}