redis_server_wrapper/
fault_proxy.rs1use std::net::SocketAddr;
33use std::sync::atomic::{AtomicU64, Ordering};
34use std::sync::{Arc, RwLock};
35use std::time::{Duration, SystemTime, UNIX_EPOCH};
36
37use tokio::io::{AsyncReadExt, AsyncWriteExt};
38use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
39use tokio::net::{TcpListener, TcpStream, ToSocketAddrs};
40use tokio::task::JoinHandle;
41
42use crate::error::Result;
43
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
46pub enum Direction {
47 ClientToUpstream,
49 UpstreamToClient,
51}
52
53impl Direction {
54 fn idx(self) -> usize {
55 match self {
56 Direction::ClientToUpstream => 0,
57 Direction::UpstreamToClient => 1,
58 }
59 }
60}
61
62#[derive(Clone, Copy, Debug)]
64pub enum Delay {
65 Fixed(Duration),
67 Random {
73 min: Duration,
75 max: Duration,
77 },
78}
79
80impl Delay {
81 fn resolve(self) -> Duration {
82 match self {
83 Delay::Fixed(d) => d,
84 Delay::Random { min, max } => {
85 if max <= min {
86 return min;
87 }
88 let span = (max - min).as_nanos().max(1) as u64;
89 min + Duration::from_nanos(next_pseudo_random() % span)
90 }
91 }
92 }
93}
94
95fn next_pseudo_random() -> u64 {
98 static COUNTER: AtomicU64 = AtomicU64::new(0);
99 let counter = COUNTER.fetch_add(1, Ordering::Relaxed);
100 let nanos = SystemTime::now()
101 .duration_since(UNIX_EPOCH)
102 .map(|d| d.as_nanos() as u64)
103 .unwrap_or(0);
104 let mut x = nanos ^ counter.wrapping_mul(0x9E37_79B9_7F4A_7C15);
105 x ^= x >> 30;
106 x = x.wrapping_mul(0xBF58_476D_1CE4_E5B9);
107 x ^= x >> 27;
108 x = x.wrapping_mul(0x94D0_49BB_1331_11EB);
109 x ^= x >> 31;
110 x
111}
112
113#[derive(Clone, Debug, Default)]
114struct FaultState {
115 delay: [Option<Delay>; 2],
116 close_after: [Option<u64>; 2],
117 chunk_size: Option<usize>,
118 drop_all: bool,
119}
120
121pub struct FaultProxy {
131 addr: SocketAddr,
132 state: Arc<RwLock<FaultState>>,
133 accept_task: JoinHandle<()>,
134}
135
136impl FaultProxy {
137 pub async fn spawn(upstream_addr: impl ToSocketAddrs) -> Result<FaultProxy> {
140 let upstream_addr = tokio::net::lookup_host(upstream_addr)
141 .await?
142 .next()
143 .ok_or_else(|| {
144 crate::error::Error::Io(std::io::Error::new(
145 std::io::ErrorKind::AddrNotAvailable,
146 "could not resolve upstream address",
147 ))
148 })?;
149
150 let listener = TcpListener::bind(("127.0.0.1", 0)).await?;
151 let addr = listener.local_addr()?;
152 let state = Arc::new(RwLock::new(FaultState::default()));
153
154 let accept_state = state.clone();
155 let accept_task = tokio::spawn(async move {
156 loop {
157 let (client, _) = match listener.accept().await {
158 Ok(pair) => pair,
159 Err(_) => break,
160 };
161 tokio::spawn(handle_connection(
162 client,
163 upstream_addr,
164 accept_state.clone(),
165 ));
166 }
167 });
168
169 Ok(FaultProxy {
170 addr,
171 state,
172 accept_task,
173 })
174 }
175
176 pub fn addr(&self) -> SocketAddr {
178 self.addr
179 }
180
181 pub fn set_delay(&self, direction: Direction, delay: Delay) {
183 self.state.write().unwrap().delay[direction.idx()] = Some(delay);
184 }
185
186 pub fn clear_delay(&self, direction: Direction) {
188 self.state.write().unwrap().delay[direction.idx()] = None;
189 }
190
191 pub fn close_after(&self, direction: Direction, bytes: u64) {
196 self.state.write().unwrap().close_after[direction.idx()] = Some(bytes);
197 }
198
199 pub fn clear_close_after(&self, direction: Direction) {
201 self.state.write().unwrap().close_after[direction.idx()] = None;
202 }
203
204 pub fn set_chunk_size(&self, size: usize) {
207 self.state.write().unwrap().chunk_size = Some(size.max(1));
208 }
209
210 pub fn clear_chunk_size(&self) {
212 self.state.write().unwrap().chunk_size = None;
213 }
214
215 pub fn set_drop_all(&self, drop_all: bool) {
220 self.state.write().unwrap().drop_all = drop_all;
221 }
222
223 pub fn reset(&self) {
226 *self.state.write().unwrap() = FaultState::default();
227 }
228}
229
230impl Drop for FaultProxy {
231 fn drop(&mut self) {
232 self.accept_task.abort();
233 }
234}
235
236async fn handle_connection(
237 mut client: TcpStream,
238 upstream_addr: SocketAddr,
239 state: Arc<RwLock<FaultState>>,
240) {
241 if state.read().unwrap().drop_all {
242 let mut buf = [0u8; 4096];
243 loop {
244 match client.read(&mut buf).await {
245 Ok(0) | Err(_) => return,
246 Ok(_) => {}
247 }
248 }
249 }
250
251 let upstream = match TcpStream::connect(upstream_addr).await {
252 Ok(s) => s,
253 Err(_) => return,
254 };
255
256 let (client_r, client_w) = client.into_split();
257 let (upstream_r, upstream_w) = upstream.into_split();
258
259 tokio::select! {
260 _ = forward(client_r, upstream_w, state.clone(), Direction::ClientToUpstream) => {},
261 _ = forward(upstream_r, client_w, state, Direction::UpstreamToClient) => {},
262 }
263}
264
265async fn forward(
270 mut reader: OwnedReadHalf,
271 mut writer: OwnedWriteHalf,
272 state: Arc<RwLock<FaultState>>,
273 direction: Direction,
274) {
275 let idx = direction.idx();
276 let mut total: u64 = 0;
277 let mut buf = [0u8; 4096];
278
279 loop {
280 let n = match reader.read(&mut buf).await {
281 Ok(0) | Err(_) => return,
282 Ok(n) => n,
283 };
284 let mut data = &buf[..n];
285
286 let (delay, close_after, chunk_size) = {
287 let s = state.read().unwrap();
288 (s.delay[idx], s.close_after[idx], s.chunk_size)
289 };
290
291 if let Some(limit) = close_after {
292 if total >= limit {
293 return;
294 }
295 let remaining = (limit - total) as usize;
296 if data.len() > remaining {
297 data = &data[..remaining];
298 }
299 }
300
301 if let Some(delay) = delay {
302 tokio::time::sleep(delay.resolve()).await;
303 }
304
305 let chunk = chunk_size.unwrap_or(data.len()).max(1);
306 for piece in data.chunks(chunk) {
307 if writer.write_all(piece).await.is_err() {
308 return;
309 }
310 }
311 total += data.len() as u64;
312
313 if let Some(limit) = close_after
314 && total >= limit
315 {
316 return;
317 }
318 }
319}