#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
#![warn(clippy::pedantic)]
#![deny(unsafe_code)]
use arti_client::{TorClient, TorClientBuilder};
use futures::future::BoxFuture;
use libp2p::{
core::transport::{ListenerId, TransportEvent},
Multiaddr, Transport, TransportError,
};
use std::sync::Arc;
use tor_rtcompat::tokio::TokioRustlsRuntime;
mod address;
mod provider;
use address::{dangerous_extract, safe_extract};
pub use provider::TokioTorStream;
pub type TorError = arti_client::Error;
#[derive(Clone)]
pub struct TorTransport {
client: Arc<TorClient<TokioRustlsRuntime>>,
pub conversion_mode: AddressConversion,
}
#[derive(Debug, Clone, Copy, Hash, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum AddressConversion {
#[default]
DnsOnly,
IpAndDns,
}
impl TorTransport {
pub fn builder() -> TorClientBuilder<TokioRustlsRuntime> {
let runtime =
TokioRustlsRuntime::current().expect("Couldn't get the current tokio rustls runtime");
TorClient::with_runtime(runtime)
}
pub async fn bootstrapped() -> Result<Self, TorError> {
let builder = Self::builder();
let ret = Self::from_builder(&builder, AddressConversion::DnsOnly)?;
ret.bootstrap().await?;
Ok(ret)
}
pub fn from_builder(
builder: &TorClientBuilder<TokioRustlsRuntime>,
conversion_mode: AddressConversion,
) -> Result<Self, TorError> {
let client = Arc::new(builder.create_unbootstrapped()?);
Ok(Self {
client,
conversion_mode,
})
}
pub fn from_client(
client: Arc<TorClient<TokioRustlsRuntime>>,
conversion_mode: AddressConversion,
) -> Self {
Self {
client,
conversion_mode,
}
}
pub async fn bootstrap(&self) -> Result<(), TorError> {
self.client.bootstrap().await
}
#[must_use]
pub fn with_address_conversion(mut self, conversion_mode: AddressConversion) -> Self {
self.conversion_mode = conversion_mode;
self
}
}
impl Transport for TorTransport {
type Output = TokioTorStream;
type Error = TorError;
type Dial = BoxFuture<'static, Result<Self::Output, Self::Error>>;
type ListenerUpgrade = futures::future::Pending<Result<Self::Output, Self::Error>>;
fn listen_on(
&mut self,
_id: ListenerId,
addr: Multiaddr,
) -> Result<(), TransportError<Self::Error>> {
Err(TransportError::MultiaddrNotSupported(addr))
}
fn remove_listener(&mut self, _id: ListenerId) -> bool {
false
}
fn dial(&mut self, addr: Multiaddr) -> Result<Self::Dial, TransportError<Self::Error>> {
let maybe_tor_addr = match self.conversion_mode {
AddressConversion::DnsOnly => safe_extract(&addr),
AddressConversion::IpAndDns => dangerous_extract(&addr),
};
let tor_address =
maybe_tor_addr.ok_or(TransportError::MultiaddrNotSupported(addr.clone()))?;
let onion_client = self.client.clone();
Ok(Box::pin(async move {
let stream = onion_client.connect(tor_address).await?;
tracing::debug!(%addr, "Established connection to peer through Tor");
Ok(stream.into())
}))
}
fn dial_as_listener(
&mut self,
addr: Multiaddr,
) -> Result<Self::Dial, TransportError<Self::Error>> {
self.dial(addr)
}
fn address_translation(&self, _listen: &Multiaddr, _observed: &Multiaddr) -> Option<Multiaddr> {
None
}
fn poll(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<TransportEvent<Self::ListenerUpgrade, Self::Error>> {
std::task::Poll::Pending
}
}