# What is CIDR notation?

_2026-09-14 · Boundstone (https://boundstone.io/blog/what-is-cidr-notation)_


If you have seen `192.168.0.0/24` in a firewall rule, an allow-list, or a cloud VPC config and wondered what the `/24` actually means, that is the whole question this post answers. CIDR notation — Classless Inter-Domain Routing — is just a compact way to write a *range* of IP addresses instead of one. A base address, a slash, and a number. The number is where all the meaning lives, and it is simpler than it looks.

## The one-line answer

`192.168.0.0/24` means "every address that shares the first 24 bits with `192.168.0.0`." Since an IPv4 address is 32 bits, fixing the first 24 leaves 8 bits free to vary. Eight free bits give you 2⁸ = 256 addresses: `192.168.0.0` through `192.168.0.255`.

So CIDR is two facts glued together: a starting point (the base address) and how many leading bits are locked (the prefix length). Everything else — how big the range is, where it ends, whether a given address is inside it — falls out of those two facts.

## Prefix length is a count of fixed bits

Picture an IPv4 address as 32 switches. The prefix length says how many switches, counting from the left, are held fixed by the network. The rest are free.

- `/24` → 24 fixed (network) bits, 8 free (host) bits.
- `/8` → 8 fixed, 24 free.
- `/32` → all 32 fixed, 0 free. That is a single address.
- `/0` → nothing fixed. That is every IPv4 address at once.

The fixed part is the **network prefix**; it identifies the block. The free part is the **host portion**; it identifies an address within the block. A bigger prefix number means more bits are pinned down, which means a *smaller* range — the numbers move in opposite directions, which trips up most people once.

## From prefix to host count

Because each free bit doubles the count, the size of any IPv4 block is `2^(32 − prefix)`:

| CIDR | Host bits | Addresses | Range |
|------|-----------|-----------|-------|
| `10.0.0.0/8` | 24 | 16,777,216 | `10.0.0.0` – `10.255.255.255` |
| `192.168.0.0/16` | 16 | 65,536 | `192.168.0.0` – `192.168.255.255` |
| `192.168.0.0/24` | 8 | 256 | `192.168.0.0` – `192.168.0.255` |
| `203.0.113.7/32` | 0 | 1 | `203.0.113.7` only |

IPv6 works the same way, just with 128 bits, so a `/64` — the standard size for a single network segment — still leaves a colossal `2^(128 − 64)` addresses. The arithmetic is identical; only the total width changes.

## Why allow-lists and classification use CIDR

Ranges are exactly what you want when the thing you care about is a *group* of addresses. Your office egress, a cloud provider's block, or the reserved private ranges from RFC 1918 are all naturally CIDR blocks:

- `10.0.0.0/8`
- `172.16.0.0/12`
- `192.168.0.0/16`

Writing "allow `10.0.0.0/8`" is both shorter and more honest than pasting sixteen million individual addresses. And the membership test — is this one address inside that block? — is a fast bitwise comparison, not a lookup.

This is also how IP *classification* works. IANA publishes its special-purpose registries as CIDR blocks: `127.0.0.0/8` for loopback, `169.254.0.0/16` for link-local, `100.64.0.0/10` for carrier-grade NAT, `192.0.2.0/24` for documentation. To classify an address, you check which registered block it lands in. Boundstone's IP endpoint does exactly that: `range_classification` matches the input against the IANA CIDR ranges and reports whether it is public, private, loopback, reserved, and so on — pure structural fact, no external data required.

## Doing it in code

For membership and sizing, you almost never need a library beyond your standard library. Python's `ipaddress` module handles CIDR completely, and for most allow-list logic it is genuinely all you need — so reach for it first:

```python
import ipaddress

net = ipaddress.ip_network("192.168.0.0/24")

print(net.num_addresses)      # 256
print(net.network_address)    # 192.168.0.0
print(net.broadcast_address)  # 192.168.0.255
print(net.netmask)            # 255.255.255.0

# the membership test — the whole point of CIDR
print(ipaddress.ip_address("192.168.0.42") in net)  # True
print(ipaddress.ip_address("192.168.1.42") in net)  # False
```

That covers "is this address in my range?" and "how big is this block?" cleanly, offline, in every language that ships an IP library. (For a fuller walkthrough of parsing and classifying addresses in Python, see [validating IPs in Python](/blog/validate-ip-python).)

The layer past that is when you want the *classification* itself — public vs. private vs. bogon — over HTTP, as one consistent contract alongside phone and email checks, without wiring the IANA registry into every service yourself:

```bash
curl -s https://api.boundstone.io/v1/verify/ip \
  -H "Authorization: Bearer bs_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ip":"8.8.8.8"}'
```

```json
{
  "valid": true,
  "version": 4,
  "normalized": "8.8.8.8",
  "classification": "public",
  "is_public": true,
  "is_bogon": false,
  "checks": {
    "performed": ["format", "version", "range_classification"],
    "not_performed": ["geolocation", "asn", "hosting_datacenter", "proxy_vpn_tor", "reputation"]
  }
}
```

You can try the same thing with no key on the [IP validator tool](/tools/ip-validator).

## Where CIDR stops

Here is the boundary, stated plainly. CIDR classification tells you *structural* facts you can verify from the address alone: which reserved block it belongs to, whether it is publicly routable, whether it is a bogon. It does **not** tell you where the address physically is, which network operates it, whether it is a datacenter, or whether it is a VPN, proxy, or Tor exit. Those live in the `not_performed` list — `geolocation`, `asn`, `hosting_datacenter`, `proxy_vpn_tor`, `reputation` — because Boundstone does not do them today. IP intelligence needs licensed data, and that has not shipped. The classification is honest about being classification. (For more on the private/reserved side specifically, see [what is a private IP](/blog/what-is-a-private-ip).)

That is the point of listing what was *not* checked: when the response says `is_public: true`, you know it means the address is not in a reserved range — no more, no less.

## The short version

- CIDR = base address `+` `/prefix`; the prefix counts the fixed leading bits.
- Bigger prefix → smaller range. IPv4 block size = `2^(32 − prefix)`.
- `/24` = 256 addresses, `/8` = ~16.7M, `/32` = one address.
- Use CIDR for allow-lists and range membership; the stdlib (`ipaddress`) handles that offline.
- Use Boundstone's `range_classification` when you want IANA-block classification as part of one contract — and read `checks.not_performed` to know precisely what a "valid" does and does not claim.
