Subnetting is splitting one IPv4 block into smaller blocks by moving the boundary between the network part and the host part of the address to the right. Every calculation in it comes down to one 32-bit pattern of ones followed by zeros. Write that pattern in dotted decimal and it is a subnet mask; count its ones and it is a prefix length. This guide works through the binary first, then the standards behind it, a variable-length plan for a real block, the /31 and /32 exceptions, and the extra addresses cloud providers hold back.

Every network value below was produced by the IP Subnet Calculator, and the test suite recomputes them from the calculator’s code. The example addresses come from the private ranges in RFC 1918 and the documentation ranges in RFC 5737 (192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24), which are never routed on the internet.

A mask and a prefix are the same 32 bits

RFC 950 (August 1985) introduced the subnet mask: a 32-bit value with a one in every bit position that belongs to the network and subnet fields. RFC 950 allowed the subnet bits to be non-contiguous but recommended that they be contiguous and placed directly after the network number. RFC 4632, the current CIDR document, removed that choice: “The only outstanding constraint is that the mask must be left contiguous.” Because the ones are always contiguous, a mask can be written as a single number, the prefix length after the slash.

Inside one octet, a contiguous mask can only take nine values:

Ones in the octetBinaryDecimalBlock size in that octet
0000000000256
110000000128128
21100000019264
31110000022432
41111000024016
5111110002488
6111111002524
7111111102542
8111111112551

To turn a prefix into a mask, fill whole octets with 255 for every 8 bits, look up the remainder in the table, and pad with zeros. /20 is 8 + 8 + 4: 255.255.240.0. To go the other way, add the ones octet by octet: 255.255.255.192 is 8 + 8 + 8 + 2 = /26. A value such as 255.255.255.200 (11001000) has a zero between ones and is not a valid mask.

The wildcard mask used in Cisco ACLs and OSPF network statements is the bitwise inverse: subtract each octet from 255, so /20 becomes 0.0.15.255. The last column of the table is the step between subnets in the octet where the mask changes, 256 − mask octet. It is the shortcut for the next section.

Finding the subnet an address belongs to

A router finds the network of an address with a bitwise AND of the address and the mask. Take 172.31.100.5/20:

Address    10101100.00011111.0110|0100.00000101   172.31.100.5
Mask       11111111.11111111.1111|0000.00000000   255.255.240.0
Network    10101100.00011111.0110|0000.00000000   172.31.96.0
Broadcast  10101100.00011111.0110|1111.11111111   172.31.111.255

The bar marks the end of the 20-bit prefix. The network row keeps the bits to the left of it and sets the 12 host bits to 0; the broadcast row sets them to 1.

The shortcut does the same in decimal. The interesting octet is the third one, where the mask is 240, so subnets step by 256 − 240 = 16: .0, .16, .32, .48, .64, .80, .96, .112. The address has 100 in that octet, and 100 lies between 96 and 112, so the network is 172.31.96.0 and the broadcast address is one below the next network, 172.31.111.255. Twelve host bits give 2^12 = 4,096 addresses, and 4,094 of them are usable once the all-zeros network address and the all-ones broadcast address are removed. The calculator returns exactly these values: first host 172.31.96.1, last host 172.31.111.254, wildcard 0.0.15.255.

The same steps answer the question you get from a log line. With a /27 mask, 198.51.100.200 steps by 32 in the last octet, and 200 falls in the block that starts at 192:

The network is 198.51.100.192, the broadcast 198.51.100.223, and 30 hosts are usable. An address such as 198.51.100.230 is in the next subnet, so traffic between the two goes through a router even though both addresses look alike.

How many subnets, how many hosts

Borrowing n bits from the host part gives 2^n subnets, and each keeps 2^(h) addresses, where h is the number of host bits left. For a /24:

PrefixBorrowed bitsSubnets in a /24Addresses eachUsable hosts each
/2512128126
/26246462
/27383230
/284161614
/2953286
/3066442

Older textbooks subtract 2 from the subnet count as well. That comes from RFC 950, which said the values of all zeros and all ones in the subnet field “should not be assigned to actual (physical) subnets” (in its 6-bit example, “any value except 0 and 63”). RFC 1878 (December 1995) marked this as history: “This practice is obsolete! Modern software will be able to utilize all definable networks.” The host count still loses two addresses, because the network and broadcast addresses inside each subnet keep their meaning. The exceptions are /31 and /32, covered below.

