IP Addressing and Subnetting

Every device that speaks to the Internet wears a numeric name badge: an IP address. You have seen them — 192.168.1.42, 8.8.8.8. They look like four friendly numbers, but that dotted-decimal costume hides what an address really is: a single 32-bit binary number, and — this is the whole game — a number split into two halves. The left half says which network you are on; the right half says which host you are within it. Master that split and you can look at any address-and-mask and instantly answer the questions a router lives by: what network is this? are these two machines neighbours or strangers? how many hosts fit here?

This page is about reading and computing on IP addresses like the router in the previous lesson does — as bits, not decimals. It is one of the few places in networking where a little clean bit-arithmetic replaces a lot of hand-waving, so we will lean into the binary and let a runnable calculator do the sums.

32 bits in a trench coat

An IPv4 address is 32 bits. To make it human-readable we chop it into four 8-bit octets, write each as a decimal 0–255, and join them with dots. So 192.168.1.42 is really:

\underbrace{11000000}_{192}\;.\;\underbrace{10101000}_{168}\;.\;\underbrace{00000001}_{1}\;.\;\underbrace{00101010}_{42}

Because each octet is one byte, each ranges 0–255, and the whole 32-bit space holds 2^{32} = 4{,}294{,}967{,}296 addresses — about 4.3 billion. (That this "huge" number turned out to be far too small is the story of the next lesson.) The dots are pure decoration for our eyes; the router sees one 32-bit integer and does everything with bitwise AND.

The line in the sand: network bits and host bits

Here is the pivotal idea. An address is divided into a network portion (the leading bits, shared by everyone on the same network) and a host portion (the trailing bits, unique to each machine). Where is the dividing line? Wherever the subnet mask puts it.

A subnet mask is another 32-bit pattern: a run of 1s covering the network bits, followed by 0s covering the host bits. CIDR notation just writes the count of 1s after a slash. So /24 means "the first 24 bits are network, the last 8 are host":

\text{/24 mask} = \underbrace{11111111.11111111.11111111}_{24\text{ network bits}}.\underbrace{00000000}_{8\text{ host bits}} = 255.255.255.0

To find the network address of any host, you AND the address with the mask — this zeroes out the host bits, leaving only the network part. To find the broadcast address (the "everyone on this network" address), you set all the host bits to 1. And the number of usable hosts follows a clean formula from the count of host bits:

\text{host bits} = 32 - n \qquad\Longrightarrow\qquad \text{usable hosts} = 2^{\,32-n} - 2

The −2 is the classic trap and deserves a moment: the all-host-bits-zero address is the network address itself (a name for the block, not a host), and the all-host-bits-one address is the broadcast address. Neither can be handed to an actual machine. So a /24 has 2^{8} = 256 addresses but only 256 - 2 = 254 usable host addresses.

CIDRMaskHost bitsTotal addressesUsable hosts
/24255.255.255.08256254
/25255.255.255.1287128126
/26255.255.255.19266462
/30255.255.255.252242

Notice each extra network bit halves the block: a /25 is half a /24, a /26 a quarter. That /30 at the bottom — 2 usable hosts — is exactly what you put on a point-to-point link between two routers, where you need precisely two addresses and nothing wasted.

A worked example, done in binary

Take the host 192.168.10.130 with mask /26. Let us find its network address, broadcast, and whether it shares a subnet with 192.168.10.100. A /26 means 26 network bits, so 6 host bits — and only the last octet is interesting (the first three octets are fully network). In binary the last octet is 130 = 10000010, and the mask's last octet is 11000000 (the top 2 bits of this octet are network, the bottom 6 are host).

The two-addresses-same-subnet test is just: AND both with the mask and compare the results. Equal ⇒ same subnet ⇒ deliver directly on the link. Different ⇒ send it to the router. That single comparison is how every host decides "is this local or do I hand it off?" on every packet it sends.

Compute it live

