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 {
39 max
40 } else {
41 bits
42 }
43 }
44
45 #[inline]
47 const fn mask_ipv4(bits: u8) -> u32 {
48 if bits == 0 {
49 return 0;
50 }
51
52 (!0u32) << (32 - bits as u32)
53 }
54
55 #[inline]
57 const fn mask_ipv6(bits: u8) -> u128 {
58 if bits == 0 {
59 return 0;
60 }
61
62 (!0u128) << (128 - bits as u32)
63 }
64}
65
66#[inline]
68fn ipv4_subnet(ip: Ipv4Addr, mask: &SubnetMask) -> IpAddr {
69 IpAddr::V4(Ipv4Addr::from(u32::from(ip) & mask.ipv4))
70}
71
72#[inline]
74fn ipv6_subnet(ip: Ipv6Addr, mask: &SubnetMask) -> IpAddr {
75 IpAddr::V6(Ipv6Addr::from(u128::from(ip) & mask.ipv6))
76}
77
78pub trait IpAddrExt {
80 fn subnet(&self, mask: &SubnetMask) -> Subnet;
82
83 fn is_global(&self) -> bool;
87}
88
89impl IpAddrExt for IpAddr {
90 fn subnet(&self, mask: &SubnetMask) -> Subnet {
91 match self {
92 IpAddr::V4(v4) => Subnet {
93 addr: ipv4_subnet(*v4, mask),
94 },
95 IpAddr::V6(v6) => {
96 if let Some(v4) = v6.to_ipv4_mapped() {
97 return Subnet {
98 addr: ipv4_subnet(v4, mask),
99 };
100 }
101
102 Subnet {
103 addr: ipv6_subnet(*v6, mask),
104 }
105 }
106 }
107 }
108
109 fn is_global(&self) -> bool {
110 match self {
111 IpAddr::V4(ip) => is_global_v4(*ip),
112 IpAddr::V6(ip) => is_global_v6(*ip),
113 }
114 }
115}
116
117#[inline]
118const fn is_future_protocol_v4(ip: Ipv4Addr) -> bool {
119 ip.octets()[0] == 192
120 && ip.octets()[1] == 0
121 && ip.octets()[2] == 0
122 && ip.octets()[3] != 9
123 && ip.octets()[3] != 10
124}
125
126#[inline]
127const fn is_shared_v4(ip: Ipv4Addr) -> bool {
128 ip.octets()[0] == 100 && (ip.octets()[1] & 0b1100_0000 == 0b0100_0000)
129}
130
131#[inline]
132const fn is_benchmarking_v4(ip: Ipv4Addr) -> bool {
133 ip.octets()[0] == 198 && (ip.octets()[1] & 0xfe) == 18
134}
135
136#[inline]
137const fn is_reserved_v4(ip: Ipv4Addr) -> bool {
138 ip.octets()[0] & 240 == 240 && !ip.is_broadcast()
139}
140
141#[inline]
142const fn is_global_v4(ip: Ipv4Addr) -> bool {
143 !(ip.octets()[0] == 0 || ip.is_private()
145 || is_shared_v4(ip)
146 || ip.is_loopback()
147 || ip.is_link_local()
148 || is_future_protocol_v4(ip)
149 || ip.is_documentation()
150 || is_benchmarking_v4(ip)
151 || is_reserved_v4(ip)
152 || ip.is_broadcast())
153}
154
155#[inline]
156const fn is_documentation_v6(ip: Ipv6Addr) -> bool {
157 (ip.segments()[0] == 0x2001) && (ip.segments()[1] == 0xdb8)
158}
159
160#[inline]
161const fn is_unique_local_v6(ip: Ipv6Addr) -> bool {
162 (ip.segments()[0] & 0xfe00) == 0xfc00
163}
164
165#[inline]
166const fn is_unicast_link_local_v6(ip: Ipv6Addr) -> bool {
167 (ip.segments()[0] & 0xffc0) == 0xfe80
168}
169
170#[inline]
171const fn is_global_v6(ip: Ipv6Addr) -> bool {
172 !(ip.is_unspecified()
173 || ip.is_loopback()
174 || matches!(ip.segments(), [0, 0, 0, 0, 0, 0xffff, _, _])
176 || matches!(ip.segments(), [0x64, 0xff9b, 1, _, _, _, _, _])
178 || matches!(ip.segments(), [0x100, 0, 0, 0, _, _, _, _])
180 || (matches!(ip.segments(), [0x2001, b, _, _, _, _, _, _] if b < 0x200)
182 && !(
183 u128::from_be_bytes(ip.octets()) == 0x2001_0001_0000_0000_0000_0000_0000_0001
185 || u128::from_be_bytes(ip.octets()) == 0x2001_0001_0000_0000_0000_0000_0000_0002
187 || matches!(ip.segments(), [0x2001, 3, _, _, _, _, _, _])
189 || matches!(ip.segments(), [0x2001, 4, 0x112, _, _, _, _, _])
191 || matches!(ip.segments(), [0x2001, b, _, _, _, _, _, _] if b >= 0x20 && b <= 0x3F)
194 ))
195 || matches!(ip.segments(), [0x2002, _, _, _, _, _, _, _])
198 || is_documentation_v6(ip)
199 || is_unique_local_v6(ip)
200 || is_unicast_link_local_v6(ip))
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206 use std::str::FromStr;
207
208 const TEST_MASK: SubnetMask = SubnetMask::new(24, 48);
210
211 #[test]
212 fn ipv4_subnet_zeroes_lower_8_bits() {
213 let ip = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 123));
214 assert_eq!(
215 ip.subnet(&TEST_MASK).addr,
216 IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0))
217 );
218 }
219
220 #[test]
221 fn ipv6_subnet_zeroes_lower_80_bits() {
222 let ip = IpAddr::V6(Ipv6Addr::new(
223 0x2001, 0xdb8, 0x1234, 0x5678, 0x9abc, 0xdef0, 0x1357, 0x2468,
224 ));
225 assert_eq!(
226 ip.subnet(&TEST_MASK).addr,
227 IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0x1234, 0, 0, 0, 0, 0))
228 );
229 }
230
231 #[test]
232 fn ipv4_mapped_ipv6_subnet_uses_ipv4_truncation() {
233 let ip = IpAddr::from_str("::ffff:192.168.1.123").unwrap();
234 assert_eq!(
235 ip.subnet(&TEST_MASK).addr,
236 IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0))
237 );
238 }
239
240 #[test]
241 fn subnet_mask_max() {
242 let mask = SubnetMask::new(40, 200);
243 assert_eq!(mask.ipv4, u32::MAX);
244 assert_eq!(mask.ipv6, u128::MAX);
245 }
246
247 #[test]
248 fn subnet_mask_min() {
249 let mask = SubnetMask::new(0, 0);
250 assert_eq!(mask.ipv4, 0);
251 assert_eq!(mask.ipv6, 0);
252 }
253
254 #[test]
255 #[allow(unstable_name_collisions)]
256 fn test_is_global_v4() {
257 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());
267
268 assert!(!IpAddr::V4(Ipv4Addr::new(100, 64, 0, 1)).is_global());
270 assert!(!IpAddr::V4(Ipv4Addr::new(100, 127, 255, 254)).is_global());
271
272 assert!(!IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)).is_global());
274 assert!(!IpAddr::V4(Ipv4Addr::new(127, 255, 255, 254)).is_global());
275
276 assert!(!IpAddr::V4(Ipv4Addr::new(169, 254, 0, 1)).is_global());
278 assert!(!IpAddr::V4(Ipv4Addr::new(169, 254, 255, 254)).is_global());
279
280 assert!(!IpAddr::V4(Ipv4Addr::new(192, 0, 0, 1)).is_global());
282 assert!(!IpAddr::V4(Ipv4Addr::new(192, 0, 0, 254)).is_global());
283 assert!(IpAddr::V4(Ipv4Addr::new(192, 0, 0, 9)).is_global());
285 assert!(IpAddr::V4(Ipv4Addr::new(192, 0, 0, 10)).is_global());
286
287 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());
294 assert!(!IpAddr::V4(Ipv4Addr::new(198, 19, 255, 254)).is_global());
295
296 assert!(!IpAddr::V4(Ipv4Addr::new(240, 0, 0, 1)).is_global());
298 assert!(!IpAddr::V4(Ipv4Addr::new(254, 255, 255, 254)).is_global());
299
300 assert!(!IpAddr::V4(Ipv4Addr::new(255, 255, 255, 255)).is_global());
302 }
303
304 #[test]
305 #[allow(unstable_name_collisions)]
306 fn test_is_global_v6() {
307 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!(
311 IpAddr::V6(Ipv6Addr::from_str("2005:1db8:85a3:0000:0000:8a2e:0370:7334").unwrap())
312 .is_global()
313 ); assert!(!IpAddr::V6(Ipv6Addr::UNSPECIFIED).is_global());
317
318 assert!(!IpAddr::V6(Ipv6Addr::LOCALHOST).is_global());
320
321 assert!(!IpAddr::V6(Ipv6Addr::from_str("::ffff:192.0.2.128").unwrap()).is_global());
323
324 assert!(!IpAddr::V6(Ipv6Addr::from_str("64:ff9b:1::1").unwrap()).is_global());
326
327 assert!(!IpAddr::V6(Ipv6Addr::from_str("100::1").unwrap()).is_global());
329
330 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());
344
345 assert!(!IpAddr::V6(Ipv6Addr::from_str("2001:db8::1").unwrap()).is_global());
347
348 assert!(!IpAddr::V6(Ipv6Addr::from_str("fc00::1").unwrap()).is_global()); assert!(!IpAddr::V6(
351 Ipv6Addr::from_str("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff").unwrap()
352 )
353 .is_global()); assert!(!IpAddr::V6(Ipv6Addr::from_str("fe80::1").unwrap()).is_global());
357
358 assert!(IpAddr::V6(Ipv6Addr::from_str("ff00::1").unwrap()).is_global());
360
361 assert!(IpAddr::V6(Ipv6Addr::from_str("2003::1").unwrap()).is_global());
363 }
364
365 #[test]
366 #[allow(unstable_name_collisions)]
367 fn test_is_global_ipaddr() {
368 assert!(IpAddr::V4(Ipv4Addr::from_str("1.2.3.4").unwrap()).is_global());
371 assert!(!IpAddr::V4(Ipv4Addr::from_str("10.0.0.1").unwrap()).is_global());
373
374 assert!(IpAddr::V6(Ipv6Addr::from_str("2001:4860:4860::8888").unwrap()).is_global());
376 assert!(!IpAddr::V6(Ipv6Addr::from_str("fe80::1").unwrap()).is_global());
378 }
379}