From 4a44ef56c0743775d49eca138a253f11e93c7a35 Mon Sep 17 00:00:00 2001 From: trinity-1686a Date: Sat, 5 Mar 2022 19:49:38 +0100 Subject: [PATCH 1/7] add udp to runtime --- crates/arti-client/examples/hook-tcp.rs | 2 +- crates/arti-config/src/options.rs | 10 ++++ crates/arti-testing/src/main.rs | 1 + crates/arti/src/dns.rs | 21 ++++++++ crates/arti/src/lib.rs | 58 ++++++++++++++++++---- crates/arti/src/{proxy.rs => socks.rs} | 0 crates/tor-rtcompat/src/async_std.rs | 8 +-- crates/tor-rtcompat/src/compound.rs | 50 +++++++++++++++---- crates/tor-rtcompat/src/impls/async_std.rs | 47 +++++++++++++++++- crates/tor-rtcompat/src/impls/tokio.rs | 54 +++++++++++++++++++- crates/tor-rtcompat/src/lib.rs | 1 + crates/tor-rtcompat/src/opaque.rs | 10 ++++ crates/tor-rtcompat/src/tokio.rs | 36 +++++++++++--- crates/tor-rtcompat/src/traits.rs | 27 ++++++++++ crates/tor-rtmock/src/net_runtime.rs | 15 +++++- crates/tor-rtmock/src/sleep_runtime.rs | 11 +++- 16 files changed, 314 insertions(+), 37 deletions(-) create mode 100644 crates/arti/src/dns.rs rename crates/arti/src/{proxy.rs => socks.rs} (100%) diff --git a/crates/arti-client/examples/hook-tcp.rs b/crates/arti-client/examples/hook-tcp.rs index 4ad631ee..0b4950f1 100644 --- a/crates/arti-client/examples/hook-tcp.rs +++ b/crates/arti-client/examples/hook-tcp.rs @@ -30,7 +30,7 @@ async fn main() -> Result<()> { // Instantiate our custom TCP provider (see implementation below). let tcp_rt = CustomTcpProvider { inner: rt.clone() }; // Create a `CompoundRuntime`, swapping out the TCP part of the preferred runtime for our custom one. - let rt = CompoundRuntime::new(rt.clone(), rt.clone(), tcp_rt, rt); + let rt = CompoundRuntime::new(rt.clone(), rt.clone(), tcp_rt, rt.clone(), rt); eprintln!("connecting to Tor..."); // Pass in our custom runtime using `with_runtime`. diff --git a/crates/arti-config/src/options.rs b/crates/arti-config/src/options.rs index 9401cb17..8c180639 100644 --- a/crates/arti-config/src/options.rs +++ b/crates/arti-config/src/options.rs @@ -171,6 +171,10 @@ pub struct ProxyConfig { #[serde(default = "default_socks_port")] #[builder(default = "default_socks_port()")] socks_port: Option, + /// Port to lisen on (at localhost) for incoming DNS connections. + #[serde(default)] + #[builder(default)] + dns_port: Option, } /// Return the default value for `socks_port` @@ -196,6 +200,12 @@ impl ProxyConfig { pub fn socks_port(&self) -> Option { self.socks_port } + + /// Return the configured DNS port for this proxy configuration, + /// if one is enabled. + pub fn dns_port(&self) -> Option { + self.dns_port + } } /// Structure to hold Arti's configuration options, whether from a diff --git a/crates/arti-testing/src/main.rs b/crates/arti-testing/src/main.rs index dec085cc..9842daee 100644 --- a/crates/arti-testing/src/main.rs +++ b/crates/arti-testing/src/main.rs @@ -279,6 +279,7 @@ impl Job { runtime.clone(), runtime.clone(), counting_tcp.clone(), + runtime.clone(), runtime, ); let client = self.make_client(runtime)?; diff --git a/crates/arti/src/dns.rs b/crates/arti/src/dns.rs new file mode 100644 index 00000000..3c6c498d --- /dev/null +++ b/crates/arti/src/dns.rs @@ -0,0 +1,21 @@ +//! Implement a simple DNS resolver that relay request over Tor. +//! +//! A resolver is launched with [`run_dns_resolver()`], which listens for new +//! connections and then runs + +use arti_client::TorClient; +use tor_rtcompat::Runtime; + +use anyhow::Result; + +/// Launch a DNS resolver to lisetn on a given local port, and run +/// indefinitely. +pub(crate) async fn run_dns_resolver( + runtime: R, + _tor_client: TorClient, + _dns_port: u16, +) -> Result<()> { + loop { + runtime.sleep(std::time::Duration::from_secs(5)).await; + } +} diff --git a/crates/arti/src/lib.rs b/crates/arti/src/lib.rs index d109745d..b5f0f273 100644 --- a/crates/arti/src/lib.rs +++ b/crates/arti/src/lib.rs @@ -114,9 +114,10 @@ #![warn(clippy::unseparated_literal_suffix)] #![deny(clippy::unwrap_used)] +pub mod dns; pub mod exit; pub mod process; -pub mod proxy; +pub mod socks; pub mod trace; pub mod watch_cfg; @@ -138,6 +139,7 @@ use std::convert::TryInto; pub async fn run( runtime: R, socks_port: u16, + dns_port: u16, config_sources: arti_config::ConfigurationSources, arti_config: arti_config::ArtiConfig, client_config: TorClientConfig, @@ -153,11 +155,36 @@ pub async fn run( if arti_config.application().watch_configuration() { watch_cfg::watch_for_config_changes(config_sources, arti_config, client.clone())?; } + + let mut proxy: Vec>>>> = Vec::new(); + if socks_port != 0 { + proxy.push(Box::pin(socks::run_socks_proxy( + runtime.clone(), + client.isolated_client(), + socks_port, + ))); + } + + if dns_port != 0 { + proxy.push(Box::pin(dns::run_dns_resolver( + runtime.clone(), + client.isolated_client(), + dns_port, + ))); + } + + if proxy.is_empty() { + // TODO change this message so it's not only about socks_port + warn!("No proxy port set; specify -p PORT or use the `socks_port` configuration option."); + return Ok(()); + } + + let proxy = futures::future::select_all(proxy); futures::select!( r = exit::wait_for_ctrl_c().fuse() => r.context("waiting for termination signal"), - r = proxy::run_socks_proxy(runtime, client.clone(), socks_port).fuse() - => r.context("SOCKS proxy failure"), + r = proxy.fuse() + => r.0.context("SOCKS proxy failure"), r = async { client.bootstrap().await?; info!("Sufficiently bootstrapped; system SOCKS now functional."); @@ -270,12 +297,16 @@ pub fn main_main() -> Result<()> { ) { (Some(p), _) => p.parse().expect("Invalid port specified"), (None, Some(s)) => s, - (None, None) => { - warn!( - "No SOCKS port set; specify -p PORT or use the `socks_port` configuration option." - ); - return Ok(()); - } + (None, None) => 0, + }; + + let dns_port = match ( + proxy_matches.value_of("dns-port"), + config.proxy().dns_port(), + ) { + (Some(p), _) => p.parse().expect("Invalid port specified"), + (None, Some(s)) => s, + (None, None) => 0, }; let client_config = config.tor_client_config()?; @@ -303,7 +334,14 @@ pub fn main_main() -> Result<()> { let runtime = ChosenRuntime::create()?; let rt_copy = runtime.clone(); - rt_copy.block_on(run(runtime, socks_port, cfg_sources, config, client_config))?; + rt_copy.block_on(run( + runtime, + socks_port, + dns_port, + cfg_sources, + config, + client_config, + ))?; Ok(()) } else { panic!("Subcommand added to clap subcommand list, but not yet implemented") diff --git a/crates/arti/src/proxy.rs b/crates/arti/src/socks.rs similarity index 100% rename from crates/arti/src/proxy.rs rename to crates/arti/src/socks.rs diff --git a/crates/tor-rtcompat/src/async_std.rs b/crates/tor-rtcompat/src/async_std.rs index 6ac3a950..99059217 100644 --- a/crates/tor-rtcompat/src/async_std.rs +++ b/crates/tor-rtcompat/src/async_std.rs @@ -35,7 +35,7 @@ pub struct AsyncStdNativeTlsRuntime { /// Implementation type for AsyncStdRuntime. #[cfg(all(feature = "native-tls"))] -type NativeTlsInner = CompoundRuntime; +type NativeTlsInner = CompoundRuntime; #[cfg(all(feature = "native-tls"))] crate::opaque::implement_opaque_runtime! { @@ -52,7 +52,7 @@ pub struct AsyncStdRustlsRuntime { /// Implementation type for AsyncStdRustlsRuntime. #[cfg(feature = "rustls")] -type RustlsInner = CompoundRuntime; +type RustlsInner = CompoundRuntime; #[cfg(feature = "rustls")] crate::opaque::implement_opaque_runtime! { @@ -69,7 +69,7 @@ impl AsyncStdNativeTlsRuntime { pub fn create() -> IoResult { let rt = create_runtime_impl(); Ok(AsyncStdNativeTlsRuntime { - inner: CompoundRuntime::new(rt, rt, rt, NativeTlsProvider::default()), + inner: CompoundRuntime::new(rt, rt, rt, NativeTlsProvider::default(), rt), }) } @@ -113,7 +113,7 @@ impl AsyncStdRustlsRuntime { pub fn create() -> IoResult { let rt = create_runtime_impl(); Ok(AsyncStdRustlsRuntime { - inner: CompoundRuntime::new(rt, rt, rt, RustlsProvider::default()), + inner: CompoundRuntime::new(rt, rt, rt, RustlsProvider::default(), rt), }) } diff --git a/crates/tor-rtcompat/src/compound.rs b/crates/tor-rtcompat/src/compound.rs index f5163658..dbcbfd7d 100644 --- a/crates/tor-rtcompat/src/compound.rs +++ b/crates/tor-rtcompat/src/compound.rs @@ -21,16 +21,16 @@ use std::io::Result as IoResult; /// new runtime from pieces. #[derive(Educe)] #[educe(Clone)] // #[derive(Clone)] wrongly infers Clone bounds on the generic parameters -pub struct CompoundRuntime { +pub struct CompoundRuntime { /// The actual collection of Runtime objects. /// /// We wrap this in an Arc rather than requiring that each item implement /// Clone, though we could change our minds later on. - inner: Arc>, + inner: Arc>, } /// A collection of objects implementing that traits that make up a [`Runtime`] -struct Inner { +struct Inner { /// A `Spawn` and `BlockOn` implementation. spawn: SpawnR, /// A `SleepProvider` implementation. @@ -39,23 +39,26 @@ struct Inner { tcp: TcpR, /// A `TcpProvider` implementation. tls: TlsR, + /// A `UdpProvider` implementation + udp: UdpR, } -impl CompoundRuntime { +impl CompoundRuntime { /// Construct a new CompoundRuntime from its components. - pub fn new(spawn: SpawnR, sleep: SleepR, tcp: TcpR, tls: TlsR) -> Self { + pub fn new(spawn: SpawnR, sleep: SleepR, tcp: TcpR, tls: TlsR, udp: UdpR) -> Self { CompoundRuntime { inner: Arc::new(Inner { spawn, sleep, tcp, tls, + udp, }), } } } -impl Spawn for CompoundRuntime +impl Spawn for CompoundRuntime where SpawnR: Spawn, { @@ -65,7 +68,7 @@ where } } -impl BlockOn for CompoundRuntime +impl BlockOn for CompoundRuntime where SpawnR: BlockOn, { @@ -75,7 +78,8 @@ where } } -impl SleepProvider for CompoundRuntime +impl SleepProvider + for CompoundRuntime where SleepR: SleepProvider, { @@ -88,13 +92,15 @@ where } #[async_trait] -impl TcpProvider for CompoundRuntime +impl TcpProvider + for CompoundRuntime where TcpR: TcpProvider, SpawnR: Send + Sync + 'static, SleepR: Send + Sync + 'static, TcpR: Send + Sync + 'static, TlsR: Send + Sync + 'static, + UdpR: Send + Sync + 'static, { type TcpStream = TcpR::TcpStream; @@ -111,7 +117,8 @@ where } } -impl TlsProvider for CompoundRuntime +impl TlsProvider + for CompoundRuntime where TcpR: TcpProvider, TlsR: TlsProvider, @@ -125,8 +132,29 @@ where } } -impl std::fmt::Debug for CompoundRuntime { +impl std::fmt::Debug + for CompoundRuntime +{ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("CompoundRuntime").finish_non_exhaustive() } } + +#[async_trait] +impl UdpProvider + for CompoundRuntime +where + UdpR: UdpProvider, + SpawnR: Send + Sync + 'static, + SleepR: Send + Sync + 'static, + TcpR: Send + Sync + 'static, + TlsR: Send + Sync + 'static, + UdpR: Send + Sync + 'static, +{ + type UdpSocket = UdpR::UdpSocket; + + #[inline] + async fn bind(&self, addr: &SocketAddr) -> IoResult { + self.inner.udp.bind(addr).await + } +} diff --git a/crates/tor-rtcompat/src/impls/async_std.rs b/crates/tor-rtcompat/src/impls/async_std.rs index 4e6c2d8e..7c5ef4de 100644 --- a/crates/tor-rtcompat/src/impls/async_std.rs +++ b/crates/tor-rtcompat/src/impls/async_std.rs @@ -9,7 +9,7 @@ mod net { use crate::traits; - use async_std_crate::net::{TcpListener, TcpStream}; + use async_std_crate::net::{TcpListener, TcpStream, UdpSocket as StdUdpSocket}; use async_trait::async_trait; use futures::future::Future; use futures::stream::Stream; @@ -109,6 +109,51 @@ mod net { TcpListener::bind(*addr).await } } + + #[async_trait] + impl traits::UdpProvider for async_executors::AsyncStd { + type UdpSocket = UdpSocket; + + async fn bind(&self, addr: &std::net::SocketAddr) -> IoResult { + StdUdpSocket::bind(*addr) + .await + .map(|socket| UdpSocket { socket, addr: None }) + } + } + + /// Wrap a AsyncStd UdpSocket + pub struct UdpSocket { + /// The underlying UdpSocket + socket: StdUdpSocket, + /// The remote address if the socket is connected + addr: Option, + } + + #[async_trait] + impl traits::UdpSocket for UdpSocket { + async fn recv(&mut self, buf: &mut [u8]) -> IoResult<(usize, SocketAddr)> { + if let Some(addr) = self.addr { + self.socket.recv(buf).await.map(|r| (r, addr)) + } else { + self.socket.recv_from(buf).await + } + } + + async fn send(&mut self, buf: &[u8], target: &SocketAddr) -> IoResult { + if let Some(addr) = self.addr { + debug_assert!(addr == *target); + self.socket.send(buf).await + } else { + self.socket.send_to(buf, target).await + } + } + + async fn connect(&mut self, addr: &SocketAddr) -> IoResult<()> { + self.socket.connect(addr).await?; + self.addr = Some(*addr); + Ok(()) + } + } } // ============================== diff --git a/crates/tor-rtcompat/src/impls/tokio.rs b/crates/tor-rtcompat/src/impls/tokio.rs index a7838d79..d75eec99 100644 --- a/crates/tor-rtcompat/src/impls/tokio.rs +++ b/crates/tor-rtcompat/src/impls/tokio.rs @@ -9,7 +9,7 @@ pub(crate) mod net { use async_trait::async_trait; pub(crate) use tokio_crate::net::{ - TcpListener as TokioTcpListener, TcpStream as TokioTcpStream, + TcpListener as TokioTcpListener, TcpStream as TokioTcpStream, UdpSocket as TokioUdpSocket, }; use futures::io::{AsyncRead, AsyncWrite}; @@ -98,6 +98,49 @@ pub(crate) mod net { self.lis.local_addr() } } + + /// Wrap a Tokio UdpSocket + pub struct UdpSocket { + /// The underelying UdpSocket + socket: TokioUdpSocket, + /// The remote address if the socket is connected + addr: Option, + } + + impl UdpSocket { + /// Bind a UdpSocket + pub async fn bind(addr: SocketAddr) -> IoResult { + TokioUdpSocket::bind(addr) + .await + .map(|socket| UdpSocket { socket, addr: None }) + } + } + + #[async_trait] + impl traits::UdpSocket for UdpSocket { + async fn recv(&mut self, buf: &mut [u8]) -> IoResult<(usize, SocketAddr)> { + if let Some(addr) = self.addr { + self.socket.recv(buf).await.map(|r| (r, addr)) + } else { + self.socket.recv_from(buf).await + } + } + + async fn send(&mut self, buf: &[u8], target: &SocketAddr) -> IoResult { + if let Some(addr) = self.addr { + debug_assert!(addr == *target); + self.socket.send(buf).await + } else { + self.socket.send_to(buf, target).await + } + } + + async fn connect(&mut self, addr: &SocketAddr) -> IoResult<()> { + self.socket.connect(addr).await?; + self.addr = Some(*addr); + Ok(()) + } + } } // ============================== @@ -130,6 +173,15 @@ impl crate::traits::TcpProvider for TokioRuntimeHandle { } } +#[async_trait] +impl crate::traits::UdpProvider for TokioRuntimeHandle { + type UdpSocket = net::UdpSocket; + + async fn bind(&self, addr: &std::net::SocketAddr) -> IoResult { + net::UdpSocket::bind(*addr).await + } +} + /// Create and return a new Tokio multithreaded runtime. pub(crate) fn create_runtime() -> IoResult { let mut builder = async_executors::TokioTpBuilder::new(); diff --git a/crates/tor-rtcompat/src/lib.rs b/crates/tor-rtcompat/src/lib.rs index 91ae56ed..d0296cd3 100644 --- a/crates/tor-rtcompat/src/lib.rs +++ b/crates/tor-rtcompat/src/lib.rs @@ -181,6 +181,7 @@ mod traits; use std::io; pub use traits::{ BlockOn, CertifiedConn, Runtime, SleepProvider, TcpListener, TcpProvider, TlsProvider, + UdpProvider, UdpSocket, }; pub use timer::{SleepProviderExt, Timeout, TimeoutError}; diff --git a/crates/tor-rtcompat/src/opaque.rs b/crates/tor-rtcompat/src/opaque.rs index 3326e10f..be20b38f 100644 --- a/crates/tor-rtcompat/src/opaque.rs +++ b/crates/tor-rtcompat/src/opaque.rs @@ -57,6 +57,16 @@ macro_rules! implement_opaque_runtime { } } + #[async_trait::async_trait] + impl $crate::traits::UdpProvider for $t { + type UdpSocket = <$mty as $crate::traits::UdpProvider>::UdpSocket; + + #[inline] + async fn bind(&self, addr: &std::net::SocketAddr) -> std::io::Result { + self.$member.bind(addr).await + } + } + impl std::fmt::Debug for $t { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct(stringify!($t)).finish_non_exhaustive() diff --git a/crates/tor-rtcompat/src/tokio.rs b/crates/tor-rtcompat/src/tokio.rs index 3434a850..9c649d3c 100644 --- a/crates/tor-rtcompat/src/tokio.rs +++ b/crates/tor-rtcompat/src/tokio.rs @@ -39,7 +39,7 @@ pub struct TokioNativeTlsRuntime { /// Implementation type for a TokioRuntimeHandle. #[cfg(feature = "native-tls")] -type HandleInner = CompoundRuntime; +type HandleInner = CompoundRuntime; /// A [`Runtime`](crate::Runtime) built around a Handle to a tokio runtime, and `rustls`. #[derive(Clone)] @@ -51,7 +51,7 @@ pub struct TokioRustlsRuntime { /// Implementation for a TokioRuntimeRustlsHandle #[cfg(feature = "rustls")] -type RustlsHandleInner = CompoundRuntime; +type RustlsHandleInner = CompoundRuntime; #[cfg(feature = "native-tls")] crate::opaque::implement_opaque_runtime! { @@ -68,7 +68,13 @@ impl From for TokioNativeTlsRuntime { fn from(h: tokio_crate::runtime::Handle) -> Self { let h = Handle::new(h); TokioNativeTlsRuntime { - inner: CompoundRuntime::new(h.clone(), h.clone(), h, NativeTlsProvider::default()), + inner: CompoundRuntime::new( + h.clone(), + h.clone(), + h.clone(), + NativeTlsProvider::default(), + h, + ), } } } @@ -78,7 +84,13 @@ impl From for TokioRustlsRuntime { fn from(h: tokio_crate::runtime::Handle) -> Self { let h = Handle::new(h); TokioRustlsRuntime { - inner: CompoundRuntime::new(h.clone(), h.clone(), h, RustlsProvider::default()), + inner: CompoundRuntime::new( + h.clone(), + h.clone(), + h.clone(), + RustlsProvider::default(), + h, + ), } } } @@ -94,7 +106,13 @@ impl TokioNativeTlsRuntime { /// [`TokioNativeTlsRuntime::current()`]. pub fn create() -> IoResult { crate::impls::tokio::create_runtime().map(|r| TokioNativeTlsRuntime { - inner: CompoundRuntime::new(r.clone(), r.clone(), r, NativeTlsProvider::default()), + inner: CompoundRuntime::new( + r.clone(), + r.clone(), + r.clone(), + NativeTlsProvider::default(), + r, + ), }) } @@ -146,7 +164,13 @@ impl TokioRustlsRuntime { /// [`TokioRustlsRuntime::current()`]. pub fn create() -> IoResult { crate::impls::tokio::create_runtime().map(|r| TokioRustlsRuntime { - inner: CompoundRuntime::new(r.clone(), r.clone(), r, RustlsProvider::default()), + inner: CompoundRuntime::new( + r.clone(), + r.clone(), + r.clone(), + RustlsProvider::default(), + r, + ), }) } diff --git a/crates/tor-rtcompat/src/traits.rs b/crates/tor-rtcompat/src/traits.rs index 6fe9802a..41c6f785 100644 --- a/crates/tor-rtcompat/src/traits.rs +++ b/crates/tor-rtcompat/src/traits.rs @@ -52,6 +52,7 @@ pub trait Runtime: + SleepProvider + TcpProvider + TlsProvider + + UdpProvider + 'static { } @@ -65,6 +66,7 @@ impl Runtime for T where + SleepProvider + TcpProvider + TlsProvider + + UdpProvider + 'static { } @@ -180,6 +182,31 @@ pub trait TcpListener { fn local_addr(&self) -> IoResult; } +/// Trait for a runtime that can send and receive UDP datagrams. +#[async_trait] +pub trait UdpProvider { + /// The type of Udp Socket returned by [`Self::bind()`] + type UdpSocket: UdpSocket + Send + Sync + Unpin + 'static; + + /// Bind a local port to send and receive packets from + async fn bind(&self, addr: &SocketAddr) -> IoResult; +} + +/// Trait for a localy bound Udp socket that can send and receive datagrams. +/// +/// These objects are returned by instances of [`UdpProvider`]. +#[async_trait] +pub trait UdpSocket { + /// Wait for an incoming datagram; return it along its address. + async fn recv(&mut self, buf: &mut [u8]) -> IoResult<(usize, SocketAddr)>; + /// Send a datagram to the provided address. + async fn send(&mut self, buf: &[u8], target: &SocketAddr) -> IoResult; + /// Connect to a remote address. After calling this [`UdpSocket::recv`] may only + /// return that same address, and the target provided to [`UdpSocket::send`] must + /// be this address. + async fn connect(&mut self, addr: &SocketAddr) -> IoResult<()>; +} + /// An object with a peer certificate: typically a TLS connection. pub trait CertifiedConn { /// Try to return the (DER-encoded) peer certificate for this diff --git a/crates/tor-rtmock/src/net_runtime.rs b/crates/tor-rtmock/src/net_runtime.rs index bc59f723..12b706cb 100644 --- a/crates/tor-rtmock/src/net_runtime.rs +++ b/crates/tor-rtmock/src/net_runtime.rs @@ -4,7 +4,7 @@ // we should make it so that more code is more shared. use crate::net::MockNetProvider; -use tor_rtcompat::{BlockOn, Runtime, SleepProvider, TcpProvider, TlsProvider}; +use tor_rtcompat::{BlockOn, Runtime, SleepProvider, TcpProvider, TlsProvider, UdpProvider}; use crate::io::LocalStream; use async_trait::async_trait; @@ -20,7 +20,7 @@ use std::time::{Duration, Instant, SystemTime}; pub struct MockNetRuntime { /// The underlying runtime. Most calls get delegated here. runtime: R, - /// A MockNetProvider. Time-related calls get delegated here. + /// A MockNetProvider. Network-related calls get delegated here. net: MockNetProvider, } @@ -75,6 +75,17 @@ impl TlsProvider for MockNetRuntime { } } +#[async_trait] +impl UdpProvider for MockNetRuntime { + type UdpSocket = R::UdpSocket; + + #[inline] + async fn bind(&self, addr: &SocketAddr) -> IoResult { + // TODO this should probably get delegated to MockNetProvider instead + self.runtime.bind(addr).await + } +} + impl SleepProvider for MockNetRuntime { type SleepFuture = R::SleepFuture; fn sleep(&self, dur: Duration) -> Self::SleepFuture { diff --git a/crates/tor-rtmock/src/sleep_runtime.rs b/crates/tor-rtmock/src/sleep_runtime.rs index 59855cb4..bcbc9c30 100644 --- a/crates/tor-rtmock/src/sleep_runtime.rs +++ b/crates/tor-rtmock/src/sleep_runtime.rs @@ -1,7 +1,7 @@ //! Declare MockSleepRuntime. use crate::time::MockSleepProvider; -use tor_rtcompat::{BlockOn, Runtime, SleepProvider, TcpProvider, TlsProvider}; +use tor_rtcompat::{BlockOn, Runtime, SleepProvider, TcpProvider, TlsProvider, UdpProvider}; use async_trait::async_trait; use futures::task::{FutureObj, Spawn, SpawnError}; @@ -113,6 +113,15 @@ impl TlsProvider for MockSleepRuntime { } } +#[async_trait] +impl UdpProvider for MockSleepRuntime { + type UdpSocket = R::UdpSocket; + + async fn bind(&self, addr: &SocketAddr) -> IoResult { + self.runtime.bind(addr).await + } +} + impl SleepProvider for MockSleepRuntime { type SleepFuture = crate::time::Sleeping; fn sleep(&self, dur: Duration) -> Self::SleepFuture { -- GitLab From 9b3be0ad47ea5eca08596d83d3a25bd3ae0c2abf Mon Sep 17 00:00:00 2001 From: trinity-1686a Date: Sun, 6 Mar 2022 17:34:13 +0100 Subject: [PATCH 2/7] add skeleton for DNS handling --- crates/arti/src/dns.rs | 94 ++++++++++++++++++++-- crates/arti/src/socks.rs | 4 +- crates/tor-rtcompat/src/impls/async_std.rs | 4 +- crates/tor-rtcompat/src/impls/tokio.rs | 4 +- crates/tor-rtcompat/src/traits.rs | 7 +- 5 files changed, 99 insertions(+), 14 deletions(-) diff --git a/crates/arti/src/dns.rs b/crates/arti/src/dns.rs index 3c6c498d..5b15364e 100644 --- a/crates/arti/src/dns.rs +++ b/crates/arti/src/dns.rs @@ -3,19 +3,101 @@ //! A resolver is launched with [`run_dns_resolver()`], which listens for new //! connections and then runs +use futures::stream::StreamExt; +use futures::task::SpawnExt; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use std::sync::Arc; +use tracing::{error, info, warn}; + use arti_client::TorClient; -use tor_rtcompat::Runtime; +use tor_rtcompat::{Runtime, UdpSocket}; + +use anyhow::{anyhow, Result}; -use anyhow::Result; +/// Given a datagram containing a DNS query, resolve the query over +/// the Tor network and send the response back. +async fn handle_dns_req( + _tor_client: TorClient, + packet: &[u8], + addr: SocketAddr, + socket: Arc, +) -> Result<()> +where + R: Runtime, + U: UdpSocket, +{ + // TODO actually process the request + socket.send(packet, &addr).await?; + Ok(()) +} /// Launch a DNS resolver to lisetn on a given local port, and run /// indefinitely. pub(crate) async fn run_dns_resolver( runtime: R, - _tor_client: TorClient, - _dns_port: u16, + tor_client: TorClient, + dns_port: u16, ) -> Result<()> { - loop { - runtime.sleep(std::time::Duration::from_secs(5)).await; + let mut listeners = Vec::new(); + + // We actually listen on two ports: one for ipv4 and one for ipv6. + let localhosts: [IpAddr; 2] = [Ipv4Addr::LOCALHOST.into(), Ipv6Addr::LOCALHOST.into()]; + + // Try to bind to the DNS ports. + for localhost in &localhosts { + let addr: SocketAddr = (*localhost, dns_port).into(); + match runtime.bind(&addr).await { + Ok(listener) => { + info!("Listening on {:?}.", addr); + listeners.push(listener); + } + Err(e) => warn!("Can't listen on {:?}: {}", addr, e), + } } + // We weren't able to bind any ports: There's nothing to do. + if listeners.is_empty() { + error!("Couldn't open any DNS listeners."); + return Err(anyhow!("Couldn't open SOCKS listeners")); + } + + let mut incoming = futures::stream::select_all( + listeners + .into_iter() + .map(|socket| { + futures::stream::unfold(Arc::new(socket), |socket| async { + let mut packet = [0; 1536]; + let packet = socket + .recv(&mut packet) + .await + .map(|(size, remote)| (packet, size, remote, socket.clone())); + Some((packet, socket)) + }) + }) + .enumerate() + .map(|(listener_id, incoming_packet)| { + Box::pin(incoming_packet.map(move |packet| (packet, listener_id))) + }), + ); + + while let Some((packet, _id)) = incoming.next().await { + let (packet, size, addr, socket) = match packet { + Ok(packet) => packet, + Err(err) => { + // TODO move socks::accept_err_is_fatal somewhere else and use it here? + warn!("Incoming datagram failed: {}", err); + continue; + } + }; + + let client_ref = tor_client.clone(); + // TODO implement isolation + runtime.spawn(async move { + let res = handle_dns_req(client_ref, &packet[..size], addr, socket).await; + if let Err(e) = res { + warn!("connection exited with error: {}", e); + } + })?; + } + + Ok(()) } diff --git a/crates/arti/src/socks.rs b/crates/arti/src/socks.rs index 35cdf8ec..5a49ec31 100644 --- a/crates/arti/src/socks.rs +++ b/crates/arti/src/socks.rs @@ -444,8 +444,8 @@ pub async fn run_socks_proxy( } // We weren't able to bind any ports: There's nothing to do. if listeners.is_empty() { - error!("Couldn't open any listeners."); - return Err(anyhow!("Couldn't open listeners")); + error!("Couldn't open any SOCKS listeners."); + return Err(anyhow!("Couldn't open SOCKS listeners")); } // Create a stream of (incoming socket, listener_id) pairs, selected diff --git a/crates/tor-rtcompat/src/impls/async_std.rs b/crates/tor-rtcompat/src/impls/async_std.rs index 7c5ef4de..dae76ff1 100644 --- a/crates/tor-rtcompat/src/impls/async_std.rs +++ b/crates/tor-rtcompat/src/impls/async_std.rs @@ -131,7 +131,7 @@ mod net { #[async_trait] impl traits::UdpSocket for UdpSocket { - async fn recv(&mut self, buf: &mut [u8]) -> IoResult<(usize, SocketAddr)> { + async fn recv(&self, buf: &mut [u8]) -> IoResult<(usize, SocketAddr)> { if let Some(addr) = self.addr { self.socket.recv(buf).await.map(|r| (r, addr)) } else { @@ -139,7 +139,7 @@ mod net { } } - async fn send(&mut self, buf: &[u8], target: &SocketAddr) -> IoResult { + async fn send(&self, buf: &[u8], target: &SocketAddr) -> IoResult { if let Some(addr) = self.addr { debug_assert!(addr == *target); self.socket.send(buf).await diff --git a/crates/tor-rtcompat/src/impls/tokio.rs b/crates/tor-rtcompat/src/impls/tokio.rs index d75eec99..849f26f5 100644 --- a/crates/tor-rtcompat/src/impls/tokio.rs +++ b/crates/tor-rtcompat/src/impls/tokio.rs @@ -118,7 +118,7 @@ pub(crate) mod net { #[async_trait] impl traits::UdpSocket for UdpSocket { - async fn recv(&mut self, buf: &mut [u8]) -> IoResult<(usize, SocketAddr)> { + async fn recv(&self, buf: &mut [u8]) -> IoResult<(usize, SocketAddr)> { if let Some(addr) = self.addr { self.socket.recv(buf).await.map(|r| (r, addr)) } else { @@ -126,7 +126,7 @@ pub(crate) mod net { } } - async fn send(&mut self, buf: &[u8], target: &SocketAddr) -> IoResult { + async fn send(&self, buf: &[u8], target: &SocketAddr) -> IoResult { if let Some(addr) = self.addr { debug_assert!(addr == *target); self.socket.send(buf).await diff --git a/crates/tor-rtcompat/src/traits.rs b/crates/tor-rtcompat/src/traits.rs index 41c6f785..6901a891 100644 --- a/crates/tor-rtcompat/src/traits.rs +++ b/crates/tor-rtcompat/src/traits.rs @@ -198,12 +198,15 @@ pub trait UdpProvider { #[async_trait] pub trait UdpSocket { /// Wait for an incoming datagram; return it along its address. - async fn recv(&mut self, buf: &mut [u8]) -> IoResult<(usize, SocketAddr)>; + async fn recv(&self, buf: &mut [u8]) -> IoResult<(usize, SocketAddr)>; /// Send a datagram to the provided address. - async fn send(&mut self, buf: &[u8], target: &SocketAddr) -> IoResult; + async fn send(&self, buf: &[u8], target: &SocketAddr) -> IoResult; /// Connect to a remote address. After calling this [`UdpSocket::recv`] may only /// return that same address, and the target provided to [`UdpSocket::send`] must /// be this address. + // rational for taking &mut self: this changes the behavior of the whole socket, + // so it should probably only be used when you have ownership of the socket and + // not when sharing it. async fn connect(&mut self, addr: &SocketAddr) -> IoResult<()>; } -- GitLab From 604362bf8054032163643017a813f6f17aa46dd6 Mon Sep 17 00:00:00 2001 From: trinity-1686a Date: Sun, 6 Mar 2022 20:44:50 +0100 Subject: [PATCH 3/7] actually add DNS support --- Cargo.lock | 56 +++++++++++++++++++++ crates/arti-client/src/client.rs | 2 +- crates/arti/Cargo.toml | 1 + crates/arti/src/dns.rs | 84 ++++++++++++++++++++++++++++++-- crates/arti/src/lib.rs | 7 +++ 5 files changed, 145 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5ed30754..f40d923c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -96,6 +96,7 @@ dependencies = [ "tracing-appender", "tracing-journald", "tracing-subscriber", + "trust-dns-proto", "winapi 0.3.9", ] @@ -849,6 +850,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + [[package]] name = "der" version = "0.4.5" @@ -1020,6 +1027,18 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "enum-as-inner" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21cdad81446a7f7dc43f6a77409efeb9733d2fa65553efef6018ef257c959b73" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "enum-ordinalize" version = "3.1.10" @@ -1395,6 +1414,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -1571,6 +1596,12 @@ dependencies = [ "libc", ] +[[package]] +name = "ipnet" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" + [[package]] name = "itertools" version = "0.10.3" @@ -3684,6 +3715,31 @@ dependencies = [ "syn", ] +[[package]] +name = "trust-dns-proto" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2861b3ed517888174d13909e675c4e94b3291867512068be59d76533e4d1270c" +dependencies = [ + "async-trait", + "cfg-if 1.0.0", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "lazy_static", + "log", + "rand 0.8.5", + "smallvec", + "thiserror", + "tinyvec", + "tokio", + "url", +] + [[package]] name = "try-lock" version = "0.2.3" diff --git a/crates/arti-client/src/client.rs b/crates/arti-client/src/client.rs index c8693945..d728ef2d 100644 --- a/crates/arti-client/src/client.rs +++ b/crates/arti-client/src/client.rs @@ -724,7 +724,7 @@ impl TorClient { hostname: &str, prefs: &StreamPrefs, ) -> crate::Result> { - let addr = (hostname, 0).into_tor_addr().map_err(wrap_err)?; + let addr = (hostname, 1).into_tor_addr().map_err(wrap_err)?; addr.enforce_config(&self.addrcfg.get()).map_err(wrap_err)?; let circ = self.get_or_launch_exit_circ(&[], prefs).await?; diff --git a/crates/arti/Cargo.toml b/crates/arti/Cargo.toml index 7022723c..d7eadd44 100644 --- a/crates/arti/Cargo.toml +++ b/crates/arti/Cargo.toml @@ -43,6 +43,7 @@ tokio-crate = { package = "tokio", version = "1.7", optional = true, features = clap = "2.33.0" tracing-journald = { version = "0.2.0", optional = true } tracing-appender = "0.2.0" +trust-dns-proto = "0.21.1" [target.'cfg(unix)'.dependencies] libc = { version = "0.2", default-features = false } diff --git a/crates/arti/src/dns.rs b/crates/arti/src/dns.rs index 5b15364e..034daa2a 100644 --- a/crates/arti/src/dns.rs +++ b/crates/arti/src/dns.rs @@ -8,16 +8,28 @@ use futures::task::SpawnExt; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::sync::Arc; use tracing::{error, info, warn}; +use trust_dns_proto::op::{ + header::MessageType, op_code::OpCode, response_code::ResponseCode, Message, +}; +use trust_dns_proto::rr::{DNSClass, Name, RData, Record, RecordType}; +use trust_dns_proto::serialize::binary::{BinDecodable, BinEncodable}; use arti_client::TorClient; use tor_rtcompat::{Runtime, UdpSocket}; use anyhow::{anyhow, Result}; +/// Send an error DNS response with code NotImplemented +async fn not_implemented(id: u16, addr: &SocketAddr, socket: &U) -> Result<()> { + let response = Message::error_msg(id, OpCode::Query, ResponseCode::NotImp); + socket.send(&response.to_bytes()?, addr).await?; + Ok(()) +} + /// Given a datagram containing a DNS query, resolve the query over /// the Tor network and send the response back. async fn handle_dns_req( - _tor_client: TorClient, + tor_client: TorClient, packet: &[u8], addr: SocketAddr, socket: Arc, @@ -26,8 +38,72 @@ where R: Runtime, U: UdpSocket, { - // TODO actually process the request - socket.send(packet, &addr).await?; + let mut query = Message::from_bytes(packet)?; + let id = query.id(); + + let mut answers = Vec::new(); + + for query in query.queries() { + let mut a = Vec::new(); + let mut ptr = Vec::new(); + // TODO maybe support ANY? + match query.query_class() { + DNSClass::IN => { + match query.query_type() { + typ @ RecordType::A | typ @ RecordType::AAAA => { + let mut name = query.name().clone(); + // name would be "torproject.org." without this + name.set_fqdn(false); + let res = tor_client.resolve(&name.to_utf8()).await?; + for ip in res { + a.push((query.name().clone(), ip, typ)); + } + } + RecordType::PTR => { + let addr = query.name().parse_arpa_name()?.addr(); + let res = tor_client.resolve_ptr(addr).await?; + for domain in res { + let domain = Name::from_utf8(domain)?; + ptr.push((query.name().clone(), domain)); + } + } + _ => { + return not_implemented(id, &addr, &*socket).await; + } + } + } + _ => { + return not_implemented(id, &addr, &*socket).await; + } + } + for (name, ip, typ) in a { + match (ip, typ) { + (IpAddr::V4(v4), RecordType::A) => { + answers.push(Record::from_rdata(name, 3600, RData::A(v4))); + } + (IpAddr::V6(v6), RecordType::AAAA) => { + answers.push(Record::from_rdata(name, 3600, RData::AAAA(v6))); + } + _ => (), + } + } + for (ptr, name) in ptr { + answers.push(Record::from_rdata(ptr, 3600, RData::PTR(name))); + } + } + + let mut response = Message::new(); + response + .set_id(id) + .set_message_type(MessageType::Response) + .set_op_code(OpCode::Query) + .set_recursion_desired(query.recursion_desired()) + .set_recursion_available(true) + .add_queries(query.take_queries()) + .add_answers(answers); + // TODO maybe add some edns? + + socket.send(&response.to_bytes()?, &addr).await?; Ok(()) } @@ -83,7 +159,7 @@ pub(crate) async fn run_dns_resolver( let (packet, size, addr, socket) = match packet { Ok(packet) => packet, Err(err) => { - // TODO move socks::accept_err_is_fatal somewhere else and use it here? + // TODO move crate::socks::accept_err_is_fatal somewhere else and use it here? warn!("Incoming datagram failed: {}", err); continue; } diff --git a/crates/arti/src/lib.rs b/crates/arti/src/lib.rs index b5f0f273..87dbd775 100644 --- a/crates/arti/src/lib.rs +++ b/crates/arti/src/lib.rs @@ -260,6 +260,13 @@ pub fn main_main() -> Result<()> { .value_name("PORT") .help("Port to listen on for SOCKS connections (overrides the port in the config if specified).") ) + .arg( + Arg::with_name("dns-port") + .short("d") + .takes_value(true) + .value_name("PORT") + .help("Port to listen on for DNS request (overrides the port in the config if specified).") + ) ) .setting(AppSettings::SubcommandRequiredElseHelp) .get_matches(); -- GitLab From fa2992568921f2e5ce5be590c69f9dc618519292 Mon Sep 17 00:00:00 2001 From: trinity-1686a Date: Mon, 7 Mar 2022 23:06:38 +0100 Subject: [PATCH 4/7] fix typos and minor issues --- crates/arti/src/dns.rs | 7 +++++-- crates/arti/src/lib.rs | 31 ++++++++++++++++++------------- crates/tor-rtcompat/src/traits.rs | 2 +- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/crates/arti/src/dns.rs b/crates/arti/src/dns.rs index 034daa2a..8e3970c9 100644 --- a/crates/arti/src/dns.rs +++ b/crates/arti/src/dns.rs @@ -19,6 +19,9 @@ use tor_rtcompat::{Runtime, UdpSocket}; use anyhow::{anyhow, Result}; +/// Maximum lenght for receiving a single datagram +const MAX_DATAGRAM_SIZE: usize = 1536; + /// Send an error DNS response with code NotImplemented async fn not_implemented(id: u16, addr: &SocketAddr, socket: &U) -> Result<()> { let response = Message::error_msg(id, OpCode::Query, ResponseCode::NotImp); @@ -133,7 +136,7 @@ pub(crate) async fn run_dns_resolver( // We weren't able to bind any ports: There's nothing to do. if listeners.is_empty() { error!("Couldn't open any DNS listeners."); - return Err(anyhow!("Couldn't open SOCKS listeners")); + return Err(anyhow!("Couldn't open any DNS listeners")); } let mut incoming = futures::stream::select_all( @@ -141,7 +144,7 @@ pub(crate) async fn run_dns_resolver( .into_iter() .map(|socket| { futures::stream::unfold(Arc::new(socket), |socket| async { - let mut packet = [0; 1536]; + let mut packet = [0; MAX_DATAGRAM_SIZE]; let packet = socket .recv(&mut packet) .await diff --git a/crates/arti/src/lib.rs b/crates/arti/src/lib.rs index 87dbd775..e4d357d7 100644 --- a/crates/arti/src/lib.rs +++ b/crates/arti/src/lib.rs @@ -131,6 +131,9 @@ use tracing::{info, warn}; use std::convert::TryInto; +/// Shorthand for a boxed and pinned Future. +type PinnedFuture = std::pin::Pin>>; + /// Run the main loop of the proxy. /// /// # Panics @@ -156,21 +159,23 @@ pub async fn run( watch_cfg::watch_for_config_changes(config_sources, arti_config, client.clone())?; } - let mut proxy: Vec>>>> = Vec::new(); + let mut proxy: Vec, &str)>> = Vec::new(); if socks_port != 0 { - proxy.push(Box::pin(socks::run_socks_proxy( - runtime.clone(), - client.isolated_client(), - socks_port, - ))); + let runtime = runtime.clone(); + let client = client.isolated_client(); + proxy.push(Box::pin(async move { + let res = socks::run_socks_proxy(runtime, client, socks_port).await; + (res, "SOCKS") + })); } if dns_port != 0 { - proxy.push(Box::pin(dns::run_dns_resolver( - runtime.clone(), - client.isolated_client(), - dns_port, - ))); + let runtime = runtime.clone(); + let client = client.isolated_client(); + proxy.push(Box::pin(async move { + let res = dns::run_dns_resolver(runtime, client, dns_port).await; + (res, "DNS") + })); } if proxy.is_empty() { @@ -179,12 +184,12 @@ pub async fn run( return Ok(()); } - let proxy = futures::future::select_all(proxy); + let proxy = futures::future::select_all(proxy).map(|(finished, _index, _others)| finished); futures::select!( r = exit::wait_for_ctrl_c().fuse() => r.context("waiting for termination signal"), r = proxy.fuse() - => r.0.context("SOCKS proxy failure"), + => r.0.context(format!("{} proxy failure", r.1)), r = async { client.bootstrap().await?; info!("Sufficiently bootstrapped; system SOCKS now functional."); diff --git a/crates/tor-rtcompat/src/traits.rs b/crates/tor-rtcompat/src/traits.rs index 6901a891..7c028d57 100644 --- a/crates/tor-rtcompat/src/traits.rs +++ b/crates/tor-rtcompat/src/traits.rs @@ -204,7 +204,7 @@ pub trait UdpSocket { /// Connect to a remote address. After calling this [`UdpSocket::recv`] may only /// return that same address, and the target provided to [`UdpSocket::send`] must /// be this address. - // rational for taking &mut self: this changes the behavior of the whole socket, + // rationale for taking &mut self: this changes the behavior of the whole socket, // so it should probably only be used when you have ownership of the socket and // not when sharing it. async fn connect(&mut self, addr: &SocketAddr) -> IoResult<()>; -- GitLab From b7daa9ff1272d79ce3d4981af6a3d73dac26a2d6 Mon Sep 17 00:00:00 2001 From: trinity-1686a Date: Fri, 11 Mar 2022 17:56:41 +0100 Subject: [PATCH 5/7] add integration test and fill semver_status --- .gitlab-ci.yml | 4 ++-- doc/semver_status.md | 6 +++++- tests/chutney/setup | 2 +- tests/chutney/test | 6 ++++++ 4 files changed, 14 insertions(+), 4 deletions(-) create mode 100755 tests/chutney/test diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bb601776..f5dcb5c8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -137,9 +137,9 @@ integration: image: debian:stable-slim script: - apt update - - apt install -y tor git python3 curl + - apt install -y tor git python3 curl dnsutils - ./tests/chutney/setup proxy - - curl http://example.com -vs --socks5-hostname 127.0.0.1:9150 -o /dev/null + - ./tests/chutney/test - ./tests/chutney/stop-arti - RUST_LOG=debug target/x86_64-unknown-linux-gnu/release/arti-bench -c ./chutney/net/nodes/arti.toml --socks5 127.0.0.1:9008 -o benchmark_results.json - ./tests/chutney/teardown diff --git a/doc/semver_status.md b/doc/semver_status.md index 6bdb51c3..07dbb24b 100644 --- a/doc/semver_status.md +++ b/doc/semver_status.md @@ -37,7 +37,7 @@ arti: tor-llcrypto: - new-api: Added RsaIdentity::from_hex(). + new-api: Added RsaIdentity::from\_hex(). arti-client: @@ -62,3 +62,7 @@ tor-basic-utils: Remove `humantime_serde_option` module. (Use `humantime_serde::option` instead.) + +tor-rtcompt: + + api-break: Runtime require an additional supertrait UdpProvider diff --git a/tests/chutney/setup b/tests/chutney/setup index 7e929e1f..d97beb6b 100755 --- a/tests/chutney/setup +++ b/tests/chutney/setup @@ -88,7 +88,7 @@ fi ( set +e - "$cmd" proxy -c "${CHUTNEY_PATH}/net/nodes/arti.toml" & + "$cmd" proxy -c "${CHUTNEY_PATH}/net/nodes/arti.toml" -p 5353 & pid=$! echo "target=$target" > tests/chutney/arti.run echo "pid=$pid" >> tests/chutney/arti.run diff --git a/tests/chutney/test b/tests/chutney/test new file mode 100755 index 00000000..c20c4c15 --- /dev/null +++ b/tests/chutney/test @@ -0,0 +1,6 @@ +#!/bin/bash -xe + +curl http://example.com -vs --socks5-hostname 127.0.0.1:9150 -o /dev/null + +[ "$(dig @127.0.0.1 -p 5353 +short example.com A)" == "93.184.216.34" ] +[ "$(dig @127.0.0.1 -p 5353 +short example.com AAAA)" == "2606:2800:220:1:248:1893:25c8:1946" ] -- GitLab From f4581ffd0438a0e07839036f36960ad41742be03 Mon Sep 17 00:00:00 2001 From: trinity-1686a Date: Fri, 11 Mar 2022 18:27:59 +0100 Subject: [PATCH 6/7] add simple unit test on UDP --- crates/tor-rtcompat/src/impls/async_std.rs | 4 +++ crates/tor-rtcompat/src/impls/tokio.rs | 4 +++ crates/tor-rtcompat/src/lib.rs | 39 ++++++++++++++++++++-- crates/tor-rtcompat/src/traits.rs | 2 ++ tests/chutney/setup | 2 +- 5 files changed, 48 insertions(+), 3 deletions(-) diff --git a/crates/tor-rtcompat/src/impls/async_std.rs b/crates/tor-rtcompat/src/impls/async_std.rs index dae76ff1..956d35b2 100644 --- a/crates/tor-rtcompat/src/impls/async_std.rs +++ b/crates/tor-rtcompat/src/impls/async_std.rs @@ -148,6 +148,10 @@ mod net { } } + fn local_addr(&self) -> IoResult { + self.socket.local_addr() + } + async fn connect(&mut self, addr: &SocketAddr) -> IoResult<()> { self.socket.connect(addr).await?; self.addr = Some(*addr); diff --git a/crates/tor-rtcompat/src/impls/tokio.rs b/crates/tor-rtcompat/src/impls/tokio.rs index 849f26f5..5e662ecc 100644 --- a/crates/tor-rtcompat/src/impls/tokio.rs +++ b/crates/tor-rtcompat/src/impls/tokio.rs @@ -135,6 +135,10 @@ pub(crate) mod net { } } + fn local_addr(&self) -> IoResult { + self.socket.local_addr() + } + async fn connect(&mut self, addr: &SocketAddr) -> IoResult<()> { self.socket.connect(addr).await?; self.addr = Some(*addr); diff --git a/crates/tor-rtcompat/src/lib.rs b/crates/tor-rtcompat/src/lib.rs index d0296cd3..3d8605a9 100644 --- a/crates/tor-rtcompat/src/lib.rs +++ b/crates/tor-rtcompat/src/lib.rs @@ -532,7 +532,7 @@ mod test { // Try connecting to ourself and sending a little data. // // NOTE: requires Ipv4 localhost. - fn self_connect(runtime: &R) -> IoResult<()> { + fn self_connect_tcp(runtime: &R) -> IoResult<()> { let localhost = SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0); let rt1 = runtime.clone(); @@ -562,6 +562,40 @@ mod test { }) } + // Try connecting to ourself and sending a little data. + // + // NOTE: requires Ipv4 localhost. + fn self_connect_udp(runtime: &R) -> IoResult<()> { + let localhost = SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0); + let rt1 = runtime.clone(); + + let socket1 = runtime.block_on(rt1.bind(&(localhost.into())))?; + let addr1 = socket1.local_addr()?; + + let socket2 = runtime.block_on(rt1.bind(&(localhost.into())))?; + let addr2 = socket2.local_addr()?; + + runtime.block_on(async { + let task1 = async { + let mut buf = vec![0_u8; 16]; + let (len, addr) = socket1.recv(&mut buf[..]).await?; + IoResult::Ok((buf[..len].to_vec(), addr)) + }; + let task2 = async { + socket2.send(b"Hello world", &addr1).await?; + IoResult::Ok(()) + }; + + let (recv_r, send_r) = futures::join!(task1, task2); + send_r?; + let (buff, addr) = recv_r?; + assert_eq!(addr2, addr); + assert_eq!(&buff, b"Hello world"); + + Ok(()) + }) + } + // Try out our incoming connection stream code. // // We launch a few connections and make sure that we can read data on @@ -731,7 +765,8 @@ mod test { small_timeout_ok, small_timeout_expire, tiny_wallclock, - self_connect, + self_connect_tcp, + self_connect_udp, listener_stream, } diff --git a/crates/tor-rtcompat/src/traits.rs b/crates/tor-rtcompat/src/traits.rs index 7c028d57..c1f749ef 100644 --- a/crates/tor-rtcompat/src/traits.rs +++ b/crates/tor-rtcompat/src/traits.rs @@ -201,6 +201,8 @@ pub trait UdpSocket { async fn recv(&self, buf: &mut [u8]) -> IoResult<(usize, SocketAddr)>; /// Send a datagram to the provided address. async fn send(&self, buf: &[u8], target: &SocketAddr) -> IoResult; + /// Return the local address that this socket is bound to. + fn local_addr(&self) -> IoResult; /// Connect to a remote address. After calling this [`UdpSocket::recv`] may only /// return that same address, and the target provided to [`UdpSocket::send`] must /// be this address. diff --git a/tests/chutney/setup b/tests/chutney/setup index d97beb6b..b0ec81c9 100755 --- a/tests/chutney/setup +++ b/tests/chutney/setup @@ -88,7 +88,7 @@ fi ( set +e - "$cmd" proxy -c "${CHUTNEY_PATH}/net/nodes/arti.toml" -p 5353 & + "$cmd" proxy -c "${CHUTNEY_PATH}/net/nodes/arti.toml" -d 5353 & pid=$! echo "target=$target" > tests/chutney/arti.run echo "pid=$pid" >> tests/chutney/arti.run -- GitLab From 3a6eac1367179199b37e4acd186626de8c10baf0 Mon Sep 17 00:00:00 2001 From: trinity-1686a Date: Sat, 12 Mar 2022 09:24:51 +0100 Subject: [PATCH 7/7] comment AAAA test and explain why it's disabled --- tests/chutney/test | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/chutney/test b/tests/chutney/test index c20c4c15..60afeb51 100755 --- a/tests/chutney/test +++ b/tests/chutney/test @@ -3,4 +3,7 @@ curl http://example.com -vs --socks5-hostname 127.0.0.1:9150 -o /dev/null [ "$(dig @127.0.0.1 -p 5353 +short example.com A)" == "93.184.216.34" ] -[ "$(dig @127.0.0.1 -p 5353 +short example.com AAAA)" == "2606:2800:220:1:248:1893:25c8:1946" ] + +## This test only work on a chutney network with IPv6 support such as ipv6-exit-min, +## sadly such a network can't run in CI because there is no IPv6 in docker. +#[ "$(dig @127.0.0.1 -p 5353 +short example.com AAAA)" == "2606:2800:220:1:248:1893:25c8:1946" ] -- GitLab