A Python port of the Invisible Internet Project (I2P)
at main 128 lines 4.1 kB view raw
1"""Addresses — IP address validation and local address detection. 2 3Ported from net.i2p.util.Addresses. 4""" 5 6import ipaddress 7import socket 8from enum import Enum, auto 9from typing import Optional, Set 10 11 12class AddressType(Enum): 13 IPV4 = auto() 14 IPV6 = auto() 15 YGG = auto() 16 17 18class Addresses: 19 """Static utilities for IP address operations.""" 20 21 @staticmethod 22 def is_ipv4(addr: str) -> bool: 23 """Check if string is a valid IPv4 address.""" 24 try: 25 ipaddress.IPv4Address(addr) 26 return True 27 except (ipaddress.AddressValueError, ValueError): 28 return False 29 30 @staticmethod 31 def is_ipv6(addr: str) -> bool: 32 """Check if string is a valid IPv6 address.""" 33 try: 34 ipaddress.IPv6Address(addr) 35 return True 36 except (ipaddress.AddressValueError, ValueError): 37 return False 38 39 @staticmethod 40 def is_ip_address(addr: str) -> bool: 41 """Check if string is a valid IP address (v4 or v6).""" 42 return Addresses.is_ipv4(addr) or Addresses.is_ipv6(addr) 43 44 @staticmethod 45 def get_ip(host: str) -> Optional[bytes]: 46 """Resolve hostname to IP bytes, or return None.""" 47 try: 48 result = socket.getaddrinfo(host, None, socket.AF_UNSPEC, socket.SOCK_STREAM) 49 if result: 50 addr = result[0][4][0] 51 return ipaddress.ip_address(addr).packed 52 except (socket.gaierror, OSError, ValueError): 53 pass 54 return None 55 56 @staticmethod 57 def to_ip_string(ip_bytes: bytes) -> str: 58 """Convert IP bytes to string representation.""" 59 return str(ipaddress.ip_address(ip_bytes)) 60 61 @staticmethod 62 def is_public(addr: str) -> bool: 63 """Check if an IP address is publicly routable.""" 64 try: 65 ip = ipaddress.ip_address(addr) 66 return ip.is_global 67 except ValueError: 68 return False 69 70 @staticmethod 71 def is_local(addr: str) -> bool: 72 """Check if an IP address is local/private.""" 73 try: 74 ip = ipaddress.ip_address(addr) 75 return ip.is_private or ip.is_loopback or ip.is_link_local 76 except ValueError: 77 return False 78 79 @staticmethod 80 def get_addresses(include_local: bool = False, 81 include_ipv6: bool = False) -> set[str]: 82 """Get local network addresses. 83 84 Args: 85 include_local: include loopback and private addresses 86 include_ipv6: include IPv6 addresses 87 88 Returns: 89 Set of IP address strings. 90 """ 91 addrs: set[str] = set() 92 try: 93 hostname = socket.gethostname() 94 for info in socket.getaddrinfo(hostname, None, socket.AF_UNSPEC, socket.SOCK_STREAM): 95 family, _, _, _, sockaddr = info 96 addr_str = sockaddr[0] 97 try: 98 ip = ipaddress.ip_address(addr_str) 99 except ValueError: 100 continue 101 102 if isinstance(ip, ipaddress.IPv6Address) and not include_ipv6: 103 continue 104 if not include_local and (ip.is_loopback or ip.is_link_local): 105 continue 106 addrs.add(str(ip)) 107 except (socket.gaierror, OSError): 108 pass 109 return addrs 110 111 @staticmethod 112 def get_any_address() -> Optional[str]: 113 """Return first non-local IPv4 address, or None.""" 114 addrs = Addresses.get_addresses(include_local=False, include_ipv6=False) 115 return next(iter(sorted(addrs)), None) 116 117 @staticmethod 118 def is_connected() -> bool: 119 """Check if we have any non-loopback address.""" 120 return len(Addresses.get_addresses(include_local=False)) > 0 121 122 @staticmethod 123 def is_connected_ipv6() -> bool: 124 """Check if we have any non-loopback IPv6 address.""" 125 for addr in Addresses.get_addresses(include_local=False, include_ipv6=True): 126 if ":" in addr: 127 return True 128 return False