rust 47 lines · 8 steps

Parsing and matching IPv4 CIDR ranges in Rust

An Ipv4Cidr type stores a network and mask as u32s so address membership is a single bitwise comparison.

Explained by highlit
1use std::net::Ipv4Addr;
2use std::str::FromStr;
3 
4#[derive(Debug, Clone, Copy)]
5pub struct Ipv4Cidr {
6 network: u32,
7 mask: u32,
8}
9 
10impl Ipv4Cidr {
11 pub fn contains(&self, addr: Ipv4Addr) -> bool {
12 (u32::from(addr) & self.mask) == self.network
13 }
14 
15 pub fn broadcast(&self) -> Ipv4Addr {
16 Ipv4Addr::from(self.network | !self.mask)
17 }
18}
19 
20impl FromStr for Ipv4Cidr {
21 type Err = String;
22 
23 fn from_str(s: &str) -> Result<Self, Self::Err> {
24 let (addr, prefix) = s
25 .split_once('/')
26 .ok_or_else(|| format!("missing '/' in CIDR: {s}"))?;
27 
28 let addr: Ipv4Addr = addr
29 .parse()
30 .map_err(|_| format!("invalid IPv4 address: {addr}"))?;
31 
32 let prefix: u32 = prefix
33 .parse()
34 .map_err(|_| format!("invalid prefix length: {prefix}"))?;
35 
36 if prefix > 32 {
37 return Err(format!("prefix out of range: /{prefix}"));
38 }
39 
40 let mask = if prefix == 0 { 0 } else { u32::MAX << (32 - prefix) };
41 
42 Ok(Ipv4Cidr {
43 network: u32::from(addr) & mask,
44 mask,
45 })
46 }
47}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Representing IP addresses as u32 turns subnet membership into a cheap masking-and-compare operation.
  2. 2Building the netmask by shifting u32::MAX left by 32 minus the prefix cleanly separates network from host bits.
  3. 3Implementing FromStr with a String error and the ? operator gives ergonomic, descriptive parsing failures.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Parsing and matching IPv4 CIDR ranges in Rust — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code