Remove adsMember
Theme: SystemContrast: Standard
On this page
  1. A slash is a number of bits
  2. Doing it in your head
  3. Longest prefix wins
  4. /31 and /32 are weird
  5. Mistakes you find two years later
  6. The next hop can be IPv6

CIDR, the netmask and the router's longest match

By 7 min readIntermediate
Copy article
Share this article

Your team carves a VPC out of 10.0.0.0/16, builds on it and ships. A year later the company buys another company. Their VPC? Also 10.0.0.0/16. Both networks are fine on their own. Together they’re an expensive lesson in why two machines can’t both be 10.0.0.5.

CIDR feels like trivia right up until that day. So: what the slash means, how a router picks between overlapping routes, how to get the network and broadcast address without a calculator, why /31 and /32 don’t follow the usual rule, and the planning mistakes that take an hour to make and years to undo.

A slash is a number of bits

An IPv4 address is 32 bits. Dotted-quad is just a readable way to write four bytes in decimal: 192.168.10.37 is the integer 0xC0A80A25 and nothing else.

CIDR notation (RFC 4632 [1]) adds a prefix length. 192.168.10.0/24 says the first 24 bits are fixed and the last 8 are free. The netmask says the same thing as a bit pattern, 24 ones and then 8 zeros, which in dotted form is 255.255.255.0.

Which means a valid netmask is always a run of ones followed by a run of zeros. 255.0.255.0 isn’t a netmask at all. The prefix length already says everything; the dotted mask is redundant.

A block holds 2^(32 - n) addresses. A /24 has 256, a /20 4096, a /30 four. Each step of one in the prefix doubles or halves the block.

To check whether dst is inside net/n, a host or router does one AND: dst & mask, then compare with net. That’s the whole test.

python
import ipaddress

net = ipaddress.ip_network("192.168.10.0/24")
print(net.netmask)                 # 255.255.255.0
print(net.num_addresses)           # 256
print(ipaddress.ip_address("192.168.10.37") in net)   # True

Doing it in your head

Which block is 172.16.37.200/21 in?

A /21 leaves 11 host bits. 21 = 8 + 8 + 5, so the prefix ends inside the third octet, with 5 network bits and 3 host bits there. Block size in that octet: 2^3 = 8. The third octet is 37, and the largest multiple of 8 at or below 37 is 32.

So the network is 172.16.32.0. The broadcast is the last address in the block, 172.16.39.255 (third octet 32 + 8 - 1, fourth octet all ones). That leaves 2^11 - 2 = 2046 usable hosts, 172.16.32.1 through 172.16.39.254.

This works for any prefix. Find the octet where the prefix ends. The block size there is 2^(host bits in that octet). Round the address down to a multiple of it; the broadcast is the next multiple minus one.

Prefix Mask Addresses Usable hosts (classic)
/24 255.255.255.0 256 254
/26 255.255.255.192 64 62
/28 255.255.255.240 16 14
/30 255.255.255.252 4 2
/31 255.255.255.254 2 2 (see below)
/32 255.255.255.255 1 1 (a single host)

The classic rule takes two off. The all-zeros host part is the network address, the name of the subnet. The all-ones host part is the directed broadcast. You can’t give either one to an ordinary host.

Do ten of these on paper with random addresses and prefixes, then check yourself in the CIDR calculator. After ten the octet trick sticks.

Longest prefix wins

A routing table is a list of prefix -> next hop entries, and they overlap on purpose. One rule makes that work: of all entries whose prefix contains the destination, use the one with the longest prefix.

text
0.0.0.0/0        -> upstream        (default route)
10.0.0.0/8       -> core-router
10.20.0.0/16     -> site-a
10.20.30.0/24    -> lab-switch
10.20.30.7/32    -> special-box

10.20.30.99 matches the default, the /8, the /16 and the /24. The /24 wins. 10.20.30.7 matches all five and goes to special-box. 10.99.0.1 matches only the default and the /8, so it goes to the core.

Nested prefixes with the longest match highlighted for one destination address

Order doesn’t matter here. This isn’t a firewall rule list where the first match wins; the table is a set and specificity decides. The default route is simply /0: it matches everything and loses to everything, which is exactly what a fallback should do.

The uncomfortable side: whoever can inject a more specific route captures that traffic. Announce a /24 inside someone else’s /22 and the internet prefers you. That’s how BGP prefix hijacks work, and why operators filter on the maximum prefix length they’ll accept.

Routers do this in hardware with tries or TCAM, not a linear scan, but the result is the same as this loop:

python
import ipaddress

routes = {
    "0.0.0.0/0": "upstream",
    "10.0.0.0/8": "core",
    "10.20.0.0/16": "site-a",
    "10.20.30.0/24": "lab",
}

