>_ Exploit the Edge

Home › Networking

Subnet Masks and CIDR: A Practical 2026 Reference

Published 29 Sep 2026 · 4 min read

What a subnet mask actually does

A subnet mask is a bitmask that tells a host which bits of an IP address identify the network and which identify the host. In binary, contiguous 1 bits mark the network portion; 0 bits mark the host portion. The mask is always contiguous — no gaps — because routing decisions are made by simple bitwise AND.

Example (IPv4):

IP address:   192.168.10.45   -> 11000000.10101000.00001010.00101101
Mask /24:     255.255.255.0   -> 11111111.11111111.11111111.00000000
Network:      192.168.10.0    -> 11000000.10101000.00001010.00000000

The network address is IP AND mask. The broadcast address is network OR (NOT mask). Usable hosts = 2^(host bits) - 2 (all-zeros and all-ones reserved).

Source: RFC 791 (IPv4), RFC 950 (subnetting), RFC 4632 (CIDR) [1][2][3]

CIDR notation replaces classful addressing

The original article used Class A/B/C. That model was deprecated in 1993 by RFC 1519 (CIDR). Modern networks use Variable Length Subnet Masking (VLSM) — any prefix length from /0 to /32 (IPv4) or /0 to /128 (IPv6).

CIDR Dotted mask Host bits Usable hosts Typical use
/8 255.0.0.0 24 16,777,214 Large ISP allocation
/16 255.255.0.0 16 65,534 Campus / large org
/24 255.255.255.0 8 254 Standard LAN
/26 255.255.255.192 6 62 Department / VLAN
/30 255.255.255.252 2 2 Point-to-point link
/31 255.255.255.254 1 2* P2P links (RFC 3021)
/32 255.255.255.255 0 1 Loopback / host route
  • /31 allows 2 usable addresses on point-to-point links per RFC 3021 [4].

Quick calculations on the command line

ipcalc (Debian/Ubuntu/Fedora/Arch)

# Install: apt install ipcalc / dnf install ipcalc / pacman -S ipcalc
ipcalc 192.168.10.45/26

Output includes network, broadcast, host range, and wildcard mask.

ip (iproute2, standard on Linux)

# Show all interfaces with CIDR prefixes
ip -brief address show

# Add an address with CIDR
sudo ip address add 10.0.5.10/24 dev eth0

nmcli (NetworkManager)

# Set static IP with CIDR on a connection
nmcli con mod "Wired connection 1" ipv4.addresses 192.168.20.5/24
nmcli con mod "Wired connection 1" ipv4.gateway 192.168.20.1
nmcli con mod "Wired connection 1" ipv4.dns "1.1.1.1 9.9.9.9"
nmcli con mod "Wired connection 1" ipv4.method manual
nmcli con up "Wired connection 1"

Python one-liner (no extra deps)

python3 -c "import ipaddress; n=ipaddress.ip_network('192.168.10.0/26'); print(f'Network: {n.network_address}\nBroadcast: {n.broadcast_address}\nUsable: {n.num_addresses-2}\nFirst: {list(n.hosts())[0]}\nLast: {list(n.hosts())[-1]}')"

VLSM example: carving a /24 into departments

You have 10.10.0.0/24 (256 addresses). You need: - Engineering: 60 hosts - Sales: 25 hosts - Mgmt: 10 hosts - Two P2P links

Allocate largest first (VLSM rule):

Subnet CIDR Mask Range Use
10.10.0.0/26 /26 255.255.255.192 .1–.62 Engineering (62 usable)
10.10.0.64/27 /27 255.255.255.224 .65–.94 Sales (30 usable)
10.10.0.96/28 /28 255.255.255.240 .97–.110 Mgmt (14 usable)
10.10.0.112/30 /30 255.255.255.252 .113–.114 P2P link 1
10.10.0.116/30 /30 255.255.255.252 .117–.118 P2P link 2
10.10.0.120/29 /29 255.255.255.248 .121–.126 Future growth

Verify no overlaps:

python3 -c "
import ipaddress
nets = [ipaddress.ip_network(n) for n in [
    '10.10.0.0/26','10.10.0.64/27','10.10.0.96/28',
    '10.10.0.112/30','10.10.0.116/30','10.10.0.120/29'
]]
for i, a in enumerate(nets):
    for b in nets[i+1:]:
        if a.overlaps(b):
            print(f'OVERLAP: {a} <-> {b}')
            exit(1)
print('No overlaps')
"

IPv6 subnetting: different rules, same idea

IPv6 uses prefix length only — no dotted masks. Standard LAN prefix is /64 (RFC 4291 [5]). The lower 64 bits are the Interface Identifier (often EUI-64 or random per RFC 7217 [6]).

Prefix Use case
/32–/48 ISP allocation to site
/48 Typical site assignment (65k /64 subnets)
/56 Small business / home (256 /64 subnets)
/64 Single LAN / VLAN (required for SLAAC)
/127 Point-to-point links (RFC 6164 [7])
/128 Loopback / host route

Key difference from IPv4: IPv6 reserves the all-zeros interface identifier for the subnet-router anycast address (RFC 4291 §2.6.1). A /64 therefore has 2^64 − 1 usable unicast addresses. The all-ones value has no special reservation. No broadcast exists — IPv6 uses multicast (ff02::1).

