"""Pick the most reachable address when a host advertises several. mDNS hosts with virtual bridges (libvirt, docker) or multiple NICs announce every local IP. Many are not routable from us. Prefer an address in our own subnet, then drop known-virtual ranges, else fall back to the first. """ import ipaddress import socket # Bridge/virtual ranges that are usually not reachable across hosts. _AVOID = [ ipaddress.ip_network("192.168.122.0/24"), # libvirt default (virbr0) ipaddress.ip_network("172.17.0.0/16"), # docker default (docker0) ipaddress.ip_network("169.254.0.0/16"), # link-local autoconf ] def local_ipv4() -> str | None: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: s.connect(("8.8.8.8", 80)) return s.getsockname()[0] except OSError: return None finally: s.close() def _avoided(ip: str) -> bool: try: addr = ipaddress.ip_address(ip) except ValueError: return True return any(addr in net for net in _AVOID) def pick_best(candidates: list[str], family: str = "v4") -> str | None: """Return the best-reachable address, or None if none look routable. Returning None on all-unreachable is deliberate: it lets the caller fall through to the next resolution method (e.g. Tailscale) instead of handing back a dead virtual-bridge IP. """ uniq: list[str] = [] for c in candidates: if c and c not in uniq: uniq.append(c) if not uniq: return None if family == "v4": mine = local_ipv4() if mine: try: my_net = ipaddress.ip_network(mine + "/24", strict=False) same = [c for c in uniq if ipaddress.ip_address(c) in my_net] if same: return same[0] except ValueError: pass # Drop known virtual/bridge ranges; if nothing remains, signal None. good = [c for c in uniq if not _avoided(c)] return good[0] if good else None # IPv6: prefer global/ULA over link-local (fe80::) non_ll = [c for c in uniq if not c.lower().startswith("fe80")] return non_ll[0] if non_ll else uniq[0]