//! Implement a tcpProvider that can break things. #![allow(clippy::missing_docs_in_private_items)] // required for pin_project(enum) use futures::Stream; use tor_rtcompat::{Runtime, TcpListener, TcpProvider}; use anyhow::anyhow; use async_trait::async_trait; use futures::io::{AsyncRead, AsyncWrite}; use pin_project::pin_project; use rand::{thread_rng, Rng}; use std::io::{Error as IoError, ErrorKind as IoErrorKind, Result as IoResult}; use std::net::SocketAddr; use std::pin::Pin; use std::str::FromStr; use std::sync::{Arc, Mutex}; use std::task::{Context, Poll}; use std::time::Duration; /// An action that we can take upon trying to make a TCP connection. #[derive(Debug, Copy, Clone)] pub(crate) enum Action { /// Let the connection work as intended. Work, /// Wait for a random interval up to the given duration, then return an error. Fail(Duration, IoErrorKind), /// Time out indefinitely. Timeout, /// Succeed, then drop all data. Blackhole, } /// When should an Action apply? #[derive(Debug, Clone)] pub(crate) enum ActionPat { /// always apply Always, /// Apply to all ipv4 V4, /// apply to all ipv6 V6, /// apply to all ports but 443 Non443, } /// An Action plus a set of conditions when it applies. /// /// (When the action doesn't apply, connections will just `Action::Work`. #[derive(Debug, Clone)] pub(crate) struct ConditionalAction { /// The underlying action pub(crate) action: Action, /// When should the action apply? pub(crate) when: ActionPat, } impl FromStr for Action { type Err = anyhow::Error; fn from_str(s: &str) -> Result { Ok(match s { "none" | "work" => Action::Work, "error" => Action::Fail(Duration::from_millis(10), IoErrorKind::Other), "timeout" => Action::Timeout, "blackhole" => Action::Blackhole, _ => return Err(anyhow!("unrecognized tcp breakage action {:?}", s)), }) } } impl FromStr for ActionPat { type Err = anyhow::Error; fn from_str(s: &str) -> Result { Ok(match s { "all" => ActionPat::Always, "v4" => ActionPat::V4, "v6" => ActionPat::V6, "non443" => ActionPat::Non443, _ => return Err(anyhow!("unrecognized tcp breakage condition {:?}", s)), }) } } impl ConditionalAction { fn applies_to(&self, addr: &SocketAddr) -> bool { match (addr, &self.when) { (_, ActionPat::Always) => true, (SocketAddr::V4(_), ActionPat::V4) => true, (SocketAddr::V6(_), ActionPat::V6) => true, (sa, ActionPat::Non443) if sa.port() != 443 => true, (_, _) => false, } } } impl Default for ConditionalAction { fn default() -> Self { Self { action: Action::Work, when: ActionPat::Always, } } } /// A TcpProvider that can make its connections fail. #[pin_project] #[derive(Debug, Clone)] pub(crate) struct BrokenTcpProvider { /// An underlying TcpProvider to use when we actually want our connections to succeed #[pin] inner: R, /// The action to take when we try to make an outbound connection. action: Arc>, } impl BrokenTcpProvider { /// Construct a new BrokenTcpProvider which responds to all outbound /// connections by taking the specified action. pub(crate) fn new(inner: R, action: ConditionalAction) -> Self { Self { inner, action: Arc::new(Mutex::new(action)), } } /// Cause the provider to respond to all outbound connection attempts /// with the specified action. pub(crate) fn set_action(&self, action: ConditionalAction) { *self.action.lock().expect("Lock poisoned") = action; } /// Return the action to take for a connection to `addr`. fn get_action(&self, addr: &SocketAddr) -> Action { let action = self.action.lock().expect("Lock poisoned"); if action.applies_to(addr) { action.action } else { Action::Work } } } #[async_trait] impl TcpProvider for BrokenTcpProvider { type TcpStream = BreakableTcpStream; type TcpListener = BrokenTcpProvider; async fn connect(&self, addr: &SocketAddr) -> IoResult { match self.get_action(addr) { Action::Work => { let conn = self.inner.connect(addr).await?; Ok(BreakableTcpStream::Present(conn)) } Action::Fail(dur, kind) => { let d = thread_rng().gen_range(Duration::from_secs(0)..dur); self.inner.sleep(d).await; Err(IoError::new(kind, anyhow::anyhow!("intentional failure"))) } Action::Timeout => futures::future::pending().await, Action::Blackhole => Ok(BreakableTcpStream::Broken), } } async fn listen(&self, addr: &SocketAddr) -> IoResult { let listener = self.inner.listen(addr).await?; Ok(BrokenTcpProvider { inner: listener, action: self.action.clone(), }) } } /// A TCP stream that is either present, or black-holed. #[pin_project(project = BreakableTcpStreamP)] #[derive(Debug, Clone)] pub(crate) enum BreakableTcpStream { /// The stream is black-holed: there is nothing to read, and all writes /// succeed but are ignored. Broken, /// The stream is present and should work normally. Present(#[pin] S), } impl AsyncRead for BreakableTcpStream { fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { let this = self.project(); match this { BreakableTcpStreamP::Present(s) => s.poll_read(cx, buf), BreakableTcpStreamP::Broken => Poll::Pending, } } } impl AsyncWrite for BreakableTcpStream { fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { match self.project() { BreakableTcpStreamP::Present(s) => s.poll_write(cx, buf), BreakableTcpStreamP::Broken => Poll::Ready(Ok(buf.len())), } } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match self.project() { BreakableTcpStreamP::Present(s) => s.poll_flush(cx), BreakableTcpStreamP::Broken => Poll::Ready(Ok(())), } } fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match self.project() { BreakableTcpStreamP::Present(s) => s.poll_close(cx), BreakableTcpStreamP::Broken => Poll::Ready(Ok(())), } } } #[async_trait] impl TcpListener for BrokenTcpProvider { type TcpStream = BreakableTcpStream; type Incoming = BrokenTcpProvider; async fn accept(&self) -> IoResult<(Self::TcpStream, SocketAddr)> { let (inner, addr) = self.inner.accept().await?; Ok((BreakableTcpStream::Present(inner), addr)) } fn incoming(self) -> Self::Incoming { BrokenTcpProvider { inner: self.inner.incoming(), action: self.action, } } fn local_addr(&self) -> IoResult { self.inner.local_addr() } } impl Stream for BrokenTcpProvider where S: Stream>, { type Item = IoResult<(BreakableTcpStream, SocketAddr)>; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match self.project().inner.poll_next(cx) { Poll::Pending => Poll::Pending, Poll::Ready(None) => Poll::Ready(None), Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e))), Poll::Ready(Some(Ok((s, a)))) => { Poll::Ready(Some(Ok((BreakableTcpStream::Present(s), a)))) } } } }