#![no_std]
#![warn(missing_docs)]
#![forbid(unsafe_code)]
use core::net::IpAddr;
#[must_use]
pub fn ip_addr_is_bogon(ip: IpAddr) -> bool {
match ip {
IpAddr::V4(v) => {
if v.is_private()
|| v.is_loopback()
|| v.is_link_local()
|| v.is_broadcast()
|| v.is_documentation()
|| v.is_unspecified()
{
return true;
}
let octets = v.octets();
if octets[0] == 100 && (octets[1] & 0xc0) == 0x40 {
return true; }
if octets[0] == 192 && octets[1] == 0 && octets[2] == 0 {
return true; }
if octets[0] == 198 && (octets[1] & 0xfe) == 18 {
return true; }
if octets[0] == 169 && octets[1] == 254 {
return true;
}
false
}
IpAddr::V6(v) => {
if v.is_loopback() || v.is_unspecified() {
return true;
}
if let Some(mapped) = v.to_ipv4_mapped() {
return ip_addr_is_bogon(IpAddr::V4(mapped));
}
if let Some(compat) = v.to_ipv4() {
return ip_addr_is_bogon(IpAddr::V4(compat));
}
let segs = v.segments();
if segs[0] == 0x2002 {
let v4 = core::net::Ipv4Addr::new(
(segs[1] >> 8) as u8,
(segs[1] & 0xff) as u8,
(segs[2] >> 8) as u8,
(segs[2] & 0xff) as u8,
);
if ip_addr_is_bogon(IpAddr::V4(v4)) {
return true;
}
}
if segs[0] == 0x2001 && segs[1] == 0x0db8 {
return true; }
if segs[0] == 0x2001 && segs[1] == 0x0000 {
return true; }
if segs[0] == 0x2001 && (segs[1] & 0xfff0) == 0x0020 {
return true; }
if segs[0] == 0x0100 && segs[1] == 0 && segs[2] == 0 && segs[3] == 0 {
return true; }
v.is_loopback()
|| v.is_multicast()
|| v.is_unspecified()
|| v.is_unique_local()
|| v.is_unicast_link_local()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::net::{Ipv4Addr, Ipv6Addr};
fn v4(a: u8, b: u8, c: u8, d: u8) -> IpAddr {
IpAddr::V4(Ipv4Addr::new(a, b, c, d))
}
#[allow(clippy::too_many_arguments)]
fn v6(s0: u16, s1: u16, s2: u16, s3: u16, s4: u16, s5: u16, s6: u16, s7: u16) -> IpAddr {
IpAddr::V6(Ipv6Addr::new(s0, s1, s2, s3, s4, s5, s6, s7))
}
#[test]
fn rejects_rfc1918_10_8() {
assert!(ip_addr_is_bogon(v4(10, 0, 0, 1)));
assert!(ip_addr_is_bogon(v4(10, 255, 255, 254)));
}
#[test]
fn rejects_rfc1918_172_16_12() {
assert!(ip_addr_is_bogon(v4(172, 16, 0, 1)));
assert!(ip_addr_is_bogon(v4(172, 31, 255, 254)));
assert!(!ip_addr_is_bogon(v4(172, 32, 0, 1)));
}
#[test]
fn rejects_rfc1918_192_168_16() {
assert!(ip_addr_is_bogon(v4(192, 168, 1, 1)));
}
#[test]
fn rejects_loopback() {
assert!(ip_addr_is_bogon(IpAddr::V4(Ipv4Addr::LOCALHOST)));
assert!(ip_addr_is_bogon(v4(127, 1, 2, 3)));
}
#[test]
fn rejects_link_local() {
assert!(ip_addr_is_bogon(v4(169, 254, 0, 1)));
}
#[test]
fn rejects_imds_metadata_169_254_169_254() {
assert!(ip_addr_is_bogon(v4(169, 254, 169, 254)));
}
#[test]
fn rejects_unspecified_and_broadcast() {
assert!(ip_addr_is_bogon(IpAddr::V4(Ipv4Addr::UNSPECIFIED)));
assert!(ip_addr_is_bogon(IpAddr::V4(Ipv4Addr::BROADCAST)));
}
#[test]
fn rejects_documentation_and_test_net() {
assert!(ip_addr_is_bogon(v4(192, 0, 2, 1)));
assert!(ip_addr_is_bogon(v4(198, 51, 100, 1)));
assert!(ip_addr_is_bogon(v4(203, 0, 113, 1)));
}
#[test]
fn rejects_cgn_100_64_10() {
assert!(ip_addr_is_bogon(v4(100, 64, 0, 1)));
assert!(ip_addr_is_bogon(v4(100, 127, 255, 254)));
assert!(!ip_addr_is_bogon(v4(100, 128, 0, 1)));
}
#[test]
fn rejects_ietf_protocol_assignment_192_0_0_24() {
assert!(ip_addr_is_bogon(v4(192, 0, 0, 1)));
}
#[test]
fn rejects_benchmark_198_18_15() {
assert!(ip_addr_is_bogon(v4(198, 18, 0, 1)));
assert!(ip_addr_is_bogon(v4(198, 19, 0, 1)));
assert!(!ip_addr_is_bogon(v4(198, 20, 0, 1)));
}
#[test]
fn allows_public_ipv4_addresses() {
assert!(!ip_addr_is_bogon(v4(8, 8, 8, 8)));
assert!(!ip_addr_is_bogon(v4(1, 1, 1, 1)));
assert!(!ip_addr_is_bogon(v4(208, 67, 222, 222)));
}
#[test]
fn rejects_ipv6_loopback() {
assert!(ip_addr_is_bogon(IpAddr::V6(Ipv6Addr::LOCALHOST)));
}
#[test]
fn rejects_ipv6_unique_local_fc00() {
assert!(ip_addr_is_bogon(v6(0xfc00, 0, 0, 0, 0, 0, 0, 1)));
assert!(ip_addr_is_bogon(v6(0xfd00, 0, 0, 0, 0, 0, 0, 1)));
}
#[test]
fn rejects_ipv6_link_local_fe80() {
assert!(ip_addr_is_bogon(v6(0xfe80, 0, 0, 0, 0, 0, 0, 1)));
}
#[test]
fn rejects_ipv6_documentation_2001_db8() {
assert!(ip_addr_is_bogon(v6(0x2001, 0x0db8, 0, 0, 0, 0, 0, 1)));
}
#[test]
fn rejects_ipv6_teredo_2001_0000() {
assert!(ip_addr_is_bogon(v6(0x2001, 0x0000, 0, 0, 0, 0, 0, 1)));
}
#[test]
fn rejects_ipv6_orchidv2_2001_002x() {
for x in 0u16..=0x000f {
let s1 = 0x0020 | x;
assert!(
ip_addr_is_bogon(v6(0x2001, s1, 0, 0, 0, 0, 0, 1)),
"2001:{s1:04x}::/64 should be ORCHIDv2 bogon"
);
}
}
#[test]
fn rejects_ipv6_discard_100() {
assert!(ip_addr_is_bogon(v6(0x0100, 0, 0, 0, 0, 0, 0, 1)));
}
#[test]
fn rejects_ipv4_mapped_ipv6_for_private_v4() {
let v6 = Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0x0a00, 0x0001);
assert!(ip_addr_is_bogon(IpAddr::V6(v6)));
}
#[test]
fn rejects_6to4_wrapping_private_v4() {
let v6 = Ipv6Addr::new(0x2002, 0x0a00, 0x0001, 0, 0, 0, 0, 1);
assert!(ip_addr_is_bogon(IpAddr::V6(v6)));
}
#[test]
fn allows_6to4_wrapping_public_v4() {
let v6 = Ipv6Addr::new(0x2002, 0x0808, 0x0808, 0, 0, 0, 0, 1);
assert!(!ip_addr_is_bogon(IpAddr::V6(v6)));
}
#[test]
fn rejects_ipv6_multicast_and_unspecified() {
assert!(ip_addr_is_bogon(IpAddr::V6(Ipv6Addr::UNSPECIFIED)));
assert!(ip_addr_is_bogon(v6(0xff00, 0, 0, 0, 0, 0, 0, 1)));
}
#[test]
fn allows_public_ipv6_addresses() {
assert!(!ip_addr_is_bogon(v6(
0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8888
)));
assert!(!ip_addr_is_bogon(v6(
0x2606, 0x4700, 0x4700, 0, 0, 0, 0, 0x1111
)));
}
#[test]
fn known_bogon_count_pinned_so_silent_removals_break_ci() {
let known: &[IpAddr] = &[
v4(10, 0, 0, 1),
v4(172, 16, 0, 1),
v4(192, 168, 1, 1),
v4(127, 0, 0, 1),
v4(169, 254, 169, 254),
IpAddr::V4(Ipv4Addr::UNSPECIFIED),
IpAddr::V4(Ipv4Addr::BROADCAST),
v4(192, 0, 2, 1),
v4(100, 64, 0, 1),
v4(192, 0, 0, 1),
v4(198, 18, 0, 1),
IpAddr::V6(Ipv6Addr::LOCALHOST),
v6(0xfc00, 0, 0, 0, 0, 0, 0, 1),
v6(0xfe80, 0, 0, 0, 0, 0, 0, 1),
v6(0x2001, 0x0db8, 0, 0, 0, 0, 0, 1),
v6(0x2001, 0x0000, 0, 0, 0, 0, 0, 1),
v6(0x2001, 0x0020, 0, 0, 0, 0, 0, 1),
v6(0x0100, 0, 0, 0, 0, 0, 0, 1),
];
for ip in known {
assert!(ip_addr_is_bogon(*ip), "{ip:?} expected to be bogon");
}
assert_eq!(known.len(), 18, "bogon coverage count changed");
}
}