Skip to content
Networking
Lab 2 of 7·30mBeginner

Work out subnets and CIDR by hand

Calculate network, broadcast and usable range for a prefix, then size the VPC subnets you would actually deploy.

You need

  • A Linux system with a shell
  • ipcalc (apt-get install -y ipcalc) — optional, for checking your answers

Do first

Every cloud console asks you for a CIDR block and gives no help if you get it wrong. The arithmetic is small and worth being able to do without a calculator.

1. What the prefix actually means

An IPv4 address is 32 bits. The prefix length says how many leading bits are the network; the rest identify a host inside it.

ipcalc 10.0.1.0/24 2>/dev/null || echo "install ipcalc to check answers"

For 10.0.1.0/24: 24 network bits, 8 host bits. That gives 2^8 = 256 addresses, of which 254 are usable — the first is the network address and the last is broadcast.

The table worth memorising, because these are the sizes you will actually type:

PrefixAddressesUsableTypical use
/3211A single host, a security group rule
/3042A point-to-point link
/281614A small subnet
/24256254The default mental unit
/2040964094A large subnet
/166553665534A whole VPC

Each step down in prefix doubles the size. /24/23 is 512 addresses.

Verify

python3 -c "print(2**(32-24), 2**(32-24)-2)" # 256 254

2. Do it with the tool, then without

ipcalc 192.168.10.130/26

Now by hand. A /26 has 6 host bits, so blocks are 64 addresses wide and start at multiples of 64: .0, .64, .128, .192. The address .130 falls in the block starting at .128.

  • Network: 192.168.10.128
  • First usable: 192.168.10.129
  • Last usable: 192.168.10.190
  • Broadcast: 192.168.10.191

The general method: block size is 2^(32 - prefix); the network address is the largest multiple of the block size at or below your address.

python3 - <<'PY'
import ipaddress
n = ipaddress.ip_network("192.168.10.130/26", strict=False)
print("network  ", n.network_address)
print("broadcast", n.broadcast_address)
print("usable   ", list(n.hosts())[0], "-", list(n.hosts())[-1])
print("size     ", n.num_addresses)
PY

Verify

python3 -c " import ipaddress n=ipaddress.ip_network('192.168.10.130/26',strict=False) print(n.network_address, n.broadcast_address)" # 192.168.10.128 192.168.10.191

3. Decide whether two addresses can talk directly

This is what the prefix is _for_. Two hosts reach each other without a router only if they share a network.

python3 - <<'PY'
import ipaddress
pairs = [
    ("10.0.1.5/24", "10.0.1.200"),
    ("10.0.1.5/24", "10.0.2.10"),
    ("10.0.1.5/16", "10.0.2.10"),
]
for cidr, other in pairs:
    iface = ipaddress.ip_interface(cidr)
    same = ipaddress.ip_address(other) in iface.network
    print(f"{cidr:16} -> {other:12} same network: {same}")
PY

The second and third rows differ only in the prefix. Same addresses, opposite answer. A wrong netmask is why a host can reach the internet but not the server in the next rack.

Verify

python3 -c " import ipaddress print(ipaddress.ip_address('10.0.2.10') in ipaddress.ip_interface('10.0.1.5/16').network)" # True

4. Split a VPC the way you would deploy it

Given 10.0.0.0/16 and three availability zones, each needing a public and a private subnet:

python3 - <<'PY'
import ipaddress
vpc = ipaddress.ip_network("10.0.0.0/16")
subnets = list(vpc.subnets(new_prefix=20))
names = ["public-a", "public-b", "public-c",
         "private-a", "private-b", "private-c"]
for name, net in zip(names, subnets):
    print(f"{name:11} {str(net):18} {net.num_addresses - 5:>5} usable")
print(f"\nunused: {len(subnets) - len(names)} more /20 blocks")
PY

Two details that matter in practice. AWS reserves five addresses per subnet, not two — the network address, the broadcast address, the VPC router, the DNS server, and one for future use. And leave blocks unallocated: you cannot resize a subnet after creation, so the spare /20s are what let you add a zone later.

Verify

python3 -c " import ipaddress print(len(list(ipaddress.ip_network('10.0.0.0/16').subnets(new_prefix=20))))" # 16

5. Private ranges, and why you will meet them

python3 - <<'PY'
import ipaddress
for cidr in ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16",
             "100.64.0.0/10", "169.254.0.0/16", "127.0.0.0/8"]:
    n = ipaddress.ip_network(cidr)
    print(f"{cidr:18} {n.num_addresses:>11,} addresses  private={n.is_private}")
PY

The first three are RFC 1918 — the only ranges you may use inside your own network. 100.64.0.0/10 is carrier-grade NAT and shows up in some managed services. 169.254.0.0/16 is link-local: an address in that range means DHCP failed, and on a cloud instance 169.254.169.254 is the metadata endpoint.

Overlapping ranges is the mistake that cannot be fixed later: two VPCs both using 10.0.0.0/16 can never be peered. Pick non-overlapping blocks per environment on day one.

Verify

python3 -c " import ipaddress a=ipaddress.ip_network('10.0.0.0/16'); b=ipaddress.ip_network('10.0.0.0/16') print('overlaps:', a.overlaps(b))" # overlaps: True

Where this goes next

You can size and split a network. Next: how a name becomes one of these addresses.