You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The existing WiFiDeviceFinder (post-#180) does the host side of UDP broadcast discovery correctly: it binds per-NIC, filters virtual adapters, and routes replies to the originating socket. But the broadcast transport itself is unreliable on a typical home network with multiple APs / Band Steering / lossy 2.4 GHz radios — and we cannot fix that from the library side, no matter how cleanly we bind sockets.
Concrete failure session against a Nyquist1 on a default UniFi setup (Dream-Machine + wired-PoE U6 LR, single SSID, Band Steering on, Mac on 5 GHz at one AP, device on 2.4 GHz at the other). Healthy controller-side state, device fully online, signal -57 dBm, getting DHCP. From the Mac, ARP for the device was (incomplete) — broadcast frames across the AP boundary were dropping at the rate the U6 LR's 2.4 GHz radio reported (17% Tx Retry / 10% Tx Drop). UDP discovery silently returns 0 devices in this scenario. So does ICMP. So does any L2-resolution-dependent traffic.
The structural answer industry-wide is mDNS: it's the multicast group every prosumer router has tuned reflection / forwarding for (UniFi mDNS reflector, Eero cross-SSID forwarding, etc.). It also provides hostname resolution and structured TXT-record metadata as side benefits.
Companion firmware ticket: daqifi-nyquist-firmware#404. The firmware change adds an mDNS responder advertising _daqifi._tcp.local.. This ticket is the client-side counterpart.
Proposed shape
Add Daqifi.Core.Device.Discovery.MDnsDeviceFinder : IDeviceFinder, IDisposable alongside the existing WiFiDeviceFinder. Same IDeviceInfo output type, same event surface. The two are then composable (most consumers will want both, merged).
namespaceDaqifi.Core.Device.Discovery;publicclassMDnsDeviceFinder:IDeviceFinder,IDisposable{publicMDnsDeviceFinder(stringserviceType="_daqifi._tcp");// local. is implicitpubliceventEventHandler<DeviceDiscoveredEventArgs>?DeviceDiscovered;publiceventEventHandler?DiscoveryCompleted;publicTask<IEnumerable<IDeviceInfo>>DiscoverAsync(TimeSpantimeout);publicTask<IEnumerable<IDeviceInfo>>DiscoverAsync(CancellationTokencancellationToken=default);}
MDnsDeviceFinder parses the service instance + SRV (host + port) + A (IP) + TXT (pn, sn, fw, hw) records into the existing IDeviceInfo shape. LocalInterfaceAddress is populated from the receiving socket's bound IP, same trick as WiFiDeviceFinder.
Aggregator (recommended)
Most callers should not care which transport found the device. Add a thin façade that runs both finders in parallel and returns the union, deduplicated by MAC (or serial, when MAC is unavailable):
publicclassDeviceFinder:IDeviceFinder,IDisposable{publicDeviceFinder();// wires up WiFiDeviceFinder + MDnsDeviceFinder by default// ...}
Both finders run concurrently. mDNS will typically return faster on networks where it's available; UDP broadcast remains the path for older firmware that doesn't yet advertise mDNS. If both surface the same device, dedupe by MAC.
This makes the rollout strictly additive: existing devices on older firmware continue to be found via UDP broadcast; new firmware (#404) gets the better path automatically; the library needs no firmware-version awareness.
Library implementation choices
In rough order of preference:
Take a small dependency on a managed mDNS library. Candidates:
Makaretu.Dns.Multicast — actively maintained, MIT, well-tested, no native deps. Most aligned with Daqifi.Core's "pure managed, multi-platform" stance.
Tmds.MDns — older but stable.
Zeroconf — popular but heavier surface area.
The existing project already takes managed deps (Google.Protobuf, etc.); one more well-scoped one is reasonable.
Roll a minimal in-tree mDNS-SD client. mDNS-SD client (not responder) is a few hundred lines of straightforward DNS message parsing. Avoids the dependency. Worth doing if the team wants zero new deps; not worth it if not.
P/Invoke into platform Bonjour / Avahi / Windows DNS-SD APIs. Cross-platform API differs per OS; gives up the managed-everywhere story. Recommend against.
I'd start with (1) and a clear adapter boundary, so swapping to (2) later is contained.
Code locations / additions
src/Daqifi.Core/Device/Discovery/MDnsDeviceFinder.cs — new
src/Daqifi.Core/Device/Discovery/DeviceFinder.cs — new aggregator (or fold into existing if there's already a façade)
src/Daqifi.Core.Tests/Device/Discovery/MDnsDeviceFinderTests.cs — unit tests with a mocked mDNS query/response transcript
README + docs/ — document the discovery story and the older-firmware fallback
Acceptance criteria
MDnsDeviceFinder finds a device advertising _daqifi._tcp.local. and populates IDeviceInfo with name (instance), IP, port, MAC (parsed from TXT or computed from advertised hostname), serial / fw / hw / part number from TXT
Aggregator runs both finders concurrently, dedupes by MAC, surfaces results as they arrive (events) and as the merged final set
Discovery succeeds on the multi-AP UniFi setup that broke WiFiDeviceFinder (the network this issue is about), once paired with firmware-side #404
Existing WiFiDeviceFinder-only consumers continue to work unchanged (additive change, no breaking API)
Cancellation / timeout / disposal semantics match WiFiDeviceFinder exactly
Unit tests cover: TXT parsing, instance name → device-info mapping, conflict-resolved instance names (-2 suffix), goodbye packets (TTL=0 means "device gone"), partial reply (SRV without A — re-query), multi-NIC
Risks
mDNS is multicast — some networks (corporate, captive portals, hardened guest WiFi) filter all multicast. Same problem affects existing UDP broadcast and arguably more so; mDNS is more likely to be allowed because of how widespread its consumer use is. Document the --ip direct-connect fallback for these cases.
mDNS responders / clients require IPv4 multicast support. Expected on every target platform but worth verifying on .NET on Linux containers (some Docker default networks don't pass mcast).
_daqifi._tcp is not registered with IANA. Should we register it under _<your-name>._<protocol>.local. per RFC 6763 conventions? Not blocking; revisit before broad release.
A separate near-term improvement worth its own ticket: a unicast subnet-sweep fallback in WiFiDeviceFinder for networks where broadcast is filtered but L2 unicast routes fine. Helps enterprise / VPN / WSL-tunneled hosts. Doesn't help the home Wi-Fi failure mode this ticket is about (where ARP itself fails), but is cheap.
Why
The existing
WiFiDeviceFinder(post-#180) does the host side of UDP broadcast discovery correctly: it binds per-NIC, filters virtual adapters, and routes replies to the originating socket. But the broadcast transport itself is unreliable on a typical home network with multiple APs / Band Steering / lossy 2.4 GHz radios — and we cannot fix that from the library side, no matter how cleanly we bind sockets.Concrete failure session against a Nyquist1 on a default UniFi setup (Dream-Machine + wired-PoE U6 LR, single SSID, Band Steering on, Mac on 5 GHz at one AP, device on 2.4 GHz at the other). Healthy controller-side state, device fully online, signal -57 dBm, getting DHCP. From the Mac, ARP for the device was
(incomplete)— broadcast frames across the AP boundary were dropping at the rate the U6 LR's 2.4 GHz radio reported (17% Tx Retry / 10% Tx Drop). UDP discovery silently returns 0 devices in this scenario. So does ICMP. So does any L2-resolution-dependent traffic.The structural answer industry-wide is mDNS: it's the multicast group every prosumer router has tuned reflection / forwarding for (UniFi mDNS reflector, Eero cross-SSID forwarding, etc.). It also provides hostname resolution and structured TXT-record metadata as side benefits.
Companion firmware ticket: daqifi-nyquist-firmware#404. The firmware change adds an mDNS responder advertising
_daqifi._tcp.local.. This ticket is the client-side counterpart.Proposed shape
Add
Daqifi.Core.Device.Discovery.MDnsDeviceFinder : IDeviceFinder, IDisposablealongside the existingWiFiDeviceFinder. SameIDeviceInfooutput type, same event surface. The two are then composable (most consumers will want both, merged).MDnsDeviceFinderparses the service instance + SRV (host + port) + A (IP) + TXT (pn,sn,fw,hw) records into the existingIDeviceInfoshape.LocalInterfaceAddressis populated from the receiving socket's bound IP, same trick asWiFiDeviceFinder.Aggregator (recommended)
Most callers should not care which transport found the device. Add a thin façade that runs both finders in parallel and returns the union, deduplicated by MAC (or serial, when MAC is unavailable):
Both finders run concurrently. mDNS will typically return faster on networks where it's available; UDP broadcast remains the path for older firmware that doesn't yet advertise mDNS. If both surface the same device, dedupe by MAC.
This makes the rollout strictly additive: existing devices on older firmware continue to be found via UDP broadcast; new firmware (#404) gets the better path automatically; the library needs no firmware-version awareness.
Library implementation choices
In rough order of preference:
Take a small dependency on a managed mDNS library. Candidates:
Makaretu.Dns.Multicast— actively maintained, MIT, well-tested, no native deps. Most aligned with Daqifi.Core's "pure managed, multi-platform" stance.Tmds.MDns— older but stable.Zeroconf— popular but heavier surface area.The existing project already takes managed deps (Google.Protobuf, etc.); one more well-scoped one is reasonable.
Roll a minimal in-tree mDNS-SD client. mDNS-SD client (not responder) is a few hundred lines of straightforward DNS message parsing. Avoids the dependency. Worth doing if the team wants zero new deps; not worth it if not.
P/Invoke into platform Bonjour / Avahi / Windows DNS-SD APIs. Cross-platform API differs per OS; gives up the managed-everywhere story. Recommend against.
I'd start with (1) and a clear adapter boundary, so swapping to (2) later is contained.
Code locations / additions
src/Daqifi.Core/Device/Discovery/MDnsDeviceFinder.cs— newsrc/Daqifi.Core/Device/Discovery/DeviceFinder.cs— new aggregator (or fold into existing if there's already a façade)src/Daqifi.Core.Tests/Device/Discovery/MDnsDeviceFinderTests.cs— unit tests with a mocked mDNS query/response transcriptdocs/— document the discovery story and the older-firmware fallbackAcceptance criteria
MDnsDeviceFinderfinds a device advertising_daqifi._tcp.local.and populatesIDeviceInfowith name (instance), IP, port, MAC (parsed from TXT or computed from advertised hostname), serial / fw / hw / part number from TXTLocalInterfaceAddressis populated from the actual receiving NIC (same correctness PR fix: bind UDP discovery per-NIC and skip virtual adapters (#179) #180 established for the broadcast path)WiFiDeviceFinder(the network this issue is about), once paired with firmware-side #404WiFiDeviceFinder-only consumers continue to work unchanged (additive change, no breaking API)WiFiDeviceFinderexactly-2suffix), goodbye packets (TTL=0 means "device gone"), partial reply (SRV without A — re-query), multi-NICRisks
--ipdirect-connect fallback for these cases._daqifi._tcpis not registered with IANA. Should we register it under_<your-name>._<protocol>.local.per RFC 6763 conventions? Not blocking; revisit before broad release.Related
WiFiDeviceFinderfor networks where broadcast is filtered but L2 unicast routes fine. Helps enterprise / VPN / WSL-tunneled hosts. Doesn't help the home Wi-Fi failure mode this ticket is about (where ARP itself fails), but is cheap.🤖 Generated with Claude Code