2022-04-17 · 4 min read
CIDR or Classless Inter-Domain Routing is a more granular method for allocating IP addresses and routing IP packets than the now removed IP Class-based allocation.
IP addresses are split into two pieces: (1) the network prefix or subnet address (read: address of the subnet), identified by the most-significant bits and (2) the host identifier in the least-significant bits, which identifies a specific interface on a host in that subnet.
Original IPv4 addresses were categorized by "classes", which split them up according to a few of the first bits in the address (starting from the MSB). Today this is mostly a legacy detail that was later replaced, but the "class-less" in CIDR refers to the lack of distinct classes in CIDR, which instead uses "variable-length subnet masking" (VLSM) to allocate network prefixes along any bit boundary.
Subnet Mask #
To refresh, a subnet mask is just a bitmask we use to AND against the IP to retrieve the network prefix. Likewise, if we invert the subnet mask then AND against the IP, we get the host id.
For example, if we have the LAN IP
192.168.3.5 with subnet mask
255.255.254.0, then our network prefix is
192.168.2.0 and the host id is
0.0.1.5. This might be clearer if you see the bits written out explicitly.
1100 0000 . 1010 1000 . 0000 0011 . 0000 0101 ip 1111 1111 . 1111 1111 . 1111 1110 . 0000 0000 subnet_mask 1100 0000 . 1010 1000 . 0000 0010 . 0000 0000 network_prefix = ip & subnet_mask 0000 0000 . 0000 0000 . 0000 0001 . 0000 0101 host_id = ip & !subnet_mask
As we'll see in a moment, this IP+mask in equivalent CIDR notation would be
Historically, a full 32-bit bitmask was used to differentiate between which part of an IPv4 address was the network prefix and which part was the host identifier.
For a while, subnet masks could be any arbitrary non-contiguous bitmask; however, some RFCs eventually mandated that they must be contiguous, starting from the most-significant bit. As we'll see in a moment, an IP with a subnet mask and an IP in CIDR notation are 100% equivalent nowadays.
CIDR notation #
Instead of writing out the whole subnet mask, we can instead write just the number of leading ones in the subnet mask (called the mask width) alongside the IP. This format is called CIDR notation.
Using the same example as above, the ip
192.168.3.5 with subnet mask
255.255.254.0 would be written as
192.168.3.5/23 in CIDR notation.
Deriving that more explicitly in Rust code:
let ip: Ipv4Addr = "192.168.3.5".into(); let mask_width = 23; let subnet_mask = !(0xffff_ffff_u32 >> mask_width); let network_prefix: Ipv4Addr = (ip.as_u32() & subnet_mask).into(); let host_id: Ipv4Addr = (ip.as_u32() & !subnet_mask).into();
I used to get confused on whether the CIDR mask width refers to the subnet mask or host id mask. A simple memnonic helps me remember:
the CIDR number is on the right, so we shift ones right (to make the mask).
Note that CIDR notation works the exact same for IPv6, except we have 128 bits to play with instead of just 32 bits.
Another useful insight to remember: the CIDR mask width goes from least specific (/0) to most specific (/32).
CIDR block #
When describing a range of IPs, we often write an IP in CIDR notation with the bits outside the network prefix set to zero.
|Address Range||CIDR block||Number of addresses|
|192.168.0.0 -> 192.168.255.255||192.168.0.0/16||2^16 = 65,536|
|172.16.0.0 -> 172.31.255.255||172.16.0.0/12||2^20 = 1,048,576|
|10.0.0.0 -> 10.255.255.255||10.0.0.0/8||2^24 = 16,777,216|