#!/usr/bin/env python3 """mdns-browse: one-shot multicast mDNS enumeration from this vantage. Sends PTR queries for the DNS-SD service-enumeration name plus common service types, listens briefly, prints every answer record. Stdlib only. Diagnostic tool for #619 (WiFi-side multicast reachability).""" import socket import struct import sys import time MDNS_GROUP = "224.0.0.251" MDNS_PORT = 5353 LISTEN_SECONDS = float(sys.argv[1]) if len(sys.argv) > 1 else 6.0 TYPES = { 1: "A", 28: "AAAA", 12: "PTR", 16: "TXT", 33: "SRV", 47: "NONE", } QUERIES = [ "_services._dns-sd._udp.local", "_hap._tcp.local", "_airplay._tcp.local", "_googlecast._tcp.local", ] def encode_name(name): out = b"" for part in name.split("."): out += bytes([len(part)]) + part.encode("latin1") return out + b"\x00" def read_name(data, off): parts = [] seen = set() while True: if off in seen or len(seen) > 64: return ".".join(parts), off seen.add(off) length = data[off] if length == 0: off += 1 break if length & 0xC0: ptr = struct.unpack(">H", data[off:off + 2])[0] & 0x3FFF parts.append(read_name(data, ptr)[0]) off += 2 break parts.append(data[off + 1:off + 1 + length].decode("latin1")) off += 1 + length return ".".join(parts), off def build_query(names): packet = struct.pack(">HHHHHH", 0, 0, len(names), 0, 0, 0) for name in names: packet += encode_name(name) + struct.pack(">HH", 12, 1) return packet def main(): sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(("", MDNS_PORT)) mreq = socket.inet_aton(MDNS_GROUP) + socket.inet_aton("0.0.0.0") sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) sock.settimeout(0.5) sock.sendto(build_query(QUERIES), (MDNS_GROUP, MDNS_PORT)) deadline = time.time() + LISTEN_SECONDS found = set() while time.time() < deadline: try: data, addr = sock.recvfrom(16384) except socket.timeout: continue an = struct.unpack(">H", data[6:8])[0] off = 12 qd = struct.unpack(">H", data[4:6])[0] try: for _ in range(qd): _, off = read_name(data, off) off += 4 for _ in range(an): name, name_end = read_name(data, off) rtype, _rclass, ttl, rlen = struct.unpack( ">HHIH", data[name_end:name_end + 10]) detail = "" if rtype == 12: detail = " -> " + read_name(data, name_end + 10)[0] found.add((TYPES.get(rtype, rtype), name, detail, addr[0])) off = name_end + 10 + rlen except (IndexError, struct.error): continue for rtype, name, detail, src in sorted(found): print(f"{src:15} {rtype:5} {name}{detail}") print(f"-- {len(found)} records from {LISTEN_SECONDS}s listen --") if __name__ == "__main__": main()