from .net_cidr import cidr_contains from .net_ip_classify_data import SPECIAL_BLOCKS from .net_ip_classify_types import IpClassification from .net_ipv4 import is_ipv4 from .net_ipv6 import format_ipv6, is_ipv6, parse_ipv6 def classify_ip(address: str) -> IpClassification: """Which special-purpose block an address is in. From the IANA IPv4 and IPv6 Special-Purpose Address Registries (RFC 6890) plus the multicast ranges. The most specific block wins: 192.0.0.9 is Port Control Protocol Anycast (globally reachable), not the /24 of IETF assignments around it (not). An address in no block is "global". """ if is_ipv4(address): version = 4 canonical = address elif is_ipv6(address): version = 6 canonical = format_ipv6(parse_ipv6(address)) else: raise ValueError('"%s" is not an IPv4 or IPv6 address' % (address,)) best = None best_prefix = -1 for row in SPECIAL_BLOCKS: if (":" in row.block) != (version == 6): continue if not cidr_contains(row.block, canonical): continue prefix = int(row.block[row.block.index("/") + 1:]) if prefix > best_prefix: best, best_prefix = row, prefix if best is None: return IpClassification( address=canonical, version=version, category="global", name="Global unicast", block=None, rfc=None, globally_reachable=True, ) return IpClassification( address=canonical, version=version, category=best.category, name=best.name, block=best.block, rfc=best.rfc, globally_reachable=best.globally_reachable, )