use std::{
ops::{Deref, DerefMut},
sync::Arc,
};
pub use arti_client;
pub use safelog::DisplayRedacted;
pub use tor_cell;
pub use tor_hsservice;
use snafu::OptionExt;
use snafu::prelude::*;
use arti_client::{DataStream, TorClientConfig};
use futures_util::{Stream, StreamExt};
use tor_cell::relaycell::msg::Connected;
use tor_hsservice::{RendRequest, StreamRequest};
pub type TorClient = arti_client::TorClient<tor_rtcompat::PreferredRuntime>;
pub type TorClientBuilder = arti_client::TorClientBuilder<tor_rtcompat::PreferredRuntime>;
#[cfg(feature = "http-client")]
mod http_client;
#[cfg(feature = "http-client")]
pub use http_client::{HttpClient, HttpClientBuilder};
#[cfg(feature = "http-server")]
mod http_server;
#[cfg(feature = "http-server")]
pub use http_server::HttpServer;
mod error;
pub use error::*;
pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Clone)]
pub struct Tor {
pub client: TorClient,
}
impl Deref for Tor {
type Target = TorClient;
fn deref(&self) -> &Self::Target {
&self.client
}
}
impl DerefMut for Tor {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.client
}
}
#[derive(Debug, Clone)]
pub struct TorConfig {
pub bootstrap: bool,
pub client_config: Option<TorClientConfig>,
}
impl Default for TorConfig {
fn default() -> Self {
TorConfig {
bootstrap: true,
client_config: None,
}
}
}
impl TorConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_bootstrap(mut self, bootstrap: bool) -> Self {
self.bootstrap = bootstrap;
self
}
pub fn with_client_config(mut self, config: Option<TorClientConfig>) -> Self {
self.client_config = config;
self
}
}
impl Tor {
#[tracing::instrument(err, level = "info")]
pub async fn new(config: TorConfig) -> Result<Self, Error> {
let client = if config.bootstrap {
Self::new_custom(async move |b| {
b.config(config.client_config.unwrap_or_default())
.create_bootstrapped()
.await
.context(ArtiClientSnafu)
})
.await?
} else {
Self::new_custom(async move |b| {
b.config(config.client_config.unwrap_or_default())
.create_unbootstrapped_async()
.await
.context(ArtiClientSnafu)
})
.await?
};
Ok(client)
}
#[tracing::instrument(err, skip(callback), level = "info")]
pub async fn new_custom<
T: Future<Output = Result<TorClient, Error>>,
F: FnOnce(TorClientBuilder) -> T,
>(
callback: F,
) -> Result<Self, Error> {
let builder = arti_client::TorClient::builder();
let client = callback(builder).await?;
Ok(Self { client })
}
pub fn service(&self, name: &str) -> Result<HiddenService> {
HiddenService::new(self, name)
}
}
pub struct HiddenService {
tor: Tor,
service: Arc<tor_hsservice::RunningOnionService>,
stream: std::pin::Pin<Box<dyn Send + Sync + futures_util::Stream<Item = RendRequest>>>,
}
impl HiddenService {
#[tracing::instrument(skip(tor), err)]
pub fn new(tor: &Tor, name: &str) -> Result<Self> {
let service = tor_hsservice::config::OnionServiceConfigBuilder::default()
.nickname(name.parse().context(InvalidNicknameSnafu)?)
.build()
.context(ConfigBuildSnafu)?;
let (service, stream) = tor
.client
.launch_onion_service(service)
.context(ArtiClientSnafu)?
.context(EmptyOnionServiceSnafu {
name: name.to_owned(),
})?;
Ok(HiddenService {
tor: tor.clone(),
service,
stream: Box::pin(stream),
})
}
pub fn tor(&self) -> &Tor {
&self.tor
}
#[tracing::instrument(skip(self), err)]
pub async fn accept(&mut self) -> Result<Option<TorConnection>> {
if let Some(x) = self.stream.next().await {
x.accept()
.await
.map_err(Box::new)
.context(ClientSnafu)
.map(|stream| {
Option::Some(TorConnection {
stream: Box::new(stream),
})
})
} else {
Ok(None)
}
}
#[tracing::instrument(skip(self), err)]
pub async fn handle(&mut self) -> std::io::Result<Option<(Address, DataStream)>> {
if let Some(x) = self.stream.next().await
&& let Some(mut conn) = x
.accept()
.await
.map_err(|_| {
std::io::Error::new(
std::io::ErrorKind::NotConnected,
"unable to accept connection",
)
})
.map(|stream| {
Option::Some(TorConnection {
stream: Box::new(stream),
})
})?
{
return conn.accept_next().await.map_err(|_| {
std::io::Error::new(
std::io::ErrorKind::NotConnected,
"unable to open data stream",
)
});
}
Ok(None)
}
}
impl Deref for HiddenService {
type Target = tor_hsservice::RunningOnionService;
fn deref(&self) -> &Self::Target {
&self.service
}
}
pub struct TorConnection {
stream: Box<dyn 'static + Send + Stream<Item = StreamRequest> + Unpin>,
}
impl TorConnection {
#[tracing::instrument(skip(self), err)]
pub async fn accept_next(&mut self) -> Result<Option<(Address, DataStream)>> {
if let Some(conn) = self.stream.next().await {
let addr = match conn.request() {
tor_proto::client::stream::IncomingStreamRequest::Begin(begin) => Address {
addr: begin.addr().to_vec(),
port: begin.port(),
},
_ => {
conn.shutdown_circuit().context(ArtiBugSnafu)?;
return Ok(None);
}
};
let conn = conn
.accept(Connected::new_empty())
.await
.map_err(Box::new)
.context(ClientSnafu)?;
return Ok(Some((addr, conn)));
}
Ok(None)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Address {
addr: Vec<u8>,
port: u16,
}
impl Stream for TorConnection {
type Item = StreamRequest;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
self.stream.poll_next_unpin(cx)
}
}