Shiny Controls v1.0 - The Ultra Control Suite for .NET MAUI & BlazorO...M...G!
Network Discovery Releases
5.5.0 - August 26, 2026
Section titled “5.5.0 - August 26, 2026”FixAndroid
Resolving a service on Android 14 (API 34) or later no longer crashes the app with
Java.Util.Concurrent.RejectedExecutionException. NsdManager dispatches the unregistration confirmation through the Executor handed to registerServiceInfoCallback after unregisterServiceInfoCallback returns, so shutting that executor down at the end of a resolve left the framework rejecting its own task on its handler thread - an uncaught exception that killed the process. Callbacks now run inline on NsdManager’s dispatch thread with nothing to shut down. Affects Browse/BrowseOnce with MdnsBrowseConfig.ResolveServices enabled and direct IMdnsManager.Resolve(...) calls.5.4.0 - August 2026
Section titled “5.4.0 - August 2026”Feature
SSDP/UPnP support. Register
services.AddSsdp() and inject ISsdpManager to discover UPnP devices — routers, DLNA media servers, Sonos, Roku, smart TVs. SearchAll(...)/Search(...) for a one-shot sweep, Browse(...) for a live IAsyncEnumerable<SsdpBrowseResult> keyed on UDN, and Publish(...) to advertise your own root device. Supported on iOS, Mac Catalyst, macOS, Android, Windows, Linux, and server .NET.Feature
ISsdpManager.GetDescription(...) fetches and parses a device’s UPnP description document into UpnpDeviceDescription — friendly name, manufacturer, model, serial, icons, the service list (UpnpService with SCPD/control/event URLs), and embedded devices via Flatten(). Relative URLs are resolved against URLBase or the fetch location, including the classic missing-trailing-slash case, and a URLBase pointing at a different host than the document was fetched from is ignored as stale.Feature
WS-Discovery support. Register
services.AddWsDiscovery() and inject IWsDiscoveryManager to find ONVIF IP cameras, WSD printers and scanners, and Windows machines. Probe/Resolve/Hello/Bye on both the 2005 (ONVIF/Windows) and 2009 (OASIS) profiles as separate datagrams, ProbeOnvifCameras(...) as a shortcut, and Publish(...) to advertise this host as a target service. WsdTarget.GetScopeValue("name") reads ONVIF-style scopes, and PreferredAddress picks a reachable XAddr from the stale and unreachable ones devices routinely advertise.FeatureiOS
SSDP and WS-Discovery require the
com.apple.developer.networking.multicast entitlement on iOS, unlike mDNS. No OS exposes an API for either protocol — Android’s NsdManager and Apple’s Bonjour stack are strictly mDNS/DNS-SD — so both use raw multicast on every platform. Apple grants the entitlement per developer team on request, and it cannot be tested in the Simulator. mDNS is unaffected and still needs no entitlement.FeatureAndroid
SSDP and WS-Discovery additionally need
CHANGE_WIFI_MULTICAST_STATE in the manifest (without it, sends succeed and nothing is ever received over Wi-Fi) and the ACCESS_LOCAL_NETWORK runtime permission when targeting SDK 37 / Android 17 or later. The WifiManager.MulticastLock is acquired and released automatically for the lifetime of a browse or publication — no app code needed.Feature
New
DiscoveryPermissionException is thrown when the OS refuses a multicast operation, with a message naming the exact entitlement, manifest entry, runtime permission, or firewall rule that is missing. Previously — and in most discovery libraries — a missing permission is indistinguishable from an empty network. DiscoveryException is now the base type for all failures, with SsdpException and WsDiscoveryException alongside the existing MdnsException.Enhancement
The SSDP client tracks UPnP 1.1
BOOTID/CONFIGID, so a ssdp:byebye arriving late from a previous boot session no longer evicts a device that has already come back, and a CONFIGID change signals that a cached description should be re-fetched. Expiry runs on a monotonic clock and max-age is clamped to a sane range, because devices advertise everything from one second to a week.Enhancement
WS-Discovery
Types are parsed as real XML QNames, resolving each prefix against the namespace declarations in scope on the Types element or any ancestor. String-splitting that list — the common shortcut — silently fails to match devices whose prefixes are declared anywhere other than where the parser expects. Scope matching implements the RFC 3986/2396 rule properly (case-insensitive scheme and authority, case-sensitive segment-wise path prefix), plus strcmp0 and uuid; ldap is recognised and deliberately never matches.Enhancement
Both responders are hardened against reflection and amplification abuse, which is an actively exploited vector for SSDP: they answer only sources on a network this host belongs to, rate limit per source, spread an
ssdp:all burst across the requester’s own MX window, and bound every collection parsed off the wire. Device caches are LRU-bounded so a spoofing sender cannot grow them without limit.Enhancement
UPnP description fetching applies a default-deny policy:
http/https only, no credentials in the URL, no redirects, no cookies, a 512KB cap enforced while streaming rather than trusting Content-Length, a 10 second timeout, DTD processing disabled, and a literal-IP host that must match the address the advertisement came from. Override it per call with the urlFilter parameter on GetDescription when a device legitimately advertises a different host. Descriptions are parsed by local element name, ignoring XML namespaces, because shipped devices variously declare none, the wrong one, or vendor extensions.Chore
The mDNS socket layer was generalised into a shared multicast implementation now used by all three protocols — per-interface group joins, outbound interface reselection per send,
SO_REUSEADDR/SO_REUSEPORT, and rejoin on network change. mDNS behaviour is unchanged; multicast TTL is now per-protocol (255 for mDNS as RFC 6762 requires, 2 for SSDP, 1 for WS-Discovery) and the receiving interface is captured per datagram so link-local IPv6 URLs get a zone id.Feature
New
Shiny.Net.Discovery package — cross-platform mDNS/DNS-SD (Bonjour/Zeroconf) service discovery and publishing. Register with services.AddMdns() and inject IMdnsManager to browse the local link (Browse for a live IAsyncEnumerable<MdnsBrowseResult> stream, BrowseOnce for a fixed-window scan), resolve a known instance to its host/port/addresses/TXT records, and publish your own service with Publish(...). Supported on iOS, Mac Catalyst, macOS, Android, Windows, Linux, and server .NET.FeatureiOS
Apple platforms are backed by
NSNetService (Bonjour) rather than raw multicast sockets, so no com.apple.developer.networking.multicast entitlement is required — only NSLocalNetworkUsageDescription and an NSBonjourServices array listing every service type the app browses for or publishes. Browsing silently returns nothing when a type is missing from that array.FeatureAndroid
Android is backed by
NsdManager, so no CHANGE_WIFI_MULTICAST_STATE permission and no WifiManager.MulticastLock are needed — only INTERNET and ACCESS_NETWORK_STATE in the manifest. Resolution uses registerServiceInfoCallback on API 34+ (concurrent resolves) and falls back to a serialised resolveService below it, because the older API only tolerates one resolve at a time.FeatureWindows
Windows, Linux, macOS console, and server .NET are served by a dependency-free managed responder speaking mDNS directly on UDP 5353. It implements RFC 6762/6763 name compression, probing with conflict-driven renaming, announcements, goodbye packets, TTL expiry, and query answering across every multicast-capable interface, and rejoins the multicast group when the network changes.
SO_REUSEADDR/SO_REUSEPORT let it coexist with avahi-daemon, mDNSResponder, or the Windows DNS client — Avahi is not a dependency on Linux.Feature
MdnsService exposes InstanceName, ServiceType, Domain, HostName, Port, Addresses (IPv4 and IPv6), TxtRecords, FullName, IsResolved, and GetEndPoint(family?). TXT records are read with GetTxt(key) or the parsing GetTxt<T>(key, fallback) for any IParsable<T>.Feature
Publishing returns an
IMdnsPublication whose InstanceName reflects any conflict-driven rename ("My Service" → "My Service (2)"), so always read the live name back rather than reusing the requested one. Disposing the handle sends a goodbye packet and stops advertising.Enhancement
Service types are validated against RFC 6763 section 7 up front —
MdnsException explains exactly what is wrong with a malformed type rather than failing silently at the socket. Service subtypes (_sub) and domains other than local are explicitly rejected in v1.5.3.0 - August 2026
Section titled “5.3.0 - August 2026”Initial Release


