Subnet / CIDR Calculator — Python source
Compute IPv4 subnet details from a CIDR block - network, broadcast, host range, netmask, wildcard, host count, IP class, and private-range detection. Split a block into smaller subnets, all in your browser.
This is the Python implementation — the same logic the interactive tool runs, in a shareable, citable form.
"""IPv4 subnet / CIDR math: dotted-quad ⇄ int conversion, full subnet details
(network, broadcast, hosts, class, privacy), and CIDR splitting into smaller
blocks. The logic is pure and deterministic; validation failures are reported
via ``None`` (or ``[]`` for the splitter) so callers can render a graceful
error instead of catching an exception.
Language: Python
CosmoDev polyglot showcase port of ``subnet-cidr``.
Ported from src/lib/subnet-cidr.ts — functionally equivalent.
Display source — part of CosmoDev's polyglot tool pages.
"""
from __future__ import annotations
import re
from dataclasses import dataclass
from typing import Literal, Optional
# IPv4 addresses are manipulated as unsigned 32-bit integers. Python ints are
# arbitrary precision, so every bitwise result that must live in the IPv4 space
# is masked back into 32 bits with ``& 0xFFFFFFFF`` — the equivalent of
# JavaScript's ``>>> 0``. Without it, ``~mask`` would be a negative
# two's-complement value (Python models integers as sign-magnitude, so there is
# no implicit wraparound to undo the NOT).
# A whole-string digit check: rejects whitespace, signs, hex, and the empty
# string, matching the TypeScript ``/^\d+$/`` test.
_DIGITS = re.compile(r"\d+")
@dataclass
class SubnetInfo:
"""Computed properties of a CIDR block."""
network: str
broadcast: str
first_host: str
last_host: str
netmask: str
wildcard: str
host_count: int
prefix: int
ip_class: Literal["A", "B", "C", "-"]
is_private: bool
@dataclass
class Subnet:
"""One block produced by :func:`split_subnet`."""
network: str
prefix: int
def ip_to_int(ip: str) -> Optional[int]:
"""Convert a dotted-quad IPv4 string to an unsigned 32-bit integer.
Returns ``None`` if the string is malformed or any octet exceeds 255.
"""
parts = ip.split(".")
if len(parts) != 4:
return None
result = 0
for part in parts:
# Only plain decimal digits — no whitespace, signs, or hex.
if not _DIGITS.fullmatch(part):
return None
octet = int(part)
if octet > 255:
return None
result = result * 256 + octet
return result & 0xFFFFFFFF
def int_to_ip(n: int) -> str:
"""Convert an unsigned 32-bit integer to a dotted-quad IPv4 string."""
v = n & 0xFFFFFFFF
return f"{(v >> 24) & 255}.{(v >> 16) & 255}.{(v >> 8) & 255}.{v & 255}"
def parse_cidr(cidr: str) -> Optional[tuple[str, int]]:
"""Parse ``"ip/prefix"`` into a normalized ``(ip, prefix)`` pair.
The prefix is constrained to ``[0, 32]``. Returns ``None`` if invalid.
"""
if "/" not in cidr:
return None
# partition splits on the FIRST slash, so any second slash lands in the
# prefix half — which we then reject.
ip_part, _, prefix_part = cidr.partition("/")
if "/" in prefix_part: # multiple slashes ("a.b.c.d/24/16")
return None
if not _DIGITS.fullmatch(prefix_part):
return None
prefix = int(prefix_part)
if prefix > 32:
return None
ip_int = ip_to_int(ip_part)
if ip_int is None:
return None
# Re-emit through int_to_ip so the address is in canonical form.
return (int_to_ip(ip_int), prefix)
def _mask_from_prefix(prefix: int) -> int:
"""Return the 32-bit netmask for a prefix length in ``[0, 32]``.
The ``prefix == 0`` case is guarded for parity with the TS source; in
Python ``0xFFFFFFFF << 32`` is a valid bignum whose low 32 bits are zero,
but the explicit branch keeps the intent plain.
"""
if prefix == 0:
return 0
return (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF
def subnet_details(cidr: str) -> Optional[SubnetInfo]:
"""Compute the full properties of a CIDR block.
Returns ``None`` if the CIDR is invalid.
"""
parsed = parse_cidr(cidr)
if parsed is None:
return None
ip, prefix = parsed
mask = _mask_from_prefix(prefix)
wildcard = (~mask) & 0xFFFFFFFF
ip_int = ip_to_int(ip) # safe: parse_cidr validated and normalized the IP
network = (ip_int & mask) & 0xFFFFFFFF
broadcast = (network | wildcard) & 0xFFFFFFFF
# /31 (point-to-point) and /32 (single host) have no host/broadcast split.
if prefix >= 31:
first_host = network
last_host = broadcast
else:
first_host = (network + 1) & 0xFFFFFFFF
last_host = (broadcast - 1) & 0xFFFFFFFF
# Host count: 1 for /32, 2 for /31, otherwise 2^(32-prefix) − 2. Python
# ints are unbounded, so the /0 case (2^32 − 2 = 4_294_967_294) is exact.
if prefix >= 32:
host_count = 1
elif prefix == 31:
host_count = 2
else:
host_count = (1 << (32 - prefix)) - 2
first_octet = (network >> 24) & 255
if first_octet < 128:
ip_class: Literal["A", "B", "C", "-"] = "A"
elif first_octet < 192:
ip_class = "B"
elif first_octet < 224:
ip_class = "C"
else:
ip_class = "-" # class D (multicast) / E (reserved)
second_octet = (network >> 16) & 255
is_private = (
first_octet == 10
or (first_octet == 172 and 16 <= second_octet <= 31)
or (first_octet == 192 and second_octet == 168)
)
return SubnetInfo(
network=int_to_ip(network),
broadcast=int_to_ip(broadcast),
first_host=int_to_ip(first_host),
last_host=int_to_ip(last_host),
netmask=int_to_ip(mask),
wildcard=int_to_ip(wildcard),
host_count=host_count,
prefix=prefix,
ip_class=ip_class,
is_private=is_private,
)
def split_subnet(cidr: str, new_prefix: int) -> list[Subnet]:
"""Split a CIDR block into smaller subnets of ``new_prefix``.
Returns ``[]`` if the CIDR is invalid or ``new_prefix`` is outside
``(prefix, 32]``. The base IP is normalized to the network address before
splitting.
"""
parsed = parse_cidr(cidr)
if parsed is None:
return []
ip, prefix = parsed
# new_prefix is statically an int, so only the range is checked (mirrors the
# TS Number.isInteger + bounds check).
if new_prefix < prefix or new_prefix > 32:
return []
mask = _mask_from_prefix(prefix)
network = (ip_to_int(ip) & mask) & 0xFFFFFFFF
count = 1 << (new_prefix - prefix) # 2^(new_prefix − prefix)
block_size = 1 << (32 - new_prefix) # 2^(32 − new_prefix)
out: list[Subnet] = []
for i in range(count):
# The final & 0xFFFFFFFF makes addition wrap like a uint32.
offset = (i * block_size) & 0xFFFFFFFF
out.append(
Subnet(network=int_to_ip((network + offset) & 0xFFFFFFFF), prefix=new_prefix)
)
return out
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →