//! mDNS advertisement of the native punktfunk/1 service so native clients auto-discover the //! host — the native-protocol analogue of the GameStream `_nvstream._tcp` advert //! ([`crate::gamestream::mdns`]). //! //! The service type is **`_punktfunk._udp.local.`** (UDP because punktfunk/1 is QUIC, and the //! advertised port is the QUIC control/data port a client `--connect`s). TXT records carry: //! - `proto` — the wire protocol id ([`NATIVE_PROTO`]), so a future incompatible revision is //! distinguishable by discovery alone; //! - `fp` — the host certificate SHA-256 (lowercase hex), the exact value a client pins. mDNS is //! unauthenticated, so this is advisory — TOFU/pinning still verifies it on connect — but it //! lets a picker show the fingerprint and pre-pin a chosen host; //! - `pair` — `required` or `optional`, so a client can tell up front whether it must run the PIN //! pairing ceremony before it can stream; //! - `id` — the stable host uniqueid (dedup across IPs / re-advertises); //! - `mgmt` — the management API's TCP port (when it serves one), so a client can fetch the host's //! game library (`GET /api/v1/library`, mTLS) on the SAME IP without assuming the default port. //! Omitted by a host with no mgmt API (the standalone `punktfunk1-host`). //! - `mac` — the host's wake-capable NIC MAC(s) (comma-separated, routed NIC first), which a client //! persists so it can Wake-on-LAN this host after it sleeps. Advisory/unauthenticated (a wrong //! MAC only makes a wake fail). Omitted when none can be read. use anyhow::{Context, Result}; use mdns_sd::{ServiceDaemon, ServiceInfo}; use std::collections::HashMap; use std::net::IpAddr; /// The native-protocol mDNS service type. Clients browse this to find punktfunk/1 hosts. pub const NATIVE_SERVICE: &str = "_punktfunk._udp.local."; /// mDNS advertisement gate — `PUNKTFUNK_MDNS`. Default ON; `0|false|off|no` (the /// `PUNKTFUNK_ZEROCOPY` off-grammar) disables BOTH the native and GameStream adverts, for /// environments where multicast is dead or unwanted (bridged Docker, CI network namespaces, /// locked-down VLANs): the advert there reaches nobody — or fails outright and aborts the /// GameStream plane — while clients can always dial a manually-added host (mDNS-blind /// host-add works since the 0.8.4 dial-first fix). CLI `--no-mdns` sets the same knob. pub(crate) fn mdns_enabled() -> bool { !std::env::var("PUNKTFUNK_MDNS") .map(|s| mdns_off_value(&s)) .unwrap_or(false) } /// `true` iff the `PUNKTFUNK_MDNS` value means "off". Split from the env read for testability /// (env vars are process-global; tests must not race the parallel suite by setting them). fn mdns_off_value(s: &str) -> bool { matches!( s.trim().to_ascii_lowercase().as_str(), "0" | "false" | "off" | "no" ) } /// Wire protocol id advertised in the `proto` TXT record. pub const NATIVE_PROTO: &str = "punktfunk/1"; /// The DNS label a display name advertises its A record under (`