// WS-Discovery diagnostic: sends several Probe variants and listens both on the // sending socket (unicast replies) and on 239.255.255.250:3702 (multicast replies). // Usage: node scripts/probe-debug.mjs [interfaceName] [timeoutMs] [targetIp] // With targetIp, probes are sent by unicast to targetIp:3702 instead of multicast. import dgram from "node:dgram"; import os from "node:os"; import { randomUUID } from "node:crypto"; const ifaceName = process.argv[2]; const timeoutMs = Number(process.argv[3]) || 5000; const target = process.argv[4]; const GROUP = "239.255.255.250"; const PORT = 3702; const ifaces = os.networkInterfaces(); const candidates = Object.entries(ifaces).flatMap(([name, addrs]) => (addrs ?? []) .filter((a) => a.family === "IPv4" && !a.internal) .map((a) => ({ name, address: a.address })), ); console.log("IPv4 interfaces:", candidates.map((c) => `${c.name}=${c.address}`).join(", ")); const iface = ifaceName ? candidates.find((c) => c.name === ifaceName) : candidates[0]; if (!iface) { console.error(`No IPv4 interface ${ifaceName ?? ""} found`); process.exit(1); } console.log(`Using ${iface.name} (${iface.address}), timeout ${timeoutMs}ms\n`); const probe = (types) => { const id = `urn:uuid:${randomUUID()}`; return { id, xml: `` + `` + `${id}` + `urn:schemas-xmlsoap-org:ws:2005:04:discovery` + `http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe` + `${types ? `${types}` : ""}` + ``, }; }; const variants = { "NetworkVideoTransmitter": probe("dn:NetworkVideoTransmitter"), "tds:Device": probe("tds:Device"), "no Types": probe(null), }; const seen = new Set(); function report(via) { return (msg, rinfo) => { const text = msg.toString(); if (/<[^>]*:?Probe>/.test(text) && !/ProbeMatch/.test(text)) return; // our own / others' probes const relates = text.match(/RelatesTo[^>]*>([^<]+) v.id === relates)?.[0] ?? "unknown"; const xaddrs = text.match(/XAddrs[^>]*>([^<]+) console.error("unicast socket error:", e)); const mcast = dgram.createSocket({ type: "udp4", reuseAddr: true }); mcast.on("message", report("multicast 3702")); mcast.on("error", (e) => console.error(`multicast listener error (port ${PORT} busy?):`, e.message)); mcast.bind(PORT, () => { try { mcast.addMembership(GROUP, iface.address); } catch (e) { console.error("addMembership failed:", e.message); } }); unicast.bind(0, iface.address, () => { unicast.setMulticastInterface(iface.address); unicast.setMulticastTTL(4); console.log(`Sending from ${iface.address}:${unicast.address().port}`); for (const [name, { xml }] of Object.entries(variants)) { unicast.send(xml, PORT, target ?? GROUP, (err) => console.log(`sent "${name}" to ${target ?? GROUP}${err ? ` ERROR ${err.message}` : ""}`), ); } }); setTimeout(() => { console.log(`\nDone. ${seen.size} reply(ies).`); unicast.close(); mcast.close(); }, timeoutMs);