Python’s ipaddress module parses and manipulates IPv4/IPv6 addresses and networks. import ipaddress. ip_address: ipaddress.ip_address("192.168.1.1") → IPv4Address; ipaddress.ip_address("::1") → IPv6Address. ip_network: ipaddress.ip_network("192.168.1.0/24") → IPv4Network; strict=False accepts host bits set. ip_interface: ipaddress.ip_interface("192.168.1.1/24") → IPv4Interface (address + prefix). network_address: net.network_address → first address. broadcast_address: net.broadcast_address. netmask: net.netmask. prefixlen: net.prefixlen → 24. hosts: list(net.hosts()) — all usable host addresses (excludes network+broadcast). num_addresses: net.num_addresses. is_private: addr.is_private. is_loopback: addr.is_loopback. is_global: addr.is_global. is_multicast: addr.is_multicast. is_reserved: addr.is_reserved. is_link_local: addr.is_link_local. in network: addr in net. overlaps: net1.overlaps(net2). subnet_of/supernet_of: net1.subnet_of(net2) (Python 3.7+). subnets: list(net.subnets(new_prefix=25)). supernet: net.supernet(prefixlen_diff=1). packed: addr.packed — 4 or 16 bytes. int: int(addr). with_prefixlen: str(iface.with_prefixlen). collapsed_addresses: list(ipaddress.collapse_addresses([net1, net2])). summarize_address_range: list(ipaddress.summarize_address_range(start, end)). Claude Code generates firewall allow-list loaders, subnet calculators, CIDR validators, and IP geolocation helpers.
CLAUDE.md for ipaddress
## ipaddress Stack
- Stdlib: import ipaddress
- Parse: addr = ipaddress.ip_address("192.168.1.1")
- Network: net = ipaddress.ip_network("10.0.0.0/8", strict=False)
- Check: addr in net | net.is_private | addr.is_loopback
- Hosts: list(net.hosts()) — excludes network/broadcast
- Subnets: list(net.subnets(new_prefix=25))
- Collapse: list(ipaddress.collapse_addresses(nets))
ipaddress Network Pipeline
# app/netutil.py — parse, classify, CIDR, allow-lists, subnets, ACL
from __future__ import annotations
import ipaddress
from dataclasses import dataclass
from ipaddress import (
IPv4Address, IPv4Interface, IPv4Network,
IPv6Address, IPv6Interface, IPv6Network,
ip_address, ip_interface, ip_network,
collapse_addresses, summarize_address_range,
)
from typing import Union
IPAddr = Union[IPv4Address, IPv6Address]
IPNetwork = Union[IPv4Network, IPv6Network]
# ─────────────────────────────────────────────────────────────────────────────
# 1. Parsing helpers
# ─────────────────────────────────────────────────────────────────────────────
def parse_address(s: str) -> IPAddr | None:
"""
Parse an IP address string; return None on failure.
Example:
parse_address("192.168.1.1") # IPv4Address('192.168.1.1')
parse_address("::1") # IPv6Address('::1')
parse_address("not.an.ip") # None
"""
try:
return ip_address(s.strip())
except ValueError:
return None
def parse_network(s: str, strict: bool = False) -> IPNetwork | None:
"""
Parse a CIDR network string; return None on failure.
strict=False tolerates host bits set (e.g. "192.168.1.5/24" → "192.168.1.0/24").
Example:
parse_network("10.0.0.0/8")
parse_network("192.168.1.5/24", strict=False) # 192.168.1.0/24
"""
try:
return ip_network(s.strip(), strict=strict)
except ValueError:
return None
def is_valid_ip(s: str) -> bool:
"""Return True if s is a valid IPv4 or IPv6 address."""
return parse_address(s) is not None
def is_valid_cidr(s: str) -> bool:
"""Return True if s is a valid CIDR network string."""
return parse_network(s) is not None
def addr_version(s: str) -> int | None:
"""
Return 4 or 6 for the IP version, or None if invalid.
Example:
addr_version("192.168.1.1") # 4
addr_version("::1") # 6
"""
a = parse_address(s)
return a.version if a else None
# ─────────────────────────────────────────────────────────────────────────────
# 2. Address classification
# ─────────────────────────────────────────────────────────────────────────────
def classify_address(addr: IPAddr | str) -> dict[str, bool]:
"""
Return a dict of boolean properties for an IP address.
Example:
classify_address("192.168.1.1")
# {"private": True, "loopback": False, "global": False, ...}
"""
if isinstance(addr, str):
addr = ip_address(addr)
return {
"private": addr.is_private,
"loopback": addr.is_loopback,
"global": addr.is_global,
"multicast": addr.is_multicast,
"link_local": addr.is_link_local,
"reserved": addr.is_reserved,
"unspecified": addr.is_unspecified,
"ipv4": addr.version == 4,
"ipv6": addr.version == 6,
}
def is_routable(addr: str | IPAddr) -> bool:
"""
Return True if address is likely routable on the public Internet.
Example:
is_routable("8.8.8.8") # True
is_routable("192.168.1.1") # False
is_routable("127.0.0.1") # False
"""
if isinstance(addr, str):
a = parse_address(addr)
if a is None:
return False
else:
a = addr
return a.is_global and not a.is_multicast and not a.is_reserved
# ─────────────────────────────────────────────────────────────────────────────
# 3. Network operations
# ─────────────────────────────────────────────────────────────────────────────
def network_info(cidr: str) -> dict:
"""
Return detailed info about a CIDR network.
Example:
info = network_info("192.168.1.0/24")
info["num_hosts"] # 254
info["broadcast"] # "192.168.1.255"
"""
net = ip_network(cidr, strict=False)
return {
"cidr": str(net),
"network_address": str(net.network_address),
"broadcast": str(net.broadcast_address) if net.version == 4 else "N/A",
"netmask": str(net.netmask),
"prefixlen": net.prefixlen,
"num_addresses": net.num_addresses,
"num_hosts": max(0, net.num_addresses - 2) if net.version == 4 else net.num_addresses,
"version": net.version,
"is_private": net.is_private,
}
def split_network(cidr: str, new_prefix: int) -> list[str]:
"""
Split a CIDR network into subnets of new_prefix length.
Example:
split_network("10.0.0.0/24", 26)
# ["10.0.0.0/26", "10.0.0.64/26", "10.0.0.128/26", "10.0.0.192/26"]
"""
net = ip_network(cidr, strict=False)
return [str(s) for s in net.subnets(new_prefix=new_prefix)]
def summarize_range(start_ip: str, end_ip: str) -> list[str]:
"""
Return the minimal list of CIDR blocks covering start_ip to end_ip.
Example:
summarize_range("192.168.0.0", "192.168.0.255") # ["192.168.0.0/24"]
"""
start = ip_address(start_ip)
end = ip_address(end_ip)
return [str(net) for net in summarize_address_range(start, end)]
def collapse(cidrs: list[str]) -> list[str]:
"""
Collapse a list of overlapping/adjacent CIDR blocks into minimal form.
Example:
collapse(["10.0.0.0/25", "10.0.0.128/25"]) # ["10.0.0.0/24"]
collapse(["192.168.1.0/24", "192.168.1.0/28"]) # ["192.168.1.0/24"]
"""
nets = [ip_network(c, strict=False) for c in cidrs]
return [str(n) for n in collapse_addresses(nets)]
# ─────────────────────────────────────────────────────────────────────────────
# 4. Access control / allow-list
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class IPACLRule:
network: IPNetwork
action: str # "allow" or "deny"
comment: str = ""
class IPAccessList:
"""
Ordered IP access control list with allow/deny rules.
First matching rule wins.
Example:
acl = IPAccessList()
acl.add_allow("10.0.0.0/8", "Internal network")
acl.add_allow("127.0.0.1/32", "Loopback")
acl.add_deny("0.0.0.0/0", "Default deny")
acl.is_allowed("10.1.2.3") # True
acl.is_allowed("8.8.8.8") # False
"""
def __init__(self) -> None:
self._rules: list[IPACLRule] = []
def add_allow(self, cidr: str, comment: str = "") -> None:
net = ip_network(cidr, strict=False)
self._rules.append(IPACLRule(network=net, action="allow", comment=comment))
def add_deny(self, cidr: str, comment: str = "") -> None:
net = ip_network(cidr, strict=False)
self._rules.append(IPACLRule(network=net, action="deny", comment=comment))
def is_allowed(self, addr: str | IPAddr, default: bool = False) -> bool:
"""Return True if addr matches an allow rule before any deny rule."""
if isinstance(addr, str):
a = parse_address(addr)
if a is None:
return False
else:
a = addr
for rule in self._rules:
if a in rule.network:
return rule.action == "allow"
return default
def lookup(self, addr: str | IPAddr) -> IPACLRule | None:
"""Return the first matching rule, or None."""
if isinstance(addr, str):
a = parse_address(addr)
if a is None:
return None
else:
a = addr
for rule in self._rules:
if a in rule.network:
return rule
return None
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("=== ipaddress demo ===")
print("\n--- parse_address ---")
for s in ["192.168.1.1", "::1", "10.0.0.1", "255.255.255.255", "not.ip"]:
a = parse_address(s)
print(f" {s!r:20s}: {a!r}")
print("\n--- classify_address ---")
for s in ["8.8.8.8", "192.168.1.1", "127.0.0.1", "::1", "224.0.0.1"]:
c = classify_address(s)
flags = [k for k, v in c.items() if v]
print(f" {s:20s}: {flags}")
print("\n--- is_routable ---")
for s in ["8.8.8.8", "192.168.1.1", "172.16.5.1", "10.0.0.1", "100.64.0.1"]:
print(f" {s:20s}: routable={is_routable(s)}")
print("\n--- network_info ---")
info = network_info("192.168.1.0/24")
for k, v in info.items():
print(f" {k:20s}: {v}")
print("\n--- split_network ---")
subnets = split_network("10.0.0.0/24", 26)
for s in subnets:
print(f" {s}")
print("\n--- summarize_range ---")
cidrs = summarize_range("192.168.0.0", "192.168.0.255")
print(f" 192.168.0.0–255 → {cidrs}")
print("\n--- collapse ---")
before = ["10.0.0.0/25", "10.0.0.128/25", "192.168.1.0/26", "192.168.1.64/26"]
after = collapse(before)
print(f" before: {before}")
print(f" after: {after}")
print("\n--- IPAccessList ---")
acl = IPAccessList()
acl.add_allow("10.0.0.0/8", "Internal")
acl.add_allow("127.0.0.0/8", "Loopback")
acl.add_deny("0.0.0.0/0", "Default deny")
for ip in ["10.1.2.3", "127.0.0.1", "8.8.8.8", "172.16.1.1"]:
rule = acl.lookup(ip)
allowed = acl.is_allowed(ip)
comment = rule.comment if rule else "no match"
print(f" {ip:20s}: allowed={allowed} ({comment})")
print("\n=== done ===")
For the netaddr alternative — netaddr (PyPI) provides IPAddress, IPNetwork, IPSet, and IPRange objects with set algebra (IPSet([net1]) | IPSet([net2])), spanning-range support, and IP database lookup integrations; stdlib ipaddress covers the core RFC 4122 operations without set operations — use netaddr when you need IP set algebra, spanning tree merging, or EUI (MAC address) handling in network tooling, stdlib ipaddress for application-level validation, access control, and subnet calculations where standard library is preferred. For the ipwhois alternative — ipwhois (PyPI) performs WHOIS and RDAP lookups to return ASN, organization, country, and abuse contact for an IP address; stdlib ipaddress only parses and classifies addresses without performing network lookups — use ipwhois for IP enrichment pipelines, abuse reporting, and geo/org data, stdlib ipaddress for local validation, CIDR arithmetic, and firewall rule management. The Claude Skills 360 bundle includes ipaddress skill sets covering parse_address()/parse_network()/is_valid_ip()/is_valid_cidr()/addr_version() parsing helpers, classify_address()/is_routable() address classification, network_info()/split_network()/summarize_range()/collapse() network operations, and IPACLRule/IPAccessList ordered access control list with add_allow()/add_deny()/is_allowed()/lookup(). Start with the free tier to try IP network management and ipaddress pipeline code generation.