Skip to main content

nex_socket/tcp/
config.rs

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