Python ipaddress Subnet Overlaps and Host Ranges

Python’s standard library includes the ipaddress module, which provides native, robust classes for parsing, analyzing, and manipulating IPv4 and IPv6 network definitions. This article outlines the specific methods and attributes available in IPv4Network and IPv6Network to determine if CIDR blocks intersect, evaluate hierarchical containment, and extract usable host ranges and boundary addresses.

Detecting Subnet Overlaps and Containment

The ipaddress module provides built-in methods to determine whether two CIDR blocks collide or nest within one another.

Checking for Overlaps with overlaps()

The network.overlaps(other) method accepts another network object and returns a boolean indicating whether the two subnets share any IP addresses. This checks for partial, total, or mutual overlap.

import ipaddress

net1 = ipaddress.ip_network("192.168.1.0/24")
net2 = ipaddress.ip_network("192.168.1.128/25")
net3 = ipaddress.ip_network("192.168.2.0/24")

print(net1.overlaps(net2))  # True
print(net1.overlaps(net3))  # False

Checking Subnet Hierarchy with subnet_of() and supernet_of()

To determine direct containment rather than general intersection, use subnet_of() and supernet_of():

parent = ipaddress.ip_network("10.0.0.0/16")
child = ipaddress.ip_network("10.0.5.0/24")

print(child.subnet_of(parent))    # True
print(parent.supernet_of(child))  # True

You can also use Python's in operator to test if an individual IP address or a smaller network falls within a target network:

ip = ipaddress.ip_address("10.0.5.15")
print(ip in parent)  # True

Computing Host Ranges and Boundary Addresses

When working with CIDR blocks, determining the usable host addresses and the theoretical boundaries requires different properties depending on your use case.

Boundary Properties

Network objects provide direct access to edge boundaries through property attributes:

net = ipaddress.ip_network("172.16.0.0/22")

print(net.network_address)    # 172.16.0.0
print(net.broadcast_address)  # 172.16.3.255
print(net.num_addresses)      # 1024

Iterating Usable Hosts with hosts()

The network.hosts() method returns an iterator over usable IP addresses. By default, for IPv4 subnets with a prefix length shorter than /31, it automatically excludes the network address and the broadcast address:

net = ipaddress.ip_network("192.168.10.0/29")

# Iterate through usable host IPs
for host in net.hosts():
    print(host)
# Outputs: 192.168.10.1 through 192.168.10.6

(Note: For /31 point-to-point links defined under RFC 3021 and /32 single-host blocks, hosts() returns all addresses in the block rather than excluding the boundaries.)

Calculating First and Last Usable Hosts Directly

For large subnets, iterating through network.hosts() to find the first and last usable IP can be slow and memory-intensive. Because network objects support indexing, boundaries can be retrieved directly in constant time:

net = ipaddress.ip_network("10.50.0.0/16")

if net.prefixlen <= 30:
    first_usable = net[1]
    last_usable = net[-2]
else:
    first_usable = net[0]
    last_usable = net[-1]

print(f"First Host: {first_usable}")  # 10.50.0.1
print(f"Last Host:  {last_usable}")   # 10.50.255.254