Files
kumomta/crates/proxy-server/src/main.rs
T
Wez Furlong b5596183be proxy-server: simplify passthru implementation
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.
2024-07-30 16:32:39 -07:00

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(())
}