Skip to main content

veilid_tools/
ip_extra.rs

1//
2// This file really shouldn't be necessary, but 'ip' isn't a stable feature
3//
4
5use super::*;
6
7use core::hash::*;
8
9#[derive(Copy, PartialEq, Eq, Clone, Hash, Debug)]
10pub enum Ipv6MulticastScope {
11    InterfaceLocal,
12    LinkLocal,
13    RealmLocal,
14    AdminLocal,
15    SiteLocal,
16    OrganizationLocal,
17    Global,
18}
19
20#[must_use]
21pub fn ipaddr_is_unspecified(addr: &IpAddr) -> bool {
22    match addr {
23        IpAddr::V4(ip) => ipv4addr_is_unspecified(ip),
24        IpAddr::V6(ip) => ipv6addr_is_unspecified(ip),
25    }
26}
27
28#[must_use]
29pub fn ipaddr_is_loopback(addr: &IpAddr) -> bool {
30    match addr {
31        IpAddr::V4(ip) => ipv4addr_is_loopback(ip),
32        IpAddr::V6(ip) => ipv6addr_is_loopback(ip),
33    }
34}
35
36#[must_use]
37pub fn ipaddr_is_global(addr: &IpAddr) -> bool {
38    match addr {
39        IpAddr::V4(ip) => ipv4addr_is_global(ip),
40        IpAddr::V6(ip) => ipv6addr_is_global(ip),
41    }
42}
43
44#[must_use]
45pub fn ipaddr_is_multicast(addr: &IpAddr) -> bool {
46    match addr {
47        IpAddr::V4(ip) => ipv4addr_is_multicast(ip),
48        IpAddr::V6(ip) => ipv6addr_is_multicast(ip),
49    }
50}
51
52#[must_use]
53pub fn ipaddr_is_documentation(addr: &IpAddr) -> bool {
54    match addr {
55        IpAddr::V4(ip) => ipv4addr_is_documentation(ip),
56        IpAddr::V6(ip) => ipv6addr_is_documentation(ip),
57    }
58}
59
60#[must_use]
61pub fn ipv4addr_is_unspecified(addr: &Ipv4Addr) -> bool {
62    addr.octets() == [0u8, 0u8, 0u8, 0u8]
63}
64
65#[must_use]
66pub fn ipv4addr_is_loopback(addr: &Ipv4Addr) -> bool {
67    addr.octets()[0] == 127
68}
69
70#[must_use]
71pub fn ipv4addr_is_private(addr: &Ipv4Addr) -> bool {
72    match addr.octets() {
73        [10, ..] => true,
74        [172, b, ..] if (16..=31).contains(&b) => true,
75        [192, 168, ..] => true,
76        _ => false,
77    }
78}
79
80#[must_use]
81pub fn ipv4addr_is_link_local(addr: &Ipv4Addr) -> bool {
82    matches!(addr.octets(), [169, 254, ..])
83}
84
85#[must_use]
86pub fn ipv4addr_is_global(addr: &Ipv4Addr) -> bool {
87    // check if this address is 192.0.0.9 or 192.0.0.10. These addresses are the only two
88    // globally routable addresses in the 192.0.0.0/24 range.
89    if u32::from(*addr) == 0xc0000009 || u32::from(*addr) == 0xc000000a {
90        return true;
91    }
92    !ipv4addr_is_private(addr)
93        && !ipv4addr_is_loopback(addr)
94        && !ipv4addr_is_link_local(addr)
95        && !ipv4addr_is_broadcast(addr)
96        && !ipv4addr_is_documentation(addr)
97        && !ipv4addr_is_shared(addr)
98        && !ipv4addr_is_ietf_protocol_assignment(addr)
99        && !ipv4addr_is_reserved(addr)
100        && !ipv4addr_is_benchmarking(addr)
101        // Make sure the address is not in 0.0.0.0/8
102        && addr.octets()[0] != 0
103}
104
105#[must_use]
106pub fn ipv4addr_is_shared(addr: &Ipv4Addr) -> bool {
107    addr.octets()[0] == 100 && (addr.octets()[1] & 0b1100_0000 == 0b0100_0000)
108}
109
110#[must_use]
111pub fn ipv4addr_is_ietf_protocol_assignment(addr: &Ipv4Addr) -> bool {
112    addr.octets()[0] == 192 && addr.octets()[1] == 0 && addr.octets()[2] == 0
113}
114
115#[must_use]
116pub fn ipv4addr_is_benchmarking(addr: &Ipv4Addr) -> bool {
117    addr.octets()[0] == 198 && (addr.octets()[1] & 0xfe) == 18
118}
119
120#[must_use]
121pub fn ipv4addr_is_reserved(addr: &Ipv4Addr) -> bool {
122    addr.octets()[0] & 240 == 240 && !addr.is_broadcast()
123}
124
125#[must_use]
126pub fn ipv4addr_is_multicast(addr: &Ipv4Addr) -> bool {
127    addr.octets()[0] >= 224 && addr.octets()[0] <= 239
128}
129
130#[must_use]
131pub fn ipv4addr_is_broadcast(addr: &Ipv4Addr) -> bool {
132    addr.octets() == [255u8, 255u8, 255u8, 255u8]
133}
134
135#[must_use]
136pub fn ipv4addr_is_documentation(addr: &Ipv4Addr) -> bool {
137    matches!(
138        addr.octets(),
139        [192, 0, 2, _] | [198, 51, 100, _] | [203, 0, 113, _]
140    )
141}
142
143#[must_use]
144pub fn ipv6addr_is_unspecified(addr: &Ipv6Addr) -> bool {
145    addr.segments() == [0, 0, 0, 0, 0, 0, 0, 0]
146}
147
148#[must_use]
149pub fn ipv6addr_is_loopback(addr: &Ipv6Addr) -> bool {
150    addr.segments() == [0, 0, 0, 0, 0, 0, 0, 1]
151}
152
153#[must_use]
154pub fn ipv6addr_is_global(addr: &Ipv6Addr) -> bool {
155    match ipv6addr_multicast_scope(addr) {
156        Some(Ipv6MulticastScope::Global) => true,
157        None => ipv6addr_is_unicast_global(addr),
158        _ => false,
159    }
160}
161
162#[must_use]
163pub fn ipv6addr_is_unique_local(addr: &Ipv6Addr) -> bool {
164    (addr.segments()[0] & 0xfe00) == 0xfc00
165}
166
167#[must_use]
168pub fn ipv6addr_is_unicast_link_local_strict(addr: &Ipv6Addr) -> bool {
169    addr.segments()[0] == 0xfe80
170        && addr.segments()[1] == 0
171        && addr.segments()[2] == 0
172        && addr.segments()[3] == 0
173}
174
175#[must_use]
176pub fn ipv6addr_is_unicast_link_local(addr: &Ipv6Addr) -> bool {
177    (addr.segments()[0] & 0xffc0) == 0xfe80
178}
179
180#[must_use]
181pub fn ipv6addr_is_unicast_site_local(addr: &Ipv6Addr) -> bool {
182    (addr.segments()[0] & 0xffc0) == 0xfec0
183}
184
185#[must_use]
186pub fn ipv6addr_is_documentation(addr: &Ipv6Addr) -> bool {
187    (addr.segments()[0] == 0x2001) && (addr.segments()[1] == 0xdb8)
188}
189
190#[must_use]
191pub fn ipv6addr_is_unicast_global(addr: &Ipv6Addr) -> bool {
192    !ipv6addr_is_multicast(addr)
193        && !ipv6addr_is_loopback(addr)
194        && !ipv6addr_is_unicast_link_local(addr)
195        && !ipv6addr_is_unique_local(addr)
196        && !ipv6addr_is_unspecified(addr)
197        && !ipv6addr_is_documentation(addr)
198}
199
200#[must_use]
201pub fn ipv6addr_multicast_scope(addr: &Ipv6Addr) -> Option<Ipv6MulticastScope> {
202    if ipv6addr_is_multicast(addr) {
203        match addr.segments()[0] & 0x000f {
204            1 => Some(Ipv6MulticastScope::InterfaceLocal),
205            2 => Some(Ipv6MulticastScope::LinkLocal),
206            3 => Some(Ipv6MulticastScope::RealmLocal),
207            4 => Some(Ipv6MulticastScope::AdminLocal),
208            5 => Some(Ipv6MulticastScope::SiteLocal),
209            8 => Some(Ipv6MulticastScope::OrganizationLocal),
210            14 => Some(Ipv6MulticastScope::Global),
211            _ => None,
212        }
213    } else {
214        None
215    }
216}
217
218#[must_use]
219pub fn ipv6addr_is_multicast(addr: &Ipv6Addr) -> bool {
220    (addr.segments()[0] & 0xff00) == 0xff00
221}
222
223// Converts an ip to a ip block by applying a netmask
224// to the host part of the ip address
225// ipv4 addresses are treated as single hosts
226// ipv6 addresses are treated as prefix allocated blocks
227#[must_use]
228pub fn ip_to_ipblock(ip6_prefix_size: usize, addr: IpAddr) -> IpAddr {
229    match addr {
230        IpAddr::V4(_) => addr,
231        IpAddr::V6(v6) => {
232            let mut hostlen = 128usize.saturating_sub(ip6_prefix_size);
233            let mut out = v6.octets();
234            for i in (0..16).rev() {
235                if hostlen >= 8 {
236                    out[i] = 0xFF;
237                    hostlen -= 8;
238                } else {
239                    out[i] |= !(0xFFu8 << hostlen);
240                    break;
241                }
242            }
243            IpAddr::V6(Ipv6Addr::from(out))
244        }
245    }
246}
247
248#[must_use]
249pub fn ipaddr_apply_netmask(addr: IpAddr, netmask: IpAddr) -> IpAddr {
250    match addr {
251        IpAddr::V4(v4) => {
252            let v4mask = match netmask {
253                IpAddr::V4(v4mask) => v4mask,
254                IpAddr::V6(_) => {
255                    panic!("netmask doesn't match ipv4 address");
256                }
257            };
258            let v4 = v4.octets();
259            let v4mask = v4mask.octets();
260            IpAddr::V4(Ipv4Addr::new(
261                v4[0] & v4mask[0],
262                v4[1] & v4mask[1],
263                v4[2] & v4mask[2],
264                v4[3] & v4mask[3],
265            ))
266        }
267        IpAddr::V6(v6) => {
268            let v6mask = match netmask {
269                IpAddr::V4(_) => {
270                    panic!("netmask doesn't match ipv6 address");
271                }
272                IpAddr::V6(v6mask) => v6mask,
273            };
274            let v6 = v6.segments();
275            let v6mask = v6mask.segments();
276            IpAddr::V6(Ipv6Addr::new(
277                v6[0] & v6mask[0],
278                v6[1] & v6mask[1],
279                v6[2] & v6mask[2],
280                v6[3] & v6mask[3],
281                v6[4] & v6mask[4],
282                v6[5] & v6mask[5],
283                v6[6] & v6mask[6],
284                v6[7] & v6mask[7],
285            ))
286        }
287    }
288}
289
290#[must_use]
291pub fn ipaddr_in_network(addr: IpAddr, netaddr: IpAddr, netmask: IpAddr) -> bool {
292    if addr.is_ipv4() && !netaddr.is_ipv4() {
293        return false;
294    }
295    if addr.is_ipv6() && !netaddr.is_ipv6() {
296        return false;
297    }
298    ipaddr_apply_netmask(netaddr, netmask) == ipaddr_apply_netmask(addr, netmask)
299}