Skip to main content

nex_core/
ip.rs

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