The calculator below takes an address and a prefix length and does the whole job in pure bit arithmetic — exactly the operations a host or router performs. Change the numbers, hit Run, and watch the mask, network, broadcast and host count fall out. Try a /26 on 192.168.10.130 to reproduce the worked example, then try a /30 or a /16.

function ip(a: number, b: number, c: number, d: number): number { return ((a << 24) | (b << 16) | (c << 8) | d) >>> 0; // >>>0 => unsigned 32-bit } function dotted(n: number): string { return [n >>> 24, (n >>> 16) & 255, (n >>> 8) & 255, n & 255].join("."); } function describe(a: number, b: number, c: number, d: number, prefix: number): void { const addr = ip(a, b, c, d); // Mask = `prefix` leading 1s. prefix 0 -> 0; else shift. const mask = prefix === 0 ? 0 : (0xffffffff << (32 - prefix)) >>> 0; const network = (addr & mask) >>> 0; // AND clears the host bits const broadcast = (network | (~mask >>> 0)) >>> 0; // set all host bits to 1 const hostBits = 32 - prefix; const usable = hostBits <= 1 ? Math.max(0, 2 ** hostBits - 2) : 2 ** hostBits - 2; console.log(`Address: ${dotted(addr)}/${prefix}`); console.log(`Mask: ${dotted(mask)}`); console.log(`Network: ${dotted(network)}`); console.log(`Broadcast: ${dotted(broadcast)}`); console.log(`First host: ${dotted((network + 1) >>> 0)}`); console.log(`Last host: ${dotted((broadcast - 1) >>> 0)}`); console.log(`Usable hosts (2^${hostBits} - 2): ${usable}`); } describe(192, 168, 10, 130, 26); console.log("---"); describe(10, 0, 0, 7, 30); // Same-subnet test: AND both with the mask and compare. function sameSubnet(x: number, y: number, prefix: number): boolean { const mask = prefix === 0 ? 0 : (0xffffffff << (32 - prefix)) >>> 0; return ((x & mask) >>> 0) === ((y & mask) >>> 0); } console.log("---"); console.log(".130 & .100 on same /26? " + sameSubnet(ip(192, 168, 10, 130), ip(192, 168, 10, 100), 26)); console.log(".130 & .150 on same /26? " + sameSubnet(ip(192, 168, 10, 130), ip(192, 168, 10, 150), 26));

CIDR, aggregation, and the private ranges

The slash notation is not just tidy — it is what lets the Internet's routing tables stay (barely) manageable. Because prefixes nest, a router can advertise one coarse block instead of many fine ones. If an ISP owns 200.23.16.0/23 and 200.23.18.0/23 and they are numerically adjacent, it can advertise the single supernet 200.23.16.0/22 to the rest of the world — one route where there were two. This is route aggregation (a.k.a. supernetting), and it is the reason longest-prefix match exists: distant routers carry the aggregate, nearby routers carry the specifics, and the longest match naturally prefers the specific route when it is available.

These ranges are reserved for use inside private networks and are never routed on the public Internet — which is why the same 192.168.1.1 lives behind millions of home routers at once with no conflict. How a whole house full of 192.168.x.x devices shares one public address is the job of NAT, the star of the next lesson.

Two errors bite almost everyone learning this, and they are worth stamping out now.

1. The slash counts NETWORK bits, not host bits. A /24 means the first 24 bits are the network and only the remaining 8 are for hosts — not 24 host bits. A bigger slash number means a smaller network (more bits pinned to the network, fewer left for hosts). So a /30 is tiny (2 hosts) and a /8 is enormous (≈16 million). If you ever think "/24, that's loads of host bits," flip it in your head: it's loads of network bits.

2. The first and last addresses of a subnet are reserved. The all-host-bits-zero address is the network address (a name for the block) and the all-host-bits-one address is the broadcast. Assign one of those to a machine and you get baffling failures. That is the whole reason usable hosts is 2^{32-n} - 2 and not 2^{32-n}. The off-by-those-two is the single most common subnetting slip.