Skip to main content

rtc_mdns/
socket.rs

1//! Socket utilities for mDNS.
2//!
3//! This module provides [`MulticastSocket`], a builder for creating properly
4//! configured UDP sockets for mDNS communication.
5//!
6//! # Example
7//!
8//! ```rust,ignore
9//! use rtc_mdns::MulticastSocket;
10//! use std::net::SocketAddr;
11//!
12//! let bind_addr: SocketAddr = "0.0.0.0:5353".parse().unwrap();
13//! let std_socket = MulticastSocket::new(bind_addr).into_std()?;
14//!
15//! // For tokio:
16//! let socket = tokio::net::UdpSocket::from_std(std_socket)?;
17//! ```
18
19use std::io;
20use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
21
22use crate::MDNS_PORT;
23use crate::proto::MDNS_MULTICAST_IPV4;
24use socket2::{Domain, Protocol, Socket, Type};
25
26/// A builder for creating multicast UDP sockets suitable for mDNS.
27///
28/// `MulticastSocket` provides a convenient way to create properly configured
29/// UDP sockets for mDNS communication. The resulting socket will be:
30///
31/// - Bound to the specified address (typically `0.0.0.0:5353`)
32/// - Configured with `SO_REUSEADDR` enabled
33/// - Configured with `SO_REUSEPORT` enabled (on supported platforms)
34/// - Set to non-blocking mode for async compatibility
35/// - Joined to the mDNS multicast group (224.0.0.251)
36///
37/// # Examples
38///
39/// Basic usage with tokio:
40///
41/// ```rust,ignore
42/// use rtc_mdns::MulticastSocket;
43/// use std::net::SocketAddr;
44///
45/// let bind_addr: SocketAddr = "0.0.0.0:5353".parse().unwrap();
46/// let std_socket = MulticastSocket::new(bind_addr).into_std()?;
47/// let socket = tokio::net::UdpSocket::from_std(std_socket)?;
48/// ```
49///
50/// With a specific network interface:
51///
52/// ```rust,ignore
53/// use rtc_mdns::MulticastSocket;
54/// use std::net::{Ipv4Addr, SocketAddr};
55///
56/// let bind_addr: SocketAddr = "0.0.0.0:5353".parse().unwrap();
57/// let interface = Ipv4Addr::new(192, 168, 1, 100);
58/// let std_socket = MulticastSocket::new(bind_addr)
59///     .with_interface(interface)
60///     .into_std()?;
61/// ```
62#[derive(Debug, Clone)]
63pub struct MulticastSocket {
64    multicast_local_ipv4: Option<Ipv4Addr>,
65    multicast_local_port: Option<u16>,
66    interface: Option<Ipv4Addr>,
67}
68
69impl Default for MulticastSocket {
70    fn default() -> Self {
71        Self::new()
72    }
73}
74
75impl MulticastSocket {
76    /// Creates a new `MulticastSocket` builder with the specified bind address.
77    ///
78    /// # Arguments
79    ///
80    /// * `bind_addr` - The local address to bind to. Use `0.0.0.0:5353` to listen
81    ///   on all interfaces on the standard mDNS port.
82    ///
83    /// # Example
84    ///
85    /// ```rust
86    /// use rtc_mdns::MulticastSocket;
87    ///
88    /// let builder = MulticastSocket::new();
89    /// ```
90    pub fn new() -> Self {
91        Self {
92            multicast_local_ipv4: None,
93            multicast_local_port: None,
94            interface: None,
95        }
96    }
97
98    /// Sets the local IPv4 address to bind the multicast socket to.
99    ///
100    /// Defaults to unspecified (`0.0.0.0`), letting the OS choose.
101    pub fn with_multicast_local_ipv4(mut self, multicast_local_ipv4: Ipv4Addr) -> Self {
102        self.multicast_local_ipv4 = Some(multicast_local_ipv4);
103        self
104    }
105
106    /// Sets the local port to bind the multicast socket to.
107    ///
108    /// Defaults to the mDNS port (5353), which is required to receive multicast queries
109    /// from other hosts; a different port is only useful for testing.
110    pub fn with_multicast_local_port(mut self, multicast_local_port: u16) -> Self {
111        self.multicast_local_port = Some(multicast_local_port);
112        self
113    }
114
115    /// Sets a specific network interface for multicast operations.
116    ///
117    /// If not set, the socket joins the multicast group on all interfaces
118    /// (`INADDR_ANY`).
119    ///
120    /// # Arguments
121    ///
122    /// * `interface` - The IPv4 address of the network interface to use.
123    ///
124    /// # Example
125    ///
126    /// ```rust
127    /// use rtc_mdns::MulticastSocket;
128    /// use std::net::Ipv4Addr;
129    ///
130    /// let builder = MulticastSocket::new()
131    ///     .with_interface(Ipv4Addr::new(192, 168, 1, 100));
132    /// ```
133    pub fn with_interface(mut self, interface: Ipv4Addr) -> Self {
134        self.interface = Some(interface);
135        self
136    }
137
138    /// Converts this builder into a configured `std::net::UdpSocket`.
139    ///
140    /// This method creates the socket with the following configuration:
141    /// - `SO_REUSEADDR` enabled (allows multiple processes to bind)
142    /// - `SO_REUSEPORT` enabled on Unix platforms (except Solaris/illumos)
143    /// - Non-blocking mode enabled (for async compatibility)
144    /// - Joined to the mDNS multicast group (224.0.0.251)
145    ///
146    /// # Errors
147    ///
148    /// Returns an error if:
149    /// - Socket creation fails
150    /// - Setting socket options fails
151    /// - Binding to the address fails
152    /// - Joining the multicast group fails
153    ///
154    /// # Example
155    ///
156    /// ```rust,ignore
157    /// use rtc_mdns::MulticastSocket;
158    /// use std::net::SocketAddr;
159    ///
160    /// let bind_addr: SocketAddr = "0.0.0.0:5353".parse().unwrap();
161    /// let std_socket = MulticastSocket::new(bind_addr).into_std()?;
162    ///
163    /// // Use with tokio:
164    /// let socket = tokio::net::UdpSocket::from_std(std_socket)?;
165    /// ```
166    ///
167    /// # Platform Notes
168    ///
169    /// - On Unix-like systems (except Solaris/illumos), `SO_REUSEPORT` is enabled
170    ///   to allow multiple processes to bind to the same port.
171    pub fn into_std(self) -> io::Result<UdpSocket> {
172        let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP))?;
173
174        // Enable address reuse for multiple processes
175        socket.set_reuse_address(true)?;
176
177        // Enable port reuse on supported platforms
178        #[cfg(all(unix, not(target_os = "solaris"), not(target_os = "illumos")))]
179        socket.set_reuse_port(true)?;
180
181        // Set non-blocking mode for async compatibility
182        socket.set_nonblocking(true)?;
183
184        let multicast_local_ip = if let Some(multicast_local_ipv4) = self.multicast_local_ipv4 {
185            IpAddr::V4(multicast_local_ipv4)
186        } else if cfg!(target_os = "linux") {
187            IpAddr::V4(MDNS_MULTICAST_IPV4)
188        } else {
189            // DNS_MULTICAST_IPV4 doesn't work on Mac/Win,
190            // only 0.0.0.0 works fine, even 127.0.0.1 doesn't work
191            IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))
192        };
193
194        let multicast_local_port = if let Some(multicast_local_port) = self.multicast_local_port {
195            multicast_local_port
196        } else {
197            MDNS_PORT
198        };
199
200        let multicast_local_addr = SocketAddr::new(multicast_local_ip, multicast_local_port);
201
202        // Bind to the specified address
203        socket.bind(&multicast_local_addr.into())?;
204
205        // Join the mDNS multicast group
206        let iface = self.interface.unwrap_or(Ipv4Addr::UNSPECIFIED);
207        socket.join_multicast_v4(&MDNS_MULTICAST_IPV4, &iface)?;
208
209        Ok(socket.into())
210    }
211}
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216    use crate::proto::MDNS_PORT;
217    use std::str::FromStr;
218
219    #[test]
220    fn test_multicast_constants() {
221        assert_eq!(MDNS_MULTICAST_IPV4, Ipv4Addr::new(224, 0, 0, 251));
222        assert_eq!(MDNS_PORT, 5353);
223    }
224
225    #[test]
226    fn test_multicast_socket_builder() {
227        let builder = MulticastSocket::new()
228            .with_multicast_local_ipv4(Ipv4Addr::from_str("0.0.0.0").unwrap())
229            .with_multicast_local_port(5353);
230        assert!(builder.multicast_local_ipv4.is_some());
231        assert!(builder.multicast_local_port.is_some());
232        assert!(builder.interface.is_none());
233    }
234
235    #[test]
236    fn test_multicast_socket_with_interface() {
237        let interface = Ipv4Addr::new(192, 168, 1, 100);
238        let builder = MulticastSocket::new()
239            .with_multicast_local_ipv4(Ipv4Addr::from_str("0.0.0.0").unwrap())
240            .with_multicast_local_port(5353)
241            .with_interface(interface);
242        assert_eq!(builder.interface, Some(interface));
243    }
244
245    // Note: Socket creation tests would require actual network access
246    // and might conflict with other mDNS services, so we keep them minimal
247}