1use 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#[derive(Clone, Default, Debug, Copy, PartialEq, Eq, Serialize, Deserialize)]
35pub enum TorMode {
37 Arti,
39 Daemon,
41
42 #[default]
43 Off,
45}
46
47pub struct TorContext {
50 pub mode: TorMode,
52
53 pub bootstrapped_client: Option<TorClient<PreferredRuntime>>,
56 pub arti_client_config: Option<TorClientConfig>,
58 pub arti_onion_service: Option<OnionService>,
60}
61
62pub 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 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 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
92async fn initialize_arti_client(config: &Config) -> (TorClient<PreferredRuntime>, TorClientConfig) {
94 let mut tor_config = TorClientConfig::builder();
96
97 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 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 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
136pub fn transport_arti_config(config: &Config, ctx: TorContext) -> TransportConfig<Tor, Arti> {
139 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}