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    pub fn with_multicast_local_ipv4(mut self, multicast_local_ipv4: Ipv4Addr) -> Self {
99        self.multicast_local_ipv4 = Some(multicast_local_ipv4);
100        self
101    }
102
103    pub fn with_multicast_local_port(mut self, multicast_local_port: u16) -> Self {
104        self.multicast_local_port = Some(multicast_local_port);
105        self
106    }
107
108    /// Sets a specific network interface for multicast operations.
109    ///
110    /// If not set, the socket joins the multicast group on all interfaces
111    /// (`INADDR_ANY`).
112    ///
113    /// # Arguments
114    ///
115    /// * `interface` - The IPv4 address of the network interface to use.
116    ///
117    /// # Example
118    ///
119    /// ```rust
120    /// use rtc_mdns::MulticastSocket;
121    /// use std::net::Ipv4Addr;
122    ///
123    /// let builder = MulticastSocket::new()
124    ///     .with_interface(Ipv4Addr::new(192, 168, 1, 100));
125    /// ```
126    pub fn with_interface(mut self, interface: Ipv4Addr) -> Self {
127        self.interface = Some(interface);
128        self
129    }
130
131    /// Converts this builder into a configured `std::net::UdpSocket`.
132    ///
133    /// This method creates the socket with the following configuration:
134    /// - `SO_REUSEADDR` enabled (allows multiple processes to bind)
135    /// - `SO_REUSEPORT` enabled on Unix platforms (except Solaris/illumos)
136    /// - Non-blocking mode enabled (for async compatibility)
137    /// - Joined to the mDNS multicast group (224.0.0.251)
138    ///
139    /// # Errors
140    ///
141    /// Returns an error if:
142    /// - Socket creation fails
143    /// - Setting socket options fails
144    /// - Binding to the address fails
145    /// - Joining the multicast group fails
146    ///
147    /// # Example
148    ///
149    /// ```rust,ignore
150    /// use rtc_mdns::MulticastSocket;
151    /// use std::net::SocketAddr;
152    ///
153    /// let bind_addr: SocketAddr = "0.0.0.0:5353".parse().unwrap();
154    /// let std_socket = MulticastSocket::new(bind_addr).into_std()?;
155    ///
156    /// // Use with tokio:
157    /// let socket = tokio::net::UdpSocket::from_std(std_socket)?;
158    /// ```
159    ///
160    /// # Platform Notes
161    ///
162    /// - On Unix-like systems (except Solaris/illumos), `SO_REUSEPORT` is enabled
163    ///   to allow multiple processes to bind to the same port.
164    pub fn into_std(self) -> io::Result<UdpSocket> {
165        let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP))?;
166
167        // Enable address reuse for multiple processes
168        socket.set_reuse_address(true)?;
169
170        // Enable port reuse on supported platforms
171        #[cfg(all(unix, not(target_os = "solaris"), not(target_os = "illumos")))]
172        socket.set_reuse_port(true)?;
173
174        // Set non-blocking mode for async compatibility
175        socket.set_nonblocking(true)?;
176
177        let multicast_local_ip = if let Some(multicast_local_ipv4) = self.multicast_local_ipv4 {
178            IpAddr::V4(multicast_local_ipv4)
179        } else if cfg!(target_os = "linux") {
180            IpAddr::V4(MDNS_MULTICAST_IPV4)
181        } else {
182            // DNS_MULTICAST_IPV4 doesn't work on Mac/Win,
183            // only 0.0.0.0 works fine, even 127.0.0.1 doesn't work
184            IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))
185        };
186
187        let multicast_local_port = if let Some(multicast_local_port) = self.multicast_local_port {
188            multicast_local_port
189        } else {
190            MDNS_PORT
191        };
192
193        let multicast_local_addr = SocketAddr::new(multicast_local_ip, multicast_local_port);
194
195        // Bind to the specified address
196        socket.bind(&multicast_local_addr.into())?;
197
198        // Join the mDNS multicast group
199        let iface = self.interface.unwrap_or(Ipv4Addr::UNSPECIFIED);
200        socket.join_multicast_v4(&MDNS_MULTICAST_IPV4, &iface)?;
201
202        Ok(socket.into())
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209    use crate::proto::MDNS_PORT;
210    use std::str::FromStr;
211
212    #[test]
213    fn test_multicast_constants() {
214        assert_eq!(MDNS_MULTICAST_IPV4, Ipv4Addr::new(224, 0, 0, 251));
215        assert_eq!(MDNS_PORT, 5353);
216    }
217
218    #[test]
219    fn test_multicast_socket_builder() {
220        let builder = MulticastSocket::new()
221            .with_multicast_local_ipv4(Ipv4Addr::from_str("0.0.0.0").unwrap())
222            .with_multicast_local_port(5353);
223        assert!(builder.multicast_local_ipv4.is_some());
224        assert!(builder.multicast_local_port.is_some());
225        assert!(builder.interface.is_none());
226    }
227
228    #[test]
229    fn test_multicast_socket_with_interface() {
230        let interface = Ipv4Addr::new(192, 168, 1, 100);
231        let builder = MulticastSocket::new()
232            .with_multicast_local_ipv4(Ipv4Addr::from_str("0.0.0.0").unwrap())
233            .with_multicast_local_port(5353)
234            .with_interface(interface);
235        assert_eq!(builder.interface, Some(interface));
236    }
237
238    // Note: Socket creation tests would require actual network access
239    // and might conflict with other mDNS services, so we keep them minimal
240}