use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
pub fn is_blocked_ip(ip: IpAddr) -> bool {
match ip {
IpAddr::V4(v4) => is_blocked_ipv4(v4),
IpAddr::V6(v6) => match v6.to_ipv4_mapped() {
Some(v4) => is_blocked_ipv4(v4),
None => is_blocked_ipv6(v6),
},
}
}
fn is_blocked_ipv4(v4: Ipv4Addr) -> bool {
v4.is_loopback()
|| v4.is_private()
|| v4.is_link_local()
|| v4.is_broadcast()
|| v4.is_documentation()
|| v4.is_unspecified()
|| (v4.octets()[0] == 100 && (v4.octets()[1] & 0xc0) == 0x40)
}
fn is_blocked_ipv6(v6: Ipv6Addr) -> bool {
v6.is_loopback()
|| v6.is_unspecified()
|| (v6.segments()[0] & 0xfe00) == 0xfc00
|| (v6.segments()[0] & 0xffc0) == 0xfe80
}
pub fn blocked_ip_literal(host: &str) -> Option<bool> {
host.parse::<IpAddr>().ok().map(is_blocked_ip)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn metadata_endpoint_is_blocked() {
assert!(is_blocked_ip("169.254.169.254".parse().unwrap()));
}
#[test]
fn loopback_is_blocked() {
assert!(is_blocked_ip("127.0.0.1".parse().unwrap()));
assert!(is_blocked_ip("::1".parse().unwrap()));
}
#[test]
fn rfc1918_is_blocked() {
assert!(is_blocked_ip("10.0.0.1".parse().unwrap()));
assert!(is_blocked_ip("172.16.5.4".parse().unwrap()));
assert!(is_blocked_ip("192.168.1.1".parse().unwrap()));
}
#[test]
fn link_local_and_cgnat_are_blocked() {
assert!(is_blocked_ip("169.254.1.1".parse().unwrap()));
assert!(is_blocked_ip("100.64.0.1".parse().unwrap()));
assert!(is_blocked_ip("fe80::1".parse().unwrap()));
assert!(is_blocked_ip("fc00::1".parse().unwrap()));
}
#[test]
fn ipv4_mapped_v6_cannot_smuggle_blocked_v4() {
assert!(is_blocked_ip("::ffff:127.0.0.1".parse().unwrap()));
assert!(is_blocked_ip("::ffff:169.254.169.254".parse().unwrap()));
}
#[test]
fn public_ips_are_allowed() {
assert!(!is_blocked_ip("1.1.1.1".parse().unwrap()));
assert!(!is_blocked_ip("8.8.8.8".parse().unwrap()));
assert!(!is_blocked_ip("2606:4700:4700::1111".parse().unwrap()));
}
#[test]
fn blocked_ip_literal_distinguishes_names() {
assert_eq!(blocked_ip_literal("169.254.169.254"), Some(true));
assert_eq!(blocked_ip_literal("8.8.8.8"), Some(false));
assert_eq!(blocked_ip_literal("api.openai.com"), None);
}
}