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//! // Browse for a service type.
44//! let service_type = "_mdns-sd-my-test._udp.local.";
45//! let receiver = mdns.browse(service_type).expect("Failed to browse");
46//!
47//! // Receive the browse events in sync or async. Here is
48//! // an example of using a thread. Users can call `receiver.recv_async().await`
49//! // if running in async environment.
50//! std::thread::spawn(move || {
51//!     while let Ok(event) = receiver.recv() {
52//!         match event {
53//!             ServiceEvent::ServiceResolved(info) => {
54//!                 println!("Resolved a new service: {}", info.get_fullname());
55//!             }
56//!             other_event => {
57//!                 println!("Received other event: {:?}", &other_event);
58//!             }
59//!         }
60//!     }
61//! });
62//!
63//! // Gracefully shutdown the daemon.
64//! std::thread::sleep(std::time::Duration::from_secs(1));
65//! mdns.shutdown().unwrap();
66//! ```
67//!
68//! ## Example: a server publishs a service and responds to queries.
69//!
70//! ```rust
71//! use mdns_sd::{ServiceDaemon, ServiceInfo};
72//! use std::collections::HashMap;
73//!
74//! // Create a daemon
75//! let mdns = ServiceDaemon::new().expect("Failed to create daemon");
76//!
77//! // Create a service info.
78//! let service_type = "_mdns-sd-my-test._udp.local.";
79//! let instance_name = "my_instance";
80//! let ip = "192.168.1.12";
81//! let host_name = "192.168.1.12.local.";
82//! let port = 5200;
83//! let properties = [("property_1", "test"), ("property_2", "1234")];
84//!
85//! let my_service = ServiceInfo::new(
86//!     service_type,
87//!     instance_name,
88//!     host_name,
89//!     ip,
90//!     port,
91//!     &properties[..],
92//! ).unwrap();
93//!
94//! // Register with the daemon, which publishes the service.
95//! mdns.register(my_service).expect("Failed to register our service");
96//!
97//! // Gracefully shutdown the daemon
98//! std::thread::sleep(std::time::Duration::from_secs(1));
99//! mdns.shutdown().unwrap();
100//! ```
101//!
102//! # Limitations
103//!
104//! This implementation is based on the following RFCs:
105//! - mDNS:   [RFC 6762](https://tools.ietf.org/html/rfc6762)
106//! - DNS-SD: [RFC 6763](https://tools.ietf.org/html/rfc6763)
107//! - DNS:    [RFC 1035](https://tools.ietf.org/html/rfc1035)
108//!
109//! We focus on the common use cases at first, and currently have the following limitations:
110//! - Only support multicast, not unicast send/recv.
111//! - Only support 32-bit or bigger platforms, not 16-bit platforms.
112//!
113//! # Use logging in tests and examples
114//!
115//! Often times it is helpful to enable logging running tests or examples to examine the details.
116//! For tests and examples, we use [`env_logger`](https://docs.rs/env_logger/latest/env_logger/)
117//! as the logger and use [`test-log`](https://docs.rs/test-log/latest/test_log/) to enable logging for tests.
118//! For instance you can show all test logs using:
119//!
120//! ```shell
121//! RUST_LOG=debug cargo test integration_success -- --nocapture
122//! ```
123//!
124//! We also enabled the logging for the examples. For instance you can do:
125//!
126//! ```shell
127//! RUST_LOG=debug cargo run --example query _printer._tcp
128//! ```
129//!
130
131#![forbid(unsafe_code)]
132#![allow(clippy::single_component_path_imports)]
133
134// log for logging (optional).
135#[cfg(feature = "logging")]
136use log;
137
138#[cfg(not(feature = "logging"))]
139#[macro_use]
140mod log {
141    macro_rules! trace    ( ($($tt:tt)*) => {{}} );
142    macro_rules! debug    ( ($($tt:tt)*) => {{}} );
143    macro_rules! info     ( ($($tt:tt)*) => {{}} );
144    macro_rules! warn     ( ($($tt:tt)*) => {{}} );
145    macro_rules! error    ( ($($tt:tt)*) => {{}} );
146}
147
148mod dns_cache;
149mod dns_parser;
150mod error;
151mod service_daemon;
152mod service_info;
153
154pub use dns_parser::RRType;
155pub use error::{Error, Result};
156pub use service_daemon::{
157    DaemonEvent, DaemonStatus, DnsNameChange, HostnameResolutionEvent, IfKind, Metrics,
158    ServiceDaemon, ServiceEvent, UnregisterStatus, IP_CHECK_INTERVAL_IN_SECS_DEFAULT,
159    SERVICE_NAME_LEN_MAX_DEFAULT, VERIFY_TIMEOUT_DEFAULT,
160};
161pub use service_info::{
162    AsIpAddrs, IntoTxtProperties, ResolvedService, ServiceInfo, TxtProperties, TxtProperty,
163};
164
165/// A handler to receive messages from [ServiceDaemon]. Re-export from `flume` crate.
166pub use flume::Receiver;