blob: cf01245dc672dd155cfd2c8b5f595e5ec9ad5d13 [file]
//! Loopback TCP → SOCKS5 forwarder.
//!
//! mysql_async 0.34 has no hook to inject a pre-dialed stream (`Endpoint` enum
//! is closed, `OptsBuilder` exposes only direct TCP knobs). So to route MySQL
//! through a SOCKS5 proxy we bind an ephemeral loopback listener, point
//! mysql_async at it, and tunnel each accepted connection through the proxy
//! via `tokio-socks` + `copy_bidirectional`.
//!
//! The forwarder must outlive the MySQL connection — store the returned handle
//! on the connection struct and let Drop abort the accept task.
use crate::config::Socks5Config;
use crate::error::{DorisError, DorisResult};
use std::net::SocketAddr;
use tokio::net::TcpListener;
use tokio::task::JoinHandle;
use tokio_socks::tcp::Socks5Stream;
pub struct Socks5Forwarder {
pub local_addr: SocketAddr,
task: JoinHandle<()>,
}
impl Socks5Forwarder {
/// Bind an ephemeral loopback port and start forwarding every accepted
/// connection through the SOCKS5 proxy to `target_host:target_port`.
pub async fn spawn(
socks5: &Socks5Config,
target_host: String,
target_port: u16,
) -> DorisResult<Self> {
let listener = TcpListener::bind("127.0.0.1:0").await.map_err(|e| {
DorisError::connection_with_source(
"Failed to bind SOCKS5 forwarder loopback listener".to_string(),
e,
)
})?;
let local_addr = listener.local_addr().map_err(|e| {
DorisError::connection_with_source(
"Failed to read SOCKS5 forwarder local addr".to_string(),
e,
)
})?;
let s5 = socks5.clone();
let task = tokio::spawn(async move {
loop {
let (mut inbound, _peer) = match listener.accept().await {
Ok(pair) => pair,
Err(_) => return,
};
let s5 = s5.clone();
let host = target_host.clone();
let port = target_port;
tokio::spawn(async move {
let proxy_addr = format!("{}:{}", s5.host, s5.port);
let mut outbound = match Socks5Stream::connect_with_password(
proxy_addr.as_str(),
(host.as_str(), port),
&s5.user,
&s5.pass,
)
.await
{
Ok(s) => s,
Err(e) => {
tracing::warn!(
target = "doris::socks5",
"SOCKS5 dial to {host}:{port} via {proxy_addr} failed: {e}"
);
return;
}
};
let _ = tokio::io::copy_bidirectional(&mut inbound, &mut outbound).await;
});
}
});
Ok(Socks5Forwarder { local_addr, task })
}
}
impl Drop for Socks5Forwarder {
fn drop(&mut self) {
self.task.abort();
}
}