proxy: Mark ENFILES and EMFILES as survivable.

I don't love this approach, but those errors aren't distinguished by
ErrorKind, so we have to use libc or winapi, apparently.  At least
nothing here is unsafe.

Addresses part of #188.
This commit is contained in:
Nick Mathewson 2021-10-14 13:12:58 -04:00
parent 02b20edeb3
commit 0cb7231649
3 changed files with 35 additions and 2 deletions

2
Cargo.lock generated
View File

@ -98,6 +98,7 @@ dependencies = [
"async-ctrlc", "async-ctrlc",
"config", "config",
"futures", "futures",
"libc",
"once_cell", "once_cell",
"serde", "serde",
"tokio", "tokio",
@ -107,6 +108,7 @@ dependencies = [
"tracing", "tracing",
"tracing-journald", "tracing-journald",
"tracing-subscriber", "tracing-subscriber",
"winapi",
] ]
[[package]] [[package]]

View File

@ -34,3 +34,9 @@ tracing-subscriber = "0.2.19"
tokio-crate = { package="tokio", version = "1.7.0", optional = true, features = ["signal"] } tokio-crate = { package="tokio", version = "1.7.0", optional = true, features = ["signal"] }
argh = "0.1.6" argh = "0.1.6"
tracing-journald = { version = "0.1.0", optional = true } tracing-journald = { version = "0.1.0", optional = true }
[target.'cfg(unix)'.dependencies]
libc = { version = "0.2.103", default-features = false }
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.8", features = [ "winerror" ] }

View File

@ -4,7 +4,7 @@
//! connections and then runs //! connections and then runs
use futures::future::FutureExt; use futures::future::FutureExt;
use futures::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use futures::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, Error as IoError};
use futures::lock::Mutex; use futures::lock::Mutex;
use futures::stream::StreamExt; use futures::stream::StreamExt;
use futures::task::SpawnExt; use futures::task::SpawnExt;
@ -334,6 +334,21 @@ where
loop_result.or(flush_result) loop_result.or(flush_result)
} }
/// Return true if a given IoError, when received from accept, is a fatal
/// error.
fn accept_err_is_fatal(err: &IoError) -> bool {
#![allow(clippy::match_like_matches_macro)]
// Currently, EMFILE and ENFILE aren't distinguished by ErrorKind;
// we need to use OS-specific errors. :P
match err.raw_os_error() {
#[cfg(unix)]
Some(libc::EMFILE) | Some(libc::ENFILE) => false,
#[cfg(windows)]
Some(winapi::shared::winerror::WSAEMFILE) => false,
_ => true,
}
}
/// Launch a SOCKS proxy to listen on a given localhost port, and run /// Launch a SOCKS proxy to listen on a given localhost port, and run
/// indefinitely. /// indefinitely.
/// ///
@ -386,7 +401,17 @@ pub(crate) async fn run_socks_proxy<R: Runtime>(
// Loop over all incoming connections. For each one, call // Loop over all incoming connections. For each one, call
// handle_socks_conn() in a new task. // handle_socks_conn() in a new task.
while let Some((stream, sock_id)) = incoming.next().await { while let Some((stream, sock_id)) = incoming.next().await {
let (stream, addr) = stream.context("Failed to receive incoming stream on SOCKS port")?; let (stream, addr) = match stream {
Ok((s, a)) => (s, a),
Err(err) => {
if accept_err_is_fatal(&err) {
return Err(err).context("Failed to receive incoming stream on SOCKS port");
} else {
warn!("Incoming stream failed: {}", err);
continue;
}
}
};
let client_ref = Arc::clone(&tor_client); let client_ref = Arc::clone(&tor_client);
let runtime_copy = runtime.clone(); let runtime_copy = runtime.clone();
let isolation_map_ref = Arc::clone(&isolation_map); let isolation_map_ref = Arc::clone(&isolation_map);