1use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
4
5const IPV4_BITS: u8 = 32;
7
8const IPV6_BITS: u8 = 128;
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
13pub struct Subnet {
14 addr: IpAddr,
15}
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
19pub struct SubnetMask {
20 pub ipv4: u32,
21 pub ipv6: u128,
22}
23
24impl SubnetMask {
25 pub const fn new(ipv4_bits: u8, ipv6_bits: u8) -> Self {
27 let ipv4_bits = Self::clamp(ipv4_bits, IPV4_BITS);
28 let ipv6_bits = Self::clamp(ipv6_bits, IPV6_BITS);
29 Self {
30 ipv4: Self::mask_ipv4(ipv4_bits),
31 ipv6: Self::mask_ipv6(ipv6_bits),
32 }
33 }
34
35 #[inline]
37 const fn clamp(bits: u8, max: u8) -> u8 {
38 if bits > max { max } else { bits }
39 }
40
41 #[inline]
43 const fn mask_ipv4(bits: u8) -> u32 {
44 if bits == 0 {
45 return 0;
46 }
47
48 (!0u32) << (32 - bits as u32)
49 }
50
51 #[inline]
53 const fn mask_ipv6(bits: u8) -> u128 {
54 if bits == 0 {
55 return 0;
56 }
57
58 (!0u128) << (128 - bits as u32)
59 }
60}
61
62#[inline]
64fn ipv4_subnet(ip: Ipv4Addr, mask: &SubnetMask) -> IpAddr {
65 IpAddr::V4(Ipv4Addr::from(u32::from(ip) & mask.ipv4))
66}
67
68#[inline]
70fn ipv6_subnet(ip: Ipv6Addr, mask: &SubnetMask) -> IpAddr {
71 IpAddr::V6(Ipv6Addr::from(u128::from(ip) & mask.ipv6))
72}
73
74pub trait IpAddrExt {
76 fn subnet(&self, mask: &SubnetMask) -> Subnet;
78
79 fn is_global(&self) -> bool;
83}
84
85impl IpAddrExt for IpAddr {
86 fn subnet(&self, mask: &SubnetMask) -> Subnet {
87 match self {
88 Self::V4(v4) => Subnet {
89 addr: ipv4_subnet(*v4, mask),
90 },
91 Self::V6(v6) => {
92 if let Some(v4) = v6.to_ipv4_mapped() {
93 return Subnet {
94 addr: ipv4_subnet(v4, mask),
95 };
96 }
97
98 Subnet {
99 addr: ipv6_subnet(*v6, mask),
100 }
101 }
102 }
103 }
104
105 fn is_global(&self) -> bool {
106 match self {
107 Self::V4(ip) => is_global_v4(*ip),
108 Self::V6(ip) => is_global_v6(*ip),
109 }
110 }
111}
112
113#[inline]
114const fn is_future_protocol_v4(ip: Ipv4Addr) -> bool {
115 ip.octets()[0] == 192
116 && ip.octets()[1] == 0
117 && ip.octets()[2] == 0
118 && ip.octets()[3] != 9
119 && ip.octets()[3] != 10
120}
121
122#[inline]
123const fn is_shared_v4(ip: Ipv4Addr) -> bool {
124 ip.octets()[0] == 100 && (ip.octets()[1] & 0b1100_0000 == 0b0100_0000)
125}
126
127#[inline]
128const fn is_benchmarking_v4(ip: Ipv4Addr) -> bool {
129 ip.octets()[0] == 198 && (ip.octets()[1] & 0xfe) == 18
130}
131
132#[inline]
133const fn is_reserved_v4(ip: Ipv4Addr) -> bool {
134 ip.octets()[0] & 240 == 240 && !ip.is_broadcast()
135}
136
137#[inline]
138const fn is_global_v4(ip: Ipv4Addr) -> bool {
139 !(ip.octets()[0] == 0 || ip.is_private()
141 || is_shared_v4(ip)
142 || ip.is_loopback()
143 || ip.is_link_local()
144 || is_future_protocol_v4(ip)
145 || ip.is_documentation()
146 || is_benchmarking_v4(ip)
147 || is_reserved_v4(ip)
148 || ip.is_broadcast())
149}
150
151#[inline]
152const fn is_documentation_v6(ip: Ipv6Addr) -> bool {
153 (ip.segments()[0] == 0x2001) && (ip.segments()[1] == 0xdb8)
154}
155
156#[inline]
157const fn is_unique_local_v6(ip: Ipv6Addr) -> bool {
158 (ip.segments()[0] & 0xfe00) == 0xfc00
159}
160
161#[inline]
162const fn is_unicast_link_local_v6(ip: Ipv6Addr) -> bool {
163 (ip.segments()[0] & 0xffc0) == 0xfe80
164}
165
166#[inline]
167const fn is_global_v6(ip: Ipv6Addr) -> bool {
168 !(ip.is_unspecified()
169 || ip.is_loopback()
170 || matches!(ip.segments(), [0, 0, 0, 0, 0, 0xffff, _, _])
172 || matches!(ip.segments(), [0x64, 0xff9b, 1, _, _, _, _, _])
174 || matches!(ip.segments(), [0x100, 0, 0, 0, _, _, _, _])
176 || (matches!(ip.segments(), [0x2001, b, _, _, _, _, _, _] if b < 0x200)
178 && !(
179 u128::from_be_bytes(ip.octets()) == 0x2001_0001_0000_0000_0000_0000_0000_0001
181 || u128::from_be_bytes(ip.octets()) == 0x2001_0001_0000_0000_0000_0000_0000_0002
183 || matches!(ip.segments(), [0x2001, 3, _, _, _, _, _, _])
185 || matches!(ip.segments(), [0x2001, 4, 0x112, _, _, _, _, _])
187 || matches!(ip.segments(), [0x2001, b, _, _, _, _, _, _] if b >= 0x20 && b <= 0x3F)
190 ))
191 || matches!(ip.segments(), [0x2002, _, _, _, _, _, _, _])
194 || is_documentation_v6(ip)
195 || is_unique_local_v6(ip)
196 || is_unicast_link_local_v6(ip))
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202 use std::str::FromStr;
203
204 const TEST_MASK: SubnetMask = SubnetMask::new(24, 48);
206
207 #[test]
208 fn ipv4_subnet_zeroes_lower_8_bits() {
209 let ip = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 123));
210 assert_eq!(
211 ip.subnet(&TEST_MASK).addr,
212 IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0))
213 );
214 }
215
216 #[test]
217 fn ipv6_subnet_zeroes_lower_80_bits() {
218 let ip = IpAddr::V6(Ipv6Addr::new(
219 0x2001, 0xdb8, 0x1234, 0x5678, 0x9abc, 0xdef0, 0x1357, 0x2468,
220 ));
221 assert_eq!(
222 ip.subnet(&TEST_MASK).addr,
223 IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0x1234, 0, 0, 0, 0, 0))
224 );
225 }
226
227 #[test]
228 fn ipv4_mapped_ipv6_subnet_uses_ipv4_truncation() {
229 let ip = IpAddr::from_str("::ffff:192.168.1.123").unwrap();
230 assert_eq!(
231 ip.subnet(&TEST_MASK).addr,
232 IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0))
233 );
234 }
235
236 #[test]
237 fn subnet_mask_max() {
238 let mask = SubnetMask::new(40, 200);
239 assert_eq!(mask.ipv4, u32::MAX);
240 assert_eq!(mask.ipv6, u128::MAX);
241 }
242
243 #[test]
244 fn subnet_mask_min() {
245 let mask = SubnetMask::new(0, 0);
246 assert_eq!(mask.ipv4, 0);
247 assert_eq!(mask.ipv6, 0);
248 }
249
250 #[test]
251 #[allow(unstable_name_collisions)]
252 fn test_is_global_v4() {
253 assert!(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)).is_global()); assert!(IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)).is_global()); assert!(IpAddr::V4(Ipv4Addr::new(123, 45, 67, 89)).is_global()); assert!(!IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)).is_global()); assert!(!IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)).is_global()); assert!(!IpAddr::V4(Ipv4Addr::new(172, 16, 0, 1)).is_global()); assert!(!IpAddr::V4(Ipv4Addr::new(172, 31, 255, 254)).is_global());
263
264 assert!(!IpAddr::V4(Ipv4Addr::new(100, 64, 0, 1)).is_global());
266 assert!(!IpAddr::V4(Ipv4Addr::new(100, 127, 255, 254)).is_global());
267
268 assert!(!IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)).is_global());
270 assert!(!IpAddr::V4(Ipv4Addr::new(127, 255, 255, 254)).is_global());
271
272 assert!(!IpAddr::V4(Ipv4Addr::new(169, 254, 0, 1)).is_global());
274 assert!(!IpAddr::V4(Ipv4Addr::new(169, 254, 255, 254)).is_global());
275
276 assert!(!IpAddr::V4(Ipv4Addr::new(192, 0, 0, 1)).is_global());
278 assert!(!IpAddr::V4(Ipv4Addr::new(192, 0, 0, 254)).is_global());
279 assert!(IpAddr::V4(Ipv4Addr::new(192, 0, 0, 9)).is_global());
281 assert!(IpAddr::V4(Ipv4Addr::new(192, 0, 0, 10)).is_global());
282
283 assert!(!IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1)).is_global()); assert!(!IpAddr::V4(Ipv4Addr::new(198, 51, 100, 1)).is_global()); assert!(!IpAddr::V4(Ipv4Addr::new(203, 0, 113, 1)).is_global()); assert!(!IpAddr::V4(Ipv4Addr::new(198, 18, 0, 1)).is_global());
290 assert!(!IpAddr::V4(Ipv4Addr::new(198, 19, 255, 254)).is_global());
291
292 assert!(!IpAddr::V4(Ipv4Addr::new(240, 0, 0, 1)).is_global());
294 assert!(!IpAddr::V4(Ipv4Addr::new(254, 255, 255, 254)).is_global());
295
296 assert!(!IpAddr::V4(Ipv4Addr::new(255, 255, 255, 255)).is_global());
298 }
299
300 #[test]
301 #[allow(unstable_name_collisions)]
302 fn test_is_global_v6() {
303 assert!(IpAddr::V6(Ipv6Addr::from_str("2001:4860:4860::8888").unwrap()).is_global()); assert!(IpAddr::V6(Ipv6Addr::from_str("2606:4700:4700::1111").unwrap()).is_global()); assert!(
307 IpAddr::V6(Ipv6Addr::from_str("2005:1db8:85a3:0000:0000:8a2e:0370:7334").unwrap())
308 .is_global()
309 ); assert!(!IpAddr::V6(Ipv6Addr::UNSPECIFIED).is_global());
313
314 assert!(!IpAddr::V6(Ipv6Addr::LOCALHOST).is_global());
316
317 assert!(!IpAddr::V6(Ipv6Addr::from_str("::ffff:192.0.2.128").unwrap()).is_global());
319
320 assert!(!IpAddr::V6(Ipv6Addr::from_str("64:ff9b:1::1").unwrap()).is_global());
322
323 assert!(!IpAddr::V6(Ipv6Addr::from_str("100::1").unwrap()).is_global());
325
326 assert!(!IpAddr::V6(Ipv6Addr::from_str("2001:0::1").unwrap()).is_global()); assert!(IpAddr::V6(Ipv6Addr::from_str("2001:1::1").unwrap()).is_global()); assert!(IpAddr::V6(Ipv6Addr::from_str("2001:1::1").unwrap()).is_global()); assert!(IpAddr::V6(Ipv6Addr::from_str("2001:1::2").unwrap()).is_global()); assert!(IpAddr::V6(Ipv6Addr::from_str("2001:3::1").unwrap()).is_global()); assert!(IpAddr::V6(Ipv6Addr::from_str("2001:4:112::1").unwrap()).is_global()); assert!(IpAddr::V6(Ipv6Addr::from_str("2001:20::1").unwrap()).is_global()); assert!(IpAddr::V6(Ipv6Addr::from_str("2001:30::1").unwrap()).is_global()); assert!(!IpAddr::V6(Ipv6Addr::from_str("2002::1").unwrap()).is_global());
340
341 assert!(!IpAddr::V6(Ipv6Addr::from_str("2001:db8::1").unwrap()).is_global());
343
344 assert!(!IpAddr::V6(Ipv6Addr::from_str("fc00::1").unwrap()).is_global()); assert!(
347 !IpAddr::V6(Ipv6Addr::from_str("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff").unwrap())
348 .is_global()
349 ); assert!(!IpAddr::V6(Ipv6Addr::from_str("fe80::1").unwrap()).is_global());
353
354 assert!(IpAddr::V6(Ipv6Addr::from_str("ff00::1").unwrap()).is_global());
356
357 assert!(IpAddr::V6(Ipv6Addr::from_str("2003::1").unwrap()).is_global());
359 }
360
361 #[test]
362 #[allow(unstable_name_collisions)]
363 fn test_is_global_ipaddr() {
364 assert!(IpAddr::V4(Ipv4Addr::from_str("1.2.3.4").unwrap()).is_global());
367 assert!(!IpAddr::V4(Ipv4Addr::from_str("10.0.0.1").unwrap()).is_global());
369
370 assert!(IpAddr::V6(Ipv6Addr::from_str("2001:4860:4860::8888").unwrap()).is_global());
372 assert!(!IpAddr::V6(Ipv6Addr::from_str("fe80::1").unwrap()).is_global());
374 }
375}