Platform Setup
What backs each platform
Section titled “What backs each platform”mDNS/DNS-SD delegates to the OS wherever an API exists:
| Platform | Backing | Consequence |
|---|---|---|
| iOS / Mac Catalyst / macOS | NSNetService (Bonjour) |
Goes through the system mDNSResponder daemon — no multicast entitlement required |
| Android | NsdManager |
No CHANGE_WIFI_MULTICAST_STATE permission and no WifiManager.MulticastLock |
| Windows | Managed responder, UDP 5353 | Dependency-free, coexists with the Windows DNS client |
| Linux | Managed responder, UDP 5353 | Coexists with avahi-daemon; Avahi is not required |
| macOS console / server .NET | Managed responder, UDP 5353 | Coexists with mDNSResponder |
The managed responder implements RFC 6762/6763 directly: name compression, probing and conflict-driven renaming, announcements, goodbye packets, TTL expiry, and query answering across every multicast-capable interface. It rejoins the multicast group automatically when the network changes.
SSDP (UDP 1900) and WS-Discovery (UDP 3702) are managed on every platform, including iOS and
Android. That is not a design preference — neither NsdManager nor Apple’s Bonjour stack can speak
these protocols, and no platform offers another route. Raw multicast is the only option, and the
permission requirements below follow directly from that.
Requirements at a glance
Section titled “Requirements at a glance”| Platform | mDNS | SSDP / WS-Discovery |
|---|---|---|
| iOS | NSBonjourServices + usage description |
com.apple.developer.networking.multicast (Apple approval required) + usage description |
| Mac Catalyst / macOS | sandbox network entitlements | same sandbox entitlements; not the multicast entitlement. macOS 15+ prompts for Local Network |
| Android | INTERNET, ACCESS_NETWORK_STATE |
those plus CHANGE_WIFI_MULTICAST_STATE, and ACCESS_LOCAL_NETWORK at runtime from Android 17 |
| Windows | firewall UDP 5353; MSIX capability | firewall UDP 1900/3702; same capability |
| Linux | firewall UDP 5353 | firewall UDP 1900/3702; Docker bridge networking does not work |
A missing requirement throws DiscoveryPermissionException, whose message names the specific
entitlement, manifest entry, or capability to add. That exists because every one of these failures
otherwise looks identical to an empty network.
iOS, Mac Catalyst, and macOS
Section titled “iOS, Mac Catalyst, and macOS”Info.plist (required)
Section titled “Info.plist (required)”<key>NSLocalNetworkUsageDescription</key><string>This app discovers nearby devices on your local network.</string><key>NSBonjourServices</key><array> <string>_myapp._tcp</string> <string>_http._tcp</string></array>The multicast entitlement — mDNS
Section titled “The multicast entitlement — mDNS”For mDNS you do not need com.apple.developer.networking.multicast.
That entitlement applies to apps that open raw multicast sockets. The mDNS implementation
deliberately uses Bonjour (NSNetService) on Apple platforms instead, so the system daemon does
the multicast and your app does not need special permission.
The multicast entitlement — SSDP and WS-Discovery
Section titled “The multicast entitlement — SSDP and WS-Discovery”For these two, you do need it on iOS, and there is no way around it.
<!-- Entitlements.plist --><key>com.apple.developer.networking.multicast</key><true/>NSLocalNetworkUsageDescription is still required. NSBonjourServices is not relevant to
SSDP or WS-Discovery; it only gates mDNS.
macOS and Mac Catalyst do not use this entitlement at all — it is iOS-only. They need the sandbox entitlements below, and on macOS 15+ the user sees a Local Network prompt (System Settings › Privacy & Security › Local Network).
Mac Catalyst and macOS — App Sandbox entitlements
Section titled “Mac Catalyst and macOS — App Sandbox entitlements”iOS is not sandboxed this way, but Mac Catalyst and macOS builds are. Under the App Sandbox,
network access is denied unless you ask for it, so add these to your project file (or
Entitlements.plist):
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'"> <CustomEntitlements Include="com.apple.security.network.client" Type="Boolean" Value="true" /> <!-- Only needed if you publish (advertise) a service --> <CustomEntitlements Include="com.apple.security.network.server" Type="Boolean" Value="true" /></ItemGroup>com.apple.security.network.client covers browsing and resolving; publishing also needs
com.apple.security.network.server (as does whatever socket you are actually advertising).
Local network permission
Section titled “Local network permission”iOS 14+ prompts the user the first time your app touches the local network, using your
NSLocalNetworkUsageDescription string. There is no public API to query or pre-request this state,
so IMdnsManager deliberately has no CurrentAccess/RequestAccess pair. If the user declines,
browsing simply returns nothing.
Simulator
Section titled “Simulator”Bonjour works in the iOS Simulator, but it sees the host Mac’s network — including services the Mac itself publishes. Confirm behaviour on a real device.
Android
Section titled “Android”No runtime permission is required. Declare these in AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />On API 34+ resolution uses NsdManager.registerServiceInfoCallback, which supports concurrent
resolves. Below API 34 the deprecated resolveService is used and calls are serialised internally,
because that API only tolerates one resolve at a time — a large scan resolves a little more slowly
on older devices as a result.
SSDP and WS-Discovery
Section titled “SSDP and WS-Discovery”Two further permissions apply:
<!-- without this, sends succeed and nothing is EVER received over Wi-Fi --><uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" /><!-- runtime permission, mandatory when targeting SDK 37 (Android 17) or later --><uses-permission android:name="android.permission.ACCESS_LOCAL_NETWORK" />CHANGE_WIFI_MULTICAST_STATE backs the WifiManager.MulticastLock, which the Wi-Fi driver
requires before it will deliver packets not addressed to this device. You do not need to manage
the lock — it is acquired for the lifetime of a browse or publication and released afterwards, so
it does not drain the battery when idle.
ACCESS_LOCAL_NETWORK is part of Android’s Local Network Protections. It is opt-in on Android 16
and mandatory for apps targeting SDK 37+. It is a runtime (dangerous) permission in the
NEARBY_DEVICES group, so request it before discovering. Apps targeting an older SDK receive an
implicit grant, which disappears the moment you bump targetSdk.
Windows and Linux
Section titled “Windows and Linux”The managed responder binds UDP 5353 with SO_REUSEADDR (plus SO_REUSEPORT on Unix) so it
coexists with whatever responder the OS already runs — avahi-daemon, mDNSResponder, or the
Windows DNS client. Avahi does not need to be installed on Linux.
Make sure your firewall allows inbound and outbound UDP 5353. On Windows, a first run typically raises the Windows Defender Firewall prompt.
SSDP and WS-Discovery ports
Section titled “SSDP and WS-Discovery ports”SSDP uses UDP 1900 and WS-Discovery UDP 3702, both on group 239.255.255.250 (and
ff02::c). Windows already runs services on both — SSDPSRV (SSDP Discovery) and FDResPub
(Function Discovery Resource Publication) — and SO_REUSEADDR is set so this coexists with them.
Because unicast delivery on a shared port is not deterministic, searches are additionally sent from
an ephemeral socket, which also catches devices that reply to the source port rather than the well
known one.
Containers
Section titled “Containers”Docker bridge networking does not work for SSDP or WS-Discovery. Multicast does not reach the
container, and NAT does not rewrite the addresses embedded in the payloads (LOCATION for SSDP,
XAddrs for WS-Discovery), so even one-way discovery yields unreachable addresses. Use
--network host (Linux only) or macvlan to put the container directly on the LAN.
Packaged Windows apps (MSIX / WinUI)
Section titled “Packaged Windows apps (MSIX / WinUI)”A packaged app runs in the AppContainer, so declare the local-network capability in
Package.appxmanifest:
<Capabilities> <Capability Name="privateNetworkClientServer" /></Capabilities>Without it the responder starts but neither sends nor receives anything.
Host names
Section titled “Host names”The managed responder advertises its SRV target as <machine>-shiny.local rather than
<machine>.local. The plain host name is owned by the OS responder, and two responders claiming
the same name with different address sets causes cache flapping on peers. The suffix keeps the two
out of each other’s way.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Likely cause |
|---|---|
| Empty results on iOS/macOS, no error | The service type is missing from NSBonjourServices |
| Empty results on iOS, prompt was declined | Local network permission — the user must re-enable it in Settings › Privacy › Local Network |
| Empty results on Windows | Firewall is blocking UDP 5353 |
| Services found but never resolve | The publisher is answering PTR but not SRV/A — check IsResolved before connecting, and consider raising ResolveTimeout |
| Nothing found across subnets | Expected — all three protocols are link-local by design and do not cross routers |
MdnsException on startup |
The service type is malformed; the message names the exact problem |
DiscoveryPermissionException |
Read the message — it names the exact entitlement, permission, or capability that is missing |
| SSDP/WS-Discovery empty on iOS | The multicast entitlement is missing or not yet approved by Apple. The Simulator cannot confirm either way |
| SSDP/WS-Discovery empty on Android, sends appear to work | CHANGE_WIFI_MULTICAST_STATE is missing from the manifest, or ACCESS_LOCAL_NETWORK was not granted at runtime |
| SSDP/WS-Discovery empty in Docker | Bridge networking — use --network host or macvlan |
SsdpException refusing a description URL |
The device advertised a LOCATION on a different host than it answered from. Override the filter on GetDescription if that device is legitimate |
| ONVIF camera never answers a probe | Many cameras only speak the 2005 profile — leave WsdProbeConfig.Profiles at All, and give the scan at least 5 seconds |


