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}