IP Addressing, Part II: Scaling IPv4 Across the Internet
How classful addressing gave way to CIDR, and how private addresses and NAT kept the world from running out of IPv4.
Hi Friends,
Welcome to the 184th issue of the Polymathic Engineer newsletter. This week, we continue our series of articles on the Internet Protocol and addressing.
In Part I, we stayed inside a single network. We had a look at how an IPv4 address is structured, how a subnet mask splits it into network and host parts, and how a device uses both to decide whether to deliver a packet directly or forward it to its default gateway.
This week, we zoom out to the whole Internet. IPv4 gives us only about 4.29 billion addresses, and that number turned out to be far too small for the world we ended up with. So a big part of this story is about the tricks that kept IPv4 alive: a more flexible way to carve up address space, and a way for many devices to share a single public address. Along the way, we will see who actually owns the address space and how a device gets an address in the first place.
The outline is as follows:
Classful addressing and why it failed
CIDR
Private addresses and NAT
Who assigns IP addresses
DHCP
Project-based learning is the best way to develop technical skills. CodeCrafters is an excellent platform for tackling exciting projects, such as building your own Redis, Kafka, a DNS server, SQLite, an HTTP server, or Git from scratch using your favorite programming language. Now you can also try to build your own Claude Code (for free, still in beta).
Sign up and become a better software engineer.
Classful Addressing and Why It Failed
In the early days of the Internet, IPv4 addresses were assigned using a system known as classful addressing (see RFC 791). The idea was to split the address space into fixed classes. The class of an address decided where the line between the network and host portions fell.
Three of these classes were meant for regular networks:
Class A used a /8 mask: the first 8 bits were the network, leaving 24 bits for hosts. That is about 16 million hosts per network.
Class B used a /16 mask: 16 bits of network and 16 bits of host, or roughly 65,000 hosts per network.
Class C used a /24 mask: 24 bits of network, leaving 8 bits for hosts, so only 254 usable hosts per network.
The problem is that these three sizes are extremely far apart, and most organizations fit uncomfortably between them. Let’s suppose a company has 2,000 hosts. A Class C network tops out at 254 addresses, which is nowhere near enough. So the company would be given a Class B network instead, which holds more than 65,000 addresses. It uses 2,000 of them and wastes the other 63,000, and nobody else can touch that block.
Multiply that waste across thousands of organizations, and you can see the issue. The Class B space disappeared quickly, with large chunks of address ranges left unused in the given blocks, and the fixed /8, /16, and /24 limits left no way to assign anything in between. The method was simple, but far too rigid for how the Internet was actually growing.
CIDR
The fix for all this rigidity is the system we still use: Classless Inter-Domain Routing, or CIDR (pronounced “cider”), defined in RFC 4632. The name tells it all. CIDR does away with the fixed classes and allows the boundary between network and host to be anywhere.
This is exactly the slash notation from Part I. An address is written as a.b.c.d/x, where x is the number of bits in the network prefix, and x can be any value, not just 8, 16, or 24. The company with 2,000 hosts doesn’t have to take a /16 anymore. A /21 has about 2,000 addresses, which fits it almost perfectly, and the rest of the space stays free for someone else.
The x most significant bits of the address are known as the network prefix, and every device in the organization shares the same prefix. This is where CIDR does something clever for routing. To the rest of the Internet, the organization appears to be a single prefix. However, a router outside simply looks at those x bits to know where to send a packet. So one entry of the type a.b.c.d/x is sufficient to forward traffic to any device in an organization. This allows the forwarding tables in the Internet’s core routers to be far smaller than they would otherwise be.
Let’s make this concrete. Let’s suppose an ISP is allocated the block 200.23.16.0/20. It can subdivide that block into eight smaller /23 blocks and allocate one to each of eight customer organizations:
ISP block 200.23.16.0/20
Organization 0 200.23.16.0/23
Organization 1 200.23.18.0/23
Organization 2 200.23.20.0/23
...
Organization 7 200.23.30.0/23To the outside world, the ISP advertises a single route: send me anything that matches 200.23.16.0/20. The rest of the Internet has no knowledge there are eight organizations hiding behind that prefix, and it doesn’t need to. This is called address aggregation, and it is what prevents the Internet’s routing tables from expanding as more networks come online.
Aggregation works fine as long as addresses are sent down in neat blocks, but the real world is not always that simple. Normally, when an organization leaves an ISP, it hands back the addresses it was lent and renumbers its devices into a block from the new ISP. Renumbering every host and router is painful, though, so an organization sometimes holds on to its old addresses even after switching providers. Let’s follow what happens when Organization 1 keeps its 200.23.18.0/23 block but moves to a new ISP.


