Skip to Content
Course content

265: The ipaddress Module for Network Addresses

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was reviewing a PR from a developer who was building a custom firewall rule validator. He had written about fifty lines of code using regular expressions and complex bit-shifting logic just to determine if a given IP address belonged to a specific CIDR subnet. He was almost there, but he had a bug where he forgot to account for the network and broadcast addresses, leading to a few "off-by-one" errors that would have been a nightmare to debug in production. I pointed him toward the ipaddress module, and we replaced those fifty lines with about three. It's a perfect example of why you shouldn't try to do "string math" on network addresses.

Handling Individual Addresses and Versions

The ipaddress module treats IP addresses as first-class objects rather than just strings. This is huge because it allows you to perform logical comparisons and validations without worrying about whether the input is IPv4 or IPv6. When you use ipaddress.ip_address(), Python automatically detects the version for you.

import ipaddress

# This could be an IPv4 or IPv6 string
addr_str = "192.168.1.15"
addr = ipaddress.ip_address(addr_str)

print(f"Version: {addr.version}") # Output: Version: 4
print(f"Is private? {addr.is_private}") # Output: Is private? True

# It works exactly the same for IPv6
ipv6_addr = ipaddress.ip_address("2001:db8::")
print(f"Version: {ipv6_addr.version}") # Output: Version: 6

I find the is_private property especially useful when building internal tools; it's a quick way to verify if an address is within the RFC 1918 range without having to memorize those ranges yourself.

Navigating Subnets and CIDR Ranges

Dealing with subnets is where this module really shines. By using ipaddress.ip_network(), you create an object that represents an entire block of addresses. You can then check if a specific address is part of that block using the in operator, which is incredibly intuitive.

# Define a subnet
network = ipaddress.ip_network("192.168.1.0/24")

# Check if an IP is inside that subnet
ip = ipaddress.ip_address("192.168.1.50")
if ip in network:
    print("Access granted: IP is in the subnet")

# You can even iterate over all usable hosts in the network
# This excludes the network and broadcast addresses automatically
for host in network.hosts():
    # Be careful doing this with a /8 network unless you want your loop to run forever
    if host.compressed == "192.168.1.1":
        print("Found the gateway!")
        break

One thing to watch out for: if you try to create a network using an address that isn't the actual network address (e.g., ip_network("192.168.1.50/24")), Python will raise a ValueError because the host bits are set. If you don't care about that and just want the network the IP belongs to, pass strict=False.

The Distinction Between Interfaces and Networks

You'll occasionally run into a situation where you have an IP address and a subnet mask together, representing a specific device's configuration. This is an "interface," not a "network." While ip_network describes the whole neighborhood, ip_interface describes a specific house and the neighborhood it lives in.

# An interface is an IP and a mask combined
iface = ipaddress.ip_interface("192.168.1.50/24")

print(f"IP: {iface.ip}")        # 192.168.1.50
print(f"Network: {iface.network}") # 192.168.1.0/24
print(f"Netmask: {iface.netmask}") # 255.255.255.0

Using the correct object—ip_address, ip_network, or ip_interface—makes your intent clear to anyone else reading your code. It transforms network logic from "string manipulation" into "object interaction," which is exactly how you want to be thinking in Python.




📋 Practical Task

Build a Network Access Control List (ACL) Filter

You are tasked with creating a security script that filters a list of incoming connection attempts. You have a list of "Allowed Subnets" and a list of "Incoming IPs." Your goal is to create a function that returns only the IPs that are permitted based on the allowed subnets.

Requirements:

  • Define a list of allowed CIDR networks: ["10.0.0.0/8", "192.168.1.0/24", "2001:db8::/32"].
  • Define a list of incoming IP strings: ["10.5.0.1", "192.168.1.15", "172.16.0.5", "2001:db8::1", "8.8.8.8"].
  • Write a function filter_allowed_ips(incoming_ips, allowed_networks) that:
    • Converts the subnet strings into ip_network objects.
    • Converts each incoming IP string into an ip_address object.
    • Returns a list of IPs (as strings) that belong to at least one of the allowed networks.
# Expected Output for the data above: 
# ['10.5.0.1', '192.168.1.15', '2001:db8::1']
Rating
0 0

There are no comments for now.

to be the first to leave a comment.