Skip to content
Shiny Controls v1.0 - The Ultra Control Suite for .NET MAUI & BlazorO...M...G!

Platform Setup

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.

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.

<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>

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).

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.

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.

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.

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.

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 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.

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.

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.

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.

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