Skip to main content

horto_os_ui_mcp/
net.rs

1//! Local-network peer allowlist (loopback / RFC1918 / ULA / link-local).
2
3use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
4
5/// True for loopback, RFC1918, IPv6 ULA, and link-local peers.
6#[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}