use std::net::SocketAddr;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, RwLock};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
use tokio::net::{TcpListener, TcpStream, ToSocketAddrs};
use tokio::task::JoinHandle;
use crate::error::Result;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Direction {
ClientToUpstream,
UpstreamToClient,
}
impl Direction {
fn idx(self) -> usize {
match self {
Direction::ClientToUpstream => 0,
Direction::UpstreamToClient => 1,
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum Delay {
Fixed(Duration),
Random {
min: Duration,
max: Duration,
},
}
impl Delay {
fn resolve(self) -> Duration {
match self {
Delay::Fixed(d) => d,
Delay::Random { min, max } => {
if max <= min {
return min;
}
let span = (max - min).as_nanos().max(1) as u64;
min + Duration::from_nanos(next_pseudo_random() % span)
}
}
}
}
fn next_pseudo_random() -> u64 {
static COUNTER: AtomicU64 = AtomicU64::new(0);
let counter = COUNTER.fetch_add(1, Ordering::Relaxed);
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let mut x = nanos ^ counter.wrapping_mul(0x9E37_79B9_7F4A_7C15);
x ^= x >> 30;
x = x.wrapping_mul(0xBF58_476D_1CE4_E5B9);
x ^= x >> 27;
x = x.wrapping_mul(0x94D0_49BB_1331_11EB);
x ^= x >> 31;
x
}
#[derive(Clone, Debug, Default)]
struct FaultState {
delay: [Option<Delay>; 2],
close_after: [Option<u64>; 2],
chunk_size: Option<usize>,
drop_all: bool,
}
pub struct FaultProxy {
addr: SocketAddr,
state: Arc<RwLock<FaultState>>,
accept_task: JoinHandle<()>,
}
impl FaultProxy {
pub async fn spawn(upstream_addr: impl ToSocketAddrs) -> Result<FaultProxy> {
let upstream_addr = tokio::net::lookup_host(upstream_addr)
.await?
.next()
.ok_or_else(|| {
crate::error::Error::Io(std::io::Error::new(
std::io::ErrorKind::AddrNotAvailable,
"could not resolve upstream address",
))
})?;
let listener = TcpListener::bind(("127.0.0.1", 0)).await?;
let addr = listener.local_addr()?;
let state = Arc::new(RwLock::new(FaultState::default()));
let accept_state = state.clone();
let accept_task = tokio::spawn(async move {
loop {
let (client, _) = match listener.accept().await {
Ok(pair) => pair,
Err(_) => break,
};
tokio::spawn(handle_connection(
client,
upstream_addr,
accept_state.clone(),
));
}
});
Ok(FaultProxy {
addr,
state,
accept_task,
})
}
pub fn addr(&self) -> SocketAddr {
self.addr
}
pub fn set_delay(&self, direction: Direction, delay: Delay) {
self.state.write().unwrap().delay[direction.idx()] = Some(delay);
}
pub fn clear_delay(&self, direction: Direction) {
self.state.write().unwrap().delay[direction.idx()] = None;
}
pub fn close_after(&self, direction: Direction, bytes: u64) {
self.state.write().unwrap().close_after[direction.idx()] = Some(bytes);
}
pub fn clear_close_after(&self, direction: Direction) {
self.state.write().unwrap().close_after[direction.idx()] = None;
}
pub fn set_chunk_size(&self, size: usize) {
self.state.write().unwrap().chunk_size = Some(size.max(1));
}
pub fn clear_chunk_size(&self) {
self.state.write().unwrap().chunk_size = None;
}
pub fn set_drop_all(&self, drop_all: bool) {
self.state.write().unwrap().drop_all = drop_all;
}
pub fn reset(&self) {
*self.state.write().unwrap() = FaultState::default();
}
}
impl Drop for FaultProxy {
fn drop(&mut self) {
self.accept_task.abort();
}
}
async fn handle_connection(
mut client: TcpStream,
upstream_addr: SocketAddr,
state: Arc<RwLock<FaultState>>,
) {
if state.read().unwrap().drop_all {
let mut buf = [0u8; 4096];
loop {
match client.read(&mut buf).await {
Ok(0) | Err(_) => return,
Ok(_) => {}
}
}
}
let upstream = match TcpStream::connect(upstream_addr).await {
Ok(s) => s,
Err(_) => return,
};
let (client_r, client_w) = client.into_split();
let (upstream_r, upstream_w) = upstream.into_split();
tokio::select! {
_ = forward(client_r, upstream_w, state.clone(), Direction::ClientToUpstream) => {},
_ = forward(upstream_r, client_w, state, Direction::UpstreamToClient) => {},
}
}
async fn forward(
mut reader: OwnedReadHalf,
mut writer: OwnedWriteHalf,
state: Arc<RwLock<FaultState>>,
direction: Direction,
) {
let idx = direction.idx();
let mut total: u64 = 0;
let mut buf = [0u8; 4096];
loop {
let n = match reader.read(&mut buf).await {
Ok(0) | Err(_) => return,
Ok(n) => n,
};
let mut data = &buf[..n];
let (delay, close_after, chunk_size) = {
let s = state.read().unwrap();
(s.delay[idx], s.close_after[idx], s.chunk_size)
};
if let Some(limit) = close_after {
if total >= limit {
return;
}
let remaining = (limit - total) as usize;
if data.len() > remaining {
data = &data[..remaining];
}
}
if let Some(delay) = delay {
tokio::time::sleep(delay.resolve()).await;
}
let chunk = chunk_size.unwrap_or(data.len()).max(1);
for piece in data.chunks(chunk) {
if writer.write_all(piece).await.is_err() {
return;
}
}
total += data.len() as u64;
if let Some(limit) = close_after
&& total >= limit
{
return;
}
}
}