"""Network utilities.""" from ipaddress import IPv4Address, IPv6Address, ip_address, ip_network from typing import Union import yarl # RFC6890 - IP addresses of loopback interfaces LOOPBACK_NETWORKS = ( ip_network("127.0.0.0/8"), ip_network("::1/128"), ip_network("::ffff:127.0.0.0/104"), ) # RFC6890 - Address allocation for Private Internets PRIVATE_NETWORKS = ( ip_network("fd00::/8"), ip_network("10.0.0.0/8"), ip_network("172.16.0.0/12"), ip_network("192.168.0.0/16"), ) # RFC6890 - Link local ranges LINK_LOCAL_NETWORK = ip_network("169.254.0.0/16") def is_loopback(address: Union[IPv4Address, IPv6Address]) -> bool: """Check if an address is a loopback address.""" return any(address in network for network in LOOPBACK_NETWORKS) def is_private(address: Union[IPv4Address, IPv6Address]) -> bool: """Check if an address is a private address.""" return any(address in network for network in PRIVATE_NETWORKS) def is_link_local(address: Union[IPv4Address, IPv6Address]) -> bool: """Check if an address is link local.""" return address in LINK_LOCAL_NETWORK def is_local(address: Union[IPv4Address, IPv6Address]) -> bool: """Check if an address is loopback or private.""" return is_loopback(address) or is_private(address) def is_ip_address(address: str) -> bool: """Check if a given string is an IP address.""" try: ip_address(address) except ValueError: return False return True def normalize_url(address: str) -> str: """Normalize a given URL.""" url = yarl.URL(address.rstrip("/")) if url.is_default_port(): return str(url.with_port(None)) return str(url)