def lookup(dst):
    ip = ipaddress.ip_address(dst)
    best = max(
        (ipaddress.ip_network(p) for p in routes if ip in ipaddress.ip_network(p)),
        key=lambda n: n.prefixlen,
    )
    return routes[str(best)]

print(lookup("10.20.30.99"))   # lab
print(lookup("10.99.0.1"))     # core

/31 and /32 are weird

The minus-two rule breaks down at the small end.

A /32 is one address. All 32 bits are network bits, there’s no host part to subtract anything from. It’s not a subnet with zero hosts; it’s a host route. You’ll see it on loopbacks, router IDs, firewall rules aimed at exactly one machine, and the special-box line in the table above.

A /31 has two addresses, so by the classic rule it would have zero usable hosts. That’s why point-to-point links traditionally got a /30: four addresses, two usable. RFC 3021 [2] changed that. A point-to-point link has nobody to broadcast to, so both addresses of a /31 can be hosts. If you have thousands of router-to-router links, that halves the address space they use.

But only on point-to-point links. On a shared Ethernet segment with many hosts, a /31 isn’t two ordinary hosts. And check your hardware and your cloud: some platforms reserve addresses no matter the prefix length. AWS keeps the first four and the last address of every subnet for itself, which is why its smallest subnet is a /28. “Usable hosts = 2^n - 2” is a textbook number. Find out what your platform actually reserves before you size a subnet to the exact host count.

Mistakes you find two years later

Most addressing mistakes are made in the first hour of a project and found much later.

Overlapping ranges are the big one. Networks that need to talk can’t share addresses, and “need to talk” keeps changing: VPNs, peering, an acquisition, a partner, a developer’s home LAN on 192.168.1.0/24 dialling into an office on the same range. Longest-prefix match can’t help when both sides have the identical prefix; the router can’t know which side an address belongs to. RFC 1918 [3] gives you 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16, and everyone grabs 10.0.0.0/16 or 192.168.0.0/24 first. Pick something less popular, and write down what you’ve allocated, even if it’s a spreadsheet.

Then misalignment. A block has to start on a multiple of its own size. 10.1.1.0/23 isn’t valid: a /23 covers two /24s and has to start at an even third octet, so the block containing that address is 10.1.0.0/23. Hand out odd-aligned ranges and you can’t summarise them into one route, so routing tables grow.

Sizing is the third. Giving every team a /16 because it’s a round number feels generous until you notice 10.0.0.0/8 only has 256 of them. A /24 for a four-host segment wastes 250 addresses. Size for expected growth plus margin, and hand out power-of-two chunks from a pool so freed blocks can be merged back.

And you mostly can’t resize later. Once things are attached to a subnet, growing it in place rarely works because the space next to it is taken. Leave gaps, and lay things out hierarchically (region, then environment, then function), so one summary route covers a whole region.

Do Do not
Allocate on aligned power-of-two boundaries Start a /23 at an odd third octet
Pick uncommon RFC 1918 ranges and record them Default to 10.0.0.0/16 or 192.168.1.0/24
Leave gaps for growth, group by region Pack subnets back to back with no summary path
Use /31 on point-to-point links where supported Burn a /24 on a link between two routers
Check what your cloud reserves per subnet Size to 2^n - 2 and assume it is exact

The next hop can be IPv6

One last oddity, which shows how little a prefix says about forwarding. RFC 8950 [4] lets BGP announce IPv4 prefixes with an IPv6 next hop. The destination is still matched by longest prefix in the IPv4 table, but the router reaches the next device over IPv6. That’s handy in networks with IPv6-only links that still carry IPv4 traffic. What you match on (the destination prefix) and how you get there (the next hop) are separate things.

Before a production change, don’t trust the arithmetic in your head. Run it through the CIDR calculator, then look at the table you’re about to change.

Try it yourself

CIDR and IP subnet calculator

Calculate the range, mask and size of IPv4 and IPv6 networks, split them into subnets, and check what contains or overlaps what.

Open the tool

Share this article

Share this article

Sources

  1. RFC 4632: Classless Inter-domain Routing (CIDR)1
  2. RFC 3021: Using 31-Bit Prefixes on IPv4 Point-to-Point Links1
  3. RFC 1918: Address Allocation for Private Internets1
  4. RFC 8950: Advertising IPv4 NLRI with an IPv6 Next Hop1

Keyboard shortcuts

Close

Everywhere

Search tools and actions
Ctrl K
Show this help
?
Switch theme
Ctrl Shift L
Pin the selected tool (in search)
Ctrl P

On a tool

Send the output to the first suggestion
Ctrl ↵
Open a file into the input
Ctrl O
Copy the output
Ctrl Shift C
Close searchesc
toolsBase64 encoder and decoderEncode text to Base64 or decode Base64 back to text, with standard and URL-safe alphabets.EncodingJSON formatter and validatorFormat, minify and validate JSON, with the exact line and column of any error.FormattingRegex testerTest JavaScript regular expressions with live highlighting, capture groups and a plain-language explanation.TextUUID and ULID generatorGenerate UUID v4, UUID v7 and ULID in bulk, or paste an ID to see its version, variant and timestamp.GeneratorsUnix timestamp converterConvert Unix timestamps to dates and back, in any time zone, with ISO 8601, RFC 2822 and relative time.Date and timeHash and HMAC generatorCalculate MD5, SHA-1, SHA-2, SHA-3, BLAKE2, BLAKE3 and CRC-32 hashes of text or a file, with optional HMAC.SecurityPassword generator and strength checkerGenerate random passwords, passphrases and PINs, or check how strong a password is.SecurityJWT decoder and verifierDecode a JSON Web Token, check its expiry and claims, and verify its signature with a secret or public key.SecurityCIDR and IP subnet calculatorCalculate the range, mask and size of IPv4 and IPv6 networks, split them into subnets, and check what contains or overlaps what.NetworkCron expression explainerRead a cron expression in plain language, see its next runs in any time zone, and tidy it into its standard form.Date and timeSort linesSort lines of text A to Z or Z to A, in natural order (file2 before file10), by code point, by number or by length.TextRemove duplicate linesRemove duplicate lines from a list, keep the first or last copy, ignore case or spaces, or drop every line that repeats.TextCompare lists and textCompare two lists line by line: find what is in both, only in one, or in either, or see a marked line diff side by side.TextText case converter and slugifyConvert text to camelCase, snake_case, kebab-case, Title Case and more, or turn a title into a URL slug with accents handled.TextURL encode, decode and parsePercent-encode and decode text for URLs, or take a URL apart into scheme, host, path and query and edit the query as a table.EncodingValidators: IBAN, BSN, VAT, card and ISBNCheck an IBAN, Dutch BSN, credit card number, EU VAT number, email address, URL or ISBN, with the reason when it fails. One value per line.ValidationHTML entity encoder and decoderEscape text for HTML with named, decimal or hex entities, or turn entities such as & and é back into characters.EncodingEscape and unescape stringsEscape text for a JSON or JavaScript string, a SQL string, a CSV field, a regular expression or a shell word, or undo the escaping.EncodingEAN, UPC and GTIN check digitsCheck the check digit of an EAN-8, EAN-13, UPC-A or GTIN-14, add a missing one, read the country from the GS1 prefix and convert between ISBN-10 and EAN-13. One code per line.ValidationTest data generatorGenerate valid-looking test IBANs, BSNs, EAN barcodes and EU VAT numbers in bulk. They pass the checks, but belong to no one.GeneratorsSQL formatter and minifierFormat SQL so each clause sits on its own line, or minify it onto one line, for MySQL, PostgreSQL, SQLite, SQL Server and BigQuery.FormattingNumber base converterConvert numbers between binary, octal, decimal, hex and any base from 2 to 36, with big numbers, two's complement and a bit view.EncodingYAML, TOML and JSON converterConvert between JSON, YAML and TOML, or validate and pretty-print a document, with the line and column of every error.FormattingCSV and JSON converterConvert CSV to a JSON array of objects or arrays, or JSON back to CSV. Detects the delimiter and reads Dutch Excel files with semicolons and decimal commas.DataGlob and .gitignore testerTest glob patterns and .gitignore rules against a list of paths and see which paths match and which rule matched them.TextColour converterConvert a colour between HEX, RGB, HSL, HWB and OKLCH, check its WCAG contrast against a background, or build a Material 3 tonal palette from it.Formattingchmod calculatorConvert Unix file permissions between octal (755), letters (rwxr-xr-x), ls -l and chmod clauses, with a checkbox grid and the setuid, setgid and sticky bits.SecurityUser-agent parserRead a user-agent string: browser, engine, OS, device type and whether it is a bot. Leave it empty to read your own browser.NetworkConvert cURL to codePaste a cURL command and get the same request as fetch, Axios, Python requests, Go or PHP. Nothing is run and nothing is sent.NetworkMarkdown previewWrite Markdown and see it rendered as you type, or copy the HTML. Raw HTML is shown as text and links are checked, so nothing in the preview can run.FormattingBarcode and QR code generatorMake a QR code, EAN-13, UPC-A, Code 128, ITF-14 or Data Matrix barcode, download it as SVG or PNG, or read a code from an image.GeneratorspagesChangelogWhat changed in each releasepagesAboutWhat codeknife.dev is and who makes itpagesIdeasSuggest improvements and vote on other visitors’ ideas.pagesPrivacyWhat leaves your browser and what is storedpagesAccessibilityConformance, known limitations and how to report a problempagesMembershipRemove ads and raise the limitspagesactions