use std::net::IpAddr;
pub fn is_routable_ip(ip: IpAddr) -> bool {
if bogon::ip_addr_is_bogon(ip) {
return false;
}
match ip {
IpAddr::V4(v4) => {
let u = u32::from_be_bytes(v4.octets());
!(v4.is_multicast()
|| (u & 0xff000000) == 0x00000000
|| (u & 0xf0000000) == 0xf0000000)
}
IpAddr::V6(_) => true,
}
}
pub use gossan_core::net::{bounded_bytes, bounded_json, bounded_text};
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[test]
fn routable_ip_accepts_public() {
assert!(is_routable_ip("1.1.1.1".parse().unwrap()));
assert!(is_routable_ip("8.8.8.8".parse().unwrap()));
}
#[test]
fn routable_ip_rejects_private() {
assert!(!is_routable_ip("10.0.0.1".parse().unwrap()));
assert!(!is_routable_ip("192.168.1.1".parse().unwrap()));
assert!(!is_routable_ip("172.16.0.1".parse().unwrap()));
}
#[test]
fn routable_ip_rejects_loopback() {
assert!(!is_routable_ip("127.0.0.1".parse().unwrap()));
assert!(!is_routable_ip("::1".parse().unwrap()));
}
#[test]
fn routable_ip_rejects_link_local() {
assert!(!is_routable_ip("169.254.0.1".parse().unwrap()));
assert!(!is_routable_ip("fe80::1".parse().unwrap()));
}
#[test]
fn routable_ip_rejects_class_e_and_this_network() {
assert!(!is_routable_ip("240.0.0.1".parse().unwrap())); assert!(!is_routable_ip("250.1.2.3".parse().unwrap())); assert!(!is_routable_ip("0.1.2.3".parse().unwrap())); }
#[test]
fn routable_ip_accepts_public_ipv6() {
assert!(is_routable_ip("2606:4700::1111".parse().unwrap()));
}
#[test]
fn routable_ip_rejects_multicast() {
assert!(!is_routable_ip("224.0.0.1".parse().unwrap()));
assert!(!is_routable_ip("ff02::1".parse().unwrap()));
}
proptest! {
#[test]
fn is_routable_ip_never_panics(ip in any::<[u8; 16]>()) {
let v4 = IpAddr::V4(std::net::Ipv4Addr::new(ip[0], ip[1], ip[2], ip[3]));
let v6 = IpAddr::V6(std::net::Ipv6Addr::from(ip));
let _ = is_routable_ip(v4);
let _ = is_routable_ip(v6);
}
}
}