NET-110 · Lesson 5 of 12 · 28 min

Subnetting step by step

▶ Video coming soon A video version of this lesson is on the way.

Sam's plan for Ridgeline is to stop running every staff device in one big network. The tills and card terminals should sit in their own network, the roastery equipment in another, the office computers in a third, the guest Wi-Fi stays separate, and the switches and access points should be managed from a small network of their own. Each of those networks needs its own range of addresses, carved out of one larger block without overlaps and without wasting space. Carving a block into smaller networks is called subnetting, and it is the single most practised skill in introductory networking.

This lesson works through subnetting by hand, so the arithmetic becomes automatic, and then checks every answer with Python. It covers why networks are subnetted, how borrowing host bits creates subnets, the block-size method for finding subnet boundaries quickly, how to choose a prefix for a required number of hosts, variable-length subnet masks for networks of different sizes, and summarizing several networks into one route.

Why subnet?

A single flat network is simple, but it has costs that grow with size. Every broadcast, such as an ARP question or a DHCP request, reaches every device, so a large flat network spends more and more of its capacity on chatter. Every device can reach every other device directly at layer 2, so a compromised laptop can probe the tills, and there is no place to put rules between groups. Faults also spread: a looping cable or a misbehaving device can disrupt the whole network at once.

Splitting one network into subnets, each in its own VLAN, fixes these problems. Broadcasts stay within their subnet. Traffic between subnets must pass through a router or firewall, where rules decide which groups may talk to which: tills may reach the payment processor but not the office file share, guests may reach the internet and nothing else. Problems are contained within one subnet. Address plans also become easier to read: at a glance, an address in 10.20.20.0/26 is a payment device and one in 10.20.40.0/27 is roastery equipment.

Borrowing host bits

Subnetting takes a network and moves the dividing line between network and host bits to the right, borrowing some host bits to number the subnets. Borrowing n bits creates 2 to the power n subnets, and each subnet has the host bits that remain. Take 192.168.10.0/24 and borrow 2 bits: the prefix becomes /26, there are 2 squared, or 4, subnets, and each has 6 host bits, so 64 addresses and 62 usable hosts.

Splitting 192.168.10.0/24 into four /26 subnets
SubnetNetwork addressFirst hostLast hostBroadcast
1192.168.10.0/26192.168.10.1192.168.10.62192.168.10.63
2192.168.10.64/26192.168.10.65192.168.10.126192.168.10.127
3192.168.10.128/26192.168.10.129192.168.10.190192.168.10.191
4192.168.10.192/26192.168.10.193192.168.10.254192.168.10.255
A long horizontal bar representing the 256 addresses of 192.168.10.0/24, divided into four equal blocks of 64. The blocks are labelled 192.168.10.0/26, .64/26, .128/26 and .192/26. Under each block the first and last addresses are written, and the first address of each block is marked network and the last broadcast.

Each subnet's network address is a multiple of its size, and each broadcast address is one less than the next subnet's network address. Once that pattern is clear, any subnet can be written down without converting anything to binary.

Each borrowed bit doubles the number of subnets and halves their size
import ipaddress

block = ipaddress.ip_network('192.168.10.0/24')
for borrowed in (1, 2, 3, 4):
    subnets = list(block.subnets(prefixlen_diff=borrowed))
    first = subnets[0]
    print('borrow', borrowed, '->', len(subnets), 'subnets of /' + str(first.prefixlen),
          'with', first.num_addresses - 2, 'hosts each; first two:', subnets[0], subnets[1])
Output
borrow 1 -> 2 subnets of /25 with 126 hosts each; first two: 192.168.10.0/25 192.168.10.128/25
borrow 2 -> 4 subnets of /26 with 62 hosts each; first two: 192.168.10.0/26 192.168.10.64/26
borrow 3 -> 8 subnets of /27 with 30 hosts each; first two: 192.168.10.0/27 192.168.10.32/27
borrow 4 -> 16 subnets of /28 with 14 hosts each; first two: 192.168.10.0/28 192.168.10.16/28

The block-size method

The fastest way to subnet in your head is the block size, also called the magic number. Find the octet where the prefix falls, the interesting octet, and work out 256 minus the mask value in that octet. For /26 the mask is 255.255.255.192, the interesting octet is the fourth, and the block size is 256 minus 192, which is 64: subnets start at 0, 64, 128 and 192. For /27 the mask is 224 and the block is 32; for /28 the mask is 240 and the block is 16. The same works in other octets: a /20 has mask 255.255.240.0, block size 16 in the third octet, so its subnets start at x.x.0.0, x.x.16.0, x.x.32.0 and so on.

