use std::collections::HashMap;
use std::io;
use std::net::{Ipv4Addr, SocketAddr};
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex, MutexGuard};
use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
use tokio::net::{TcpListener, TcpStream};
use tokio::sync::watch;
use tokio::task::{JoinHandle, JoinSet};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Cut {
Up(usize),
Down(usize),
After(Duration),
Now,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Direction {
Up,
Down,
}
pub struct Tear {
addr: SocketAddr,
shared: Arc<Shared>,
accept: JoinHandle<()>,
}
struct Shared {
upstream: SocketAddr,
rules: Mutex<Rules>,
flowing: watch::Sender<bool>,
open: Mutex<HashMap<u64, watch::Sender<bool>>>,
next: AtomicU64,
accepted: AtomicUsize,
torn: AtomicUsize,
passed_up: AtomicUsize,
passed_down: AtomicUsize,
}
#[derive(Default)]
struct Rules {
up: Option<usize>,
down: Option<usize>,
offline: bool,
}
impl Tear {
pub async fn start(upstream: SocketAddr) -> io::Result<Self> {
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await?;
let addr = listener.local_addr()?;
let shared = Arc::new(Shared {
upstream,
rules: Mutex::new(Rules::default()),
flowing: watch::Sender::new(true),
open: Mutex::new(HashMap::new()),
next: AtomicU64::new(0),
accepted: AtomicUsize::new(0),
torn: AtomicUsize::new(0),
passed_up: AtomicUsize::new(0),
passed_down: AtomicUsize::new(0),
});
let accept = tokio::spawn(accept(listener, shared.clone()));
Ok(Self { addr, shared, accept })
}
pub fn addr(&self) -> SocketAddr {
self.addr
}
pub fn upstream(&self) -> SocketAddr {
self.shared.upstream
}
pub fn cut(&self, cut: Cut) {
match cut {
Cut::Up(bytes) => self.shared.rules().up = Some(bytes),
Cut::Down(bytes) => self.shared.rules().down = Some(bytes),
Cut::Now => self.shared.tear_open(),
Cut::After(delay) => {
let shared = Arc::downgrade(&self.shared);
tokio::spawn(async move {
tokio::time::sleep(delay).await;
if let Some(shared) = shared.upgrade() {
shared.tear_open();
}
});
}
}
}
pub fn disarm(&self) {
let mut rules = self.shared.rules();
rules.up = None;
rules.down = None;
}
pub fn offline(&self) {
self.shared.rules().offline = true;
self.shared.tear_open();
}
pub fn online(&self) {
self.shared.rules().offline = false;
}
pub fn stall(&self) {
self.shared.flowing.send_replace(false);
}
pub fn flow(&self) {
self.shared.flowing.send_replace(true);
}
pub fn connections(&self) -> usize {
self.shared.accepted.load(Ordering::SeqCst)
}
pub fn torn(&self) -> usize {
self.shared.torn.load(Ordering::SeqCst)
}
pub fn passed(&self, direction: Direction) -> usize {
match direction {
Direction::Up => self.shared.passed_up.load(Ordering::SeqCst),
Direction::Down => self.shared.passed_down.load(Ordering::SeqCst),
}
}
}
impl Drop for Tear {
fn drop(&mut self) {
self.accept.abort();
}
}
impl std::fmt::Debug for Tear {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Tear").field("addr", &self.addr).field("upstream", &self.shared.upstream).finish()
}
}
impl Shared {
fn rules(&self) -> MutexGuard<'_, Rules> {
lock(&self.rules)
}
fn tear_open(&self) {
for switch in lock(&self.open).values() {
switch.send_replace(true);
}
}
fn take(&self, direction: Direction, len: usize) -> (usize, bool) {
let mut rules = self.rules();
let budget = match direction {
Direction::Up => &mut rules.up,
Direction::Down => &mut rules.down,
};
match budget {
None => (len, false),
Some(left) if len <= *left => {
*left -= len;
(len, false)
}
Some(left) => {
let pass = *left;
*budget = None;
(pass, true)
}
}
}
fn passed(&self, direction: Direction, len: usize) {
let counter = match direction {
Direction::Up => &self.passed_up,
Direction::Down => &self.passed_down,
};
counter.fetch_add(len, Ordering::SeqCst);
}
}
async fn accept(listener: TcpListener, shared: Arc<Shared>) {
let mut connections = JoinSet::new();
loop {
while connections.try_join_next().is_some() {}
let Ok((client, _)) = listener.accept().await else {
continue;
};
shared.accepted.fetch_add(1, Ordering::SeqCst);
connections.spawn(serve(client, shared.clone()));
}
}
enum Flow {
Ended,
Cut,
Failed,
}
async fn serve(client: TcpStream, shared: Arc<Shared>) {
if shared.rules().offline {
shared.torn.fetch_add(1, Ordering::SeqCst);
reset(&client);
return;
}
let Ok(server) = TcpStream::connect(shared.upstream).await else {
reset(&client);
return;
};
let _ = client.set_nodelay(true);
let _ = server.set_nodelay(true);
let id = shared.next.fetch_add(1, Ordering::SeqCst);
let (switch, mut torn) = watch::channel(false);
lock(&shared.open).insert(id, switch);
let (mut client_read, mut client_write) = client.into_split();
let (mut server_read, mut server_write) = server.into_split();
let end = {
let up = pump(&mut client_read, &mut server_write, Direction::Up, &shared);
let down = pump(&mut server_read, &mut client_write, Direction::Down, &shared);
tokio::pin!(up, down);
let (mut up_done, mut down_done) = (false, false);
loop {
let flow = tokio::select! {
flow = &mut up, if !up_done => { up_done = true; flow }
flow = &mut down, if !down_done => { down_done = true; flow }
_ = torn.wait_for(|torn| *torn) => Flow::Cut,
};
match flow {
Flow::Ended if up_done && down_done => break Flow::Ended,
Flow::Ended => {}
end => break end,
}
}
};
lock(&shared.open).remove(&id);
if matches!(end, Flow::Ended) {
return;
}
if matches!(end, Flow::Cut) {
shared.torn.fetch_add(1, Ordering::SeqCst);
}
for (read, write) in [(client_read, client_write), (server_read, server_write)] {
if let Ok(socket) = read.reunite(write) {
reset(&socket);
}
}
}
async fn pump(from: &mut OwnedReadHalf, to: &mut OwnedWriteHalf, direction: Direction, shared: &Shared) -> Flow {
let mut flowing = shared.flowing.subscribe();
let mut buffer = vec![0u8; 16 * 1024];
loop {
let len = match from.read(&mut buffer).await {
Ok(0) => {
return match to.shutdown().await {
Ok(()) => Flow::Ended,
Err(_) => Flow::Failed,
};
}
Ok(len) => len,
Err(_) => return Flow::Failed,
};
if flowing.wait_for(|flowing| *flowing).await.is_err() {
return Flow::Failed;
}
let (pass, cut) = shared.take(direction, len);
if pass > 0 {
if to.write_all(&buffer[..pass]).await.is_err() {
return Flow::Failed;
}
shared.passed(direction, pass);
}
if cut {
return Flow::Cut;
}
}
}
fn reset(socket: &TcpStream) {
let _ = socket.set_zero_linger();
}
fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
mutex.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
}