The Python ipaddress module creates, validates, and manipulates IPv4 addresses, IPv6 addresses, CIDR networks, and interfaces. It can test membership, calculate network and broadcast addresses, split blocks, summarize ranges, classify special addresses, and convert between text, integer, and packed-byte forms.
The library does not open sockets, resolve DNS, or change operating-system configuration. It works with address values and network rules. This separation is useful for firewalls, allowlists, inventories, logs, APIs, and configuration validators.
Create an address automatically
from ipaddress import ip_address
ipv4 = ip_address("192.0.2.10")
ipv6 = ip_address("2001:db8::10")
print(ipv4.version)
print(ipv6.compressed)ip_address() selects IPv4 or IPv6 and raises ValueError for invalid input. When the expected version is known and detailed errors matter, instantiate IPv4Address or IPv6Address directly.
Validate user input
import ipaddress
def parse_ip(text: str):
try:
return ipaddress.ip_address(text.strip())
except ValueError as exc:
raise ValueError("invalid IP address") from excIPv4 strings with leading zeroes, such as 010.0.0.1, are rejected to avoid ambiguity with octal notation. Do not automatically repair suspicious values because a rejected string may silently become a different address.
Address, network, and interface
ip_address("10.0.0.5")represents one address.ip_network("10.0.0.0/24")represents a range.ip_interface("10.0.0.5/24")preserves the host and exposes its network.
from ipaddress import ip_interface
interface = ip_interface("10.0.0.5/24")
print(interface.ip)
print(interface.network)
print(interface.with_netmask)Strict network parsing
By default, ip_network() requires host bits to be clear:
from ipaddress import ip_network
network = ip_network("192.168.1.0/24")
normalized = ip_network("192.168.1.42/24", strict=False)
print(normalized) # 192.168.1.0/24Use strict=False only when normalization is intentional and documented. In security rules, silently masking host bits can hide a configuration error.
Test network membership
from ipaddress import ip_address, ip_network
network = ip_network("10.20.0.0/16")
client = ip_address("10.20.8.15")
if client in network:
print("allowed")IPv4 objects should be matched with IPv4 networks and IPv6 objects with IPv6 networks. Mixed versions normally raise TypeError, so validate the version before applying rules.
Address properties
from ipaddress import ip_address
address = ip_address("127.0.0.1")
print(address.is_loopback)
print(address.is_private)
print(address.is_global)
print(address.is_multicast)
print(address.is_link_local)
print(address.reverse_pointer)reverse_pointer returns the PTR query name; it does not perform DNS resolution. The meanings of is_private and is_global follow IANA special registries and may be corrected across Python versions. Python 3.13 fixed multiple false positives and negatives, so avoid replacing the library with stale hard-coded lists.
Private does not mean trusted
A private, loopback, or link-local address is not proof of identity. In web applications, X-Forwarded-For can be forged unless the complete proxy chain is controlled and configured. Obtain the effective client address from a trusted source.
Likewise, is_global describes reachability classification, not reputation, ownership, authentication, or authorization.
Network details and hosts
from ipaddress import ip_network
network = ip_network("192.0.2.0/29")
print(network.network_address)
print(network.broadcast_address)
print(network.netmask)
print(network.hostmask)
print(network.num_addresses)
print(list(network.hosts()))IPv4 hosts() usually excludes network and broadcast addresses. A /31 includes both endpoints for point-to-point links, and a /32 contains one host. IPv6 rules differ, so do not transfer IPv4 broadcast assumptions.
Do not materialize huge networks
An IPv6 /64 has an impractical number of addresses. Use membership tests, indexing, properties, and subnet operations instead of converting it to a list.
from ipaddress import ip_network
network = ip_network("2001:db8::/64")
print(network.num_addresses)
print(network[0])
print(network[-1])Split and expand networks
from ipaddress import ip_network
network = ip_network("10.0.0.0/24")
for subnet in network.subnets(new_prefix=26):
print(subnet)
larger = network.supernet(new_prefix=22)A subnet prefix must be longer than the original. A supernet prefix must be shorter.
Compare and overlap networks
from ipaddress import ip_network
main = ip_network("10.0.0.0/8")
branch = ip_network("10.20.0.0/16")
segment = ip_network("10.20.128.0/17")
print(branch.subnet_of(main))
print(main.supernet_of(segment))
print(branch.overlaps(segment))These operations help identify redundant rules, route conflicts, and overlapping allocations.
Exclude one range
from ipaddress import ip_network
block = ip_network("192.0.2.0/28")
reserved = ip_network("192.0.2.4/30")
for remaining in block.address_exclude(reserved):
print(remaining)The excluded network must be fully contained. The result is an iterator of CIDR networks covering the remaining addresses.
Summarize and collapse
import ipaddress
first = ipaddress.ip_address("192.0.2.0")
last = ipaddress.ip_address("192.0.2.130")
print(list(ipaddress.summarize_address_range(first, last)))
networks = [
ipaddress.ip_network("10.0.0.0/25"),
ipaddress.ip_network("10.0.0.128/25"),
]
print(list(ipaddress.collapse_addresses(networks)))collapse_addresses() merges adjacent or redundant blocks. Do not mix IPv4 and IPv6 in one call.
Convert to integers and packed bytes
from ipaddress import ip_address
address = ip_address("192.0.2.1")
print(int(address))
print(address.packed)
print(str(address))packed returns four bytes for IPv4 and sixteen for IPv6 in network byte order. Combine it with Python struct for binary headers, while explicitly documenting version and field length.
IPv6 display and scopes
from ipaddress import IPv6Address
address = IPv6Address("2001:db8::1")
print(address.compressed)
print(address.exploded)
local = IPv6Address("fe80::1234%eth0")
print(local.scope_id)Link-local addresses may include a zone ID. Equal numeric addresses with different scope IDs are not equal. Integer conversion drops the scope, so do not use it when the interface is part of identity.
IPv4-mapped IPv6
ipv4_mapped exposes the embedded IPv4 value in addresses such as ::ffff:192.0.2.1. Recent Python versions make properties such as is_private and is_global follow that embedded address. Normalize representations so equivalent endpoints do not bypass policy through different syntax.
Build an allowlist
from ipaddress import ip_address, ip_network
ALLOWED = tuple(map(ip_network, [
"192.0.2.0/24",
"2001:db8:abcd::/48",
]))
def is_allowed(text: str) -> bool:
address = ip_address(text)
return any(
address.version == network.version and address in network
for network in ALLOWED
)Parse networks during startup rather than on every request. Python configparser can load configuration, but critical access rules still require schema validation and review.
SSRF requires multiple defenses
ipaddress helps reject loopback, private, and link-local targets, but it cannot solve SSRF by itself. A hostname may resolve to multiple addresses, change between validation and connection, or redirect elsewhere. Resolve and validate every result, connect to the validated address, restrict protocols and ports, and revalidate redirects. Parse URLs with urllib.parse rather than string concatenation.
Hashing and sorting
Address and network objects are immutable and hashable, so they can be dictionary keys and set members. Same-version addresses are orderable. When a tool intentionally mixes addresses and networks, get_mixed_type_key() provides a sorting key.
Observability
Return a simple validation message to users while logging a sanitized value, the matching rule, and IP version. For complex pipelines, use Python trace. Large validation jobs can be distributed with Python queue, while parsed networks remain shared immutable objects.
Testing strategy
Test valid IPv4 and IPv6, leading zeroes, invalid prefixes, strict host bits, /31, /32, /127, /128, mapped addresses, zone IDs, overlapping networks, enormous ranges, whitespace, confusable Unicode, and overlong strings.
Best practices
- Parse strings once and keep objects.
- Use strict mode for critical rules.
- Never materialize huge networks.
- Do not treat a private address as identity.
- Support IPv4 and IPv6 deliberately.
- Normalize equivalent representations.
- Bound lists and ranges.
- Combine IP validation with DNS, proxy, and application policy.
Conclusion
Python ipaddress provides a reliable foundation for address validation, CIDR calculations, and consistent IPv4/IPv6 policies. Immutable objects replace fragile string manipulation and make network operations explicit.
Read the official ipaddress documentation and the IANA special-purpose registry. Reachability classification is useful, but authorization always needs additional context.