Prefixes in the last octet: worth memorizing
PrefixMaskBlock sizeAddressesUsable hosts
/24255.255.255.0256256254
/25255.255.255.128128128126
/26255.255.255.192646462
/27255.255.255.224323230
/28255.255.255.240161614
/29255.255.255.248886
/30255.255.255.252442

To find which subnet an address belongs to, divide the interesting octet by the block size, ignore the remainder, and multiply back. For 10.20.20.77/26, the block is 64; 77 divided by 64 is 1 with a remainder, and 1 times 64 is 64, so the subnet is 10.20.20.64/26, its broadcast is 64 plus 64 minus 1, or 127, and its hosts are .65 to .126. With practice this takes a few seconds and needs no binary at all.

The block-size method in code: no binary needed
def subnet_of(address, prefix):
    octets = [int(o) for o in address.split('.')]
    index = prefix // 8                 # which octet the prefix falls in (0-based)
    if index == 4:
        return address, address
    bits_in_octet = prefix % 8
    block = 256 >> bits_in_octet        # 256 minus the mask value in that octet
    start = octets[index] // block * block
    net = octets[:index] + [start] + [0] * (3 - index)
    bc = octets[:index] + [start + block - 1] + [255] * (3 - index)
    return '.'.join(map(str, net)), '.'.join(map(str, bc))

for a, p in (('10.20.20.77', 26), ('10.20.40.19', 27), ('172.16.37.5', 20), ('192.168.1.200', 25)):
    net, bc = subnet_of(a, p)
    print((a + '/' + str(p)).ljust(18), 'network', net.ljust(14), 'broadcast', bc)
Output
10.20.20.77/26     network 10.20.20.64    broadcast 10.20.20.127
10.20.40.19/27     network 10.20.40.0     broadcast 10.20.40.31
172.16.37.5/20     network 172.16.32.0    broadcast 172.16.47.255
192.168.1.200/25   network 192.168.1.128  broadcast 192.168.1.255

Try it yourself

Find the block size

Write block_size(prefix) that returns the block size for a prefix length between 1 and 32: the number of addresses each subnet spans in the octet where the prefix falls. For /26 it is 64, for /20 it is 16 (in the third octet), for /24 it is 256 in the fourth octet but 1 in the third, so return 256 for /8, /16 and /24, and 1 for /32.

Then write subnet_starts(prefix) that returns the list of values the interesting octet takes at the start of each subnet, for example [0, 64, 128, 192] for /26.

def block_size(prefix):
    return 256 - prefix

def subnet_starts(prefix):
    return [0]

print(block_size(26), subnet_starts(26))

Choosing a prefix for a number of hosts

Design usually starts from the other direction: you know how many devices a network must hold and need the smallest subnet that fits. Find the smallest number of host bits h for which 2 to the power h minus 2 is at least the number of hosts needed, then the prefix is 32 minus h. Always allow for growth: the guest Wi-Fi may serve forty phones on a quiet day and a hundred and fifty during a weekend festival.

For Ridgeline: the office needs about 25 devices today, so allow 60 and choose a /26 (62 hosts); the tills, card terminals and kitchen display need 8, allow 30, a /27 (30 hosts); the roastery equipment needs 6, allow 14, a /28; guests need up to 200, a /24 (254 hosts); and management, covering the switch, access points and firewall interfaces, needs 6, a /29 (6 hosts) is tight, so a /28. Lesson 12 finalizes these numbers.

The smallest prefix that holds a given number of hosts
import math

def prefix_for(hosts):
    host_bits = max(2, math.ceil(math.log2(hosts + 2)))   # +2 for the network and broadcast addresses
    return 32 - host_bits

needs = {'office': 60, 'payments': 30, 'roastery': 14, 'guest Wi-Fi': 200, 'management': 12}
for name, hosts in needs.items():
    p = prefix_for(hosts)
    print(name.ljust(12), 'needs', str(hosts).rjust(3), '-> /' + str(p), 'gives', 2 ** (32 - p) - 2, 'hosts')
Output
office       needs  60 -> /26 gives 62 hosts
payments     needs  30 -> /27 gives 30 hosts
roastery     needs  14 -> /28 gives 14 hosts
guest Wi-Fi  needs 200 -> /24 gives 254 hosts
management   needs  12 -> /28 gives 14 hosts

Using floating-point logarithms for this is safe for the sizes in a LAN, but a loop that counts up host bits until the subnet is big enough is just as short and avoids any doubt about rounding. The exercise below asks for that version.

Try it yourself

Smallest subnet for a number of hosts

