Files
netinfra/mdns/mdns-browse.py
T
2026-09-03 21:30:32 -05:00

102 lines
3.1 KiB
Python

#!/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()