Skip to main content

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_time_subtraction)]
96    #![allow(clippy::useless_vec)]
97    #![allow(clippy::needless_pass_by_value)]
98    #![allow(clippy::string_slice)] // See arti#2571
99    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
100    use super::*;
101    use std::net::TcpListener;
102
103    #[cfg(target_os = "linux")]
104    pub(crate) fn get_tcp_notsent_lowat<S: AsRawFd>(sock: &S) -> io::Result<u32> {
105        let fd = sock.as_raw_fd();
106        let mut notsent_lowat = 0;
107        let mut socklen: u32 = mem::size_of_val(&notsent_lowat) as libc::socklen_t;
108        let res = unsafe {
109            libc::getsockopt(
110                fd,
111                libc::SOL_TCP,
112                libc::TCP_NOTSENT_LOWAT,
113                &mut notsent_lowat as *mut _ as *mut libc::c_void,
114                &mut socklen as *mut _,
115            )
116        };
117
118        if res != 0 {
119            return Err(io::Error::last_os_error());
120        }
121
122        Ok(notsent_lowat)
123    }
124
125    #[test]
126    #[cfg(target_os = "linux")]
127    #[cfg_attr(miri, ignore)] // sockets are unsupported https://github.com/rust-lang/miri/issues/3449
128    fn tcp_notsent_lowat() {
129        let sock = TcpListener::bind("127.0.0.1:0").unwrap();
130        set_tcp_notsent_lowat(&sock, 1337).unwrap();
131        let notsent_lowat = get_tcp_notsent_lowat(&sock).unwrap();
132        assert_eq!(1337, notsent_lowat);
133    }
134
135    #[test]
136    #[cfg(not(target_os = "linux"))]
137    #[cfg_attr(miri, ignore)] // sockets are unsupported https://github.com/rust-lang/miri/issues/3449
138    fn tcp_notsent_lowat() {
139        let sock = TcpListener::bind("127.0.0.1:0").unwrap();
140        // Currently not supported on non-linux platforms
141        assert!(set_tcp_notsent_lowat(&sock, 1337).is_err());
142    }
143}