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;