IPv6 on Linux

# Show IPv6 addresses with prefix length
ip -6 address show

# Add static IPv6 address
sudo ip -6 address add 2001:db8:1000:1::10/64 dev eth0

# Calculate network from address
python3 -c "import ipaddress; n=ipaddress.ip_network('2001:db8:1000:1::10/64'); print(f'Network: {n.network_address}\nPrefix: {n.prefixlen}')"

Common pitfalls

  1. Non-contiguous masks — invalid. 255.255.0.255 breaks routing. The kernel rejects non-contiguous prefixes when adding addresses via iproute2 [8].
  2. Using /32 or /128 on LAN interfaces — these prefix lengths are valid for loopback/host routes and anycast, but not for regular LAN interfaces where you need a subnet [8].
  3. Forgetting the -2 for IPv4 usable hosts (except /31 and /32).
  4. Mixing prefix lengths in the same L2 segment — causes silent reachability failures.
  5. IPv6 /64 requirement for SLAAC — DHCPv6 or static can use other lengths, but SLAAC requires /64 (RFC 4291 [5]).

Cheat sheet: convert between notations

# CIDR -> dotted mask (IPv4)
python3 -c "import ipaddress; print(ipaddress.IPv4Network('0.0.0.0/26').netmask)"
# 255.255.255.192

# Dotted mask -> CIDR
python3 -c "import ipaddress; print(ipaddress.IPv4Network('192.168.1.0/255.255.255.192').prefixlen)"
# 26

# Wildcard mask (for ACLs) = NOT subnet mask
python3 -c "import ipaddress; m=ipaddress.IPv4Network('0.0.0.0/26').netmask; print(ipaddress.IPv4Address(int(m) ^ 0xffffffff))"
# 0.0.0.63

What changed since the original (2023)

  • Classful addressing removed as a teaching concept — it has not been used in production since the 1990s. CIDR/VLSM is the only relevant model.
  • IPv6 added with /64 LAN standard, SLAAC requirements, and /127 P2P links.
  • Concrete Linux commands (ip, ipcalc, nmcli, Python ipaddress) replace conceptual diagrams.
  • /31 for IPv4 P2P links (RFC 3021) and /127 for IPv6 P2P (RFC 6164) noted as current best practice.
  • Sources tied to RFCs and man pages for every technical claim.

Checklist: before you commit a subnet plan

  • [ ] All subnets use contiguous masks (CIDR)
  • [ ] No overlapping ranges (verify with ipaddress module)
  • [ ] IPv4 usable hosts = 2^(32-prefix) - 2 (except /31, /32)
  • [ ] IPv6 LANs are /64 for SLAAC compatibility
  • [ ] P2P links use /31 (IPv4) or /127 (IPv6)
  • [ ] Gateway address reserved in each subnet (typically .1 or ::1)
  • [ ] Documentation updated (NetBox, Nautobot, spreadsheet, or diagram)
  • [ ] Reverse DNS zones planned for each prefix

FAQ

Why does IPv4 subtract 2 from the host count but IPv6 does not?

IPv4 reserves the all-zeros address (network identifier) and all-ones address (broadcast). IPv6 has no broadcast; it uses multicast. However, IPv6 does reserve the all-zeros interface identifier for the subnet-router anycast address (RFC 4291), so a /64 has 2^64 − 1 usable unicast addresses.

When should I use /31 instead of /30 for IPv4 point-to-point links?

Use /31 (RFC 3021) for any IPv4 point-to-point link where both ends support it. It gives 2 usable addresses instead of 2 usable out of 4, doubling address efficiency. Most modern routing gear and Linux kernels support it.

Can I use a prefix other than /64 on a LAN segment?

Technically yes for static addressing or DHCPv6, but SLAAC (stateless address autoconfiguration) requires /64 per RFC 4291. Using a different prefix breaks SLAAC and some IPv6 features (e.g., privacy extensions per RFC 7217). Stick to /64 for LANs.

How do I verify that my VLSM plan has no overlaps?

Use Python's ipaddress module: load each network as ipaddress.ip_network() and call .overlaps() against every other network. The script in the VLSM section does exactly this and exits with an error if any overlap is found.

What is the subnet-router anycast address in IPv6?

The address with the subnet prefix and an all-zeros interface identifier (e.g., 2001:db8:1::/64 → 2001:db8:1::). Packets sent to this address are delivered to one router on the subnet. It is reserved per RFC 4291 §2.6.1 and cannot be assigned to a host interface.

Sources

  1. RFC 791 – Internet Protocol (IPv4)
  2. RFC 950 – Internet Standard Subnetting Procedure
  3. RFC 4632 – Classless Inter-Domain Routing (CIDR)
  4. RFC 3021 – Using 31-Bit Prefixes on IPv4 Point-to-Point Links
  5. RFC 4291 – IP Version 6 Addressing Architecture
  6. RFC 7217 – Stable Privacy Addresses for IPv6
  7. RFC 6164 – Using 127-Bit IPv6 Prefixes on Inter-Router Links
  8. iproute2 ip-address(8) man page
  9. Python ipaddress module documentation