Write prefix_for(hosts) that returns the longest prefix length (the smallest subnet) that provides at least hosts usable addresses, remembering that each subnet loses two addresses for the network and broadcast. Use a loop over host bits rather than logarithms. The smallest allowed subnet is a /30.

For 30 hosts the answer is 27; for 31 hosts it is 26.

def prefix_for(hosts):
    return 32 - hosts // 8

print(prefix_for(30), prefix_for(31))

Variable-length subnet masks

When every subnet must be the same size, the largest requirement forces the size of all of them, and a /24 for each of Ridgeline's five networks would waste hundreds of addresses. Variable-length subnet masking, or VLSM, gives each subnet its own prefix length. The method is to sort the requirements from largest to smallest, allocate the largest first at the start of the block, then place each smaller subnet at the next free boundary that is a multiple of its own size. Allocating largest first guarantees that every subnet starts on a proper boundary without gaps or overlaps.

VLSM: allocating different-sized subnets from one block, largest first
import ipaddress

def vlsm(block, needs):
    """Allocate subnets from block, largest first. needs maps a name to a prefix length."""
    free = ipaddress.ip_network(block)
    next_start = int(free.network_address)
    plan = []
    for name, prefix in sorted(needs.items(), key=lambda item: item[1]):   # small prefix = big subnet first
        size = 2 ** (32 - prefix)
        start = -(-next_start // size) * size                            # round up to a boundary
        net = ipaddress.ip_network((start, prefix))
        if not net.subnet_of(free):
            raise ValueError('block too small for ' + name)
        plan.append((name, net))
        next_start = start + size
    return plan

needs = {'guest Wi-Fi': 24, 'office': 26, 'payments': 27, 'roastery': 28, 'management': 28}
for name, net in vlsm('10.20.0.0/22', needs):
    print(name.ljust(12), str(net).ljust(16), 'hosts', net.num_addresses - 2)
Output
guest Wi-Fi  10.20.0.0/24     hosts 254
office       10.20.1.0/26     hosts 62
payments     10.20.1.64/27    hosts 30
roastery     10.20.1.96/28    hosts 14
management   10.20.1.112/28   hosts 14

The five networks fit into 10.20.0.0 to 10.20.1.127, three eighths of the /22, leaving the rest free for growth. In practice Sam chooses a plan that is easier to read, putting each network in its own third octet that matches its VLAN number, such as 10.20.20.0 for VLAN 20. That uses more address space than a tightly packed VLSM plan, but private address space is plentiful, and an address that tells you its VLAN at a glance saves time in every future troubleshooting session. Both approaches are correct; the choice is a design trade-off.

Try it yourself

Do two subnets overlap?

Overlapping subnets are a classic design error. Write overlaps(plan) that takes a list of networks in CIDR notation and returns a list of tuples (a, b) for every pair that overlaps, in the order they appear in the plan (each pair once, with the earlier network first). Use ipaddress.ip_network(...).overlaps(other). Return an empty list when there are no overlaps.

import ipaddress

def overlaps(plan):
    return []

print(overlaps(['10.20.0.0/24', '10.20.1.0/26', '10.20.0.128/27']))

A few checks catch most VLSM mistakes before they reach a router. Every subnet's network address must be a multiple of its block size. No two subnets may overlap, which the exercise below automates. The total of all subnet sizes must not exceed the size of the block. And each subnet must actually hold the hosts it was sized for, with the gateway counted as one of them: a /29 for six devices leaves no room for the router interface that will be their default gateway, a classic way to come up one address short on installation day.

Summarizing routes

Subnetting has a mirror image. Several contiguous subnets can be described by one shorter prefix, called a summary route or supernet. 10.20.20.0/24, 10.20.21.0/24, 10.20.22.0/24 and 10.20.23.0/24 share their first 22 bits, so they can be advertised as one route, 10.20.20.0/22. Summarization keeps routing tables small and stable: a branch office can tell head office about one route instead of twenty. It only works cleanly when the subnets are contiguous and start on a boundary of the summary's size, which is another reason to plan addresses rather than hand them out as needed. Python's ipaddress.collapse_addresses performs the calculation.

Four contiguous /24s collapse into one /22; two that straddle a boundary do not
import ipaddress

nets = [ipaddress.ip_network(n) for n in ('10.20.20.0/24', '10.20.21.0/24', '10.20.22.0/24', '10.20.23.0/24')]
print(list(ipaddress.collapse_addresses(nets)))

uneven = [ipaddress.ip_network(n) for n in ('10.20.21.0/24', '10.20.22.0/24')]
print(list(ipaddress.collapse_addresses(uneven)))   # not on a /23 boundary, so no single summary
Output
[IPv4Network('10.20.20.0/22')]
[IPv4Network('10.20.21.0/24'), IPv4Network('10.20.22.0/24')]

Worked problem

Subnetting a /24 by hand

A second Ridgeline cafe will receive 10.21.5.0/24. It needs subnets for 50 staff devices, 20 payment devices and 10 cameras, plus room to grow. Plan them by hand.

Show solution
  1. 50 hosts need 6 host bits (62 usable): /26, block size 64.
  2. 20 hosts need 5 host bits (30 usable): /27, block size 32.
  3. 10 hosts need 4 host bits (14 usable): /28, block size 16.
  4. Allocate largest first: staff 10.21.5.0/26 (hosts .1 to .62, broadcast .63).
  5. Payments at the next 32 boundary: 10.21.5.64/27 (hosts .65 to .94, broadcast .95).
  6. Cameras at the next 16 boundary: 10.21.5.96/28 (hosts .97 to .110, broadcast .111).
  7. Free for growth: 10.21.5.112 to 10.21.5.255, which is a /28 at .112 and a /25 at .128.

Answer: Staff 10.21.5.0/26, payments 10.21.5.64/27, cameras 10.21.5.96/28, with 10.21.5.112/28 and 10.21.5.128/25 left for later.

Common mistakes

  • MistakeForgetting the two reserved addresses when sizing a subnet.

    InsteadUsable hosts are 2 to the power h minus 2; 32 addresses hold 30 hosts.

  • MistakeStarting a subnet at an address that is not a multiple of its block size.

    InsteadEvery subnet must begin on a boundary of its own size.

  • MistakeAllocating small VLSM subnets first.

    InsteadAllocate largest first so each subnet lands on a valid boundary.

  • MistakeSizing networks for today's devices only.

    InsteadAllow room for growth; private address space is plentiful.

  • MistakeCreating overlapping subnets.

    InsteadCheck the plan for overlaps, by hand or with a script, before configuring anything.

  • MistakeTrying to summarize subnets that are not contiguous or not aligned.

    InsteadPlan contiguous, aligned blocks if you want to summarize them later.

Summary and key terms

  • Subnetting splits one network into smaller ones to contain broadcasts, apply rules between groups and contain faults.
  • Borrowing n host bits makes 2 to the power n subnets; each keeps the remaining host bits.
  • The block size is 256 minus the mask value in the interesting octet; subnets start at multiples of it.
  • Choose the smallest prefix whose 2 to the power h minus 2 hosts covers the need, with room to grow.
  • VLSM gives each subnet its own size; allocate largest first on proper boundaries.
  • Contiguous, aligned subnets can be summarized into one shorter prefix.
  • Python's ipaddress module can split, check, allocate and summarize networks reliably.
Subnetting
Dividing a network into smaller networks by borrowing host bits.
Subnet
One of the smaller networks created by subnetting.
Block size
The number of addresses each subnet spans in the interesting octet; 256 minus the mask value there.
Interesting octet
The octet in which the prefix boundary falls.
VLSM
Variable-length subnet masking: subnets of different sizes within one block.
Summary route
One shorter prefix that covers several contiguous subnets.
Supernet
Another name for a summary covering several networks.
Broadcast domain
The set of devices that receive each other's broadcasts; one per subnet or VLAN.

Practice problems

Work each problem on paper first, then reveal the answer to check.

  1. How many /27 subnets fit in a /24?

    Show answer

    8 (2^3).

  2. What is the block size for /29?

    Show answer

    8 (256 - 248).

  3. Which subnet contains 192.168.10.150/26?

    Show answer

    192.168.10.128/26 (hosts .129 to .190).

  4. What prefix holds 100 hosts?

    Show answer

    /25 (126 hosts).

  5. What is the broadcast address of 10.21.5.96/28?

    Show answer

    10.21.5.111.

  6. What is the third octet block size for a /21?

    Show answer

    8 (mask 255.255.248.0).

  7. Which subnet contains 172.16.37.5/20?

    Show answer

    172.16.32.0/20 (block 16 in the third octet).

  8. Summarize 10.1.4.0/24 to 10.1.7.0/24 in one route.

    Show answer

    10.1.4.0/22.

  9. Why allocate the largest VLSM subnet first?

    Show answer

    So every subnet starts on a boundary that is a multiple of its size, with no overlaps.

  10. Why might a designer use one /24 per VLAN instead of tightly packed subnets?

    Show answer

    Readable addresses that match VLAN numbers, and plenty of private space, make operations easier.

Lesson quiz · 5 questions

Score 60% or more to complete this lesson.

Take the quiz