netdev/
ip.rs

1use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
2
3/// Returns [`true`] if the address appears to be globally routable.
4pub(crate) fn is_global_ip(ip_addr: &IpAddr) -> bool {
5    match ip_addr {
6        IpAddr::V4(ip) => is_global_ipv4(ip),
7        IpAddr::V6(ip) => is_global_ipv6(ip),
8    }
9}
10
11/// Returns [`true`] if the address appears to be globally reachable
12/// as specified by the [IANA IPv4 Special-Purpose Address Registry].
13pub(crate) fn is_global_ipv4(ipv4_addr: &Ipv4Addr) -> bool {
14    !(ipv4_addr.octets()[0] == 0 // "This network"
15        || ipv4_addr.is_private()
16        || is_shared_ipv4(ipv4_addr)
17        || ipv4_addr.is_loopback()
18        || ipv4_addr.is_link_local()
19        // addresses reserved for future protocols (`192.0.0.0/24`)
20        // .9 and .10 are documented as globally reachable so they're excluded
21        || (
22            ipv4_addr.octets()[0] == 192 && ipv4_addr.octets()[1] == 0 && ipv4_addr.octets()[2] == 0
23            && ipv4_addr.octets()[3] != 9 && ipv4_addr.octets()[3] != 10
24        )
25        || ipv4_addr.is_documentation()
26        || is_benchmarking_ipv4(ipv4_addr)
27        || is_reserved_ipv4(ipv4_addr)
28        || ipv4_addr.is_broadcast())
29}
30
31/// Returns [`true`] if the address appears to be globally reachable
32/// as specified by the [IANA IPv6 Special-Purpose Address Registry].
33pub(crate) fn is_global_ipv6(ipv6_addr: &Ipv6Addr) -> bool {
34    !(ipv6_addr.is_unspecified()
35        || ipv6_addr.is_loopback()
36        // IPv4-mapped Address (`::ffff:0:0/96`)
37        || matches!(ipv6_addr.segments(), [0, 0, 0, 0, 0, 0xffff, _, _])
38        // IPv4-IPv6 Translat. (`64:ff9b:1::/48`)
39        || matches!(ipv6_addr.segments(), [0x64, 0xff9b, 1, _, _, _, _, _])
40        // Discard-Only Address Block (`100::/64`)
41        || matches!(ipv6_addr.segments(), [0x100, 0, 0, 0, _, _, _, _])
42        // IETF Protocol Assignments (`2001::/23`)
43        || (matches!(ipv6_addr.segments(), [0x2001, b, _, _, _, _, _, _] if b < 0x200)
44            && !(
45                // Port Control Protocol Anycast (`2001:1::1`)
46                u128::from_be_bytes(ipv6_addr.octets()) == 0x2001_0001_0000_0000_0000_0000_0000_0001
47                // Traversal Using Relays around NAT Anycast (`2001:1::2`)
48                || u128::from_be_bytes(ipv6_addr.octets()) == 0x2001_0001_0000_0000_0000_0000_0000_0002
49                // AMT (`2001:3::/32`)
50                || matches!(ipv6_addr.segments(), [0x2001, 3, _, _, _, _, _, _])
51                // AS112-v6 (`2001:4:112::/48`)
52                || matches!(ipv6_addr.segments(), [0x2001, 4, 0x112, _, _, _, _, _])
53                // ORCHIDv2 (`2001:20::/28`)
54                // Drone Remote ID Protocol Entity Tags (DETs) Prefix (`2001:30::/28`)`
55                || matches!(ipv6_addr.segments(), [0x2001, b, _, _, _, _, _, _] if b >= 0x20 && b <= 0x3F)
56            ))
57        // 6to4 (`2002::/16`) – it's not explicitly documented as globally reachable,
58        // IANA says N/A.
59        || matches!(ipv6_addr.segments(), [0x2002, _, _, _, _, _, _, _])
60        || is_documentation_ipv6(ipv6_addr)
61        || ipv6_addr.is_unique_local()
62        || ipv6_addr.is_unicast_link_local())
63}
64
65/// Returns [`true`] if this address is part of the Shared Address Space defined in
66/// [IETF RFC 6598] (`100.64.0.0/10`).
67///
68/// [IETF RFC 6598]: https://tools.ietf.org/html/rfc6598
69fn is_shared_ipv4(ipv4_addr: &Ipv4Addr) -> bool {
70    ipv4_addr.octets()[0] == 100 && (ipv4_addr.octets()[1] & 0b1100_0000 == 0b0100_0000)
71}
72
73/// Returns [`true`] if this address part of the `198.18.0.0/15` range, which is reserved for
74/// network devices benchmarking.
75fn is_benchmarking_ipv4(ipv4_addr: &Ipv4Addr) -> bool {
76    ipv4_addr.octets()[0] == 198 && (ipv4_addr.octets()[1] & 0xfe) == 18
77}
78
79/// Returns [`true`] if this address is reserved by IANA for future use.
80fn is_reserved_ipv4(ipv4_addr: &Ipv4Addr) -> bool {
81    ipv4_addr.octets()[0] & 240 == 240 && !ipv4_addr.is_broadcast()
82}
83
84/// Returns [`true`] if this is an address reserved for documentation
85/// (`2001:db8::/32` and `3fff::/20`).
86fn is_documentation_ipv6(ipv6_addr: &Ipv6Addr) -> bool {
87    matches!(
88        ipv6_addr.segments(),
89        [0x2001, 0xdb8, ..] | [0x3fff, 0..=0x0fff, ..]
90    )
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96    use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
97
98    #[test]
99    fn test_is_global_ipv4() {
100        let global = Ipv4Addr::new(1, 1, 1, 1); // Cloudflare
101        let private = Ipv4Addr::new(192, 168, 1, 1);
102        let loopback = Ipv4Addr::new(127, 0, 0, 1);
103        let shared = Ipv4Addr::new(100, 64, 0, 1); // RFC6598
104        let doc = Ipv4Addr::new(192, 0, 2, 1); // Documentation
105
106        assert!(is_global_ipv4(&global));
107        assert!(!is_global_ipv4(&private));
108        assert!(!is_global_ipv4(&loopback));
109        assert!(!is_global_ipv4(&shared));
110        assert!(!is_global_ipv4(&doc));
111    }
112
113    #[test]
114    fn test_is_global_ipv6() {
115        let global = Ipv6Addr::new(0x2606, 0x4700, 0, 0, 0, 0, 0, 0x1111); // Cloudflare
116        let loopback = Ipv6Addr::LOCALHOST;
117        let unspecified = Ipv6Addr::UNSPECIFIED;
118        let unique_local = Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 1);
119        let doc = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1); // Documentation
120
121        assert!(is_global_ipv6(&global));
122        assert!(!is_global_ipv6(&loopback));
123        assert!(!is_global_ipv6(&unspecified));
124        assert!(!is_global_ipv6(&unique_local));
125        assert!(!is_global_ipv6(&doc));
126    }
127
128    #[test]
129    fn test_is_global_ip() {
130        let ip_v4 = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
131        let ip_v6 = IpAddr::V6(Ipv6Addr::new(0x2606, 0x4700, 0, 0, 0, 0, 0, 0x1111)); // Cloudflare
132        let ip_private = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1));
133        let ip_ula = IpAddr::V6(Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 1));
134
135        assert!(is_global_ip(&ip_v4));
136        assert!(is_global_ip(&ip_v6));
137        assert!(!is_global_ip(&ip_private));
138        assert!(!is_global_ip(&ip_ula));
139    }
140}