tor_rtcompat/impls/
streamops.rs

1//! Helpers for implementing [`StreamOps`].
2
3use std::io;
4
5#[cfg(target_os = "linux")]
6use {
7    std::mem,
8    std::os::fd::{AsRawFd, RawFd},
9};
10
11#[cfg_attr(not(target_os = "linux"), allow(unused))]
12use crate::StreamOps;
13#[cfg(not(target_os = "linux"))]
14use crate::UnsupportedStreamOp;
15
16/// A wrapper over the file descriptor of a TCP socket.
17///
18/// Returned from various [`StreamOps::new_handle`] impls.
19#[derive(Clone, Copy)]
20#[cfg(target_os = "linux")]
21pub(crate) struct TcpSockFd(RawFd);
22
23#[cfg(target_os = "linux")]
24impl AsRawFd for TcpSockFd {
25    fn as_raw_fd(&self) -> RawFd {
26        self.0
27    }
28}
29
30#[cfg(target_os = "linux")]
31impl TcpSockFd {
32    /// Create a `TcpSockFd` out of a raw file descriptor.
33    pub(crate) fn from_fd(fd: &impl AsRawFd) -> Self {
34        Self(fd.as_raw_fd())
35    }
36}
37
38#[cfg(target_os = "linux")]
39impl StreamOps for TcpSockFd {
40    fn set_tcp_notsent_lowat(&self, notsent_lowat: u32) -> io::Result<()> {
41        set_tcp_notsent_lowat(self, notsent_lowat)
42    }
43
44    fn new_handle(&self) -> Box<dyn StreamOps + Send + Unpin> {
45        Box::new(*self)
46    }
47}
48
49/// Helper for implementing [`set_tcp_notsent_lowat`](crate::StreamOps::set_tcp_notsent_lowat).
50///
51/// Only implemented on Linux. Returns an error on all other platforms.
52#[cfg(target_os = "linux")]
53pub(crate) fn set_tcp_notsent_lowat<S: AsRawFd>(sock: &S, notsent_lowat: u32) -> io::Result<()> {
54    let fd = sock.as_raw_fd();
55    let res = unsafe {
56        libc::setsockopt(
57            fd,
58            libc::SOL_TCP,
59            libc::TCP_NOTSENT_LOWAT,
60            &notsent_lowat as *const _ as *const libc::c_void,
61            mem::size_of_val(&notsent_lowat) as libc::socklen_t,
62        )
63    };
64
65    if res != 0 {
66        return Err(io::Error::last_os_error());
67    }
68
69    Ok(())
70}
71
72/// Helper for implementing [`set_tcp_notsent_lowat`](crate::StreamOps::set_tcp_notsent_lowat).
73///
74/// Only implemented on Linux. Returns an error on all other platforms.
75#[cfg(not(target_os = "linux"))]
76pub(crate) fn set_tcp_notsent_lowat<S>(_sock: &S, _notsent_lowat: u32) -> io::Result<()> {
77    Err(UnsupportedStreamOp::new(
78        "set_tcp_notsent_lowat",
79        "unsupported on non-linux platforms",
80    )
81    .into())
82}
83
84#[cfg(test)]
85mod tests {
86    // @@ begin test lint list maintained by maint/add_warning @@
87    #![allow(clippy::bool_assert_comparison)]
88    #![allow(clippy::clone_on_copy)]
89    #![allow(clippy::dbg_macro)]
90    #![allow(clippy::mixed_attributes_style)]
91    #![allow(clippy::print_stderr)]
92    #![allow(clippy::print_stdout)]
93    #![allow(clippy::single_char_pattern)]
94    #![allow(clippy::unwrap_used)]
95    #![allow(clippy::unchecked_duration_subtraction)]
96    #![allow(clippy::useless_vec)]
97    #![allow(clippy::needless_pass_by_value)]
98    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
99    use super::*;
100    use std::net::TcpListener;
101
102    #[cfg(target_os = "linux")]
103    pub(crate) fn get_tcp_notsent_lowat<S: AsRawFd>(sock: &S) -> io::Result<u32> {
104        let fd = sock.as_raw_fd();
105        let mut notsent_lowat = 0;
106        let mut socklen: u32 = mem::size_of_val(&notsent_lowat) as libc::socklen_t;
107        let res = unsafe {
108            libc::getsockopt(
109                fd,
110                libc::SOL_TCP,
111                libc::TCP_NOTSENT_LOWAT,
112                &mut notsent_lowat as *mut _ as *mut libc::c_void,
113                &mut socklen as *mut _,
114            )
115        };
116
117        if res != 0 {
118            return Err(io::Error::last_os_error());
119        }
120
121        Ok(notsent_lowat)
122    }
123
124    #[test]
125    #[cfg(target_os = "linux")]
126    #[cfg_attr(miri, ignore)] // sockets are unsupported https://github.com/rust-lang/miri/issues/3449
127    fn tcp_notsent_lowat() {
128        let sock = TcpListener::bind("127.0.0.1:0").unwrap();
129        set_tcp_notsent_lowat(&sock, 1337).unwrap();
130        let notsent_lowat = get_tcp_notsent_lowat(&sock).unwrap();
131        assert_eq!(1337, notsent_lowat);
132    }
133
134    #[test]
135    #[cfg(not(target_os = "linux"))]
136    #[cfg_attr(miri, ignore)] // sockets are unsupported https://github.com/rust-lang/miri/issues/3449
137    fn tcp_notsent_lowat() {
138        let sock = TcpListener::bind("127.0.0.1:0").unwrap();
139        // Currently not supported on non-linux platforms
140        assert!(set_tcp_notsent_lowat(&sock, 1337).is_err());
141    }
142}