cuprated/
tor.rs

1//! Tor initialization
2//!
3//! Extract configuration and initialize Arti.
4
5//---------------------------------------------------------------------------------------------------- Imports
6
7use std::{default, sync::Arc};
8
9use arti_client::{
10    config::{onion_service::OnionServiceConfigBuilder, CfgPath, TorClientConfigBuilder},
11    KeystoreSelector, StreamPrefs, TorClient, TorClientBuilder, TorClientConfig,
12};
13use futures::Stream;
14use serde::{Deserialize, Serialize};
15use tor_hsservice::{OnionService, RendRequest, RunningOnionService};
16use tor_persist::hsnickname::HsNickname;
17use tor_rtcompat::PreferredRuntime;
18use tracing::info;
19
20use cuprate_helper::fs::CUPRATE_DATA_DIR;
21use cuprate_p2p::TransportConfig;
22use cuprate_p2p_core::{ClearNet, Tor};
23use cuprate_p2p_transport::{
24    Arti, ArtiClientConfig, ArtiServerConfig, Daemon, DaemonClientConfig, DaemonServerConfig,
25};
26use cuprate_wire::OnionAddr;
27
28use crate::{
29    config::{p2p_port, Config},
30    p2p::ProxySettings,
31};
32//---------------------------------------------------------------------------------------------------- Initialization
33
34#[derive(Clone, Default, Debug, Copy, PartialEq, Eq, Serialize, Deserialize)]
35/// Describe if Tor is enabled and how
36pub enum TorMode {
37    /// Use of the [`arti_client`] library.
38    Arti,
39    /// Use of external tor daemon
40    Daemon,
41
42    #[default]
43    /// Tor is disabled
44    Off,
45}
46
47/// Contains the necessary Tor configuration or structures
48/// for initializing P2P.
49pub struct TorContext {
50    /// Which mode are we using.
51    pub mode: TorMode,
52
53    // -------- Only in Arti mode
54    /// Arti bootstrapped [`TorClient`].
55    pub bootstrapped_client: Option<TorClient<PreferredRuntime>>,
56    /// Arti bootstrapped client config
57    pub arti_client_config: Option<TorClientConfig>,
58    /// Arti onion service address.
59    pub arti_onion_service: Option<OnionService>,
60}
61
62/// Initialize the Tor network if enabled in configuration
63///
64/// This function will bootstrap Arti if needed by Tor network zone or
65/// clearnet as a proxy.
66pub async fn initialize_tor_if_enabled(config: &Config) -> TorContext {
67    let mode = config.tor.mode;
68    let anonymize_clearnet = matches!(config.p2p.clear_net.proxy, ProxySettings::Tor);
69
70    // Start Arti client
71    let (bootstrapped_client, arti_client_config) =
72        if mode == TorMode::Arti && (config.p2p.tor_net.enabled || anonymize_clearnet) {
73            Some(initialize_arti_client(config).await)
74        } else {
75            None
76        }
77        .unzip();
78
79    // Start Arti onion service
80    let arti_onion_service = arti_client_config
81        .as_ref()
82        .map(|client_config| initialize_arti_onion_service(client_config, config));
83
84    TorContext {
85        mode,
86        bootstrapped_client,
87        arti_client_config,
88        arti_onion_service,
89    }
90}
91
92/// Initialize Arti Tor client.
93async fn initialize_arti_client(config: &Config) -> (TorClient<PreferredRuntime>, TorClientConfig) {
94    // Configuration
95    let mut tor_config = TorClientConfig::builder();
96
97    // Storage
98    tor_config
99        .storage()
100        .state_dir(CfgPath::new_literal(config.tor.arti.directory_path.clone()));
101
102    let tor_config = tor_config
103        .build()
104        .expect("Failed to build Tor client configuration.");
105
106    // Bootstrapping
107    info!("Bootstrapping Arti's TorClient...");
108    let mut tor_client = TorClient::builder()
109        .config(tor_config.clone())
110        .create_bootstrapped()
111        .await
112        .inspect_err(|err| tracing::error!("Unable to bootstrap arti: {err}"))
113        .unwrap();
114
115    // Isolation
116    if config.tor.arti.isolated_circuit {
117        let mut stream_prefs = StreamPrefs::new();
118        stream_prefs.isolate_every_stream();
119        tor_client.set_stream_prefs(stream_prefs);
120    }
121
122    (tor_client, tor_config)
123}
124
125fn initialize_arti_onion_service(client_config: &TorClientConfig, config: &Config) -> OnionService {
126    let onion_svc_config = OnionServiceConfigBuilder::default()
127        .enable_pow(config.tor.arti.onion_service_pow)
128        .nickname(HsNickname::new("cuprate".into()).unwrap())
129        .build()
130        .unwrap();
131
132    TorClient::<PreferredRuntime>::create_onion_service(client_config, onion_svc_config)
133        .expect("Unable to start Arti onion service.")
134}
135
136//---------------------------------------------------------------------------------------------------- Transport configuration
137
138pub fn transport_arti_config(config: &Config, ctx: TorContext) -> TransportConfig<Tor, Arti> {
139    // Extracting
140    let (Some(bootstrapped_client), Some(client_config)) =
141        (ctx.bootstrapped_client, ctx.arti_client_config)
142    else {
143        panic!("Arti client should be initialized");
144    };
145
146    let server_config = config.p2p.tor_net.inbound_onion.then(|| {
147        let Some(onion_svc) = ctx.arti_onion_service else {
148            panic!("inbound onion enabled, but no onion service initialized!");
149        };
150
151        ArtiServerConfig::new(
152            onion_svc,
153            p2p_port(config.p2p.tor_net.p2p_port, config.network),
154            &bootstrapped_client,
155            &client_config,
156        )
157    });
158
159    TransportConfig::<Tor, Arti> {
160        client_config: ArtiClientConfig {
161            client: bootstrapped_client,
162        },
163        server_config,
164    }
165}
166
167pub fn transport_clearnet_arti_config(ctx: &TorContext) -> TransportConfig<ClearNet, Arti> {
168    let Some(bootstrapped_client) = &ctx.bootstrapped_client else {
169        panic!("Arti enabled but no TorClient initialized!");
170    };
171
172    TransportConfig::<ClearNet, Arti> {
173        client_config: ArtiClientConfig {
174            client: bootstrapped_client.clone(),
175        },
176        server_config: None,
177    }
178}
179
180pub fn transport_daemon_config(config: &Config) -> TransportConfig<Tor, Daemon> {
181    let mut invalid_onion = false;
182
183    if config.p2p.tor_net.inbound_onion && config.tor.daemon.anonymous_inbound.is_empty() {
184        invalid_onion = true;
185        tracing::warn!("Onion inbound is enabled yet no onion host has been defined in configuration. Inbound server disabled.");
186    }
187
188    TransportConfig::<Tor, Daemon> {
189        client_config: DaemonClientConfig {
190            tor_daemon: config.tor.daemon.address,
191        },
192        server_config: (config.p2p.tor_net.inbound_onion && !invalid_onion).then_some(
193            DaemonServerConfig {
194                ip: config.tor.daemon.listening_addr.ip(),
195                port: config.tor.daemon.listening_addr.port(),
196            },
197        ),
198    }
199}