1
//! Different implementations of a common async API for use in arti
2
//!
3
//! Currently only async_std, tokio and smol are provided.
4

            
5
#[cfg(feature = "async-std")]
6
pub(crate) mod async_std;
7

            
8
#[cfg(feature = "tokio")]
9
pub(crate) mod tokio;
10

            
11
#[cfg(feature = "smol")]
12
pub(crate) mod smol;
13

            
14
#[cfg(feature = "rustls")]
15
pub(crate) mod rustls;
16

            
17
#[cfg(feature = "native-tls")]
18
pub(crate) mod native_tls;
19

            
20
pub(crate) mod streamops;
21
pub(crate) mod unimpl_tls;
22

            
23
#[cfg(unix)]
24
use tor_error::warn_report;
25

            
26
/// Connection backlog size to use for `listen()` calls on IP sockets.
27
//
28
// How this was chosen:
29
//
30
// 1. The rust standard library uses a backlog of 128 for TCP sockets. This matches `SOMAXCONN` on
31
//    most systems.
32
//
33
// 2. Mio (backend for tokio) previously used 1024. But they recently (confusingly) copied the logic
34
//    from the standard library's unix socket implementation, which uses different values on
35
//    different platforms. These values were tuned for unix sockets, so I think we should ignore
36
//    them and mio's implementation here.
37
//    https://github.com/tokio-rs/mio/pull/1896
38
//
39
// 3. Tor first tries using `INT_MAX`, and if that fails falls back to `SOMAXCONN` (using a global
40
//    to remember if it did the fallback for future listen() calls; see `tor_listen`).
41
//
42
// 4. On supported platforms, if you use a backlog that is too large, the system will supposedly
43
//    silently cap the value instead of failing.
44
//
45
//     Linux:
46
//     listen(2)
47
//     > If the backlog argument is greater than the value in /proc/sys/net/core/somaxconn, then it
48
//     > is silently capped to that value.
49
//
50
//     FreeBSD:
51
//     listen(2)
52
//     > The sysctl(3) MIB variable kern.ipc.soacceptqueue specifies a hard limit on backlog; if a
53
//     > value greater than kern.ipc.soacceptqueue or less than zero is specified, backlog is
54
//     > silently forced to kern.ipc.soacceptqueue.
55
//
56
//     OpenBSD:
57
//     listen(2)
58
//     > [BUGS] The backlog is currently limited (silently) to the value of the kern.somaxconn
59
//     > sysctl, which defaults to 128.
60
//
61
//     Windows:
62
//     https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-listen
63
//     > The backlog parameter is limited (silently) to a reasonable value as determined by the
64
//     > underlying service provider. Illegal values are replaced by the nearest legal value.
65
//
66
//     Mac OS:
67
//     Archived listen(2) docs
68
//     https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/listen.2.html
69
//     > [BUGS] The backlog is currently limited (silently) to 128.
70
//
71
// 5. While the rust APIs take a `u32`, the libc API uses `int`. So we shouldn't use a value larger
72
//    than `c_int::MAX`.
73
//
74
// 6. We should be careful not to set this too large, as supposedly some systems will truncate this to
75
//    16 bits. So for example a value of `65536` would cause a backlog of 1. But maybe they are just
76
//    referring to systems where `int` is 16 bits?
77
//    https://bugs.python.org/issue38699#msg357957
78
//
79
// Here we use `u16::MAX`. We assume that this will succeed on all supported platforms. Unlike tor,
80
// we do not try again with a smaller value since this doesn't seem to be needed on modern systems.
81
// We can add it if we find that it's needed.
82
//
83
// A value of `u16::MAX` is arguably too high, since a smaller value like 4096 would be large enough
84
// for legitimate traffic, and illegitimate traffic would be better handled by the kernel with
85
// something like SYN cookies. But it's easier for users to reduce the max using
86
// `/proc/sys/net/core/somaxconn` than to increase this max by recompiling arti.
87
const LISTEN_BACKLOG: i32 = u16::MAX as i32;
88

            
89
/// Open a listening TCP socket.
90
///
91
/// The socket will be non-blocking, and the socket handle will be close-on-exec/non-inheritable.
92
/// Other socket options may also be set depending on the socket type and platform.
93
///
94
/// Historically we relied on the runtime to create a listening socket, but we need some specific
95
/// socket options set, and not all runtimes will behave the same. It's better for us to create the
96
/// socket with the options we need and with consistent behaviour across all runtimes. For example
97
/// if each runtime were using a different `listen()` backlog size, it might be difficult to debug
98
/// related issues.
99
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
100
22
pub(crate) fn tcp_listen(addr: &std::net::SocketAddr) -> std::io::Result<std::net::TcpListener> {
101
    use socket2::{Domain, Socket, Type};
102

            
103
    // `socket2::Socket::new()`:
104
    // > This function corresponds to `socket(2)` on Unix and `WSASocketW` on Windows.
105
    // >
106
    // > On Unix-like systems, the close-on-exec flag is set on the new socket. Additionally, on
107
    // > Apple platforms `SOCK_NOSIGPIPE` is set. On Windows, the socket is made non-inheritable.
108
22
    let socket = match addr {
109
22
        std::net::SocketAddr::V4(_) => Socket::new(Domain::IPV4, Type::STREAM, None)?,
110
        std::net::SocketAddr::V6(_) => {
111
            let socket = Socket::new(Domain::IPV6, Type::STREAM, None)?;
112

            
113
            // On `cfg(unix)` systems, set `IPV6_V6ONLY` so that we can bind AF_INET and
114
            // AF_INET6 sockets to the same port.
115
            // This is `cfg(unix)` as I'm not sure what the socket option does (if anything) on
116
            // non-unix platforms.
117
            #[cfg(unix)]
118
            if let Err(e) = socket.set_only_v6(true) {
119
                // If we see this, we should exclude more platforms.
120
                warn_report!(
121
                    e,
122
                    "Failed to set `IPV6_V6ONLY` on `AF_INET6` socket. \
123
                    Please report this bug at https://gitlab.torproject.org/tpo/core/arti/-/issues",
124
                );
125
            }
126

            
127
            socket
128
        }
129
    };
130

            
131
    // Below we try to match what a `tokio::net::TcpListener::bind()` would do. This is a bit tricky
132
    // since tokio documents "Calling TcpListener::bind("127.0.0.1:8080") is equivalent to:" with
133
    // some provided example code, but this logic actually appears to happen in the mio crate, and
134
    // doesn't match exactly with tokio's documentation. So here we acknowledge that we likely do
135
    // deviate from `tokio::net::TcpListener::bind()` a bit.
136

            
137
22
    socket.set_nonblocking(true)?;
138

            
139
    // The docs for `tokio::net::TcpSocket` say:
140
    //
141
    // > // On platforms with Berkeley-derived sockets, this allows to quickly
142
    // > // rebind a socket, without needing to wait for the OS to clean up the
143
    // > // previous one.
144
    // >
145
    // > // On Windows, this allows rebinding sockets which are actively in use,
146
    // > // which allows "socket hijacking", so we explicitly don't set it here.
147
    // > // https://docs.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse
148
    //
149
    // This appears to be a comment that tokio copied from mio.
150
    //
151
    // So here we only set SO_REUSEADDR for `cfg(unix)` to match tokio.
152
    #[cfg(unix)]
153
22
    socket.set_reuse_address(true)?;
154

            
155
22
    socket.bind(&(*addr).into())?;
156

            
157
22
    socket.listen(LISTEN_BACKLOG)?;
158

            
159
22
    Ok(socket.into())
160
22
}
161

            
162
/// Stub replacement for tcp_listen on wasm32-unknown
163
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
164
pub(crate) fn tcp_listen(_addr: &std::net::SocketAddr) -> std::io::Result<std::net::TcpListener> {
165
    Err(std::io::Error::from(std::io::ErrorKind::Unsupported))
166
}
167

            
168
/// Helper: Implement an unreachable NetProvider<unix::SocketAddr> for a given runtime.
169
#[cfg(not(unix))]
170
macro_rules! impl_unix_non_provider {
171
    { $for_type:ty } => {
172

            
173
        #[async_trait]
174
        impl crate::traits::NetStreamProvider<tor_general_addr::unix::SocketAddr> for $for_type {
175
            type Stream = crate::unimpl::FakeStream;
176
            type Listener = crate::unimpl::FakeListener<tor_general_addr::unix::SocketAddr>;
177
            async fn connect(&self, _a: &tor_general_addr::unix::SocketAddr) -> IoResult<Self::Stream> {
178
                Err(tor_general_addr::unix::NoAfUnixSocketSupport::default().into())
179

            
180
            }
181
            async fn listen(&self, _a: &tor_general_addr::unix::SocketAddr) -> IoResult<Self::Listener> {
182
                Err(tor_general_addr::unix::NoAfUnixSocketSupport::default().into())
183
            }
184
        }
185
    }
186
}
187
#[cfg(not(unix))]
188
pub(crate) use impl_unix_non_provider;