Skip to main content

nuclei/net/
tcp_options.rs

1//! TCP's own options: keepalive timing ([`TcpKeepalive`]) and the user timeout on a socket and
2//! on a stream, the options a server sets before it listens (the CPU incoming connections are
3//! steered to, fast open, deferred accepts), and quick acks on a stream.
4//!
5//! Each is one system call on the descriptor, made where it is asked. An option the system does
6//! not have answers `Unsupported`, and changes nothing.
7
8use std::io;
9use std::net::TcpStream;
10use std::time::Duration;
11
12use super::TcpSocket;
13use super::opt::{self, IPPROTO_TCP, Int, SO_KEEPALIVE, SOL_SOCKET};
14use crate::io::raw::Raw;
15use crate::io::{Handle, IoObject};
16
17/// When an idle connection's keepalive probes start, how far apart they go, and how many
18/// unanswered ones drop the connection. A field left `None` keeps the system's value.
19///
20/// Times are whole seconds, as the system takes them: a part of one counts as one more.
21///
22/// ```
23/// use std::time::Duration;
24/// use nuclei::net::{TcpKeepalive, TcpSocket};
25///
26/// let socket = TcpSocket::new_v4()?;
27/// socket.set_keepalive_timing(TcpKeepalive {
28///     time: Some(Duration::from_secs(60)),
29///     interval: Some(Duration::from_secs(10)),
30///     retries: Some(5),
31/// })?;
32/// assert!(socket.keepalive()?);
33/// # std::io::Result::Ok(())
34/// ```
35#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
36pub struct TcpKeepalive {
37    /// How long the connection is idle before the first probe.
38    pub time: Option<Duration>,
39    /// How long after an unanswered probe the next one goes.
40    pub interval: Option<Duration>,
41    /// How many unanswered probes drop the connection.
42    pub retries: Option<u32>,
43}
44
45/// The options of the keepalive's idle time, interval and count, where the system has them.
46#[cfg(any(
47    target_os = "linux",
48    target_os = "android",
49    target_os = "freebsd",
50    target_os = "netbsd"
51))]
52const KEEPALIVE: Option<[Int; 3]> =
53    Some([libc::TCP_KEEPIDLE, libc::TCP_KEEPINTVL, libc::TCP_KEEPCNT]);
54#[cfg(target_vendor = "apple")]
55const KEEPALIVE: Option<[Int; 3]> =
56    Some([libc::TCP_KEEPALIVE, libc::TCP_KEEPINTVL, libc::TCP_KEEPCNT]);
57#[cfg(windows)]
58const KEEPALIVE: Option<[Int; 3]> = {
59    use crate::syscore::windows as win;
60    Some([win::TCP_KEEPALIVE, win::TCP_KEEPINTVL, win::TCP_KEEPCNT])
61};
62#[cfg(not(any(
63    target_os = "linux",
64    target_os = "android",
65    target_os = "freebsd",
66    target_os = "netbsd",
67    target_vendor = "apple",
68    windows
69)))]
70const KEEPALIVE: Option<[Int; 3]> = None;
71
72/// `SO_INCOMING_CPU`, which libc does not name: 49 on every Linux but SPARC's.
73#[cfg(all(
74    any(target_os = "linux", target_os = "android"),
75    not(any(target_arch = "sparc", target_arch = "sparc64"))
76))]
77const SO_INCOMING_CPU: Int = 49;
78#[cfg(all(
79    any(target_os = "linux", target_os = "android"),
80    any(target_arch = "sparc", target_arch = "sparc64")
81))]
82const SO_INCOMING_CPU: Int = 0x33;
83
84const SECOND: Duration = Duration::from_secs(1);
85
86/// `time` in whole seconds, as the option of `what` takes it.
87fn secs(time: Duration, what: &str) -> io::Result<Int> {
88    opt::int(opt::whole(time, SECOND), what)
89}
90
91/// Turns keepalive probes on, timed as `keepalive` says.
92fn set_keepalive_timing(raw: Raw, keepalive: TcpKeepalive) -> io::Result<()> {
93    let [idle, interval, count] =
94        KEEPALIVE.ok_or_else(|| opt::unsupported("TCP keepalive timing"))?;
95    if let Some(time) = keepalive.time {
96        opt::set(raw, IPPROTO_TCP, idle, secs(time, "keepalive time")?)?;
97    }
98    if let Some(time) = keepalive.interval {
99        opt::set(
100            raw,
101            IPPROTO_TCP,
102            interval,
103            secs(time, "keepalive interval")?,
104        )?;
105    }
106    if let Some(retries) = keepalive.retries {
107        opt::set_count(raw, IPPROTO_TCP, count, retries, "keepalive retry count")?;
108    }
109    opt::set_flag(raw, SOL_SOCKET, SO_KEEPALIVE, true)
110}
111
112/// The keepalive's timing, every field read from the system.
113fn keepalive_timing(raw: Raw) -> io::Result<TcpKeepalive> {
114    let [idle, interval, count] =
115        KEEPALIVE.ok_or_else(|| opt::unsupported("TCP keepalive timing"))?;
116    let secs = |name| opt::count(raw, IPPROTO_TCP, name).map(|s| Duration::from_secs(s.into()));
117    Ok(TcpKeepalive {
118        time: Some(secs(idle)?),
119        interval: Some(secs(interval)?),
120        retries: Some(opt::count(raw, IPPROTO_TCP, count)?),
121    })
122}
123
124/// Sets `TCP_USER_TIMEOUT` to `timeout`, in whole milliseconds; `None` or zero leaves it to the
125/// system.
126fn set_user_timeout(raw: Raw, timeout: Option<Duration>) -> io::Result<()> {
127    #[cfg(any(target_os = "linux", target_os = "android"))]
128    {
129        let ms = timeout.map_or(0, |t| opt::whole(t, Duration::from_millis(1)));
130        opt::set(
131            raw,
132            IPPROTO_TCP,
133            libc::TCP_USER_TIMEOUT,
134            opt::int(ms, "user timeout")?,
135        )
136    }
137    #[cfg(not(any(target_os = "linux", target_os = "android")))]
138    {
139        let _ = (raw, timeout);
140        Err(opt::unsupported("TCP_USER_TIMEOUT"))
141    }
142}
143
144/// `TCP_USER_TIMEOUT`, `None` where the system's own applies.
145fn user_timeout(raw: Raw) -> io::Result<Option<Duration>> {
146    #[cfg(any(target_os = "linux", target_os = "android"))]
147    {
148        let ms = opt::count(raw, IPPROTO_TCP, libc::TCP_USER_TIMEOUT)?;
149        Ok((ms > 0).then(|| Duration::from_millis(ms.into())))
150    }
151    #[cfg(not(any(target_os = "linux", target_os = "android")))]
152    {
153        let _ = raw;
154        Err(opt::unsupported("TCP_USER_TIMEOUT"))
155    }
156}
157
158/// The calls a socket to set up and a connected stream share.
159macro_rules! tcp_options {
160    ($($ty:ty => $raw:ident),* $(,)?) => {$(
161        impl $ty {
162            /// Turns keepalive probes on (`SO_KEEPALIVE`), timed as `keepalive` says
163            /// (`TCP_KEEPIDLE`, `TCP_KEEPINTVL`, `TCP_KEEPCNT`; macOS calls the first
164            /// `TCP_KEEPALIVE`).
165            ///
166            /// # Errors
167            ///
168            /// `Unsupported` where the system has no such options, `InvalidInput` for a value
169            /// past what it takes, and what `setsockopt(2)` answers (a zero time or count).
170            pub fn set_keepalive_timing(&self, keepalive: TcpKeepalive) -> io::Result<()> {
171                set_keepalive_timing(self.$raw(), keepalive)
172            }
173
174            /// The keepalive's timing: the system's values where none was set.
175            ///
176            /// # Errors
177            ///
178            /// `Unsupported` where the system has no such options.
179            pub fn keepalive_timing(&self) -> io::Result<TcpKeepalive> {
180                keepalive_timing(self.$raw())
181            }
182
183            /// Sets `TCP_USER_TIMEOUT` (Linux): how long sent bytes may go unacknowledged before
184            /// the connection is dropped, in whole milliseconds; `None` or zero leaves it to the
185            /// system.
186            ///
187            /// # Errors
188            ///
189            /// `Unsupported` off Linux, `InvalidInput` for a time past what the system takes.
190            pub fn set_user_timeout(&self, timeout: Option<Duration>) -> io::Result<()> {
191                set_user_timeout(self.$raw(), timeout)
192            }
193
194            /// `TCP_USER_TIMEOUT` (Linux), `None` where the system's own applies.
195            ///
196            /// # Errors
197            ///
198            /// `Unsupported` off Linux.
199            pub fn user_timeout(&self) -> io::Result<Option<Duration>> {
200                user_timeout(self.$raw())
201            }
202        }
203    )*};
204}
205
206tcp_options!(TcpSocket => raw, Handle<TcpStream> => raw_io);
207
208impl TcpSocket {
209    /// Sets `SO_INCOMING_CPU` (Linux): the CPU whose connections this listener takes among
210    /// listeners sharing a port with `SO_REUSEPORT`, so each core accepts the connections its
211    /// own queues received.
212    ///
213    /// # Errors
214    ///
215    /// `Unsupported` off Linux, `InvalidInput` for a CPU past what the system takes.
216    pub fn set_incoming_cpu(&self, cpu: u32) -> io::Result<()> {
217        #[cfg(any(target_os = "linux", target_os = "android"))]
218        return opt::set_count(self.raw(), SOL_SOCKET, SO_INCOMING_CPU, cpu, "CPU");
219        #[cfg(not(any(target_os = "linux", target_os = "android")))]
220        {
221            let _ = cpu;
222            Err(opt::unsupported("SO_INCOMING_CPU"))
223        }
224    }
225
226    /// `SO_INCOMING_CPU` (Linux): the CPU set, or the last to handle the socket's packets;
227    /// `None` before either.
228    ///
229    /// # Errors
230    ///
231    /// `Unsupported` off Linux.
232    pub fn incoming_cpu(&self) -> io::Result<Option<u32>> {
233        #[cfg(any(target_os = "linux", target_os = "android"))]
234        return opt::get::<Int>(self.raw(), SOL_SOCKET, SO_INCOMING_CPU)
235            .map(|cpu| u32::try_from(cpu).ok());
236        #[cfg(not(any(target_os = "linux", target_os = "android")))]
237        Err(opt::unsupported("SO_INCOMING_CPU"))
238    }
239
240    /// Sets `TCP_FASTOPEN` on a socket that will listen: connections may bring data with their
241    /// handshake. Linux keeps up to `queue` such handshakes waiting; macOS, FreeBSD and Windows
242    /// take it as on (above zero) or off.
243    ///
244    /// # Errors
245    ///
246    /// `Unsupported` where the system has no fast open, `InvalidInput` for a queue past what
247    /// it takes, and what `setsockopt(2)` answers where the system turned fast open off.
248    pub fn set_fastopen(&self, queue: u32) -> io::Result<()> {
249        #[cfg(any(target_os = "linux", target_os = "android"))]
250        return opt::set_count(
251            self.raw(),
252            IPPROTO_TCP,
253            libc::TCP_FASTOPEN,
254            queue,
255            "fast open queue",
256        );
257        #[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
258        return opt::set_flag(self.raw(), IPPROTO_TCP, libc::TCP_FASTOPEN, queue > 0);
259        #[cfg(windows)]
260        return opt::set_flag(
261            self.raw(),
262            IPPROTO_TCP,
263            crate::syscore::windows::TCP_FASTOPEN,
264            queue > 0,
265        );
266        #[cfg(not(any(
267            target_os = "linux",
268            target_os = "android",
269            target_vendor = "apple",
270            target_os = "freebsd",
271            windows
272        )))]
273        {
274            let _ = queue;
275            Err(opt::unsupported("TCP_FASTOPEN"))
276        }
277    }
278
279    /// `TCP_FASTOPEN`: the queue's length on Linux, and 1 or 0 where the system takes it as on or
280    /// off.
281    ///
282    /// # Errors
283    ///
284    /// `Unsupported` where the system has no fast open.
285    pub fn fastopen(&self) -> io::Result<u32> {
286        #[cfg(any(target_os = "linux", target_os = "android"))]
287        return opt::count(self.raw(), IPPROTO_TCP, libc::TCP_FASTOPEN);
288        #[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
289        return opt::flag(self.raw(), IPPROTO_TCP, libc::TCP_FASTOPEN).map(u32::from);
290        #[cfg(windows)]
291        return opt::flag(
292            self.raw(),
293            IPPROTO_TCP,
294            crate::syscore::windows::TCP_FASTOPEN,
295        )
296        .map(u32::from);
297        #[cfg(not(any(
298            target_os = "linux",
299            target_os = "android",
300            target_vendor = "apple",
301            target_os = "freebsd",
302            windows
303        )))]
304        Err(opt::unsupported("TCP_FASTOPEN"))
305    }
306
307    /// Sets `TCP_DEFER_ACCEPT` (Linux) on a socket that will listen: a connection is handed to
308    /// `accept` only once its first data came, or `wait` passed (whole seconds, which the
309    /// kernel rounds up to its retransmission steps); `None` hands each over at once.
310    ///
311    /// # Errors
312    ///
313    /// `Unsupported` off Linux, `InvalidInput` for a time past what the system takes.
314    pub fn set_defer_accept(&self, wait: Option<Duration>) -> io::Result<()> {
315        #[cfg(any(target_os = "linux", target_os = "android"))]
316        {
317            let secs = match wait {
318                Some(wait) => secs(wait, "deferred accept's wait")?,
319                None => 0,
320            };
321            opt::set(self.raw(), IPPROTO_TCP, libc::TCP_DEFER_ACCEPT, secs)
322        }
323        #[cfg(not(any(target_os = "linux", target_os = "android")))]
324        {
325            let _ = wait;
326            Err(opt::unsupported("TCP_DEFER_ACCEPT"))
327        }
328    }
329
330    /// `TCP_DEFER_ACCEPT` (Linux): how long a connection with no data waits, as the kernel
331    /// rounded it; `None` if connections are handed over at once.
332    ///
333    /// # Errors
334    ///
335    /// `Unsupported` off Linux.
336    pub fn defer_accept(&self) -> io::Result<Option<Duration>> {
337        #[cfg(any(target_os = "linux", target_os = "android"))]
338        {
339            let secs = opt::count(self.raw(), IPPROTO_TCP, libc::TCP_DEFER_ACCEPT)?;
340            Ok((secs > 0).then(|| Duration::from_secs(secs.into())))
341        }
342        #[cfg(not(any(target_os = "linux", target_os = "android")))]
343        Err(opt::unsupported("TCP_DEFER_ACCEPT"))
344    }
345}
346
347impl Handle<TcpStream> {
348    /// Sets `TCP_QUICKACK` (Linux): with `true` the stream acknowledges at once rather than
349    /// waiting to send it with data. The kernel turns it off again as it sees fit, so it is set
350    /// again where it matters.
351    ///
352    /// # Errors
353    ///
354    /// `Unsupported` off Linux.
355    pub fn set_quickack(&self, quickack: bool) -> io::Result<()> {
356        #[cfg(any(target_os = "linux", target_os = "android"))]
357        return opt::set_flag(self.raw_io(), IPPROTO_TCP, libc::TCP_QUICKACK, quickack);
358        #[cfg(not(any(target_os = "linux", target_os = "android")))]
359        {
360            let _ = quickack;
361            Err(opt::unsupported("TCP_QUICKACK"))
362        }
363    }
364
365    /// `TCP_QUICKACK` (Linux): whether the stream acknowledges at once now.
366    ///
367    /// # Errors
368    ///
369    /// `Unsupported` off Linux.
370    pub fn quickack(&self) -> io::Result<bool> {
371        #[cfg(any(target_os = "linux", target_os = "android"))]
372        return opt::flag(self.raw_io(), IPPROTO_TCP, libc::TCP_QUICKACK);
373        #[cfg(not(any(target_os = "linux", target_os = "android")))]
374        Err(opt::unsupported("TCP_QUICKACK"))
375    }
376}