1use std::fmt;
20use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
24pub enum Family {
25 V4,
26 V6,
27}
28
29impl Family {
30 pub const fn bits(self) -> u8 {
32 match self {
33 Family::V4 => 32,
34 Family::V6 => 128,
35 }
36 }
37
38 const fn max(self) -> u128 {
39 match self {
40 Family::V4 => u32::MAX as u128,
41 Family::V6 => u128::MAX,
42 }
43 }
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
49pub struct Range {
50 family: Family,
51 start: u128,
52 end: u128,
53}
54
55impl Range {
56 pub fn new(family: Family, start: u128, end: u128) -> Option<Self> {
59 (start <= end && end <= family.max()).then_some(Self { family, start, end })
60 }
61
62 pub fn from_ips(a: IpAddr, b: IpAddr) -> Option<Self> {
64 let ((fa, a), (fb, b)) = (from_ip(a), from_ip(b));
65 (fa == fb).then(|| Self {
66 family: fa,
67 start: a.min(b),
68 end: a.max(b),
69 })
70 }
71
72 pub fn family(&self) -> Family {
73 self.family
74 }
75
76 pub fn start(&self) -> u128 {
78 self.start
79 }
80
81 pub fn end(&self) -> u128 {
83 self.end
84 }
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
90pub struct Prefix {
91 family: Family,
92 addr: u128,
93 len: u8,
94}
95
96impl Prefix {
97 pub fn new(family: Family, addr: u128, len: u8) -> Option<Self> {
100 (len <= family.bits() && addr <= family.max() && addr & host_mask(family, len) == 0)
101 .then_some(Self { family, addr, len })
102 }
103
104 pub fn family(&self) -> Family {
105 self.family
106 }
107
108 pub fn network(&self) -> IpAddr {
110 to_ip(self.family, self.addr)
111 }
112
113 pub fn prefix_len(&self) -> u8 {
115 self.len
116 }
117
118 pub fn is_host(&self) -> bool {
120 self.len == self.family.bits()
121 }
122
123 pub fn range(&self) -> Range {
125 Range {
126 family: self.family,
127 start: self.addr,
128 end: self.addr | host_mask(self.family, self.len),
129 }
130 }
131}
132
133impl fmt::Display for Prefix {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 if self.is_host() {
138 write!(f, "{}", self.network())
139 } else {
140 write!(f, "{}/{}", self.network(), self.len)
141 }
142 }
143}
144
145#[derive(Debug, Clone, PartialEq, Eq)]
147#[non_exhaustive]
148pub enum ParseError {
149 InvalidAddress,
151 InvalidPrefixLength,
153 InvalidRange,
155 SpfFamilyMismatch,
157}
158
159impl fmt::Display for ParseError {
160 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161 f.write_str(match self {
162 ParseError::InvalidAddress => "not a valid IPv4/IPv6 address",
163 ParseError::InvalidPrefixLength => "invalid prefix length",
164 ParseError::InvalidRange => "invalid address range",
165 ParseError::SpfFamilyMismatch => "SPF mechanism does not match address family",
166 })
167 }
168}
169
170impl std::error::Error for ParseError {}
171
172pub fn parse_entry(input: &str, spf: bool) -> Result<Range, ParseError> {
186 let mut s = input.trim();
187 let mut want = None;
188 if spf {
189 if let Some(rest) = strip_prefix_ci(s, "ip4:") {
190 want = Some(Family::V4);
191 s = rest;
192 } else if let Some(rest) = strip_prefix_ci(s, "ip6:") {
193 want = Some(Family::V6);
194 s = rest;
195 }
196 }
197
198 let range = if let Some((a, b)) = s.split_once('-') {
199 let (fa, start) = parse_addr(a.trim())?;
200 let (fb, end) = parse_addr(b.trim())?;
201 if fa != fb {
202 return Err(ParseError::InvalidRange);
203 }
204 Range::new(fa, start, end).ok_or(ParseError::InvalidRange)?
205 } else if let Some((a, len)) = s.split_once('/') {
206 let (family, addr) = parse_addr(a)?;
207 let len = parse_len(len, family)?;
208 let host = host_mask(family, len);
209 Range {
210 family,
211 start: addr & !host,
212 end: (addr & !host) | host,
213 }
214 } else {
215 let (family, addr) = parse_addr(s)?;
216 Range {
217 family,
218 start: addr,
219 end: addr,
220 }
221 };
222
223 match want {
224 Some(f) if f != range.family => Err(ParseError::SpfFamilyMismatch),
225 _ => Ok(range),
226 }
227}
228
229#[derive(Debug, Default, Clone)]
248pub struct Aggregator {
249 include: Ranges,
250 exclude: Ranges,
251}
252
253#[derive(Debug, Default, Clone)]
254struct Ranges {
255 v4: Vec<(u128, u128)>,
256 v6: Vec<(u128, u128)>,
257}
258
259impl Ranges {
260 fn push(&mut self, r: Range) {
261 match r.family {
262 Family::V4 => self.v4.push((r.start, r.end)),
263 Family::V6 => self.v6.push((r.start, r.end)),
264 }
265 }
266
267 fn get(&self, family: Family) -> &[(u128, u128)] {
268 match family {
269 Family::V4 => &self.v4,
270 Family::V6 => &self.v6,
271 }
272 }
273}
274
275impl Aggregator {
276 pub fn new() -> Self {
277 Self::default()
278 }
279
280 pub fn add(&mut self, r: Range) {
281 self.include.push(r);
282 }
283
284 pub fn add_str(&mut self, input: &str, spf: bool) -> Result<(), ParseError> {
286 self.add(parse_entry(input, spf)?);
287 Ok(())
288 }
289
290 pub fn exclude(&mut self, r: Range) {
292 self.exclude.push(r);
293 }
294
295 pub fn exclude_str(&mut self, input: &str, spf: bool) -> Result<(), ParseError> {
297 self.exclude(parse_entry(input, spf)?);
298 Ok(())
299 }
300
301 pub fn is_empty(&self) -> bool {
303 self.include.v4.is_empty() && self.include.v6.is_empty()
304 }
305
306 pub fn summarize(&self) -> Vec<Prefix> {
308 let mut out = Vec::new();
309 for family in [Family::V4, Family::V6] {
310 let include = merge(self.include.get(family));
311 let exclude = merge(self.exclude.get(family));
312 for (start, end) in subtract(&include, &exclude) {
313 split(family, start, end, &mut out);
314 }
315 }
316 out
317 }
318}
319
320fn merge(ranges: &[(u128, u128)]) -> Vec<(u128, u128)> {
322 let mut sorted = ranges.to_vec();
323 sorted.sort_unstable();
324 let mut merged: Vec<(u128, u128)> = Vec::with_capacity(sorted.len());
325 for (start, end) in sorted {
326 match merged.last_mut() {
327 Some(last) if start <= last.1.saturating_add(1) => last.1 = last.1.max(end),
328 _ => merged.push((start, end)),
329 }
330 }
331 merged
332}
333
334fn subtract(include: &[(u128, u128)], exclude: &[(u128, u128)]) -> Vec<(u128, u128)> {
336 let mut out = Vec::with_capacity(include.len());
337 let mut first = 0; for &(mut start, end) in include {
339 while first < exclude.len() && exclude[first].1 < start {
340 first += 1;
341 }
342 let mut alive = true;
343 for &(x_start, x_end) in exclude[first..].iter().take_while(|x| x.0 <= end) {
344 if x_start > start {
345 out.push((start, x_start - 1));
346 }
347 if x_end >= end {
348 alive = false;
349 break;
350 }
351 start = x_end + 1; }
353 if alive {
354 out.push((start, end));
355 }
356 }
357 out
358}
359
360fn split(family: Family, mut start: u128, end: u128, out: &mut Vec<Prefix>) {
362 let width = u32::from(family.bits());
363 loop {
364 let align = if start == 0 {
365 width
366 } else {
367 start.trailing_zeros().min(width)
368 };
369 let span = end - start; let fit = if span == u128::MAX {
371 128
372 } else {
373 127 - (span + 1).leading_zeros()
374 };
375 let block = align.min(fit);
376 out.push(Prefix {
377 family,
378 addr: start,
379 len: (width - block) as u8,
380 });
381 match 1u128
382 .checked_shl(block)
383 .and_then(|size| start.checked_add(size))
384 {
385 Some(next) if next <= end && next <= family.max() => start = next,
386 _ => break,
387 }
388 }
389}
390
391fn parse_addr(s: &str) -> Result<(Family, u128), ParseError> {
392 s.parse::<IpAddr>()
395 .map(from_ip)
396 .map_err(|_| ParseError::InvalidAddress)
397}
398
399fn from_ip(ip: IpAddr) -> (Family, u128) {
400 match ip {
401 IpAddr::V4(a) => (Family::V4, u128::from(u32::from(a))),
402 IpAddr::V6(a) => (Family::V6, u128::from(a)),
403 }
404}
405
406fn parse_len(s: &str, family: Family) -> Result<u8, ParseError> {
407 let plain = !s.is_empty()
408 && s.len() <= 3
409 && s.bytes().all(|b| b.is_ascii_digit())
410 && (s == "0" || !s.starts_with('0'));
411 match s.parse::<u8>() {
412 Ok(n) if plain && n <= family.bits() => Ok(n),
413 _ => Err(ParseError::InvalidPrefixLength),
414 }
415}
416
417fn host_mask(family: Family, len: u8) -> u128 {
418 let host_bits = u32::from(family.bits() - len);
419 if host_bits == 128 {
420 u128::MAX
421 } else {
422 (1u128 << host_bits) - 1
423 }
424}
425
426fn to_ip(family: Family, v: u128) -> IpAddr {
427 match family {
428 Family::V4 => IpAddr::V4(Ipv4Addr::from(v as u32)),
429 Family::V6 => IpAddr::V6(Ipv6Addr::from(v)),
430 }
431}
432
433fn strip_prefix_ci<'a>(s: &'a str, prefix: &str) -> Option<&'a str> {
434 let head = s.get(..prefix.len())?;
435 head.eq_ignore_ascii_case(prefix)
436 .then(|| &s[prefix.len()..])
437}
438
439#[cfg(test)]
440mod tests {
441 use super::*;
442
443 fn agg(lines: &[&str]) -> Vec<String> {
444 let mut a = Aggregator::new();
445 for l in lines {
446 a.add_str(l, false).unwrap();
447 }
448 a.summarize().iter().map(ToString::to_string).collect()
449 }
450
451 fn rejects(s: &str) -> ParseError {
452 parse_entry(s, false).unwrap_err()
453 }
454
455 #[test]
456 fn merges_adjacent_and_contained() {
457 assert_eq!(
458 agg(&["10.0.0.0/24", "10.0.1.0/24", "10.0.0.128/25"]),
459 ["10.0.0.0/23"]
460 );
461 assert_eq!(
462 agg(&["10.0.0.0/24", "10.0.2.0/24"]),
463 ["10.0.0.0/24", "10.0.2.0/24"]
464 );
465 }
466
467 #[test]
468 fn masks_host_bits() {
469 assert_eq!(agg(&["10.1.2.3/8"]), ["10.0.0.0/8"]);
470 assert_eq!(agg(&["2001:db8::1/32"]), ["2001:db8::/32"]);
471 }
472
473 #[test]
474 fn host_routes_print_bare_in_both_families() {
475 assert_eq!(agg(&["1.2.3.4", "1.2.3.4/32"]), ["1.2.3.4"]);
476 assert_eq!(agg(&["2001:db8::1", "2001:db8::1/128"]), ["2001:db8::1"]);
477 }
478
479 #[test]
480 fn full_address_spaces() {
481 assert_eq!(agg(&["0.0.0.0/0", "10.0.0.0/8"]), ["0.0.0.0/0"]);
482 assert_eq!(agg(&["::/0", "2001:db8::/32"]), ["::/0"]);
483 assert_eq!(agg(&["::/1", "8000::/1"]), ["::/0"]);
484 assert_eq!(
485 agg(&["255.255.255.255", "255.255.255.254"]),
486 ["255.255.255.254/31"]
487 );
488 assert_eq!(
489 agg(&["ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"]),
490 ["ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"]
491 );
492 }
493
494 #[test]
495 fn ranges_in_both_families() {
496 assert_eq!(agg(&["10.0.0.0-10.0.0.255"]), ["10.0.0.0/24"]);
497 assert_eq!(
498 agg(&["10.0.0.1 - 10.0.0.6"]),
499 ["10.0.0.1", "10.0.0.2/31", "10.0.0.4/31", "10.0.0.6"]
500 );
501 assert_eq!(agg(&["2001:db8::-2001:db8::ffff"]), ["2001:db8::/112"]);
502 assert_eq!(rejects("10.0.0.9-10.0.0.1"), ParseError::InvalidRange);
503 assert_eq!(rejects("10.0.0.1-::1"), ParseError::InvalidRange);
504 }
505
506 #[test]
507 fn rfc5952_output() {
508 assert_eq!(agg(&["1::/16"]), ["1::/16"]);
511 assert_eq!(agg(&["ab::/16"]), ["ab::/16"]);
512 assert_eq!(agg(&["2001:db8:0:1:1:1:1:1"]), ["2001:db8:0:1:1:1:1:1"]);
513 assert_eq!(
514 agg(&["2001:558:fe14:76:68:87:28:0/120"]),
515 ["2001:558:fe14:76:68:87:28:0/120"]
516 );
517 assert_eq!(agg(&["2001:DB8::/32"]), ["2001:db8::/32"]);
518 assert_eq!(agg(&["2001:0:0:1:0:0:0:1"]), ["2001:0:0:1::1"]);
519 assert_eq!(agg(&["::ffff:1.2.3.0/120"]), ["::ffff:1.2.3.0/120"]);
520 }
521
522 #[test]
523 fn rejects_ambiguous_input() {
524 assert_eq!(rejects("010.1.2.3"), ParseError::InvalidAddress);
525 assert_eq!(rejects("555.2.2.2/30"), ParseError::InvalidAddress);
526 assert_eq!(rejects("fe80::1%eth0"), ParseError::InvalidAddress);
527 assert_eq!(rejects("2001:db8::%1/64"), ParseError::InvalidAddress);
528 assert_eq!(rejects("garbage:"), ParseError::InvalidAddress);
529 assert_eq!(rejects("192.168.8.0/33"), ParseError::InvalidPrefixLength);
530 assert_eq!(rejects("2001:db8::/129"), ParseError::InvalidPrefixLength);
531 assert_eq!(rejects("10.0.0.0/01"), ParseError::InvalidPrefixLength);
532 assert_eq!(rejects("2001:db8::/032"), ParseError::InvalidPrefixLength);
533 assert_eq!(rejects("10.0.0.0/+8"), ParseError::InvalidPrefixLength);
534 assert_eq!(rejects("10.0.0.0/"), ParseError::InvalidPrefixLength);
535 }
536
537 #[test]
538 fn spf_prefixes() {
539 assert!(parse_entry("ip4:192.0.2.0/24", true).is_ok());
540 assert!(parse_entry("IP6:2001:db8::/32", true).is_ok());
541 assert_eq!(
542 parse_entry("ip4:2001:db8::/32", true),
543 Err(ParseError::SpfFamilyMismatch)
544 );
545 assert_eq!(
546 parse_entry("ip6:192.0.2.0/24", true),
547 Err(ParseError::SpfFamilyMismatch)
548 );
549 assert!(parse_entry("ip4:192.0.2.0/24", false).is_err());
551 }
552
553 #[test]
554 fn output_is_minimal_and_exact() {
555 let mut seed = 0x9e3779b97f4a7c15u64;
558 let mut rand = move || {
559 seed ^= seed << 13;
560 seed ^= seed >> 7;
561 seed ^= seed << 17;
562 seed
563 };
564 for _ in 0..500 {
565 let mut a = Aggregator::new();
566 let mut want = [false; 1024];
567 for _ in 0..(rand() % 20 + 1) {
568 let len = 22 + (rand() % 11) as u8;
569 let addr = (rand() % 1024) as u128;
570 let host = host_mask(Family::V4, len);
571 let (s, e) = (addr & !host, addr | host);
572 a.add(Range {
573 family: Family::V4,
574 start: s,
575 end: e,
576 });
577 want[s as usize..=e as usize]
578 .iter_mut()
579 .for_each(|w| *w = true);
580 }
581 let out = a.summarize();
582 let mut got = [false; 1024];
583 for p in &out {
584 let e = p.addr | host_mask(Family::V4, p.len);
585 got[p.addr as usize..=e as usize]
586 .iter_mut()
587 .for_each(|g| *g = true);
588 }
589 assert_eq!(want, got);
590 for w in out.windows(2) {
591 let (x, y) = (w[0], w[1]);
592 let sibling = x.len == y.len && x.addr ^ y.addr == 1u128 << (32 - x.len);
593 assert!(!sibling, "mergeable siblings {x} {y}");
594 }
595 }
596 }
597
598 #[test]
599 fn constructors_reject_invalid_values() {
600 assert!(Range::new(Family::V4, 0, u128::from(u32::MAX)).is_some());
601 assert!(Range::new(Family::V4, 1 << 40, 1 << 40).is_none());
602 assert!(Range::new(Family::V6, 10, 5).is_none());
603 assert!(Range::new(Family::V6, 0, u128::MAX).is_some());
604
605 let (a, b) = ("10.0.0.9".parse().unwrap(), "10.0.0.1".parse().unwrap());
606 assert_eq!(
607 Range::from_ips(a, b),
608 Range::new(Family::V4, 0x0a00_0001, 0x0a00_0009)
609 );
610 assert!(Range::from_ips(a, "::1".parse().unwrap()).is_none());
611
612 assert!(Prefix::new(Family::V4, 0x0a00_0000, 8).is_some());
613 assert!(Prefix::new(Family::V4, 0x0a00_0001, 8).is_none()); assert!(Prefix::new(Family::V4, 0, 33).is_none());
615 assert!(Prefix::new(Family::V4, 1 << 32, 32).is_none());
616 assert!(Prefix::new(Family::V6, 0, 129).is_none());
617 assert_eq!(
618 Prefix::new(Family::V4, 0x0a00_0000, 24).unwrap().range(),
619 Range::new(Family::V4, 0x0a00_0000, 0x0a00_00ff).unwrap()
620 );
621 }
622
623 fn agg_excluding(include: &[&str], exclude: &[&str]) -> Vec<String> {
624 let mut a = Aggregator::new();
625 include.iter().for_each(|l| a.add_str(l, false).unwrap());
626 exclude
627 .iter()
628 .for_each(|l| a.exclude_str(l, false).unwrap());
629 a.summarize().iter().map(ToString::to_string).collect()
630 }
631
632 #[test]
633 fn exclusion_splits_overlapping_prefixes() {
634 assert_eq!(
635 agg_excluding(&["10.0.0.0/24"], &["10.0.0.0/25"]),
636 ["10.0.0.128/25"]
637 );
638 assert_eq!(
639 agg_excluding(&["10.0.0.0/30"], &["10.0.0.1"]),
640 ["10.0.0.0", "10.0.0.2/31"]
641 );
642 assert_eq!(
644 agg_excluding(&["140.82.112.2", "140.82.112.3"], &["140.82.112.3"]),
645 ["140.82.112.2"]
646 );
647 assert_eq!(
649 agg_excluding(
650 &["10.0.0.0/24", "10.0.2.0/24", "10.0.4.0/24"],
651 &["10.0.0.128-10.0.4.127"]
652 ),
653 ["10.0.0.0/25", "10.0.4.128/25"]
654 );
655 assert_eq!(
657 agg_excluding(&["10.0.0.0/29"], &["10.0.0.1", "10.0.0.6"]),
658 ["10.0.0.0", "10.0.0.2/31", "10.0.0.4/31", "10.0.0.7"]
659 );
660 }
661
662 #[test]
663 fn exclusion_boundaries() {
664 assert_eq!(
666 agg_excluding(&["10.0.0.0/24"], &["10.0.1.0/24", "9.255.255.255"]),
667 ["10.0.0.0/24"]
668 );
669 assert!(agg_excluding(&["10.1.0.0/16"], &["10.0.0.0/8"]).is_empty());
671 assert!(agg_excluding(&["10.0.0.0/8"], &["10.0.0.0/8"]).is_empty());
672 assert!(agg_excluding(&[], &["10.0.0.0/8"]).is_empty());
674 assert_eq!(
676 agg_excluding(&["2001:db8::/32"], &["0.0.0.0/0"]),
677 ["2001:db8::/32"]
678 );
679 assert_eq!(agg_excluding(&["10.0.0.0/8"], &["::/0"]), ["10.0.0.0/8"]);
680 }
681
682 #[test]
683 fn exclusion_at_address_space_edges() {
684 let v4 = agg_excluding(&["0.0.0.0/0"], &["0.0.0.0", "255.255.255.255"]);
685 assert_eq!(v4.first().unwrap(), "0.0.0.1");
686 assert_eq!(v4.last().unwrap(), "255.255.255.254");
687 assert_eq!(v4.len(), 62); let v6 = agg_excluding(
690 &["::/0"],
691 &["::", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"],
692 );
693 assert_eq!(v6.first().unwrap(), "::1");
694 assert_eq!(
695 v6.last().unwrap(),
696 "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe"
697 );
698 assert_eq!(v6.len(), 254);
699
700 assert_eq!(agg_excluding(&["::/0"], &["::/1"]), ["8000::/1"]);
701 assert_eq!(agg_excluding(&["::/0"], &["8000::/1"]), ["::/1"]);
702 }
703
704 #[test]
705 fn exclusion_is_order_independent() {
706 let mut a = Aggregator::new();
707 a.exclude_str("10.0.0.5", false).unwrap();
708 a.add_str("10.0.0.0/29", false).unwrap();
709 let mut b = Aggregator::new();
710 b.add_str("10.0.0.0/29", false).unwrap();
711 b.exclude_str("10.0.0.5", false).unwrap();
712 assert_eq!(a.summarize(), b.summarize());
713 assert!(!a.is_empty());
714 let mut only_excludes = Aggregator::new();
715 only_excludes.exclude_str("10.0.0.0/8", false).unwrap();
716 assert!(only_excludes.is_empty());
717 }
718}