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

설치로 이동

소스 정보

저장소
shinyorg/skills
최근 소스 활동
2026년 8월 15일 17:13
감지된 SKILL.md 언어
영어
스타
4
포크
0

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
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") {
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기