use std::net::{IpAddr, Ipv6Addr};
use anyhow::{Result, bail};
pub fn ensure_safe_ip(ip: IpAddr, allow_private_ips: bool) -> Result<()> {
if is_blocked_ip(ip, allow_private_ips) {
bail!("blocked potentially unsafe IP address `{ip}`");
}
Ok(())
}
pub fn is_blocked_ip(ip: IpAddr, allow_private_ips: bool) -> bool {
match ip {
IpAddr::V4(v4) => {
(!allow_private_ips && (v4.is_private() || v4.is_loopback()))
|| v4.is_link_local()
|| v4.is_multicast()
|| v4.is_broadcast()
|| v4.is_unspecified()
|| is_ipv4_reserved_range(v4)
|| v4.is_documentation()
|| is_ipv4_shared_space(v4)
|| is_ipv4_benchmarking(v4)
|| is_ipv4_ietf_protocol_assignments(v4)
|| v4.octets() == [169, 254, 169, 254]
|| v4.octets() == [100, 100, 100, 200]
}
IpAddr::V6(v6) => {
if let Some(mapped) = v6.to_ipv4_mapped() {
return is_blocked_ip(IpAddr::V4(mapped), allow_private_ips);
}
(!allow_private_ips
&& (v6.is_loopback() || v6.is_unique_local() || is_ipv6_site_local(v6)))
|| v6.is_unspecified()
|| v6.is_multicast()
|| v6.is_unicast_link_local()
|| is_ipv6_documentation(v6)
|| is_ipv6_metadata(v6)
}
}
}
fn is_ipv4_shared_space(ip: std::net::Ipv4Addr) -> bool {
let octets = ip.octets();
octets[0] == 100 && (octets[1] & 0b1100_0000) == 0b0100_0000
}
fn is_ipv4_reserved_range(ip: std::net::Ipv4Addr) -> bool {
ip.octets()[0] >= 240
}
fn is_ipv4_benchmarking(ip: std::net::Ipv4Addr) -> bool {
let octets = ip.octets();
octets[0] == 198 && (octets[1] == 18 || octets[1] == 19)
}
fn is_ipv4_ietf_protocol_assignments(ip: std::net::Ipv4Addr) -> bool {
let octets = ip.octets();
octets[0] == 192 && octets[1] == 0 && octets[2] == 0
}
fn is_ipv6_documentation(ip: Ipv6Addr) -> bool {
ip.segments()[0] == 0x2001 && ip.segments()[1] == 0x0db8
}
fn is_ipv6_site_local(ip: Ipv6Addr) -> bool {
(ip.segments()[0] & 0xffc0) == 0xfec0
}
fn is_ipv6_metadata(ip: Ipv6Addr) -> bool {
ip == Ipv6Addr::new(0xfd00, 0xec2, 0, 0, 0, 0, 0, 0x254)
|| ip == Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xa9fe, 0xa9fe)
}