mod common;
use std::net::TcpStream;
use std::sync::Arc;
use std::time::Duration;
use common::mint_key;
use gunnar_ssh::Access;
use rustix::process::{getrlimit, setrlimit, Resource};
fn open_fds() -> u64 {
std::fs::read_dir("/proc/self/fd")
.map(|d| d.count() as u64)
.unwrap_or(0)
}
async fn authenticates(addr: std::net::SocketAddr, key: &russh::keys::PrivateKey) -> bool {
struct Trusting;
impl russh::client::Handler for Trusting {
type Error = russh::Error;
async fn check_server_key(
&mut self,
_: &russh::keys::PublicKeyOrCertificate,
) -> Result<bool, Self::Error> {
Ok(true)
}
}
let config = Arc::new(russh::client::Config::default());
let connect = russh::client::connect(config, addr, Trusting);
let Ok(Ok(mut session)) = tokio::time::timeout(Duration::from_secs(5), connect).await else {
return false;
};
let auth = session.authenticate_publickey(
"git",
russh::keys::PrivateKeyWithHashAlg::new(Arc::new(key.clone()), None),
);
matches!(tokio::time::timeout(Duration::from_secs(5), auth).await, Ok(Ok(a)) if a.success())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn the_listener_survives_running_out_of_descriptors() {
let key = mint_key();
let fx = common::start_with_key(&key, Access::Read).await;
let addr = fx.server.local_addr();
assert!(
authenticates(addr, &key).await,
"the listener must work before the test starves it"
);
let original = getrlimit(Resource::Nofile);
let mut starved = original;
starved.current = Some(open_fds() + 6);
setrlimit(Resource::Nofile, starved).expect("lower the soft limit");
let mut held = Vec::new();
let mut client_refused = None;
for _ in 0..64 {
match TcpStream::connect(addr) {
Ok(s) => held.push(s),
Err(e) => {
client_refused = Some(e);
break;
}
}
}
let refused = client_refused.expect("the process never ran out of descriptors");
assert_eq!(
refused.raw_os_error(),
Some(24),
"expected EMFILE, got {refused}"
);
tokio::time::sleep(Duration::from_millis(300)).await;
drop(held);
setrlimit(Resource::Nofile, original).expect("restore the soft limit");
tokio::time::sleep(Duration::from_millis(300)).await;
assert!(
authenticates(addr, &key).await,
"the listener went deaf after EMFILE — the accept loop died with the first failed accept"
);
fx.server.shutdown("test over").await.expect("shutdown");
}