Skip to main content

nex_socket/udp/
config.rs

1use std::{io, net::SocketAddr, time::Duration};
2
3use socket2::Type as SockType;
4
5use crate::SocketFamily;
6
7/// UDP socket type, either DGRAM or RAW.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9#[non_exhaustive]
10pub enum UdpSocketType {
11    Dgram,
12    Raw,
13}
14
15impl UdpSocketType {
16    /// Returns true if the socket type is DGRAM.
17    pub fn is_dgram(&self) -> bool {
18        matches!(self, UdpSocketType::Dgram)
19    }
20
21    /// Returns true if the socket type is RAW.
22    pub fn is_raw(&self) -> bool {
23        matches!(self, UdpSocketType::Raw)
24    }
25
26    /// Converts the UDP socket type to a `socket2::Type`.
27    pub(crate) fn to_sock_type(self) -> SockType {
28        match self {
29            UdpSocketType::Dgram => SockType::DGRAM,
30            UdpSocketType::Raw => SockType::RAW,
31        }
32    }
33}
34
35/// Configuration options for a UDP socket.
36#[derive(Debug, Clone)]
37#[non_exhaustive]
38pub struct UdpConfig {
39    /// The socket family.
40    pub socket_family: SocketFamily,
41    /// The socket type (DGRAM or RAW).
42    pub socket_type: UdpSocketType,
43    /// Address to bind. If `None`, the operating system chooses the address.
44    pub bind_addr: Option<SocketAddr>,
45    /// Enable address reuse (`SO_REUSEADDR`).
46    pub reuseaddr: Option<bool>,
47    /// Whether to allow port reuse (`SO_REUSEPORT`) where supported.
48    pub reuseport: Option<bool>,
49    /// Allow broadcast (`SO_BROADCAST`).
50    pub broadcast: Option<bool>,
51    /// Time to live value.
52    pub ttl: Option<u32>,
53    /// Hop limit value.
54    pub hoplimit: Option<u32>,
55    /// Read timeout for the socket.
56    pub read_timeout: Option<Duration>,
57    /// Write timeout for the socket.
58    pub write_timeout: Option<Duration>,
59    /// Optional receive buffer size in bytes.
60    pub recv_buffer_size: Option<usize>,
61    /// Optional send buffer size in bytes.
62    pub send_buffer_size: Option<usize>,
63    /// Optional IPv4 TOS / DSCP field value.
64    pub tos: Option<u32>,
65    /// Optional IPv6 traffic class value (`IPV6_TCLASS`) where supported.
66    pub tclass_v6: Option<u32>,
67    /// Enable receiving packet info ancillary data (`IP_PKTINFO` / `IPV6_RECVPKTINFO`) where supported.
68    pub recv_pktinfo: Option<bool>,
69    /// Whether to force IPv6-only behavior on dual-stack sockets.
70    pub only_v6: Option<bool>,
71    /// Bind to a specific interface (Linux only).
72    pub bind_device: Option<String>,
73}
74
75impl Default for UdpConfig {
76    fn default() -> Self {
77        Self {
78            socket_family: SocketFamily::IPV4,
79            socket_type: UdpSocketType::Dgram,
80            bind_addr: None,
81            reuseaddr: None,
82            reuseport: None,
83            broadcast: None,
84            ttl: None,
85            hoplimit: None,
86            read_timeout: None,
87            write_timeout: None,
88            recv_buffer_size: None,
89            send_buffer_size: None,
90            tos: None,
91            tclass_v6: None,
92            recv_pktinfo: None,
93            only_v6: None,
94            bind_device: None,
95        }
96    }
97}
98
99impl UdpConfig {
100    /// Create a new UDP configuration with default values.
101    pub fn new() -> Self {
102        Self::default()
103    }
104
105    /// Create a new UDP configuration for a specific socket family.
106    pub fn new_with_family(socket_family: SocketFamily) -> Self {
107        Self {
108            socket_family,
109            ..Self::default()
110        }
111    }
112
113    /// Set the socket family.
114    pub fn with_socket_family(mut self, socket_family: SocketFamily) -> Self {
115        self.socket_family = socket_family;
116        self
117    }
118
119    /// Set the bind address.
120    pub fn with_bind_addr(mut self, addr: SocketAddr) -> Self {
121        self.bind_addr = Some(addr);
122        self
123    }
124
125    /// Set the bind address.
126    pub fn with_bind(self, addr: SocketAddr) -> Self {
127        self.with_bind_addr(addr)
128    }
129
130    /// Enable address reuse.
131    pub fn with_reuseaddr(mut self, on: bool) -> Self {
132        self.reuseaddr = Some(on);
133        self
134    }
135
136    /// Enable port reuse.
137    pub fn with_reuseport(mut self, on: bool) -> Self {
138        self.reuseport = Some(on);
139        self
140    }
141
142    /// Allow broadcast.
143    pub fn with_broadcast(mut self, on: bool) -> Self {
144        self.broadcast = Some(on);
145        self
146    }
147
148    /// Set the time to live value.
149    pub fn with_ttl(mut self, ttl: u32) -> Self {
150        self.ttl = Some(ttl);
151        self
152    }
153
154    /// Set the hop limit value.
155    pub fn with_hoplimit(mut self, hops: u32) -> Self {
156        self.hoplimit = Some(hops);
157        self
158    }
159
160    /// Set the hop limit value.
161    pub fn with_hop_limit(self, hops: u32) -> Self {
162        self.with_hoplimit(hops)
163    }
164
165    /// Set the read timeout.
166    pub fn with_read_timeout(mut self, timeout: Duration) -> Self {
167        self.read_timeout = Some(timeout);
168        self
169    }
170
171    /// Set the write timeout.
172    pub fn with_write_timeout(mut self, timeout: Duration) -> Self {
173        self.write_timeout = Some(timeout);
174        self
175    }
176
177    /// Set the receive buffer size.
178    pub fn with_recv_buffer_size(mut self, size: usize) -> Self {
179        self.recv_buffer_size = Some(size);
180        self
181    }
182
183    /// Set the send buffer size.
184    pub fn with_send_buffer_size(mut self, size: usize) -> Self {
185        self.send_buffer_size = Some(size);
186        self
187    }
188
189    /// Set the IPv4 TOS / DSCP field value.
190    pub fn with_tos(mut self, tos: u32) -> Self {
191        self.tos = Some(tos);
192        self
193    }
194
195    /// Set the IPv6 traffic class value.
196    pub fn with_tclass_v6(mut self, tclass: u32) -> Self {
197        self.tclass_v6 = Some(tclass);
198        self
199    }
200
201    /// Enable packet-info ancillary data receiving.
202    pub fn with_recv_pktinfo(mut self, on: bool) -> Self {
203        self.recv_pktinfo = Some(on);
204        self
205    }
206
207    /// Set whether the socket is IPv6 only.
208    pub fn with_only_v6(mut self, only_v6: bool) -> Self {
209        self.only_v6 = Some(only_v6);
210        self
211    }
212
213    /// Bind to a specific interface (Linux only).
214    pub fn with_bind_device(mut self, iface: impl Into<String>) -> Self {
215        self.bind_device = Some(iface.into());
216        self
217    }
218
219    /// Validate the configuration before socket creation.
220    pub fn validate(&self) -> io::Result<()> {
221        if let Some(addr) = self.bind_addr {
222            let addr_family = crate::SocketFamily::from_socket_addr(&addr);
223            if addr_family != self.socket_family {
224                return Err(io::Error::new(
225                    io::ErrorKind::InvalidInput,
226                    "bind_addr family does not match socket_family",
227                ));
228            }
229        }
230
231        if self.socket_family.is_v4() {
232            if self.hoplimit.is_some() {
233                return Err(io::Error::new(
234                    io::ErrorKind::InvalidInput,
235                    "hoplimit is only supported for IPv6 UDP sockets",
236                ));
237            }
238            if self.tclass_v6.is_some() {
239                return Err(io::Error::new(
240                    io::ErrorKind::InvalidInput,
241                    "tclass_v6 is only supported for IPv6 UDP sockets",
242                ));
243            }
244            if self.only_v6.is_some() {
245                return Err(io::Error::new(
246                    io::ErrorKind::InvalidInput,
247                    "only_v6 is only supported for IPv6 UDP sockets",
248                ));
249            }
250        }
251
252        if self.socket_family.is_v6() {
253            if self.ttl.is_some() {
254                return Err(io::Error::new(
255                    io::ErrorKind::InvalidInput,
256                    "ttl is only supported for IPv4 UDP sockets",
257                ));
258            }
259            if self.broadcast.is_some() {
260                return Err(io::Error::new(
261                    io::ErrorKind::InvalidInput,
262                    "broadcast is only supported for IPv4 UDP sockets",
263                ));
264            }
265        }
266
267        if matches!(self.read_timeout, Some(timeout) if timeout.is_zero()) {
268            return Err(io::Error::new(
269                io::ErrorKind::InvalidInput,
270                "read_timeout must be greater than zero",
271            ));
272        }
273
274        if matches!(self.write_timeout, Some(timeout) if timeout.is_zero()) {
275            return Err(io::Error::new(
276                io::ErrorKind::InvalidInput,
277                "write_timeout must be greater than zero",
278            ));
279        }
280
281        if matches!(self.recv_buffer_size, Some(0)) {
282            return Err(io::Error::new(
283                io::ErrorKind::InvalidInput,
284                "recv_buffer_size must be greater than zero",
285            ));
286        }
287
288        if matches!(self.send_buffer_size, Some(0)) {
289            return Err(io::Error::new(
290                io::ErrorKind::InvalidInput,
291                "send_buffer_size must be greater than zero",
292            ));
293        }
294
295        if matches!(self.bind_device.as_deref(), Some("")) {
296            return Err(io::Error::new(
297                io::ErrorKind::InvalidInput,
298                "bind_device must not be empty",
299            ));
300        }
301
302        Ok(())
303    }
304}
305
306#[cfg(test)]
307mod tests {
308    use super::*;
309
310    #[test]
311    fn udp_config_default_values() {
312        let cfg = UdpConfig::default();
313        assert!(cfg.bind_addr.is_none());
314        assert!(cfg.reuseaddr.is_none());
315        assert!(cfg.reuseport.is_none());
316        assert!(cfg.broadcast.is_none());
317        assert!(cfg.ttl.is_none());
318        assert!(cfg.recv_buffer_size.is_none());
319        assert!(cfg.send_buffer_size.is_none());
320        assert!(cfg.tos.is_none());
321        assert!(cfg.tclass_v6.is_none());
322        assert!(cfg.recv_pktinfo.is_none());
323        assert!(cfg.only_v6.is_none());
324        assert!(cfg.bind_device.is_none());
325    }
326
327    #[test]
328    fn udp_config_with_family_builder() {
329        let cfg =
330            UdpConfig::new_with_family(SocketFamily::IPV6).with_bind("[::1]:0".parse().unwrap());
331        assert_eq!(cfg.socket_family, SocketFamily::IPV6);
332        assert!(cfg.bind_addr.is_some());
333    }
334
335    #[test]
336    fn udp_config_validate_rejects_ipv6_broadcast() {
337        let cfg = UdpConfig::new_with_family(SocketFamily::IPV6).with_broadcast(true);
338        assert!(cfg.validate().is_err());
339    }
340}