mdns-sd

A safe, dependency-light Rust library for mDNS-based service discovery, supporting both browsing and publishing services.

Library
Cargo
v0.21.0
219stars
Apache License 2.0

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
80/100Excellent
Development Activity80
Maintenance88
Community76
Maturity56
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
83/100Excellent
Architecture85
Code Quality85
Innovation82
Learning Curve80

mdns-sd is a small implementation of Multicast DNS (mDNS) and DNS-Based Service Discovery (DNS-SD) written in safe Rust (#![forbid(unsafe_code)]). It lets an application act as a querier that browses the local network for services of a given type, and/or as a responder that publishes its own service so other hosts can find it, all without depending on any particular async runtime.

Internally the daemon runs its own event loop on a dedicated thread and exposes its API to callers through flume channels, which support both blocking recv() and recv_async() calls. That design lets the same daemon be driven from plain synchronous code, a Tokio task, or any other async executor with no adapter layer. The crate implements a meaningful slice of RFC 6762 (mDNS) and RFC 6763 (DNS-SD) — probing, conflict resolution with automatic name changes, known-answer suppression, cache-flush announcements, and goodbye packets — and runs on macOS, Linux, and Windows over both IPv4 and IPv6.

What You Get

  • A ServiceDaemon that runs mDNS on its own background thread and is cheaply cloneable across threads
  • browse() and browse_cache() to discover instances of a service type and receive ServiceEvents (found/resolved/removed) over a channel
  • register() / unregister() to publish and retract a ServiceInfo (service type, instance name, hostname, addresses, port, TXT properties)
  • resolve_hostname() to resolve a plain hostname to IP addresses via mDNS, independent of any service
  • Automatic RFC 6762 conflict resolution with DaemonEvent::NameChange notifications when a name collision forces a rename
  • Runtime controls for interface selection (enable_interface/disable_interface/IfKind), multicast loopback, IPv6, and Apple P2P interfaces
  • Optional serde feature for serializing ServiceInfo/TXT properties, and an optional logging feature (on by default) built on the log crate

Common Use Cases

  • Discovering printers, cameras, smart-home devices, or other IoT peripherals that advertise themselves via mDNS/Bonjour
  • Publishing a local network service (e.g. a media server, dev tool, or peer-to-peer node) so other devices on the LAN can find it without manual configuration
  • Building cross-platform zero-configuration networking features that need to interoperate with Apple’s Bonjour or Linux’s Avahi
  • Resolving .local hostnames on networks without a conventional DNS server

Under The Hood

Architecture — The crate is organized around a single ServiceDaemon (src/service_daemon.rs, ~6,200 lines) that owns a background thread running an mio-based poll loop (Zeroconf) over raw UDP sockets bound to the mDNS multicast group. The public struct is just a thin, cloneable handle holding a flume::Sender<Command>; every public method (browse, register, unregister, monitor, shutdown, set_ip_check_interval, etc.) constructs a Command and does a non-blocking try_send() into that channel, so callers never block on the daemon and the same handle works from sync or async code. Inbound results flow back out through per-call flume::Receivers (e.g. Receiver<ServiceEvent> from browse, Receiver<UnregisterStatus> from unregister), which support both recv() and recv_async(). DNS wire parsing/building lives in src/dns_parser.rs (~3,000 lines) and the resource-record cache with TTL/known-answer-suppression logic lives in src/dns_cache.rs (~1,000 lines), keeping protocol mechanics separate from the daemon’s command loop.

Tech Stack — Pure safe Rust (#![forbid(unsafe_code)]), edition 2018, MSRV 1.71. Core dependencies are deliberately minimal: flume for channels (with an optional async feature), mio for cross-platform socket polling, socket2/socket-pktinfo for low-level UDP/pktinfo socket options, if-addrs for enumerating local interfaces, and fastrand for jitter/backoff timing. log is pulled in only behind the default logging feature, and serde is fully optional. There is no dependency on Tokio, async-std, or any other runtime — async support is purely a channel-compatibility feature (flume/async), not a runtime integration.

Code Quality — The crate ships a large, purpose-built test suite: tests/mdns_test.rs (~2,600 lines, ~80 test functions covering browse/register/resolve/conflict flows end-to-end over real sockets), plus tests/shutdown_test.rs and tests/addr_parse.rs. Errors are modeled as a small #[non_exhaustive] Error enum (Again, DaemonShutdown, Msg, ParseIpAddr) with Display/std::error::Error impls rather than panics or stringly-typed failures, and public methods consistently return Result<T>. Logging is gated behind a feature flag with a zero-cost no-op macro fallback when disabled, so the library imposes no runtime cost on consumers who don’t want logging.

API Design — The public surface centers on two types, ServiceDaemon and ServiceInfo, with builder-style construction (ServiceInfo::new(...).enable_addr_auto()) and a small, consistent verb set (browse, register, unregister, monitor, shutdown, status). Every mutating call funnels through the same request/response-channel pattern, so once a caller learns one method they know the shape of all of them. Extensive doc comments on lib.rs include runnable end-to-end examples for both the querier and responder roles, and the examples/ directory ships register.rs and query.rs as copy-pasteable CLI programs with their own --help-style usage text.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search