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;
84}
85
86impl IpAddrExt for IpAddr {
87 fn subnet(&self, mask: &SubnetMask) -> Subnet {
88 match self {
89 Self::V4(v4) => Subnet {
90 addr: ipv4_subnet(*v4, mask),
91 },
92 Self::V6(v6) => {
93 if let Some(v4) = v6.to_ipv4_mapped() {
94 return Subnet {
95 addr: ipv4_subnet(v4, mask),
96 };
97 }
98
99 Subnet {
100 addr: ipv6_subnet(*v6, mask),
101 }
102 }
103 }
104 }
105
106 fn is_global(&self) -> bool {
107 match self {
108 Self::V4(ip) => is_global_v4(*ip),
109 Self::V6(ip) => is_global_v6(*ip),
110 }
111 }
112}
113
114#[inline]
115const fn is_future_protocol_v4(ip: Ipv4Addr) -> bool {
116 ip.octets()[0] == 192
117 && ip.octets()[1] == 0
118 && ip.octets()[2] == 0
119 && ip.octets()[3] != 9
120 && ip.octets()[3] != 10
121}
122
123#[inline]
124const fn is_shared_v4(ip: Ipv4Addr) -> bool {
125 ip.octets()[0] == 100 && (ip.octets()[1] & 0b1100_0000 == 0b0100_0000)
126}
127
128#[inline]
129const fn is_benchmarking_v4(ip: Ipv4Addr) -> bool {
130 ip.octets()[0] == 198 && (ip.octets()[1] & 0xfe) == 18
131}
132
133#[inline]
134const fn is_reserved_v4(ip: Ipv4Addr) -> bool {
135 ip.octets()[0] & 240 == 240 && !ip.is_broadcast()
136}
137
138#[inline]
139const fn is_global_v4(ip: Ipv4Addr) -> bool {
140 !(ip.octets()[0] == 0 || ip.is_private()
142 || is_shared_v4(ip)
143 || ip.is_loopback()
144 || ip.is_link_local()
145 || is_future_protocol_v4(ip)
146 || matches!(ip.octets(), [192, 88, 99, 2])
148 || ip.is_documentation()
149 || is_benchmarking_v4(ip)
150 || is_reserved_v4(ip)
151 || ip.is_broadcast())
152}
153
154#[inline]
155const fn is_documentation_v6(ip: Ipv6Addr) -> bool {
156 matches!(
157 ip.segments(),
158 [0x2001, 0xdb8, ..] | [0x3fff, 0..=0x0fff, ..]
159 )
160}
161
162#[inline]
163const fn is_global_v6(ip: Ipv6Addr) -> bool {
164 !(ip.is_unspecified()
165 || ip.is_loopback()
166 || matches!(ip.segments(), [0, 0, 0, 0, 0, 0xffff, _, _])
168 || matches!(ip.segments(), [0x64, 0xff9b, 1, _, _, _, _, _])
170 || matches!(ip.segments(), [0x100, 0, 0, 0, _, _, _, _])
172 || matches!(ip.segments(), [0x100, 0, 0, 1, _, _, _, _])
174 || (matches!(ip.segments(), [0x2001, b, _, _, _, _, _, _] if b < 0x200)
176 && !(
177 u128::from_be_bytes(ip.octets()) == 0x2001_0001_0000_0000_0000_0000_0000_0001
179 || u128::from_be_bytes(ip.octets()) == 0x2001_0001_0000_0000_0000_0000_0000_0002
181 || u128::from_be_bytes(ip.octets()) == 0x2001_0001_0000_0000_0000_0000_0000_0003
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, _, _, _, _, _, _, _])
193 || is_documentation_v6(ip)
194 || matches!(ip.segments(), [0x5f00, ..])
196 || ip.is_unique_local()
197 || ip.is_unicast_link_local())
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203 use std::str::FromStr;
204
205 const TEST_MASK: SubnetMask = SubnetMask::new(24, 48);
207
208 #[test]
209 fn ipv4_subnet_zeroes_lower_8_bits() {
210 let ip = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 123));
211 assert_eq!(
212 ip.subnet(&TEST_MASK).addr,
213 IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0))
214 );
215 }
216
217 #[test]
218 fn ipv6_subnet_zeroes_lower_80_bits() {
219 let ip = IpAddr::V6(Ipv6Addr::new(
220 0x2001, 0xdb8, 0x1234, 0x5678, 0x9abc, 0xdef0, 0x1357, 0x2468,
221 ));
222 assert_eq!(
223 ip.subnet(&TEST_MASK).addr,
224 IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0x1234, 0, 0, 0, 0, 0))
225 );
226 }
227
228 #[test]
229 fn ipv4_mapped_ipv6_subnet_uses_ipv4_truncation() {
230 let ip = IpAddr::from_str("::ffff:192.168.1.123").unwrap();
231 assert_eq!(
232 ip.subnet(&TEST_MASK).addr,
233 IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0))
234 );
235 }
236
237 #[test]
238 fn subnet_mask_max() {
239 let mask = SubnetMask::new(40, 200);
240 assert_eq!(mask.ipv4, u32::MAX);
241 assert_eq!(mask.ipv6, u128::MAX);
242 }
243
244 #[test]
245 fn subnet_mask_min() {
246 let mask = SubnetMask::new(0, 0);
247 assert_eq!(mask.ipv4, 0);
248 assert_eq!(mask.ipv6, 0);
249 }
250
251 #[test]
252 #[allow(unstable_name_collisions)]
253 fn test_is_global_v4() {
254 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());
264
265 assert!(!IpAddr::V4(Ipv4Addr::new(100, 64, 0, 1)).is_global());
267 assert!(!IpAddr::V4(Ipv4Addr::new(100, 127, 255, 254)).is_global());
268
269 assert!(!IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)).is_global());
271 assert!(!IpAddr::V4(Ipv4Addr::new(127, 255, 255, 254)).is_global());
272
273 assert!(!IpAddr::V4(Ipv4Addr::new(169, 254, 0, 1)).is_global());
275 assert!(!IpAddr::V4(Ipv4Addr::new(169, 254, 255, 254)).is_global());
276
277 assert!(!IpAddr::V4(Ipv4Addr::new(192, 0, 0, 1)).is_global());
279 assert!(!IpAddr::V4(Ipv4Addr::new(192, 0, 0, 254)).is_global());
280 assert!(IpAddr::V4(Ipv4Addr::new(192, 0, 0, 9)).is_global());
282 assert!(IpAddr::V4(Ipv4Addr::new(192, 0, 0, 10)).is_global());
283
284 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(192, 88, 99, 2)).is_global());
292 assert!(IpAddr::V4(Ipv4Addr::new(192, 88, 99, 1)).is_global());
293 assert!(IpAddr::V4(Ipv4Addr::new(192, 88, 99, 3)).is_global());
294
295 assert!(!IpAddr::V4(Ipv4Addr::new(198, 18, 0, 1)).is_global());
297 assert!(!IpAddr::V4(Ipv4Addr::new(198, 19, 255, 254)).is_global());
298
299 assert!(!IpAddr::V4(Ipv4Addr::new(240, 0, 0, 1)).is_global());
301 assert!(!IpAddr::V4(Ipv4Addr::new(254, 255, 255, 254)).is_global());
302
303 assert!(!IpAddr::V4(Ipv4Addr::new(255, 255, 255, 255)).is_global());
305 }
306
307 #[test]
308 #[allow(unstable_name_collisions)]
309 fn test_is_global_v6() {
310 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!(
314 IpAddr::V6(Ipv6Addr::from_str("2005:1db8:85a3:0000:0000:8a2e:0370:7334").unwrap())
315 .is_global()
316 ); assert!(!IpAddr::V6(Ipv6Addr::UNSPECIFIED).is_global());
320
321 assert!(!IpAddr::V6(Ipv6Addr::LOCALHOST).is_global());
323
324 assert!(!IpAddr::V6(Ipv6Addr::from_str("::ffff:192.0.2.128").unwrap()).is_global());
326
327 assert!(!IpAddr::V6(Ipv6Addr::from_str("64:ff9b:1::1").unwrap()).is_global());
329
330 assert!(!IpAddr::V6(Ipv6Addr::from_str("100::1").unwrap()).is_global());
332
333 assert!(!IpAddr::V6(Ipv6Addr::from_str("100:0:0:1::1").unwrap()).is_global());
335 assert!(
336 !IpAddr::V6(Ipv6Addr::from_str("100:0:0:1:ffff:ffff:ffff:ffff").unwrap()).is_global()
337 );
338 assert!(IpAddr::V6(Ipv6Addr::from_str("100:0:0:2::1").unwrap()).is_global()); assert!(!IpAddr::V6(Ipv6Addr::from_str("2001:0::1").unwrap()).is_global()); assert!(!IpAddr::V6(Ipv6Addr::from_str("2001:1ff::1").unwrap()).is_global()); assert!(IpAddr::V6(Ipv6Addr::from_str("2001:200::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:1::3").unwrap()).is_global()); assert!(!IpAddr::V6(Ipv6Addr::from_str("2001:1::4").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());
357
358 assert!(!IpAddr::V6(Ipv6Addr::from_str("2001:db8::1").unwrap()).is_global());
360 assert!(!IpAddr::V6(Ipv6Addr::from_str("3fff::1").unwrap()).is_global());
361 assert!(
362 !IpAddr::V6(Ipv6Addr::from_str("3fff:fff:ffff:ffff:ffff:ffff:ffff:ffff").unwrap())
363 .is_global()
364 );
365 assert!(IpAddr::V6(Ipv6Addr::from_str("3fff:1000::1").unwrap()).is_global()); assert!(!IpAddr::V6(Ipv6Addr::from_str("5f00::1").unwrap()).is_global());
369 assert!(
370 !IpAddr::V6(Ipv6Addr::from_str("5f00:ffff:ffff:ffff:ffff:ffff:ffff:ffff").unwrap())
371 .is_global()
372 );
373 assert!(IpAddr::V6(Ipv6Addr::from_str("5eff::1").unwrap()).is_global()); assert!(IpAddr::V6(Ipv6Addr::from_str("5f01::1").unwrap()).is_global()); assert!(!IpAddr::V6(Ipv6Addr::from_str("fc00::1").unwrap()).is_global()); assert!(
379 !IpAddr::V6(Ipv6Addr::from_str("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff").unwrap())
380 .is_global()
381 ); assert!(!IpAddr::V6(Ipv6Addr::from_str("fe80::1").unwrap()).is_global());
385
386 assert!(IpAddr::V6(Ipv6Addr::from_str("ff00::1").unwrap()).is_global());
388
389 assert!(IpAddr::V6(Ipv6Addr::from_str("2003::1").unwrap()).is_global());
391 }
392
393 #[test]
394 #[allow(unstable_name_collisions)]
395 fn test_is_global_ipaddr() {
396 assert!(IpAddr::V4(Ipv4Addr::from_str("1.2.3.4").unwrap()).is_global());
399 assert!(!IpAddr::V4(Ipv4Addr::from_str("10.0.0.1").unwrap()).is_global());
401
402 assert!(IpAddr::V6(Ipv6Addr::from_str("2001:4860:4860::8888").unwrap()).is_global());
404 assert!(!IpAddr::V6(Ipv6Addr::from_str("fe80::1").unwrap()).is_global());
406 }
407}