1use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
2
3#[must_use]
4pub fn is_denied_loopback_or_wildcard(ip: IpAddr) -> bool {
5 ip.is_loopback() || ip.is_unspecified()
6}
7
8#[must_use]
9pub fn is_link_local(ip: IpAddr) -> bool {
10 match ip {
11 IpAddr::V4(ip) => ip.is_link_local(),
12 IpAddr::V6(ip) => ip.is_unicast_link_local(),
13 }
14}
15
16#[must_use]
17pub fn is_private_or_cgnat(ip: IpAddr) -> bool {
18 match ip {
19 IpAddr::V4(ip) => {
20 ip.is_private() || ip.octets()[0] == 100 && (64..=127).contains(&ip.octets()[1])
21 }
22 IpAddr::V6(ip) => ip.is_unique_local(),
23 }
24}
25
26#[must_use]
27pub fn is_metadata_ip(ip: IpAddr) -> bool {
28 match ip {
29 IpAddr::V4(ip) => ip == Ipv4Addr::new(169, 254, 169, 254),
30 IpAddr::V6(ip) => ip == Ipv6Addr::LOCALHOST,
31 }
32}
33
34#[must_use]
35pub fn host_is_private_tld_surprise(host: &str) -> bool {
36 let normalized = host.trim_end_matches('.').to_ascii_lowercase();
37 normalized == "localhost"
38 || !normalized.contains('.')
39 || normalized.ends_with(".local")
40 || normalized.ends_with(".internal")
41 || normalized.ends_with(".lan")
42}
43
44#[cfg(test)]
45#[path = "net_tests.rs"]
46mod tests;