enodia-sentinal/enodia_sentinel/netutil.py
Luna 586f74b929 Relicense under GPL-3.0-or-later
Replace MIT with the full GNU GPLv3 text, update license metadata in
pyproject.toml (+ trove classifiers) and PKGBUILD, and add
SPDX-License-Identifier headers to all Python modules and shell scripts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 06:52:34 -07:00

63 lines
2 KiB
Python

# SPDX-License-Identifier: GPL-3.0-or-later
"""Network address helpers, backed by the stdlib ``ipaddress`` module.
The bash prototype hand-rolled integer/CIDR math; here we lean on ``ipaddress``
for correct handling of private ranges, link-local, CGNAT, and IPv6.
"""
from __future__ import annotations
import ipaddress
from collections.abc import Iterable
def parse_addr(addr: str) -> ipaddress._BaseAddress | None:
"""Parse an address that may carry a ``%iface`` zone or ``[..]`` brackets."""
if not addr:
return None
addr = addr.strip().strip("[]")
addr = addr.split("%", 1)[0] # drop IPv6 zone id
try:
return ipaddress.ip_address(addr)
except ValueError:
return None
def is_public_ip(addr: str) -> bool:
"""True if ``addr`` is a globally-routable address.
Excludes loopback, RFC1918 private, link-local, CGNAT (100.64/10), and
other non-global ranges — i.e. a connection to such an address is one that
actually leaves the host to the public internet.
"""
ip = parse_addr(addr)
if ip is None:
return False
return ip.is_global
def ip_in_cidrs(addr: str, cidrs: Iterable[str]) -> bool:
"""True if ``addr`` falls inside any CIDR in ``cidrs`` (the trust list)."""
ip = parse_addr(addr)
if ip is None:
return False
for cidr in cidrs:
cidr = cidr.strip()
if not cidr:
continue
try:
net = ipaddress.ip_network(cidr, strict=False)
except ValueError:
continue
if ip.version == net.version and ip in net:
return True
return False
def split_host_port(endpoint: str) -> tuple[str, str]:
"""Split an ``ss`` endpoint like ``10.0.0.2%eth0:443`` or ``[::1]:22``."""
endpoint = endpoint.strip()
if endpoint.startswith("["):
host, _, port = endpoint.partition("]")
return host.lstrip("["), port.lstrip(":")
host, _, port = endpoint.rpartition(":")
return host, port