use crate::args::Arguments;
use crate::args::get_formatter_by_kind;
use bytes::BytesMut;
use logged_stream::ConsoleLogger;
use logged_stream::DefaultFilter;
use logged_stream::LoggedStream;
use logged_stream::RecordKind;
use logged_stream::RecordKindFilter;
use std::sync::Arc;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use std::time::Duration;
use tokio::io::AsyncRead;
use tokio::io::AsyncReadExt;
use tokio::io::AsyncWrite;
use tokio::io::AsyncWriteExt;
use tokio::io::{self};
use tokio::net as tokio_net;
use tokio::sync::Semaphore;
use tokio::time::Instant;
use tokio::time::sleep;
use tokio::time::sleep_until;
pub async fn initialize_tcp_listener(arguments: Arguments) -> io::Result<()> {
let listener = match tokio_net::TcpListener::bind(arguments.bind_listener_addr).await {
Ok(listener) => listener,
Err(error) => {
log::error!(
"Failed to bind listener on {}: {error}",
arguments.bind_listener_addr
);
return Err(error);
}
};
let bound_addr = listener.local_addr()?;
log::info!("Listener bound to {bound_addr}, waiting for incoming connections...");
tokio::select! {
_ = run_accept_loop(listener, arguments) => {}
result = tokio::signal::ctrl_c() => match result {
Ok(()) => log::info!("Received shutdown signal, stopping listener."),
Err(error) => log::error!("Failed to listen for shutdown signal: {error}"),
},
}
Ok(())
}
pub(crate) const ACCEPT_BACKOFF_MIN: Duration = Duration::from_millis(10);
pub(crate) const ACCEPT_BACKOFF_MAX: Duration = Duration::from_secs(1);
pub(crate) fn next_accept_backoff(current: Duration) -> Duration {
(current * 2).min(ACCEPT_BACKOFF_MAX)
}
pub(crate) async fn run_accept_loop(listener: tokio_net::TcpListener, arguments: Arguments) {
let connection_limit = Arc::new(Semaphore::new(arguments.max_connections as usize));
let mut accept_backoff = ACCEPT_BACKOFF_MIN;
loop {
let Ok(permit) = connection_limit.clone().acquire_owned().await else {
break; };
let cloned_arguments = arguments.clone();
match listener.accept().await {
Ok((stream, addr)) => {
accept_backoff = ACCEPT_BACKOFF_MIN; log::info!("Incoming connection from {addr}");
tokio::spawn(async move {
incoming_connection_handle(cloned_arguments, stream).await;
drop(permit); });
}
Err(e) => {
log::error!("Failed to accept incoming connection due to {e}");
drop(permit);
sleep(accept_backoff).await;
accept_backoff = next_accept_backoff(accept_backoff);
}
}
}
}
async fn incoming_connection_handle(arguments: Arguments, source_stream: tokio_net::TcpStream) {
let (source_stream_read_half, source_stream_write_half) = io::split(LoggedStream::new(
source_stream,
get_formatter_by_kind(arguments.formatting, arguments.separator.as_str()),
DefaultFilter,
ConsoleLogger::new_unchecked("debug"),
));
let destination_stream = match tokio_net::TcpStream::connect(arguments.remote_addr).await {
Ok(stream) => stream,
Err(error) => {
log::error!(
"Failed to connect to destination {}: {error}",
arguments.remote_addr
);
return;
}
};
let (destination_stream_read_half, destination_stream_write_half) =
io::split(LoggedStream::new(
destination_stream,
get_formatter_by_kind(arguments.formatting, arguments.separator.as_str()),
RecordKindFilter::new(&[RecordKind::Drop, RecordKind::Error, RecordKind::Shutdown]),
ConsoleLogger::new_unchecked("debug"),
));
match arguments.timeout {
None => {
tokio::join!(
relay(source_stream_read_half, destination_stream_write_half, None),
relay(destination_stream_read_half, source_stream_write_half, None),
);
}
Some(seconds) => {
let idle = Duration::from_secs(seconds);
let clock = ActivityClock::new();
let relays = async {
tokio::join!(
relay(
source_stream_read_half,
destination_stream_write_half,
Some(&clock),
),
relay(
destination_stream_read_half,
source_stream_write_half,
Some(&clock),
),
);
};
tokio::select! {
_ = relays => {}
_ = wait_until_idle(&clock, idle) => {
log::info!("Closing idle connection after {seconds}s of inactivity");
}
}
}
}
}
struct ActivityClock {
started: Instant,
last_active_millis: AtomicU64,
}
impl ActivityClock {
fn new() -> Self {
Self {
started: Instant::now(),
last_active_millis: AtomicU64::new(0),
}
}
fn record(&self) {
self.last_active_millis
.store(self.started.elapsed().as_millis() as u64, Ordering::Relaxed);
}
fn idle_deadline(&self, idle: Duration) -> Instant {
let last_active = Duration::from_millis(self.last_active_millis.load(Ordering::Relaxed));
self.started + last_active + idle
}
}
async fn wait_until_idle(clock: &ActivityClock, idle: Duration) {
loop {
sleep_until(clock.idle_deadline(idle)).await;
if Instant::now() >= clock.idle_deadline(idle) {
return;
}
}
}
async fn relay<R, W>(mut reader: R, mut writer: W, activity: Option<&ActivityClock>)
where
R: AsyncRead + Unpin,
W: AsyncWrite + Unpin,
{
let mut buffer = BytesMut::with_capacity(2048);
while let Ok(read_length) = reader.read_buf(&mut buffer).await {
if read_length == 0 {
break;
}
if let Some(activity) = activity {
activity.record();
}
if writer.write_all(&buffer[0..read_length]).await.is_err() {
break;
}
buffer.clear();
}
let _ = writer.shutdown().await;
}