1use std::fmt;
7use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4};
8use std::str::FromStr;
9
10#[inline]
25#[must_use]
26pub fn normalize(ip: IpAddr) -> IpAddr {
27 match ip {
28 IpAddr::V6(v6) => match v6.to_ipv4_mapped() {
29 Some(v4) => IpAddr::V4(v4),
30 None => IpAddr::V6(v6),
31 },
32 v4 => v4,
33 }
34}
35
36#[inline]
45#[must_use]
46pub fn normalize_socket(addr: SocketAddr) -> SocketAddr {
47 match addr {
48 SocketAddr::V6(v6) => match v6.ip().to_ipv4_mapped() {
49 Some(v4) => SocketAddr::V4(SocketAddrV4::new(v4, v6.port())),
50 None => SocketAddr::V6(v6),
51 },
52 v4 => v4,
53 }
54}
55
56#[non_exhaustive]
65#[derive(Clone, Copy, Debug, PartialEq, Eq)]
66pub enum AddrError {
67 Syntax {
69 at: usize,
71 },
72 PrefixTooLong {
74 max: u8,
76 },
77 HostBitsSet,
79 TooMany {
81 max: usize,
83 },
84 Empty,
86}
87
88impl fmt::Display for AddrError {
89 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90 match self {
91 AddrError::Syntax { at } => write!(f, "not an address or CIDR block (at byte {at})"),
92 AddrError::PrefixTooLong { max } => write!(f, "prefix length above {max}"),
93 AddrError::HostBitsSet => f.write_str(
94 "bits beyond the prefix are set; write the network address or use new_masked",
95 ),
96 AddrError::TooMany { max } => write!(f, "more than {max} entries"),
97 AddrError::Empty => f.write_str("empty address text"),
98 }
99 }
100}
101
102impl std::error::Error for AddrError {}
103
104fn masked_bytes(ip: IpAddr, prefix: u8) -> [u8; 16] {
106 let mut out = [0u8; 16];
107 let (bytes, total): ([u8; 16], u8) = match ip {
108 IpAddr::V4(v4) => {
109 let mut b = [0u8; 16];
110 b[..4].copy_from_slice(&v4.octets());
111 (b, 32)
112 }
113 IpAddr::V6(v6) => (v6.octets(), 128),
114 };
115 let prefix = prefix.min(total);
116 let full = usize::from(prefix / 8);
117 out[..full].copy_from_slice(&bytes[..full]);
118 let rem = prefix % 8;
119 if rem != 0 && full < 16 {
120 out[full] = bytes[full] & (0xFFu8 << (8 - rem));
121 }
122 out
123}
124
125fn ip_from_bytes(v4: bool, bytes: &[u8; 16]) -> IpAddr {
126 if v4 {
127 IpAddr::V4(Ipv4Addr::new(bytes[0], bytes[1], bytes[2], bytes[3]))
128 } else {
129 IpAddr::V6(Ipv6Addr::from(*bytes))
130 }
131}
132
133fn max_prefix(ip: IpAddr) -> u8 {
134 if ip.is_ipv4() { 32 } else { 128 }
135}
136
137#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
152pub struct Cidr {
153 network: IpAddr,
154 prefix: u8,
155}
156
157impl Cidr {
158 pub fn new(ip: IpAddr, prefix: u8) -> Result<Cidr, AddrError> {
170 let ip = normalize(ip);
171 let max = max_prefix(ip);
172 if prefix > max {
173 return Err(AddrError::PrefixTooLong { max });
174 }
175 let masked = masked_bytes(ip, prefix);
176 let network = ip_from_bytes(ip.is_ipv4(), &masked);
177 if network != ip {
178 return Err(AddrError::HostBitsSet);
179 }
180 Ok(Cidr { network, prefix })
181 }
182
183 pub fn new_masked(ip: IpAddr, prefix: u8) -> Result<Cidr, AddrError> {
195 let ip = normalize(ip);
196 let max = max_prefix(ip);
197 if prefix > max {
198 return Err(AddrError::PrefixTooLong { max });
199 }
200 let masked = masked_bytes(ip, prefix);
201 Ok(Cidr {
202 network: ip_from_bytes(ip.is_ipv4(), &masked),
203 prefix,
204 })
205 }
206
207 pub fn parse(text: &str) -> Result<Cidr, AddrError> {
222 if text.is_empty() {
223 return Err(AddrError::Empty);
224 }
225 let (addr_text, prefix_text) = match text.find('/') {
226 Some(slash) => (&text[..slash], Some((slash, &text[slash + 1..]))),
227 None => (text, None),
228 };
229 let ip = IpAddr::from_str(addr_text).map_err(|_| AddrError::Syntax { at: 0 })?;
230 let prefix = match prefix_text {
231 None => max_prefix(normalize(ip)),
232 Some((slash, p)) => parse_prefix(p).ok_or(AddrError::Syntax { at: slash + 1 })?,
233 };
234 let (ip, prefix) = match ip {
236 IpAddr::V6(v6) if v6.to_ipv4_mapped().is_some() && prefix_text.is_some() => {
237 if prefix < 96 {
238 return Err(AddrError::Syntax { at: 0 });
239 }
240 (ip, prefix - 96)
241 }
242 _ => (ip, prefix),
243 };
244 Cidr::new(ip, prefix)
245 }
246
247 #[inline]
255 #[must_use]
256 pub fn contains(&self, ip: IpAddr) -> bool {
257 let ip = normalize(ip);
258 if ip.is_ipv4() != self.network.is_ipv4() {
259 return false;
260 }
261 ip_from_bytes(ip.is_ipv4(), &masked_bytes(ip, self.prefix)) == self.network
262 }
263
264 #[must_use]
272 pub fn network(&self) -> IpAddr {
273 self.network
274 }
275
276 #[must_use]
283 pub fn prefix_len(&self) -> u8 {
284 self.prefix
285 }
286
287 #[must_use]
294 pub fn is_ipv4(&self) -> bool {
295 self.network.is_ipv4()
296 }
297}
298
299fn parse_prefix(text: &str) -> Option<u8> {
300 if text.is_empty() || text.len() > 3 || (text.len() > 1 && text.starts_with('0')) {
301 return None;
302 }
303 let mut value: u16 = 0;
304 for b in text.bytes() {
305 if !b.is_ascii_digit() {
306 return None;
307 }
308 value = value * 10 + u16::from(b - b'0');
309 }
310 u8::try_from(value).ok()
311}
312
313impl FromStr for Cidr {
314 type Err = AddrError;
315 fn from_str(s: &str) -> Result<Cidr, AddrError> {
316 Cidr::parse(s)
317 }
318}
319
320impl fmt::Display for Cidr {
321 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
322 write!(f, "{}/{}", self.network, self.prefix)
323 }
324}
325
326#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
339pub struct PrefixPolicy {
340 v4: u8,
341 v6: u8,
342}
343
344impl PrefixPolicy {
345 pub const HOST: PrefixPolicy = PrefixPolicy { v4: 32, v6: 128 };
347 pub const DEFAULT: PrefixPolicy = PrefixPolicy { v4: 32, v6: 64 };
349
350 pub const fn new(v4: u8, v6: u8) -> Result<PrefixPolicy, AddrError> {
362 if v4 > 32 {
363 return Err(AddrError::PrefixTooLong { max: 32 });
364 }
365 if v6 > 128 {
366 return Err(AddrError::PrefixTooLong { max: 128 });
367 }
368 Ok(PrefixPolicy { v4, v6 })
369 }
370
371 #[must_use]
373 pub const fn v4(&self) -> u8 {
374 self.v4
375 }
376
377 #[must_use]
379 pub const fn v6(&self) -> u8 {
380 self.v6
381 }
382}
383
384impl Default for PrefixPolicy {
385 fn default() -> PrefixPolicy {
386 PrefixPolicy::DEFAULT
387 }
388}
389
390#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
404pub struct PrefixKey {
405 family: u8,
406 bytes: [u8; 16],
407 len: u8,
408}
409
410impl PrefixKey {
411 #[inline]
413 #[must_use]
414 pub fn of(ip: IpAddr, policy: PrefixPolicy) -> PrefixKey {
415 let ip = normalize(ip);
416 let len = if ip.is_ipv4() { policy.v4 } else { policy.v6 };
417 PrefixKey {
418 family: if ip.is_ipv4() { 4 } else { 6 },
419 bytes: masked_bytes(ip, len),
420 len,
421 }
422 }
423
424 #[must_use]
431 pub fn exact(ip: IpAddr) -> PrefixKey {
432 PrefixKey::of(ip, PrefixPolicy::HOST)
433 }
434
435 #[must_use]
443 pub fn network(&self) -> IpAddr {
444 ip_from_bytes(self.family == 4, &self.bytes)
445 }
446
447 #[must_use]
449 pub fn prefix_len(&self) -> u8 {
450 self.len
451 }
452}
453
454impl fmt::Display for PrefixKey {
455 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
456 write!(f, "{}/{}", self.network(), self.len)
457 }
458}
459
460#[cfg(test)]
461mod tests {
462 use super::*;
463
464 fn ip(s: &str) -> IpAddr {
465 s.parse().unwrap()
466 }
467
468 #[test]
469 fn test_normalize_mapped_v6_becomes_v4() {
470 assert_eq!(normalize(ip("::ffff:1.2.3.4")), ip("1.2.3.4"));
471 }
472
473 #[test]
474 fn test_normalize_nat64_untouched() {
475 assert_eq!(normalize(ip("64:ff9b::102:304")), ip("64:ff9b::102:304"));
476 }
477
478 #[test]
479 fn test_normalize_socket_keeps_port_and_scope() {
480 let a: SocketAddr = "[fe80::1%3]:80".parse().unwrap();
481 assert_eq!(normalize_socket(a), a);
482 }
483
484 #[test]
485 fn test_cidr_parse_valid_forms() {
486 assert_eq!(Cidr::parse("10.0.0.0/8").unwrap().to_string(), "10.0.0.0/8");
487 assert_eq!(
488 Cidr::parse("2001:db8::/32").unwrap().to_string(),
489 "2001:db8::/32"
490 );
491 assert_eq!(Cidr::parse("192.0.2.7").unwrap().prefix_len(), 32);
492 assert_eq!(Cidr::parse("0.0.0.0/0").unwrap().prefix_len(), 0);
493 }
494
495 #[test]
496 fn test_cidr_parse_rejects_bad_text() {
497 assert_eq!(Cidr::parse(""), Err(AddrError::Empty));
498 assert!(matches!(
499 Cidr::parse("10.0.0.0/"),
500 Err(AddrError::Syntax { .. })
501 ));
502 assert!(matches!(
503 Cidr::parse("10.0.0.0/08"),
504 Err(AddrError::Syntax { .. })
505 ));
506 assert!(matches!(
507 Cidr::parse("10.0.0.0/+8"),
508 Err(AddrError::Syntax { .. })
509 ));
510 assert!(matches!(
511 Cidr::parse("010.0.0.0/8"),
512 Err(AddrError::Syntax { .. })
513 ));
514 assert!(matches!(
515 Cidr::parse(" 10.0.0.0/8"),
516 Err(AddrError::Syntax { .. })
517 ));
518 assert!(matches!(
519 Cidr::parse("fe80::1%3/64"),
520 Err(AddrError::Syntax { .. })
521 ));
522 assert_eq!(
523 Cidr::parse("10.0.0.0/33"),
524 Err(AddrError::PrefixTooLong { max: 32 })
525 );
526 assert_eq!(
527 Cidr::parse("::/129"),
528 Err(AddrError::PrefixTooLong { max: 128 })
529 );
530 assert_eq!(Cidr::parse("10.0.0.1/8"), Err(AddrError::HostBitsSet));
531 }
532
533 #[test]
534 fn test_cidr_mapped_block_normalizes_to_v4() {
535 let c = Cidr::parse("::ffff:10.0.0.0/104").unwrap();
536 assert_eq!(c.to_string(), "10.0.0.0/8");
537 assert!(matches!(
538 Cidr::parse("::ffff:10.0.0.0/64"),
539 Err(AddrError::Syntax { .. })
540 ));
541 }
542
543 #[test]
544 fn test_cidr_contains_boundaries() {
545 let c = Cidr::parse("192.0.2.0/31").unwrap();
546 assert!(c.contains(ip("192.0.2.0")));
547 assert!(c.contains(ip("192.0.2.1")));
548 assert!(!c.contains(ip("192.0.2.2")));
549 assert!(!c.contains(ip("::1")));
550 let all = Cidr::parse("::/0").unwrap();
551 assert!(all.contains(ip("2001:db8::1")));
552 assert!(!all.contains(ip("1.1.1.1")));
553 }
554
555 #[test]
556 fn test_cidr_new_masked_clears_bits() {
557 let c = Cidr::new_masked(ip("2001:db8:ffff::1"), 33).unwrap();
558 assert_eq!(c.to_string(), "2001:db8:8000::/33");
559 }
560
561 #[test]
562 fn test_prefix_key_groups_by_policy() {
563 let p = PrefixPolicy::new(24, 56).unwrap();
564 assert_eq!(
565 PrefixKey::of(ip("1.2.3.4"), p),
566 PrefixKey::of(ip("1.2.3.200"), p)
567 );
568 assert_ne!(
569 PrefixKey::of(ip("1.2.3.4"), p),
570 PrefixKey::of(ip("1.2.4.4"), p)
571 );
572 assert_eq!(
573 PrefixKey::of(ip("2001:db8:0:ff12::1"), p).to_string(),
574 "2001:db8:0:ff00::/56"
575 );
576 }
577
578 #[test]
579 fn test_prefix_key_v4_and_v6_never_equal() {
580 let p = PrefixPolicy::new(0, 0).unwrap();
581 assert_ne!(PrefixKey::of(ip("0.0.0.0"), p), PrefixKey::of(ip("::"), p));
582 }
583
584 #[test]
585 fn test_prefix_key_mapped_equals_v4() {
586 assert_eq!(
587 PrefixKey::exact(ip("::ffff:9.9.9.9")),
588 PrefixKey::exact(ip("9.9.9.9"))
589 );
590 }
591
592 #[test]
593 fn test_prefix_key_size_is_eighteen_bytes() {
594 assert_eq!(std::mem::size_of::<PrefixKey>(), 18);
595 }
596
597 #[test]
598 fn test_policy_bounds() {
599 assert!(PrefixPolicy::new(32, 128).is_ok());
600 assert_eq!(
601 PrefixPolicy::new(0, 129),
602 Err(AddrError::PrefixTooLong { max: 128 })
603 );
604 }
605
606 #[test]
608 fn prop_cidr_and_prefix_key_match_integer_masks() {
609 use crate::testing::Gen;
610 let mut g = Gen::new(0xADD2_0001);
611 for _ in 0..if cfg!(miri) { 200 } else { 20_000 } {
612 if g.chance(2) {
613 let net = g.next_u64() as u32;
614 let p = g.below(33) as u32;
615 let mask = if p == 0 { 0 } else { u32::MAX << (32 - p) };
616 let ip = if g.chance(2) {
618 (net & mask) | (g.next_u64() as u32 & !mask)
619 } else {
620 g.next_u64() as u32
621 };
622 let (net_ip, probe) = (
623 IpAddr::from(std::net::Ipv4Addr::from(net)),
624 IpAddr::from(std::net::Ipv4Addr::from(ip)),
625 );
626 let block = Cidr::new_masked(net_ip, p as u8).unwrap();
627 let inside = ip & mask == net & mask;
628 assert_eq!(block.contains(probe), inside, "{block} {probe}");
629 let policy = PrefixPolicy::new(p as u8, 64).unwrap();
630 assert_eq!(
631 PrefixKey::of(probe, policy) == PrefixKey::of(net_ip, policy),
632 inside
633 );
634 } else {
635 let net = (u128::from(g.next_u64()) << 64) | u128::from(g.next_u64());
636 let net = net | (1u128 << 127);
638 let p = g.below(129) as u32;
639 let mask = if p == 0 { 0 } else { u128::MAX << (128 - p) };
640 let rand =
641 (u128::from(g.next_u64()) << 64) | u128::from(g.next_u64()) | (1u128 << 127);
642 let ip = if g.chance(2) {
643 (net & mask) | (rand & !mask)
644 } else {
645 rand
646 };
647 let (net_ip, probe) = (
648 IpAddr::from(std::net::Ipv6Addr::from(net)),
649 IpAddr::from(std::net::Ipv6Addr::from(ip)),
650 );
651 let block = Cidr::new_masked(net_ip, p as u8).unwrap();
652 let inside = ip & mask == net & mask;
653 assert_eq!(block.contains(probe), inside, "{block} {probe}");
654 let policy = PrefixPolicy::new(32, p as u8).unwrap();
655 assert_eq!(
656 PrefixKey::of(probe, policy) == PrefixKey::of(net_ip, policy),
657 inside
658 );
659 }
660 }
661 }
662}