mirror of
https://github.com/mailscope/kumomta.git
synced 2026-09-11 13:02:14 +00:00
We've been trying to run down an issue where a user has reported that some sessions that are running via the proxy seem to hang waiting for a response from the peer. The common theme is that the size of the payload is approximately 1MB in size, and that the proxy is in use. I haven't been able to get it to reproduce at all, but in looking carefully at the code here, my splice(2) implementation used a pipe buffer that was 1MB in size, and doing non-blocking IO outside of tokio's internals is a bit of a black art, so I'd buy the theory that we might be getting stuck somewhere if we did fill up that pipe buffer. Since I couldn't catch it in the act, I've opted to go for the simple and safer route here, as a speculative remediation: * Added a `--no-splice` command line parameter to opt out of using `splice(2)` completely on Linux so that we can rule out weirdness with splice completely. The result will have lower theoretical max throughput, but a simpler internal implementation. * There now exists a `tokio-splice` crate that has the same functionality as my own splice_copy code does, but a different implementation. Adopt that for the `splice(2)` mode. * Switch away from splitting the stream into read/write halves: there are now utility functions available in tokio and tokio_splice that don't require splitting the streams, which further simplifies the implementation.
78 lines
1.8 KiB
Rust
78 lines
1.8 KiB
Rust
use anyhow::Context;
|
|
use clap::Parser;
|
|
use tokio::net::TcpListener;
|
|
|
|
mod proxy_handler;
|
|
|
|
/// KumoProxy SOCKS5 Proxy Server
|
|
#[derive(Debug, Parser)]
|
|
#[command(about)]
|
|
pub struct Opt {
|
|
#[arg(long)]
|
|
listen: Vec<String>,
|
|
|
|
#[arg(long)]
|
|
no_splice: bool,
|
|
|
|
#[arg(long, default_value = "60")]
|
|
timeout_seconds: u64,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> anyhow::Result<()> {
|
|
env_logger::init();
|
|
let opts = Opt::parse();
|
|
|
|
if opts.listen.is_empty() {
|
|
anyhow::bail!("No listeners defined! use the --listen option to specify at least one!");
|
|
}
|
|
|
|
for endpoint in &opts.listen {
|
|
start_listener(
|
|
endpoint,
|
|
std::time::Duration::from_secs(opts.timeout_seconds),
|
|
opts.no_splice,
|
|
)
|
|
.await?;
|
|
}
|
|
|
|
tokio::signal::ctrl_c().await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn start_listener(
|
|
endpoint: &str,
|
|
timeout: std::time::Duration,
|
|
no_splice: bool,
|
|
) -> anyhow::Result<()> {
|
|
let listener = TcpListener::bind(endpoint)
|
|
.await
|
|
.with_context(|| format!("failed to bind to {endpoint}"))?;
|
|
|
|
let addr = listener.local_addr()?;
|
|
log::info!("proxy listener on {addr:?}");
|
|
|
|
tokio::spawn(async move {
|
|
loop {
|
|
let (socket, peer_address) = match listener.accept().await {
|
|
Ok(tuple) => tuple,
|
|
Err(err) => {
|
|
log::error!("accept failed: {err:#}");
|
|
return;
|
|
}
|
|
};
|
|
|
|
tokio::spawn(async move {
|
|
if let Err(err) =
|
|
proxy_handler::handle_proxy_client(socket, peer_address, timeout, no_splice)
|
|
.await
|
|
{
|
|
log::error!("proxy session error: {err:#}");
|
|
}
|
|
});
|
|
}
|
|
});
|
|
Ok(())
|
|
}
|