mdns-sd-discovery 0.2.1

Async wrapper for native DNS-SD/mDNS service discovery
Documentation

mdns-sd-discovery

GitHub Release Crates.io GitHub branch check runs Documentation License: MIT Quality Gate Status

Access the operating system's built-in DNS-SD / mDNS stack for service discovery.

This crate provides a cross-platform async API (using Tokio) to browse DNS-SD services via native OS facilities — no bundled mDNS implementation, no extra system dependencies to install. Service registration lives in a separate crate.

Platform Support

Platform Backend Minimum Version
macOS native DNS-SD framework macOS 10.12
Windows native Win32 DNS-SD API Windows 10
Linux / BSD Avahi via D-Bus Avahi daemon running
  • macOS and Windows link against system libraries that are always present on supported OS versions.
  • Linux/FreeBSD communicate with Avahi over D-Bus — there is no binary dependency on libavahi or the Bonjour compatibility layer. Binaries will run on systems without Avahi installed but return an error when attempting to browse.

Why Use the OS Stack?

There exist various crates implementing DNS-SD/mDNS in pure Rust. Compared to these, using the operating system's DNS-SD stack has the following benefits:

  • Battle-tested: OS stacks have been tested widely over many years (sometimes decades) to handle OS-dependent edge cases (sleep/wake, interface changes) properly
  • Shared cache: all applications on the system share a single DNS-SD/mDNS responder & cache, reducing network traffic and keeping answers consistent.
  • Smaller binary: no embedded mDNS responder; just thin FFI/D-Bus bindings.

There also exist various crates implementing a wrapper on top of the Apple DNS-SD API, which is natively available on macOS, but only on Windows if users install Bonjour for Windows, and on Linux if the libavahi-compat-libdnssd library is installed. Compared to these, this crate requires no additional dependencies to install for end users.

Examples

Browse for services

By default a ServiceBrowser discovers all service types on the network; filter on DiscoveredService::service_type, or narrow to a single type with .service_type("_http._tcp").

use mdns_sd_discovery::{BrowseEvent, ServiceBrowserBuilder};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut browser = ServiceBrowserBuilder::new().browse().await?;

    while let Some(event) = browser.recv().await {
        match event? {
            BrowseEvent::Found(svc) => {
                println!("+ [{}] {} at {}:{}", svc.service_type, svc.name, svc.host_name, svc.port);
            }
            BrowseEvent::Removed(svc) => println!("- [{}] {}", svc.service_type, svc.name),
        }
    }
    // Dropping `browser` stops the browse.
    Ok(())
}

Re-confirm a service is still alive

ServiceResolverBuilder resolves a single, already-known instance now, mapping the identity fields from a previous browse event back to a connectable DiscoveredService. A vanished instance fails with ServiceBrowseError::ResolveFailed (rather than hanging), making it a useful liveness probe — handy for the no-goodbye case where a host powered off or dropped off the network without multicasting mDNS goodbye packets, so no Removed event is delivered until the browse PTR record's (long) TTL expires.

use mdns_sd_discovery::{ServiceBrowseError, ServiceResolverBuilder};
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let result = ServiceResolverBuilder::new("My Web Server", "_http._tcp", "local")
        .timeout(Duration::from_secs(5))
        .resolve()
        .await;

    match result {
        Ok(svc) => println!("still alive at {}:{}", svc.host_name, svc.port),
        Err(ServiceBrowseError::ResolveFailed(name, _)) => println!("{name} is gone"),
        Err(err) => return Err(err.into()),
    }
    Ok(())
}

See the examples/ directory for a full CLI tool that browses (discover-service) services; pass --resolve NAME --type _http._tcp to probe a single instance instead.

Platform note: on Windows, service removal (BrowseEvent::Removed) is not currently reported; appearances and resolution are. Removal events are delivered on macOS and Linux.

License

MIT