use anyhow::{Context, Result};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use std::net::SocketAddr;
use std::sync::Arc;
use parking_lot::RwLock;
use crate::config::PortMapping;
#[derive(Debug, Default, Clone)]
pub struct TcpStats {
pub connections_active: u64,
pub connections_total: u64,
pub bytes_sent: u64,
pub bytes_received: u64,
}
pub struct TcpForwarder {
stats: Arc<RwLock<TcpStats>>,
}
impl TcpForwarder {
pub fn new() -> Self {
Self {
stats: Arc::new(RwLock::new(TcpStats::default())),
}
}
pub async fn start_forwarding(&self, mapping: &PortMapping) -> Result<()> {
let listener = TcpListener::bind(format!("0.0.0.0:{}", mapping.remote_port))
.await
.context(format!("Failed to bind TCP port {}", mapping.remote_port))?;
tracing::info!(
"TCP forwarder listening on :{} -> localhost:{}",
mapping.remote_port,
mapping.local_port
);
let stats = self.stats.clone();
let local_port = mapping.local_port;
tokio::spawn(async move {
loop {
match listener.accept().await {
Ok((stream, addr)) => {
let stats = stats.clone();
tokio::spawn(async move {
if let Err(e) = Self::handle_connection(stream, addr, local_port, stats).await {
tracing::error!("TCP connection error: {}", e);
}
});
}
Err(e) => {
tracing::error!("Failed to accept TCP connection: {}", e);
}
}
}
});
Ok(())
}
async fn handle_connection(
client_stream: TcpStream,
client_addr: SocketAddr,
local_port: u16,
stats: Arc<RwLock<TcpStats>>,
) -> Result<()> {
let local_addr = format!("127.0.0.1:{}", local_port);
let local_stream = TcpStream::connect(&local_addr)
.await
.context(format!("Failed to connect to {}", local_addr))?;
tracing::debug!("TCP connection: {} -> :{} -> {}", client_addr, local_port, local_addr);
{
let mut s = stats.write();
s.connections_active += 1;
s.connections_total += 1;
}
let (mut client_read, mut client_write) = client_stream.into_split();
let (mut local_read, mut local_write) = local_stream.into_split();
let stats_send = stats.clone();
let stats_recv = stats.clone();
let send_task = tokio::spawn(async move {
let mut buf = vec![0u8; 8192];
let mut total = 0u64;
loop {
match client_read.read(&mut buf).await {
Ok(0) => break,
Ok(n) => {
if local_write.write_all(&buf[..n]).await.is_err() {
break;
}
total += n as u64;
}
Err(_) => break,
}
}
let mut s = stats_send.write();
s.bytes_sent += total;
s.connections_active = s.connections_active.saturating_sub(1);
});
let recv_task = tokio::spawn(async move {
let mut buf = vec![0u8; 8192];
let mut total = 0u64;
loop {
match local_read.read(&mut buf).await {
Ok(0) => break,
Ok(n) => {
if client_write.write_all(&buf[..n]).await.is_err() {
break;
}
total += n as u64;
}
Err(_) => break,
}
}
let mut s = stats_recv.write();
s.bytes_received += total;
});
tokio::select! {
_ = send_task => {},
_ = recv_task => {},
}
Ok(())
}
pub fn stats(&self) -> TcpStats {
self.stats.read().clone()
}
}
impl Default for TcpForwarder {
fn default() -> Self {
Self::new()
}
}