1use std::{
20 net::{IpAddr, Ipv4Addr, Ipv6Addr},
21 str::FromStr,
22};
23use thiserror::Error;
24
25#[derive(Debug, Error, PartialEq)]
27pub enum IpError {
28 #[error("Invalid range: start address {0} is greater than end address {1}")]
30 InvalidRange(IpAddr, IpAddr),
31
32 #[error("Invalid CIDR prefix: {0}")]
34 InvalidPrefix(u8),
35
36 #[error("Network error: {0}")]
38 NetworkError(String),
39
40 #[error("Failed to parse IP address: {0}")]
42 AddrParse(#[from] std::net::AddrParseError),
43
44 #[error("Invalid IP range format: {0}")]
46 InvalidFormat(String),
47
48 #[error("Invalid prefix number format: {0}")]
50 PrefixParse(#[from] std::num::ParseIntError),
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
61pub struct Ipv4Range {
62 pub start_addr: Ipv4Addr,
64 pub end_addr: Ipv4Addr,
66}
67
68impl Ipv4Range {
69 pub fn new(start: Ipv4Addr, end: Ipv4Addr) -> Result<Self, IpError> {
75 if u32::from(start) <= u32::from(end) {
76 Ok(Self {
77 start_addr: start,
78 end_addr: end,
79 })
80 } else {
81 Err(IpError::InvalidRange(IpAddr::V4(start), IpAddr::V4(end)))
82 }
83 }
84
85 pub fn to_iter(&self) -> impl Iterator<Item = IpAddr> {
92 let start: u32 = self.start_addr.into();
93 let end: u32 = self.end_addr.into();
94 (start..=end).map(|ip| IpAddr::V4(Ipv4Addr::from(ip)))
95 }
96
97 pub fn contains(&self, ip: &Ipv4Addr) -> bool {
99 let start: u32 = self.start_addr.into();
100 let end: u32 = self.end_addr.into();
101 let ip_u32: u32 = (*ip).into();
102 ip_u32 >= start && ip_u32 <= end
103 }
104
105 pub fn len(&self) -> u64 {
107 let s_u32: u64 = u32::from(self.start_addr) as u64;
108 let e_u32: u64 = u32::from(self.end_addr) as u64;
109 (e_u32 - s_u32) + 1
110 }
111
112 pub fn is_empty(&self) -> bool {
114 self.len() == 0
115 }
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
126pub struct Ipv6Range {
127 pub start_addr: Ipv6Addr,
129 pub end_addr: Ipv6Addr,
131}
132
133impl Ipv6Range {
134 pub fn new(start: Ipv6Addr, end: Ipv6Addr) -> Result<Self, IpError> {
140 if u128::from(start) <= u128::from(end) {
141 Ok(Self {
142 start_addr: start,
143 end_addr: end,
144 })
145 } else {
146 Err(IpError::InvalidRange(IpAddr::V6(start), IpAddr::V6(end)))
147 }
148 }
149
150 pub fn to_iter(&self) -> impl Iterator<Item = IpAddr> {
158 let start: u128 = self.start_addr.into();
159 let end: u128 = self.end_addr.into();
160 (start..=end).map(|ip| IpAddr::V6(Ipv6Addr::from(ip)))
161 }
162
163 pub fn contains(&self, ip: &Ipv6Addr) -> bool {
165 let start: u128 = self.start_addr.into();
166 let end: u128 = self.end_addr.into();
167 let ip_u128: u128 = (*ip).into();
168 ip_u128 >= start && ip_u128 <= end
169 }
170
171 pub fn len(&self) -> u128 {
173 let s_u128: u128 = u128::from(self.start_addr);
174 let e_u128: u128 = u128::from(self.end_addr);
175 (e_u128 - s_u128) + 1
176 }
177
178 pub fn is_empty(&self) -> bool {
180 self.len() == 0
181 }
182}
183
184#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
193pub enum IpRange {
194 V4(Ipv4Range),
196 V6(Ipv6Range),
198}
199
200impl IpRange {
201 pub fn start_addr(&self) -> IpAddr {
203 match self {
204 IpRange::V4(r) => IpAddr::V4(r.start_addr),
205 IpRange::V6(r) => IpAddr::V6(r.start_addr),
206 }
207 }
208
209 pub fn end_addr(&self) -> IpAddr {
211 match self {
212 IpRange::V4(r) => IpAddr::V4(r.end_addr),
213 IpRange::V6(r) => IpAddr::V6(r.end_addr),
214 }
215 }
216
217 pub fn contains(&self, ip: &IpAddr) -> bool {
222 match (self, ip) {
223 (IpRange::V4(r), IpAddr::V4(ip)) => r.contains(ip),
224 (IpRange::V6(r), IpAddr::V6(ip)) => r.contains(ip),
225 _ => false,
226 }
227 }
228
229 pub fn len(&self) -> u128 {
231 match self {
232 IpRange::V4(r) => r.len() as u128,
233 IpRange::V6(r) => r.len(),
234 }
235 }
236
237 pub fn is_empty(&self) -> bool {
239 self.len() == 0
240 }
241}
242
243impl FromStr for IpRange {
244 type Err = IpError;
245
246 fn from_str(s: &str) -> Result<Self, Self::Err> {
253 let s = s.trim();
254
255 if let Some(pos) = s.find('/') {
257 let ip = s[..pos].parse::<IpAddr>()?;
258 let prefix = s[pos + 1..].parse::<u8>()?;
259 return cidr_range(ip, prefix);
260 }
261
262 if let Some(pos) = s.find('-') {
264 let start_str = s[..pos].trim();
265 let end_str = s[pos + 1..].trim();
266
267 if let Ok(start) = start_str.parse::<Ipv4Addr>() {
268 let end = end_str.parse::<Ipv4Addr>()?;
269 return Ok(IpRange::V4(Ipv4Range::new(start, end)?));
270 } else if let Ok(start) = start_str.parse::<Ipv6Addr>() {
271 let end = end_str.parse::<Ipv6Addr>()?;
272 return Ok(IpRange::V6(Ipv6Range::new(start, end)?));
273 }
274 return Err(IpError::InvalidFormat(s.to_string()));
275 }
276
277 let ip = s.parse::<IpAddr>()?;
279 match ip {
280 IpAddr::V4(v4) => Ok(IpRange::V4(Ipv4Range::new(v4, v4).unwrap())),
281 IpAddr::V6(v6) => Ok(IpRange::V6(Ipv6Range::new(v6, v6).unwrap())),
282 }
283 }
284}
285
286pub fn cidr_range(ip: IpAddr, prefix: u8) -> Result<IpRange, IpError> {
298 match ip {
299 IpAddr::V4(v4) => {
300 if prefix > 32 {
301 return Err(IpError::InvalidPrefix(prefix));
302 }
303
304 let ip_u32 = u32::from(v4);
305 let mask = if prefix == 0 {
306 0
307 } else {
308 !u32::MAX.checked_shr(prefix as u32).unwrap_or(0)
309 };
310
311 let network = ip_u32 & mask;
312 let broadcast = ip_u32 | !mask;
313
314 Ok(IpRange::V4(
315 Ipv4Range::new(Ipv4Addr::from(network), Ipv4Addr::from(broadcast)).unwrap(),
316 ))
317 }
318 IpAddr::V6(v6) => {
319 if prefix > 128 {
320 return Err(IpError::InvalidPrefix(prefix));
321 }
322
323 let ip_u128 = u128::from(v6);
324 let mask = if prefix == 0 {
325 0
326 } else {
327 !u128::MAX.checked_shr(prefix as u32).unwrap_or(0)
328 };
329
330 let network = ip_u128 & mask;
331 let broadcast = ip_u128 | !mask;
332
333 Ok(IpRange::V6(
334 Ipv6Range::new(Ipv6Addr::from(network), Ipv6Addr::from(broadcast)).unwrap(),
335 ))
336 }
337 }
338}
339
340#[cfg(test)]
350mod tests {
351 use super::*;
352
353 #[test]
356 fn new_valid_v4() {
357 let start = Ipv4Addr::new(192, 168, 1, 1);
358 let end = Ipv4Addr::new(192, 168, 1, 10);
359 let range = Ipv4Range::new(start, end).unwrap();
360 assert_eq!(range.start_addr, start);
361 assert_eq!(range.end_addr, end);
362 }
363
364 #[test]
365 fn len_calculations_v4() {
366 let cases = vec![
367 (Ipv4Addr::new(10, 0, 0, 0), Ipv4Addr::new(10, 0, 0, 0), 1),
368 (
369 Ipv4Addr::new(10, 0, 0, 0),
370 Ipv4Addr::new(10, 0, 0, 255),
371 256,
372 ),
373 (Ipv4Addr::new(0, 0, 0, 0), Ipv4Addr::new(0, 0, 0, 10), 11),
374 ];
375
376 for (start, end, expected_len) in cases {
377 let range = Ipv4Range::new(start, end).unwrap();
378 assert_eq!(range.len(), expected_len);
379 }
380 }
381
382 #[test]
383 fn contains_logic_v4() {
384 let range =
385 Ipv4Range::new(Ipv4Addr::new(172, 16, 0, 10), Ipv4Addr::new(172, 16, 0, 20)).unwrap();
386 assert!(range.contains(&Ipv4Addr::new(172, 16, 0, 10)));
387 assert!(range.contains(&Ipv4Addr::new(172, 16, 0, 15)));
388 assert!(range.contains(&Ipv4Addr::new(172, 16, 0, 20)));
389 assert!(!range.contains(&Ipv4Addr::new(172, 16, 0, 9)));
390 assert!(!range.contains(&Ipv4Addr::new(172, 16, 0, 21)));
391 }
392
393 #[test]
394 fn iteration_values_v4() {
395 let range = Ipv4Range::new(Ipv4Addr::new(1, 1, 1, 1), Ipv4Addr::new(1, 1, 1, 3)).unwrap();
396 let ips: Vec<IpAddr> = range.to_iter().collect();
397 assert_eq!(
398 ips,
399 vec![
400 IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)),
401 IpAddr::V4(Ipv4Addr::new(1, 1, 1, 2)),
402 IpAddr::V4(Ipv4Addr::new(1, 1, 1, 3)),
403 ]
404 );
405 }
406
407 #[test]
408 fn max_u32_range_boundaries() {
409 let start = Ipv4Addr::new(255, 255, 255, 254);
410 let end = Ipv4Addr::new(255, 255, 255, 255);
411 let range = Ipv4Range::new(start, end).unwrap();
412 assert_eq!(range.len(), 2);
413 }
414
415 #[test]
418 fn ipv6_range_basics() {
419 let start = Ipv6Addr::from(100);
420 let end = Ipv6Addr::from(200);
421 let range = Ipv6Range::new(start, end).unwrap();
422 assert_eq!(range.len(), 101);
423 assert!(range.contains(&Ipv6Addr::from(150)));
424 assert!(!range.contains(&Ipv6Addr::from(201)));
425 }
426
427 #[test]
428 fn ipv6_large_len() {
429 let range = cidr_range(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 64).unwrap();
430 assert_eq!(range.len(), 1u128 << 64);
431 }
432
433 #[test]
434 fn iteration_ipv6_small() {
435 let range = Ipv6Range::new(Ipv6Addr::from(1), Ipv6Addr::from(3)).unwrap();
436 let ips: Vec<_> = range.to_iter().collect();
437 assert_eq!(ips.len(), 3);
438 assert_eq!(ips[0], IpAddr::V6(Ipv6Addr::from(1)));
439 }
440
441 #[test]
444 fn from_str_comprehensive() {
445 assert_eq!("10.0.0.0/24".parse::<IpRange>().unwrap().len(), 256);
446 assert_eq!("192.168.1.0/24".parse::<IpRange>().unwrap().len(), 256);
447 assert_eq!("::1/120".parse::<IpRange>().unwrap().len(), 256);
448 assert_eq!("1.1.1.1-1.1.1.5".parse::<IpRange>().unwrap().len(), 5);
449 assert_eq!("8.8.8.8".parse::<IpRange>().unwrap().len(), 1);
450 }
451
452 #[test]
453 fn invalid_range_order() {
454 let v4_err = Ipv4Range::new(Ipv4Addr::new(1, 1, 1, 2), Ipv4Addr::new(1, 1, 1, 1));
455 assert!(matches!(v4_err, Err(IpError::InvalidRange(_, _))));
456
457 let v6_err = Ipv6Range::new(Ipv6Addr::from(2), Ipv6Addr::from(1));
458 assert!(matches!(v6_err, Err(IpError::InvalidRange(_, _))));
459 }
460
461 #[test]
462 fn error_formatting() {
463 let prefix_err = IpError::InvalidPrefix(40);
464 assert_eq!(format!("{prefix_err}"), "Invalid CIDR prefix: 40");
465
466 let range_err = IpError::InvalidRange(
467 IpAddr::V4(Ipv4Addr::new(1, 1, 1, 2)),
468 IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)),
469 );
470 assert!(format!("{range_err}").contains("is greater than"));
471 }
472}
473
474#[cfg(test)]
475mod property_tests {
476 use super::*;
477 use proptest::prelude::*;
478
479 fn any_ipv4() -> impl Strategy<Value = Ipv4Addr> {
480 proptest::prelude::any::<u32>().prop_map(Ipv4Addr::from)
481 }
482
483 fn any_ipv6() -> impl Strategy<Value = Ipv6Addr> {
484 proptest::prelude::any::<u128>().prop_map(Ipv6Addr::from)
485 }
486
487 fn any_ipv4_range() -> impl Strategy<Value = Ipv4Range> {
488 (any_ipv4(), 0..5000u32).prop_map(|(start, len)| {
489 let start_u32 = u32::from(start);
490 let end_u32 = start_u32.saturating_add(len);
491 Ipv4Range::new(start, Ipv4Addr::from(end_u32)).unwrap()
492 })
493 }
494
495 fn any_ipv6_range() -> impl Strategy<Value = Ipv6Range> {
496 (any_ipv6(), 0..5000u128).prop_map(|(start, len)| {
497 let start_u128 = u128::from(start);
498 let end_u128 = start_u128.saturating_add(len);
499 Ipv6Range::new(start, Ipv6Addr::from(end_u128)).unwrap()
500 })
501 }
502
503 proptest::proptest! {
504 #[test]
505 fn ipv4_range_invariant(a in any_ipv4(), b in any_ipv4()) {
506 let start = std::cmp::min(a, b);
507 let end = std::cmp::max(a, b);
508 let range = Ipv4Range::new(start, end).unwrap();
509 prop_assert!(range.contains(&start));
510 prop_assert!(range.contains(&end));
511 prop_assert_eq!(range.len(), (u32::from(end) - u32::from(start)) as u64 + 1);
512 }
513
514 #[test]
515 fn ipv6_range_invariant(a in any_ipv6(), b in any_ipv6()) {
516 let start = std::cmp::min(a, b);
517 let end = std::cmp::max(a, b);
518 let range = Ipv6Range::new(start, end).unwrap();
519 prop_assert!(range.contains(&start));
520 prop_assert!(range.contains(&end));
521 prop_assert_eq!(range.len(), (u128::from(end) - u128::from(start)) + 1);
522 }
523
524 #[test]
525 fn ipv4_iterator_consistency(range in any_ipv4_range()) {
526 prop_assert_eq!(range.to_iter().count() as u64, range.len());
527 }
528
529 #[test]
530 fn ipv6_iterator_consistency(range in any_ipv6_range()) {
531 prop_assert_eq!(range.to_iter().count() as u128, range.len());
532 }
533
534 #[test]
535 fn cidr_v4_roundtrip(v4 in any_ipv4(), prefix in 1..=32u8) {
536 let range = cidr_range(IpAddr::V4(v4), prefix).unwrap();
537 prop_assert_eq!(range.len(), 1u128 << (32 - prefix));
538 }
539
540 #[test]
541 fn cidr_v6_roundtrip(v6 in any_ipv6(), prefix in 1..=128u8) {
542 let range = cidr_range(IpAddr::V6(v6), prefix).unwrap();
543 prop_assert_eq!(range.len(), 1u128 << (128 - prefix));
544 }
545 }
546}