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}