Skip to main content

shiny-discovery

Generate code using Shiny.Net.Discovery for cross-platform local network discovery - mDNS/DNS-SD (Bonjour/Zeroconf), SSDP/UPnP, and WS-Discovery (ONVIF) browsing, resolution, and publishing on iOS, Android, Mac Catalyst, macOS, Windows, and Linux

Quellinformationen

Repository
shinyorg/skills
Letzte Quellaktivität
15. August 2026 um 17:13
Erkannte Sprache von SKILL.md
Englisch
Sterne
4
Forks
0

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
shiny-discovery
description
Generate code using Shiny.Net.Discovery for cross-platform local network discovery - mDNS/DNS-SD (Bonjour/Zeroconf), SSDP/UPnP, and WS-Discovery (ONVIF) browsing, resolution, and publishing on iOS, Android, Mac Catalyst, macOS, Windows, and Linux
auto_invoke
true
triggers
["mDNS","DNS-SD","Bonjour","Zeroconf","SSDP","UPnP","DLNA","M-SEARCH","ssdp:discover","ssdp:all","upnp:rootdevice","WS-Discovery","WSD","WSDAPI","ONVIF","ONVIF camera","IP camera discovery","find a printer on the network","find a media server","find a Chromecast","find a Sonos","service discovery","network discovery","local network discovery","discover devices on the network","find devices on LAN","advertise a service","publish a service","IMdnsManager","ISsdpManager","IWsDiscoveryManager","SsdpDevice","[Truncated]"]
# Shiny.Net.Discovery Skill You are an expert in Shiny.Net.Discovery, a cross-platform local network discovery library covering three protocols: **mDNS/DNS-SD** (Bonjour/Zeroconf), **SSDP/UPnP**, and **WS-Discovery**. ## When to Use This Skill Invoke this skill when the user wants to: - Discover services or devices on the local network (printers, Chromecasts, IoT devices, peer apps) - Advertise their own app as a discoverable service - Find UPnP/DLNA devices, routers, Sonos, Roku, or smart TVs (SSDP) - Find ONVIF cameras, WSD printers/scanners, or Windows machines (WS-Discovery) - Read TXT record metadata, or a UPnP device description - Resolve a known service instance to a host, port, and IP addresses - Ask about Bonjour/mDNS on iOS without the multicast entitlement - Register any of the three protocols in DI ## Choosing a protocol | Looking for | Use | Registration | |-------------|-----|--------------| | Apple devices, AirPlay, IPP printers, peer apps, your own services | mDNS/DNS-SD | `AddMdns()` | | Routers, UPnP/DLNA media servers, Sonos, Roku, smart TVs | SSDP | `AddSsdp()` | | ONVIF IP cameras, WSD printers/scanners, Windows machines | WS-Discovery | `AddWsDiscovery()` | They are independent - register only what you need. A device that speaks one usually does not speak the others. ## Library Overview | Item | Value | |------------|-------------------------------------------------------------------------------------------| | GitHub | https://github.com/shinyorg/shiny | | NuGet | `Shiny.Net.Discovery` | | Namespace | `Shiny.Net.Discovery` (types); `Shiny` (registration extensions) | | Platforms | iOS, Mac Catalyst, macOS, Android, Windows, Linux, server .NET | ### How each platform is backed **mDNS** delegates to the OS wherever there is an API: | Platform | Implementation | Why | |------------------------------|-------------------------------|-----------------------------------------------------------| | iOS / Mac Catalyst / macOS | `NSNetService` (Bonjour) | Goes through the system mDNSResponder, so **no** `com.apple.developer.networking.multicast` entitlement | | Android | `NsdManager` | No `CHANGE_WIFI_MULTICAST_STATE` and no `WifiManager.MulticastLock` | | Windows / Linux / server .NET| Managed responder on UDP 5353 | No OS DNS-SD API to lean on; dependency-free, AOT-safe | **SSDP and WS-Discovery are managed on every platform**, including iOS and Android. This is not a design choice - neither `NsdManager` nor Apple's Bonjour stack can speak these protocols, and no OS exposes any other API for them. They therefore use raw UDP multicast everywhere. ### ⚠️ The entitlement rule differs by protocol **For mDNS: do not tell users they need the iOS multicast entitlement.** That requirement applies to raw multicast sockets, which the mDNS implementation deliberately avoids on Apple platforms. **For SSDP and WS-Discovery the opposite is true** - they *do* need it on iOS, and it is approval-gated by Apple: | Platform | mDNS | SSDP / WS-Discovery | |----------|------|---------------------| | iOS | nothing | **`com.apple.developer.networking.multicast`** - request at https://developer.apple.com/contact/request/networking-multicast, granted per developer team. Cannot be tested in the simulator | | Mac Catalyst / macOS | sandbox network entitlements | same sandbox entitlements; **not** the multicast entitlement (iOS only). macOS 15+ also prompts for Local Network | | Android | nothing | `CHANGE_WIFI_MULTICAST_STATE` (the lock is acquired for you) **plus** `ACCESS_LOCAL_NETWORK` when targeting SDK 37+ | | Windows | firewall; MSIX capability | same | | Linux | firewall | same; Docker bridge networking does not work | A missing permission throws `DiscoveryPermissionException` with a message naming exactly what to add, rather than silently returning nothing. ## Setup ```csharp using Shiny; builder.Services.AddMdns(); // IMdnsManager builder.Services.AddSsdp(); // ISsdpManager builder.Services.AddWsDiscovery(); // IWsDiscoveryManager ``` Register only the protocols you use. `AddSsdp()` also registers a named `HttpClient` (`SsdpConstants.HttpClientName`) for fetching device descriptions, with redirects and cookies disabled deliberately - you can reconfigure it with `services.AddHttpClient(SsdpConstants.HttpClientName)`. ### iOS / Mac Catalyst / macOS — Info.plist (required) Browsing silently returns **nothing** if the service type is not declared. Both keys are required: ```xml <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> ``` Every service type the app browses for **and** publishes must be listed in `NSBonjourServices`. The local-network permission prompt appears automatically on first use; there is no API to query or pre-request it. ### Mac Catalyst / macOS — App Sandbox entitlements (csproj) For mDNS, iOS needs no entitlement at all. Mac Catalyst and macOS run sandboxed, so network access must be declared — browsing/resolving needs the client entitlement, publishing also needs the server one. These apply to all three protocols: ```xml <ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'"> <CustomEntitlements Include="com.apple.security.network.client" Type="Boolean" Value="true" /> <!-- publish/advertise only --> <CustomEntitlements Include="com.apple.security.network.server" Type="Boolean" Value="true" /> </ItemGroup> ``` Still **not** `com.apple.developer.networking.multicast` — that is for raw multicast sockets. ### Android — AndroidManifest.xml No runtime permission is needed, but declare: ```xml <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> ``` ### Windows / Linux The managed responder binds UDP 5353. It sets `SO_REUSEADDR` (plus `SO_REUSEPORT` on Unix) so it coexists with `avahi-daemon`, `mDNSResponder`, or the Windows DNS client. Ensure the firewall allows UDP 5353. A packaged Windows app (MSIX/WinUI) also needs the local-network capability in `Package.appxmanifest`, otherwise the responder sends and receives nothing: ```xml <Capabilities> <Capability Name="privateNetworkClientServer" /> </Capabilities> ``` ### SSDP / WS-Discovery — extra platform setup Everything above still applies. These two protocols additionally need the following, because they use raw multicast rather than an OS discovery API. **iOS — the multicast entitlement (blocking).** Apple grants this per developer team on request; budget days to weeks. Without it, sends fail and nothing is received. ```xml <!-- Entitlements.plist --> <key>com.apple.developer.networking.multicast</key> <true/> ``` Also set `NSLocalNetworkUsageDescription` in Info.plist. **This cannot be tested in the simulator** - local network privacy is not enforced there, so the simulator neither reproduces the failure nor proves the fix. `NSBonjourServices` is irrelevant to SSDP/WSD; it only gates mDNS. **Android:** ```xml <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- required, or 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" /> ``` The `WifiManager.MulticastLock` is acquired and released automatically for the lifetime of a browse or publication - do **not** write code to manage it. `ACCESS_LOCAL_NETWORK` is a runtime (dangerous) permission in the `NEARBY_DEVICES` group, so request it before discovering. **Ports:** SSDP is UDP 1900, WS-Discovery is UDP 3702, both on group 239.255.255.250 (and `ff02::c`). On Windows the system `SSDPSRV` and `FDResPub` services already hold those ports; `SO_REUSEADDR` is set so this coexists with them. **Docker:** bridge networking does not work - NAT does not rewrite the addresses embedded in `LOCATION` and `XAddrs`. Use `--network host` or macvlan. ## mDNS API Reference ### IMdnsManager ```csharp public interface IMdnsManager { IAsyncEnumerable<MdnsBrowseResult> Browse(MdnsBrowseConfig config, CancellationToken ct = default); Task<MdnsService?> Resolve(string instanceName, string serviceType, TimeSpan? timeout = null, CancellationToken ct = default); Task<IMdnsPublication> Publish(MdnsServiceRegistration registration, CancellationToken ct = default); } ``` Extension helpers (`MdnsExtensions`): ```csharp IAsyncEnumerable<MdnsBrowseResult> Browse(string serviceType, CancellationToken ct = default); Task<IReadOnlyList<MdnsService>> BrowseOnce(string serviceType, TimeSpan? scanTime = null, CancellationToken ct = default); Task<IMdnsPublication> Publish(string instanceName, string serviceType, int port, CancellationToken ct = default); ``` ### Browsing (live) `Browse` **never completes on its own** — it runs until the token is cancelled. Always pass a `CancellationToken`. ```csharp await foreach (var result in mdns.Browse("_http._tcp", ct)) { switch (result.Status) { case MdnsBrowseStatus.Found: // upsert keyed on result.Service.FullName - a re-announcing service emits Found again devices[result.Service.FullName] = result.Service; break; case MdnsBrowseStatus.Lost: // only InstanceName/ServiceType/Domain are populated on a Lost result devices.Remove(result.Service.FullName); break; } } ``` ### Browsing (one-shot scan) ```csharp var found = await mdns.BrowseOnce("_ipp._tcp", TimeSpan.FromSeconds(5), ct); foreach (var printer in found) Console.WriteLine($"{printer.InstanceName} => {printer.GetEndPoint()}"); ``` ### Publishing You are responsible for having something listening on the port — publishing only advertises it. ```csharp await using var publication = await mdns.Publish( new MdnsServiceRegistration("Allan's Laptop", "_myapp._tcp", 8080) { TxtRecords = new Dictionary<string, string> { ["version"] = "2", ["path"] = "/api" } }, ct ); // ALWAYS read the name back - the responder renames on conflict ("Allan's Laptop (2)") Console.WriteLine($"advertising as {publication.InstanceName}"); ``` Disposing the publication sends a goodbye packet and stops advertising. ### Resolving a known instance ```csharp var service = await mdns.Resolve("Allan's Laptop", "_myapp._tcp", TimeSpan.FromSeconds(5), ct); if (service?.IsResolved == true) await socket.ConnectAsync(service.GetEndPoint()!); ``` Returns `null` when the instance did not answer within the timeout. ## mDNS Models ### MdnsService | Member | Notes | |-----------------|---------------------------------------------------------------------------| | `InstanceName` | Human readable, unescaped; may contain spaces, dots, and UTF8 | | `ServiceType` | eg `_http._tcp` | | `Domain` | Almost always `local` | | `HostName` | SRV target, eg `printer.local`; null when unresolved | | `Port` | 0 when unresolved | | `Addresses` | `IReadOnlyList<IPAddress>`; often both IPv4 and IPv6 | | `TxtRecords` | Case-insensitive key/value map; empty when there are none | | `FullName` | `"Allan's Laptop._myapp._tcp.local"` — use this as the dictionary key | | `IsResolved` | `Port > 0 && Addresses.Count > 0` | | `GetEndPoint(family?)` | First matching `IPEndPoint`, or null when unresolved | TXT helpers: ```csharp string? path = service.GetTxt("path"); int version = service.GetTxt<int>("version", 1); // any IParsable<T>, falls back on miss bool secure = service.GetTxt<bool>("secure"); ``` ### MdnsBrowseConfig ```csharp new MdnsBrowseConfig("_http._tcp") {
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen