1use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
4
5#[must_use]
7pub fn is_local_network_ip(ip: IpAddr) -> bool {
8 match ip {
9 IpAddr::V4(v4) => is_local_ipv4(v4),
10 IpAddr::V6(v6) => is_local_ipv6(v6),
11 }
12}
13
14const fn is_local_ipv4(ip: Ipv4Addr) -> bool {
15 ip.is_loopback() || ip.is_private() || ip.is_link_local() || ip.is_unspecified()
16}
17
18fn is_local_ipv6(ip: Ipv6Addr) -> bool {
19 if ip.is_loopback() || ip.is_unicast_link_local() {
20 return true;
21 }
22 let octets = ip.octets();
23 (octets[0] & 0xfe) == 0xfc || ip.to_ipv4_mapped().is_some_and(is_local_ipv4)
24}
25
26#[cfg(test)]
27mod tests {
28 use super::*;
29
30 #[test]
31 fn accepts_lan_rejects_public() {
32 assert!(is_local_network_ip(IpAddr::V4(Ipv4Addr::LOCALHOST)));
33 assert!(is_local_network_ip(IpAddr::V4(Ipv4Addr::new(
34 192, 168, 0, 1
35 ))));
36 assert!(is_local_network_ip(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2))));
37 assert!(is_local_network_ip(IpAddr::V4(Ipv4Addr::new(
38 172, 16, 0, 1
39 ))));
40 assert!(is_local_network_ip(IpAddr::V4(Ipv4Addr::new(
41 169, 254, 1, 1
42 ))));
43 assert!(!is_local_network_ip(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8))));
44 assert!(!is_local_network_ip(IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1))));
45 assert!(is_local_network_ip(IpAddr::V6(Ipv6Addr::LOCALHOST)));
46 let ula = Ipv6Addr::new(0xfd12, 0, 0, 0, 0, 0, 0, 1);
47 assert!(is_local_network_ip(IpAddr::V6(ula)));
48 let link = Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 1);
49 assert!(is_local_network_ip(IpAddr::V6(link)));
50 let global = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1);
51 assert!(!is_local_network_ip(IpAddr::V6(global)));
52 let mapped_lan = Ipv4Addr::new(192, 168, 1, 5).to_ipv6_mapped();
53 assert!(is_local_network_ip(IpAddr::V6(mapped_lan)));
54 let mapped_pub = Ipv4Addr::new(8, 8, 8, 8).to_ipv6_mapped();
55 assert!(!is_local_network_ip(IpAddr::V6(mapped_pub)));
56 }
57}