mdns_sd/lib.rs
1//! A small and safe library for Multicast DNS-SD (Service Discovery).
2//!
3//! This library creates one new thread to run a mDNS daemon, and exposes
4//! its API that interacts with the daemon via a
5//! [`flume`](https://crates.io/crates/flume) channel. The channel supports
6//! both `recv()` and `recv_async()`.
7//!
8//! For example, a client querying (browsing) a service behaves like this:
9//!```text
10//! Client <channel> mDNS daemon thread
11//! | | starts its run-loop.
12//! | --- Browse --> |
13//! | | detects services
14//! | | finds service instance A
15//! | <-- Found A -- |
16//! | ... | resolves service A
17//! | <-- Resolved A -- |
18//! | ... |
19//!```
20//! All commands in the public API are sent to the daemon using the unblocking `try_send()`
21//! so that the caller can use it with both sync and async code, with no dependency on any
22//! particular async runtimes.
23//!
24//! # Usage
25//!
26//! The user starts with creating a daemon by calling [`ServiceDaemon::new()`].
27//! Then as a mDNS querier, the user would call [`browse`](`ServiceDaemon::browse`) to
28//! search for services, and/or as a mDNS responder, call [`register`](`ServiceDaemon::register`)
29//! to publish (i.e. announce) its own service. And, the daemon type can be cloned and passed
30//! around between threads.
31//!
32//! The user can also call [`resolve_hostname`](`ServiceDaemon::resolve_hostname`) to
33//! resolve a hostname to IP addresses using mDNS, regardless if the host publishes a service name.
34//!
35//! ## Example: a client querying for a service type.
36//!
37//! ```rust
38//! use mdns_sd::{ServiceDaemon, ServiceEvent};
39//!
40//! // Create a daemon
41//! let mdns = ServiceDaemon::new().expect("Failed to create daemon");
42//!
43//! // Use recently added `ServiceEvent::ServiceData`.
44//! mdns.use_service_data(true).expect("Failed to use ServiceData");
45//!
46//! // Browse for a service type.
47//! let service_type = "_mdns-sd-my-test._udp.local.";
48//! let receiver = mdns.browse(service_type).expect("Failed to browse");
49//!
50//! // Receive the browse events in sync or async. Here is
51//! // an example of using a thread. Users can call `receiver.recv_async().await`
52//! // if running in async environment.
53//! std::thread::spawn(move || {
54//! while let Ok(event) = receiver.recv() {
55//! match event {
56//! ServiceEvent::ServiceData(resolved) => {
57//! println!("Resolved a new service: {}", resolved.fullname);
58//! }
59//! other_event => {
60//! println!("Received other event: {:?}", &other_event);
61//! }
62//! }
63//! }
64//! });
65//!
66//! // Gracefully shutdown the daemon.
67//! std::thread::sleep(std::time::Duration::from_secs(1));
68//! mdns.shutdown().unwrap();
69//! ```
70//!
71//! ## Example: a server publishs a service and responds to queries.
72//!
73//! ```rust
74//! use mdns_sd::{ServiceDaemon, ServiceInfo};
75//! use std::collections::HashMap;
76//!
77//! // Create a daemon
78//! let mdns = ServiceDaemon::new().expect("Failed to create daemon");
79//!
80//! // Create a service info.
81//! let service_type = "_mdns-sd-my-test._udp.local.";
82//! let instance_name = "my_instance";
83//! let ip = "192.168.1.12";
84//! let host_name = "192.168.1.12.local.";
85//! let port = 5200;
86//! let properties = [("property_1", "test"), ("property_2", "1234")];
87//!
88//! let my_service = ServiceInfo::new(
89//! service_type,
90//! instance_name,
91//! host_name,
92//! ip,
93//! port,
94//! &properties[..],
95//! ).unwrap();
96//!
97//! // Register with the daemon, which publishes the service.
98//! mdns.register(my_service).expect("Failed to register our service");
99//!
100//! // Gracefully shutdown the daemon
101//! std::thread::sleep(std::time::Duration::from_secs(1));
102//! mdns.shutdown().unwrap();
103//! ```
104//!
105//! ## Conflict resolution
106//!
107//! When a service responder receives another DNS record with the same name as its own record, a conflict occurs.
108//! The mDNS [RFC 6762 section 9](https://datatracker.ietf.org/doc/html/rfc6762#section-9) defines a conflict resolution
109//! mechanism, which is implemented in this library. When an application wishes to be notified of conflict resolutions,
110//! it follows the steps below:
111//!
112//! 1. The application calls [`ServiceDaemon::monitor()`] to monitor all events from the daemon service responder.
113//! 2. When a conflict resolution causes a name change, the library sends an event to the application: [`DaemonEvent::NameChange`],
114//! which provides [`DnsNameChange`] with details.
115//!
116//! # Limitations
117//!
118//! This implementation is based on the following RFCs:
119//! - mDNS: [RFC 6762](https://tools.ietf.org/html/rfc6762)
120//! - DNS-SD: [RFC 6763](https://tools.ietf.org/html/rfc6763)
121//! - DNS: [RFC 1035](https://tools.ietf.org/html/rfc1035)
122//!
123//! We focus on the common use cases at first, and currently have the following limitations:
124//! - Only support multicast, not unicast send/recv.
125//! - Only support 32-bit or bigger platforms, not 16-bit platforms.
126//!
127//! # Use logging in tests and examples
128//!
129//! Often times it is helpful to enable logging running tests or examples to examine the details.
130//! For tests and examples, we use [`env_logger`](https://docs.rs/env_logger/latest/env_logger/)
131//! as the logger and use [`test-log`](https://docs.rs/test-log/latest/test_log/) to enable logging for tests.
132//! For instance you can show all test logs using:
133//!
134//! ```shell
135//! RUST_LOG=debug cargo test integration_success -- --nocapture
136//! ```
137//!
138//! We also enabled the logging for the examples. For instance you can do:
139//!
140//! ```shell
141//! RUST_LOG=debug cargo run --example query _printer._tcp
142//! ```
143//!
144
145#![forbid(unsafe_code)]
146#![allow(clippy::single_component_path_imports)]
147
148// log for logging (optional).
149#[cfg(feature = "logging")]
150use log;
151
152#[cfg(not(feature = "logging"))]
153#[macro_use]
154mod log {
155 macro_rules! trace ( ($($tt:tt)*) => {{}} );
156 macro_rules! debug ( ($($tt:tt)*) => {{}} );
157 macro_rules! info ( ($($tt:tt)*) => {{}} );
158 macro_rules! warn ( ($($tt:tt)*) => {{}} );
159 macro_rules! error ( ($($tt:tt)*) => {{}} );
160}
161
162mod dns_cache;
163mod dns_parser;
164mod error;
165mod service_daemon;
166mod service_info;
167
168pub use dns_parser::{InterfaceId, RRType, ScopedIp, ScopedIpV4, ScopedIpV6};
169pub use error::{Error, Result};
170pub use service_daemon::{
171 DaemonEvent, DaemonStatus, DnsNameChange, HostnameResolutionEvent, IfKind, Metrics,
172 ServiceDaemon, ServiceEvent, UnregisterStatus, IP_CHECK_INTERVAL_IN_SECS_DEFAULT,
173 SERVICE_NAME_LEN_MAX_DEFAULT, VERIFY_TIMEOUT_DEFAULT,
174};
175pub use service_info::{
176 AsIpAddrs, IntoTxtProperties, ResolvedService, ServiceInfo, TxtProperties, TxtProperty,
177};
178
179/// A handler to receive messages from [ServiceDaemon]. Re-export from `flume` crate.
180pub use flume::Receiver;