Skip to main content

rtc_mdns/
lib.rs

1//! # rtc-mdns
2//!
3//! A sans-I/O implementation of mDNS (Multicast DNS) for Rust.
4//!
5//! This crate provides an mDNS client/server that implements the [`sansio::Protocol`] trait,
6//! allowing it to be integrated with any I/O framework (tokio, async-std, smol, or synchronous I/O).
7//!
8//! ## What is mDNS?
9//!
10//! Multicast DNS (mDNS) is a protocol that allows devices on a local network to discover
11//! each other without a central DNS server. It's commonly used for:
12//!
13//! - Service discovery (finding printers, media servers, etc.)
14//! - WebRTC ICE candidate gathering (resolving `.local` hostnames)
15//! - Zero-configuration networking (Bonjour, Avahi)
16//!
17//! ## Sans-I/O Design
18//!
19//! This crate follows the [sans-I/O](https://sans-io.readthedocs.io/) pattern, which means:
20//!
21//! - **No runtime dependency**: Works with tokio, async-std, smol, or blocking I/O
22//! - **Testable**: Protocol logic can be tested without network I/O
23//! - **Predictable**: No hidden threads, timers, or background tasks
24//! - **Composable**: Easy to integrate with existing event loops
25//!
26//! The caller is responsible for:
27//! 1. Reading packets from the network and calling `handle_read()`
28//! 2. Sending packets returned by `poll_write()`
29//! 3. Calling `handle_timeout()` when `poll_timeout()` expires
30//! 4. Processing events from `poll_event()`
31//!
32//! ## Features
33//!
34//! - **Query Support**: Send mDNS queries and receive answers
35//! - **Server Support**: Respond to mDNS questions for configured local names
36//! - **Automatic Retries**: Queries are automatically retried at configurable intervals
37//! - **Multiple Queries**: Track multiple concurrent queries by ID
38//!
39//! ## Quick Start
40//!
41//! ### Client: Query for a hostname
42//!
43//! ```rust
44//! use rtc_mdns::{MdnsConfig, Mdns, MdnsEvent};
45//! use sansio::Protocol;
46//! use std::time::{Duration, Instant};
47//!
48//! // Create an mDNS connection
49//! let config = MdnsConfig::default()
50//!     .with_query_interval(Duration::from_secs(1));
51//! let mut conn = Mdns::new(config);
52//!
53//! // Start a query - returns a unique ID to track this query
54//! let query_id = conn.query("mydevice.local");
55//! assert!(conn.is_query_pending(query_id));
56//!
57//! // Get the packet to send (would be sent via UDP to 224.0.0.251:5353)
58//! let packet = conn.poll_write().expect("should have a query packet");
59//! assert_eq!(packet.transport.peer_addr.to_string(), "224.0.0.251:5353");
60//!
61//! // When a response arrives, call handle_read() and check for events
62//! // Events will contain QueryAnswered with the resolved address
63//! ```
64//!
65//! ### Server: Respond to queries
66//!
67//! ```rust
68//! use rtc_mdns::{MdnsConfig, Mdns};
69//! use std::net::{IpAddr, Ipv4Addr};
70//!
71//! // MdnsConfigure with local names to respond to
72//! let config = MdnsConfig::default()
73//!     .with_local_names(vec!["myhost.local".to_string()])
74//!     .with_local_ip(
75//!         IpAddr::V4(Ipv4Addr::new(192, 168, 1, 100)),
76//!     );
77//!
78//! let conn = Mdns::new(config);
79//!
80//! // When queries for "myhost.local" arrive via handle_read(),
81//! // the connection will automatically queue response packets
82//! // that can be retrieved via poll_write()
83//! ```
84//!
85//! ## Integration with Tokio
86//!
87//! Here's a complete example showing how to integrate with tokio:
88//!
89//! ```rust,ignore
90//! use bytes::BytesMut;
91//! use rtc_mdns::{MdnsConfig, Mdns, MdnsEvent, MDNS_DEST_ADDR};
92//! use sansio::Protocol;
93//! use shared::{TaggedBytesMut, TransportContext, TransportProtocol};
94//! use std::net::SocketAddr;
95//! use std::time::{Duration, Instant};
96//! use tokio::net::UdpSocket;
97//!
98//! async fn run_mdns_query(name: &str) -> Option<SocketAddr> {
99//!     let bind_addr: SocketAddr = "0.0.0.0:5353".parse().unwrap();
100//!     let socket = UdpSocket::bind(bind_addr).await.unwrap();
101//!
102//!     let mut conn = Mdns::new(MdnsConfig::default());
103//!     let query_id = conn.query(name);
104//!
105//!     let timeout = Instant::now() + Duration::from_secs(5);
106//!     let mut buf = vec![0u8; 1500];
107//!
108//!     loop {
109//!         // Send queued packets
110//!         while let Some(pkt) = conn.poll_write() {
111//!             socket.send_to(&pkt.message, pkt.transport.peer_addr).await.ok();
112//!         }
113//!
114//!         if Instant::now() >= timeout {
115//!             return None; // Query timed out
116//!         }
117//!
118//!         // Wait for packets or timeout
119//!         tokio::select! {
120//!             Ok((len, src)) = socket.recv_from(&mut buf) => {
121//!                 let msg = TaggedBytesMut {
122//!                     now: Instant::now(),
123//!                     transport: TransportContext {
124//!                         local_addr: bind_addr,
125//!                         peer_addr: src,
126//!                         transport_protocol: TransportProtocol::UDP,
127//!                         ecn: None,
128//!                     },
129//!                     message: BytesMut::from(&buf[..len]),
130//!                 };
131//!                 conn.handle_read(msg).ok();
132//!             }
133//!             _ = tokio::time::sleep(Duration::from_millis(100)) => {
134//!                 conn.handle_timeout(Instant::now()).ok();
135//!             }
136//!         }
137//!
138//!         // Check for answers
139//!         while let Some(event) = conn.poll_event() {
140//!             if let MdnsEvent::QueryAnswered(id, addr) = event {
141//!                 if id == query_id {
142//!                     return Some(addr);
143//!                 }
144//!             }
145//!         }
146//!     }
147//! }
148//! ```
149//!
150//! ## Event Loop Pattern
151//!
152//! The typical event loop for using this crate:
153//!
154//! ```text
155//! loop {
156//!     // 1. Send any queued packets
157//!     while let Some(packet) = conn.poll_write() {
158//!         socket.send_to(&packet.message, packet.transport.peer_addr);
159//!     }
160//!
161//!     // 2. Wait for network activity or timeout
162//!     select! {
163//!         packet = socket.recv_from() => {
164//!             conn.handle_read(packet);
165//!         }
166//!         _ = sleep_until(conn.poll_timeout()) => {
167//!             conn.handle_timeout(Instant::now());
168//!         }
169//!     }
170//!
171//!     // 3. Process events
172//!     while let Some(event) = conn.poll_event() {
173//!         match event {
174//!             MdnsEvent::QueryAnswered(id, addr) => { /* handle answer */ }
175//!             MdnsEvent::QueryTimeout(id) => { /* handle timeout */ }
176//!         }
177//!     }
178//! }
179//! ```
180//!
181//! ## Protocol Details
182//!
183//! - **Multicast Address**: 224.0.0.251:5353 (IPv4)
184//! - **Record Types**: Supports A (IPv4) and AAAA (IPv6) queries
185//! - **TTL**: Responses use a default TTL of 120 seconds
186//! - **Compression**: DNS name compression is supported for efficiency
187
188#![warn(rust_2018_idioms)]
189#![allow(dead_code)]
190
191pub(crate) mod config;
192pub(crate) mod message;
193pub(crate) mod proto;
194pub(crate) mod socket;
195
196pub use config::MdnsConfig;
197pub use proto::{MDNS_DEST_ADDR, MDNS_MULTICAST_IPV4, MDNS_PORT, Mdns, MdnsEvent, QueryId};
198
199// Re-export socket utilities for convenience
200pub use shared::ifaces;
201pub use socket::MulticastSocket;