rtc-mdns
A sans-I/O implementation of mDNS (Multicast DNS) for Rust.
This crate provides an mDNS client/server that implements the [sansio::Protocol] trait,
allowing it to be integrated with any I/O framework (tokio, async-std, smol, or synchronous I/O).
What is mDNS?
Multicast DNS (mDNS) is a protocol that allows devices on a local network to discover each other without a central DNS server. It's commonly used for:
- Service discovery (finding printers, media servers, etc.)
- WebRTC ICE candidate gathering (resolving
.localhostnames) - Zero-configuration networking (Bonjour, Avahi)
Sans-I/O Design
This crate follows the sans-I/O pattern, which means:
- No runtime dependency: Works with tokio, async-std, smol, or blocking I/O
- Testable: Protocol logic can be tested without network I/O
- Predictable: No hidden threads, timers, or background tasks
- Composable: Easy to integrate with existing event loops
The caller is responsible for:
- Reading packets from the network and calling
handle_read() - Sending packets returned by
poll_write() - Calling
handle_timeout()whenpoll_timeout()expires - Processing events from
poll_event()
Features
- Query Support: Send mDNS queries and receive answers
- Server Support: Respond to mDNS questions for configured local names
- Automatic Retries: Queries are automatically retried at configurable intervals
- Multiple Queries: Track multiple concurrent queries by ID
Quick Start
Client: Query for a hostname
use ;
use Protocol;
use ;
// Create an mDNS connection
let config = default
.with_query_interval;
let mut conn = new;
// Start a query - returns a unique ID to track this query
let query_id = conn.query;
assert!;
// Get the packet to send (would be sent via UDP to 224.0.0.251:5353)
let packet = conn.poll_write.expect;
assert_eq!;
// When a response arrives, call handle_read() and check for events
// Events will contain QueryAnswered with the resolved address
Server: Respond to queries
use ;
use ;
// MdnsConfigure with local names to respond to
let config = default
.with_local_names
.with_local_ip;
let conn = new;
// When queries for "myhost.local" arrive via handle_read(),
// the connection will automatically queue response packets
// that can be retrieved via poll_write()
Integration with Tokio
Here's a complete example showing how to integrate with tokio:
use BytesMut;
use ;
use Protocol;
use ;
use SocketAddr;
use ;
use UdpSocket;
async
Event Loop Pattern
The typical event loop for using this crate:
loop {
// 1. Send any queued packets
while let Some(packet) = conn.poll_write() {
socket.send_to(&packet.message, packet.transport.peer_addr);
}
// 2. Wait for network activity or timeout
select! {
packet = socket.recv_from() => {
conn.handle_read(packet);
}
_ = sleep_until(conn.poll_timeout()) => {
conn.handle_timeout(Instant::now());
}
}
// 3. Process events
while let Some(event) = conn.poll_event() {
match event {
MdnsEvent::QueryAnswered(id, addr) => { /* handle answer */ }
MdnsEvent::QueryTimeout(id) => { /* handle timeout */ }
}
}
}
Protocol Details
- Multicast Address: 224.0.0.251:5353 (IPv4)
- Record Types: Supports A (IPv4) and AAAA (IPv6) queries
- TTL: Responses use a default TTL of 120 seconds
- Compression: DNS name compression is supported for efficiency