What CIDR changed

Before CIDR, the first bits of an address fixed its network size: class A was a /8, class B a /16, class C a /24. An organisation that needed 2,000 addresses got a class B with 65,536, or several class C networks that each took a separate route. RFC 4632 (2006, which replaced RFC 1519 from 1993) writes every network as a prefix with an explicit length, so a block can be any power of two from one address to 2^32. Its own example: the legacy class B network 172.16.0.0 “is defined as the prefix 172.16.0.0/16”.

Two consequences matter in day-to-day work. First, aggregation: 192.168.0.0/24 and 192.168.1.0/24 are the two halves of 192.168.0.0/23 (mask 255.255.254.0, from 192.168.0.0 to 192.168.1.255), so one route can cover both.

Second, longest-match forwarding (RFC 4632 §5.1: “Forwarding in the Internet is done on a longest-match basis”). If a routing table holds both 10.0.0.0/8 and 10.20.1.0/24, a packet for 10.20.1.9 follows the /24, because it matches more bits. This is why an overlapping subnet does not fail loudly: the more specific route quietly wins, and part of the bigger network becomes unreachable from that router.

VLSM: carving one block for several networks

Variable-length subnet masking means giving each network the prefix it needs instead of cutting the whole block into equal pieces. Suppose a site has 10.20.0.0/22 (1,024 addresses) and needs a user LAN with 300 hosts, a server LAN with 120, a Wi-Fi LAN with 50, a management network with 10, and two router-to-router links.

  1. Size each network: the smallest prefix whose usable count covers the hosts. 300 needs 9 host bits, a /23 (510 usable); 120 needs a /25 (126); 50 needs a /26 (62); 10 needs a /28 (14); each link takes a /31.
  2. Sort from largest to smallest and allocate in that order. A block of 2^h addresses has to start on a multiple of 2^h. Placing the large blocks first keeps every later block aligned without gaps.
  3. Write each network down and check it.
NetworkHosts neededAllocationRange (network – broadcast)Usable
Users30010.20.0.0/2310.20.0.0 – 10.20.1.255510
Servers12010.20.2.0/2510.20.2.0 – 10.20.2.127126
Wi-Fi5010.20.2.128/2610.20.2.128 – 10.20.2.19162
Management1010.20.2.192/2810.20.2.192 – 10.20.2.20714
Link 1210.20.2.208/3110.20.2.208 – 10.20.2.2092
Link 2210.20.2.210/3110.20.2.210 – 10.20.2.2112

Everything from 10.20.2.212 to 10.20.3.255 is still free: a /30, a /29 and a /27 up to the end of 10.20.2.x, then the whole 10.20.3.0/24. If the small networks had been allocated first, the /23 could not start at 10.20.0.4: it must start at an address whose third octet is even and whose fourth octet is 0.

/31 and /32: blocks with no broadcast address

A point-to-point link has exactly two ends, and nothing on it needs a broadcast. With a /30, half of its four addresses are spent on the network and broadcast. RFC 3021 (Standards Track, December 2000) allows a 31-bit mask on such links and states that the two addresses “MUST be interpreted as host addresses”.

The calculator follows RFC 3021: for 203.0.113.9/31 it reports 2 usable hosts, 203.0.113.8 and 203.0.113.9. Its Broadcast Address row shows None for /31 and /32, with the reason. A /32 is a single address; it appears in host routes, loopback interfaces, and allow-lists. Type 198.51.100.7 and pick /32 in the dropdown, and the network, first and last addresses are all 198.51.100.7 with 1 usable host. Python’s ipaddress module applies the same rules: hosts() on a /31 returns both addresses and on a /32 returns the one address.

Cloud subnets reserve more than two addresses

Cloud VPCs take extra addresses from every subnet for their own router and DNS, so the usable count is lower than the calculator’s figure:

ProviderAddresses reserved per subnetUsable in a /24Usable in a /28Subnet size limits
Classic subnet (calculator)2: network, broadcast25414—
AWS VPC5: the first four and the last25111/28 to /16
Azure Virtual Network5: network, gateway, two for Azure DNS, broadcast25111smallest /29, largest /2
Google Cloud VPC4 in the primary range: network, gateway, second-to-last, broadcast25212/29 to /4

AWS documents the five addresses for 10.0.0.0/24 as the network address, .1 for the VPC router, .2 for the DNS server, .3 “for future use”, and .255: “We do not support broadcast in a VPC, therefore we reserve this address.” AWS also normalises a subnet entered with host bits set, the same way the calculator does: its example turns 100.68.0.18/18 into 100.68.0.0/18. Google Cloud reserves nothing in secondary IPv4 ranges. So the 172.31.100.5/20 example above has 4,094 usable hosts on a classic LAN, 4,091 in an AWS or Azure subnet, and 4,092 in a Google Cloud primary range. Subtract the provider’s reservation yourself when you size a cloud subnet.

Checking a plan with Python’s ipaddress

import ipaddress as ip

lan = ip.ip_network("10.20.0.0/22")
print([str(n) for n in lan.subnets(new_prefix=24)])  # -> ['10.20.0.0/24', '10.20.1.0/24', '10.20.2.0/24', '10.20.3.0/24']
print(ip.ip_network("10.20.0.0/23").overlaps(ip.ip_network("10.20.1.0/24")))  # -> True
print(list(ip.collapse_addresses([ip.ip_network("192.168.0.0/24"), ip.ip_network("192.168.1.0/24")])))  # -> [IPv4Network('192.168.0.0/23')]
print(ip.ip_network("0.0.0.0/255.255.240.0").prefixlen)  # -> 20
print(ip.ip_interface("172.31.100.5/20").network)  # -> 172.31.96.0/20
print([str(h) for h in ip.ip_network("203.0.113.8/31").hosts()])  # -> ['203.0.113.8', '203.0.113.9']
try:
    ip.ip_network("10.0.0.1/24")
except ValueError as e:
    print(e)  # -> 10.0.0.1/24 has host bits set

ip_network is strict: 10.0.0.1/24 is a valid interface address but not a network, because a host bit is set. Use ip_interface when you hold an address with its prefix, or pass strict=False. The calculator takes the lenient route: any address inside the subnet works, and the CIDR Notation field shows the network form, 10.0.0.0/24. The output above was produced with Python 3.12.

Mistakes that keep coming back

  • Dotted masks where a prefix is expected. The calculator takes /28 after the slash and rejects /255.255.255.240; Python accepts both forms. Convert with the first table.
  • Leading zeros. 10.0.0.010 is rejected by inet_pton, Python, and this calculator. Some older parsers read 010 as octal 8, so the same string can mean two different hosts.
  • Overlapping plans. A VPC peering or VPN between two networks that both use 10.0.0.0/16 will not work; AWS refuses to create a peering connection between VPCs “that have matching or overlapping IPv4 or IPv6 CIDR blocks”. Pick distinct ranges early; besides RFC 1918, Azure accepts the shared address space 100.64.0.0/10 from RFC 6598 as private.
  • Wildcard and subnet masks swapped. An ACL line written with 255.255.255.0 where 0.0.0.255 was meant matches a completely different set of addresses.

Practice problems

Work each one by hand, then type the input into the calculator to check:

QuestionAnswer
Network of 10.170.70.19 with mask 255.255.255.240?/28, step 16: network 10.170.70.16, broadcast 10.170.70.31, 14 hosts
Mask, network and hosts for 172.16.45.200/21?255.255.248.0, step 8 in the third octet: 172.16.40.0 to 172.16.47.255, 2,046 hosts
Which /29 holds 203.0.113.77?203.0.113.72/29, broadcast 203.0.113.79, hosts .73 to .78, 6 usable
How large is 100.64.0.0/10?Mask 255.192.0.0, 100.64.0.0 to 100.127.255.255, 4,194,302 usable

The IP Subnet Calculator takes an address with a prefix (or an address plus a prefix from the dropdown) and returns the network, broadcast, both masks, the first and last host, and the usable count. It handles one IPv4 subnet at a time: it does not split a block, list addresses, or compare two ranges, so use the Python snippets above for those.