mirror of
https://github.com/MAGICGrants/cuprate-for-explorer.git
synced 2026-01-09 03:37:54 -05:00
In `workspace`: - New dependencies: `arti_client`, `Cuprate/tokio-socks.git`, `tor-cell`, `tor-config-path`, `tor-hsservice`, `tor-persist`, `tor-proto`, `tor-rtcompat` (yes nothing was exported). - In `deny.toml`, whitelisted `Unlicense` license for `arti_client`. In `cuprate-p2p-transport`: - Implemented `Transport<ClearNet>` and `Transport<Tor>` for `Arti`. New `ArtiClientConfig`, `ArtiServerConfig` configuration. New `OnionListener` for accepting inbound connections from arti's generated onion service. - Implemented `Transport<Tor>` for `Daemon`. New `DaemonClientConfig`, `DaemonServerConfig` configuration. New `DaemonInboundStream` listening for incoming TCP connections from the tor daemon. - `DisabledListener` as a polyfill for transports with inbound disabled, such as `Transport<ClearNet> for Arti` and in the future `Transport<ClearNet> for Socks5`. In `cuprate-p2p-core`: - Removed `Default` and `Debug` bound from `Transport::ClientConfig` and `Transport::ServerConfig`. - Removed `Clone` bound from `Transport::ServerConfig`. In `cuprate-p2p`: - Changed some function visibility to `pub(super)` instead of `pub`. In `cuprate-wire`: - Added `borsh` dependency and `BorshSerialize` and `BorshDeserialize` derived implementation to `OnionAddr` for `BorshNetworkZone` requirement in address book. In `cuprated`: - New `tor` module containing the initialization of Arti, config helpers and context structure `TorContext` to pass down to p2p initialization function and other helpers. - New `config/tor` module containing the `[tor]` configuration table. It define tor daemon related variables, as well as arti settings. - Added `enable_inbound` field to `ClearNetConfig` to disable incoming listener by default. - Added `proxy` field to `ClearNetConfig` for enabling clearnet over arti and in the future proxy urls. - Added `TorNetConfig` for setting `Tor` network zone parameters such as listening ports, enabling arti inbound server, or setting an anonymous inbound onion address from an external daemon. - Modified `initialize_zones_p2p` to now start Tor network zone and use the correct transport depending on user configuration. - In `txpool/*`, generalized `DiffuseService`, `OutboundPeerStream` and `ConcreteDandelionRouter` for `Z: NetworkZone`. Created a new `MainDandelionRouter` service that will route local txs to a Tor router instead of clearnet if available. Adapted initialization to the changes. --------- Co-authored-by: Boog900 <54e72d8a-345f-4599-bd90-c6b9bc7d0ec5@aleeas.com>
222 lines
7.2 KiB
Rust
222 lines
7.2 KiB
Rust
//! Arti Transport
|
|
//!
|
|
//! This module defines a transport method for the `Tor` network zone using the `arti_client` library.
|
|
//!
|
|
|
|
//---------------------------------------------------------------------------------------------------- Imports
|
|
|
|
use std::{
|
|
io::{self, ErrorKind},
|
|
pin::Pin,
|
|
sync::Arc,
|
|
task::{Context, Poll},
|
|
};
|
|
|
|
use arti_client::{DataReader, DataWriter, TorClient, TorClientConfig};
|
|
use async_trait::async_trait;
|
|
use futures::{Stream, StreamExt};
|
|
use tokio_util::codec::{FramedRead, FramedWrite};
|
|
use tor_cell::relaycell::msg::Connected;
|
|
use tor_config_path::CfgPathResolver;
|
|
use tor_hsservice::{handle_rend_requests, OnionService, RunningOnionService};
|
|
use tor_proto::stream::IncomingStreamRequest;
|
|
use tor_rtcompat::PreferredRuntime;
|
|
|
|
use cuprate_p2p_core::{ClearNet, NetworkZone, Tor, Transport};
|
|
use cuprate_wire::MoneroWireCodec;
|
|
|
|
use crate::DisabledListener;
|
|
|
|
//---------------------------------------------------------------------------------------------------- Configuration
|
|
|
|
#[derive(Clone)]
|
|
pub struct ArtiClientConfig {
|
|
/// Arti bootstrapped client
|
|
pub client: TorClient<PreferredRuntime>,
|
|
}
|
|
|
|
pub struct ArtiServerConfig {
|
|
/// Arti onion service
|
|
pub onion_svc: OnionService,
|
|
/// Listening port
|
|
pub port: u16,
|
|
|
|
// Mandatory resources for launching the onion service
|
|
client: TorClient<PreferredRuntime>,
|
|
path_resolver: Arc<CfgPathResolver>,
|
|
}
|
|
|
|
impl ArtiServerConfig {
|
|
pub fn new(
|
|
onion_svc: OnionService,
|
|
port: u16,
|
|
client: &TorClient<PreferredRuntime>,
|
|
config: &TorClientConfig,
|
|
) -> Self {
|
|
let path_resolver: &CfgPathResolver = config.as_ref();
|
|
|
|
Self {
|
|
onion_svc,
|
|
port,
|
|
client: client.clone(),
|
|
path_resolver: Arc::new(path_resolver.clone()),
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------------------------- Transport
|
|
|
|
type PinnedStream<I> = Pin<Box<dyn Stream<Item = I> + Send>>;
|
|
|
|
/// An onion service listening for incoming peer connections.
|
|
pub struct OnionListener {
|
|
/// A handle to the onion service instance.
|
|
_onion_svc: Arc<RunningOnionService>,
|
|
/// A modified stream that produce a data stream and sink from rendez-vous requests.
|
|
listener: PinnedStream<Result<(DataReader, DataWriter), io::Error>>,
|
|
}
|
|
|
|
impl Stream for OnionListener {
|
|
type Item = Result<
|
|
(
|
|
Option<<Tor as NetworkZone>::Addr>,
|
|
FramedRead<DataReader, MoneroWireCodec>,
|
|
FramedWrite<DataWriter, MoneroWireCodec>,
|
|
),
|
|
io::Error,
|
|
>;
|
|
|
|
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
|
match self.listener.poll_next_unpin(cx) {
|
|
Poll::Pending => Poll::Pending,
|
|
Poll::Ready(req) => Poll::Ready(req.map(|r| {
|
|
r.map(|(stream, sink)| {
|
|
(
|
|
None, // Inbound is anonymous
|
|
FramedRead::new(stream, MoneroWireCodec::default()),
|
|
FramedWrite::new(sink, MoneroWireCodec::default()),
|
|
)
|
|
})
|
|
})),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub struct Arti;
|
|
|
|
#[async_trait]
|
|
impl Transport<Tor> for Arti {
|
|
type ClientConfig = ArtiClientConfig;
|
|
type ServerConfig = ArtiServerConfig;
|
|
|
|
type Stream = FramedRead<DataReader, MoneroWireCodec>;
|
|
type Sink = FramedWrite<DataWriter, MoneroWireCodec>;
|
|
type Listener = OnionListener;
|
|
|
|
async fn connect_to_peer(
|
|
addr: <Tor as NetworkZone>::Addr,
|
|
config: &Self::ClientConfig,
|
|
) -> Result<(Self::Stream, Self::Sink), io::Error> {
|
|
config
|
|
.client
|
|
.connect((addr.addr_string(), addr.port()))
|
|
.await
|
|
.map_err(|e| io::Error::new(ErrorKind::ConnectionAborted, e.to_string()))
|
|
.map(|stream| {
|
|
let (stream, sink) = stream.split();
|
|
(
|
|
FramedRead::new(stream, MoneroWireCodec::default()),
|
|
FramedWrite::new(sink, MoneroWireCodec::default()),
|
|
)
|
|
})
|
|
}
|
|
|
|
async fn incoming_connection_listener(
|
|
config: Self::ServerConfig,
|
|
) -> Result<Self::Listener, io::Error> {
|
|
tracing::info!(
|
|
"Listening for incoming Tor P2P connections on address: {}:{}",
|
|
config.onion_svc.onion_address().unwrap(),
|
|
config.port
|
|
);
|
|
|
|
// Launch onion service
|
|
#[expect(clippy::clone_on_ref_ptr)]
|
|
let (svc, rdv_stream) = config
|
|
.onion_svc
|
|
.launch(
|
|
config.client.runtime().clone(),
|
|
config.client.dirmgr().clone(),
|
|
config.client.hs_circ_pool().clone(),
|
|
config.path_resolver,
|
|
)
|
|
.unwrap();
|
|
|
|
// Accept all rendez-vous and await correct stream request
|
|
let req_stream = handle_rend_requests(rdv_stream).then(move |sreq| async move {
|
|
match sreq.request() {
|
|
// As specified in: <https://spec.torproject.org/rend-spec/managing-streams.html>
|
|
//
|
|
// A client that wishes to open a data stream with us needs to send a BEGIN message with an empty address
|
|
// and no flags. We additionally filter requests to the correct port configured and advertised on P2P.
|
|
IncomingStreamRequest::Begin(r)
|
|
if r.port() == config.port && r.addr().is_empty() && r.flags().is_empty() =>
|
|
{
|
|
let stream = sreq
|
|
.accept(Connected::new_empty())
|
|
.await
|
|
.map_err(|e| io::Error::new(ErrorKind::BrokenPipe, e.to_string()))?;
|
|
|
|
Ok(stream.split())
|
|
}
|
|
_ => {
|
|
sreq.shutdown_circuit()
|
|
.expect("Should never panic, unless programming error from arti's end.");
|
|
|
|
Err(io::Error::other("Received invalid command"))
|
|
}
|
|
}
|
|
});
|
|
|
|
Ok(OnionListener {
|
|
_onion_svc: svc,
|
|
listener: Box::pin(req_stream),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Transport<ClearNet> for Arti {
|
|
type ClientConfig = ArtiClientConfig;
|
|
type ServerConfig = ();
|
|
|
|
type Stream = FramedRead<DataReader, MoneroWireCodec>;
|
|
type Sink = FramedWrite<DataWriter, MoneroWireCodec>;
|
|
type Listener = DisabledListener<ClearNet, DataReader, DataWriter>;
|
|
|
|
async fn connect_to_peer(
|
|
addr: <ClearNet as NetworkZone>::Addr,
|
|
config: &Self::ClientConfig,
|
|
) -> Result<(Self::Stream, Self::Sink), io::Error> {
|
|
config
|
|
.client
|
|
.connect(addr.to_string())
|
|
.await
|
|
.map_err(|e| io::Error::new(ErrorKind::ConnectionAborted, e.to_string()))
|
|
.map(|stream| {
|
|
let (stream, sink) = stream.split();
|
|
(
|
|
FramedRead::new(stream, MoneroWireCodec::default()),
|
|
FramedWrite::new(sink, MoneroWireCodec::default()),
|
|
)
|
|
})
|
|
}
|
|
|
|
async fn incoming_connection_listener(
|
|
_config: Self::ServerConfig,
|
|
) -> Result<Self::Listener, io::Error> {
|
|
panic!("In anonymized clearnet mode, inbound is disabled!");
|
|
}
|
|
}
|