arti_client/client.rs
1//! A general interface for Tor client usage.
2//!
3//! To construct a client, run the [`TorClient::create_bootstrapped`] method.
4//! Once the client is bootstrapped, you can make anonymous
5//! connections ("streams") over the Tor network using
6//! [`TorClient::connect`].
7
8#[cfg(feature = "rpc")]
9use {derive_deftly::Deftly, tor_rpcbase::templates::*};
10
11use crate::address::{IntoTorAddr, ResolveInstructions, StreamInstructions};
12
13use crate::config::{
14 ClientAddrConfig, SoftwareStatusOverrideConfig, StreamTimeoutConfig, TorClientConfig,
15};
16use safelog::{Sensitive, sensitive};
17use tor_async_utils::{DropNotifyWatchSender, PostageWatchSenderExt};
18use tor_chanmgr::ChanMgrConfig;
19use tor_circmgr::ClientDataTunnel;
20use tor_circmgr::isolation::{Isolation, StreamIsolation};
21use tor_circmgr::{IsolationToken, TargetPort, isolation::StreamIsolationBuilder};
22use tor_config::MutCfg;
23#[cfg(feature = "bridge-client")]
24use tor_dirmgr::bridgedesc::BridgeDescMgr;
25use tor_dirmgr::{DirMgrStore, Timeliness};
26use tor_error::{Bug, error_report, internal};
27use tor_guardmgr::{GuardMgr, RetireCircuits};
28use tor_keymgr::Keystore;
29use tor_memquota::MemoryQuotaTracker;
30use tor_netdir::{NetDirProvider, params::NetParameters};
31use tor_persist::StateMgr;
32#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
33use tor_persist::TestingStateMgr;
34#[cfg(feature = "onion-service-service")]
35use tor_persist::state_dir::StateDirectory;
36use tor_proto::client::stream::{DataStream, IpVersionPreference, StreamParameters};
37#[cfg(all(
38 any(feature = "native-tls", feature = "rustls"),
39 any(feature = "async-std", feature = "tokio"),
40))]
41use tor_rtcompat::PreferredRuntime;
42use tor_rtcompat::{Runtime, SleepProviderExt};
43#[cfg(feature = "onion-service-client")]
44use {
45 tor_config::BoolOrAuto,
46 tor_hsclient::{HsClientConnector, HsClientDescEncKeypairSpecifier, HsClientSecretKeysBuilder},
47 tor_hscrypto::pk::{HsClientDescEncKey, HsClientDescEncKeypair, HsClientDescEncSecretKey},
48 tor_netdir::DirEvent,
49};
50
51#[cfg(all(feature = "onion-service-service", feature = "experimental-api"))]
52use tor_hsservice::HsIdKeypairSpecifier;
53#[cfg(all(feature = "onion-service-client", feature = "experimental-api"))]
54use {tor_hscrypto::pk::HsId, tor_hscrypto::pk::HsIdKeypair, tor_keymgr::KeystoreSelector};
55
56use tor_keymgr::{ArtiNativeKeystore, KeyMgr, KeyMgrBuilder, config::ArtiKeystoreKind};
57
58#[cfg(feature = "ephemeral-keystore")]
59use tor_keymgr::ArtiEphemeralKeystore;
60
61#[cfg(feature = "ctor-keystore")]
62use tor_keymgr::{CTorClientKeystore, CTorServiceKeystore};
63
64use futures::StreamExt as _;
65use futures::lock::Mutex as AsyncMutex;
66use std::net::IpAddr;
67use std::result::Result as StdResult;
68use std::sync::{Arc, Mutex};
69use tor_rtcompat::SpawnExt;
70
71use crate::err::ErrorDetail;
72use crate::{TorClientBuilder, status, util};
73#[cfg(feature = "geoip")]
74use tor_geoip::CountryCode;
75use tor_rtcompat::scheduler::TaskHandle;
76use tracing::{debug, info, instrument};
77
78#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
79use tor_persist::FsStateMgr as UsingStateMgr;
80
81// TODO wasm: This is not the right choice, but at least it compiles.
82#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
83use tor_persist::TestingStateMgr as UsingStateMgr;
84
85/// An active client session on the Tor network.
86///
87/// While it's running, it will fetch directory information, build
88/// circuits, and make connections for you.
89///
90/// Cloning this object makes a new reference to the same underlying
91/// handles: it's usually better to clone the `TorClient` than it is to
92/// create a new one.
93///
94/// # In the Arti RPC System
95///
96/// An open client on the Tor network.
97///
98/// A `TorClient` can be used to open anonymous connections,
99/// and (eventually) perform other activities.
100///
101/// You can use an `RpcSession` as a `TorClient`, or use the `isolated_client` method
102/// to create a new `TorClient` whose stream will not share circuits with any other Tor client.
103///
104/// This ObjectID for this object can be used as the target of a SOCKS stream.
105// TODO(nickm): This type now has 5 Arcs inside it, and 2 types that have
106// implicit Arcs inside them! maybe it's time to replace much of the insides of
107// this with an Arc<TorClientInner>?
108#[derive(Clone)]
109#[cfg_attr(
110 feature = "rpc",
111 derive(Deftly),
112 derive_deftly(Object),
113 deftly(rpc(expose_outside_of_session))
114)]
115pub struct TorClient<R: Runtime> {
116 /// Asynchronous runtime object.
117 runtime: R,
118 /// Default isolation token for streams through this client.
119 ///
120 /// This is eventually used for `owner_token` in `tor-circmgr/src/usage.rs`, and is orthogonal
121 /// to the `stream_isolation` which comes from `connect_prefs` (or a passed-in `StreamPrefs`).
122 /// (ie, both must be the same to share a circuit).
123 client_isolation: IsolationToken,
124 /// Connection preferences. Starts out as `Default`, Inherited by our clones.
125 connect_prefs: StreamPrefs,
126 /// Memory quota tracker
127 memquota: Arc<MemoryQuotaTracker>,
128 /// Channel manager, used by circuits etc.,
129 ///
130 /// Used directly by client only for reconfiguration.
131 chanmgr: Arc<tor_chanmgr::ChanMgr<R>>,
132 /// Circuit manager for keeping our circuits up to date and building
133 /// them on-demand.
134 circmgr: Arc<tor_circmgr::CircMgr<R>>,
135 /// Directory manager persistent storage.
136 #[cfg_attr(not(feature = "bridge-client"), allow(dead_code))]
137 dirmgr_store: DirMgrStore<R>,
138 /// Directory manager for keeping our directory material up to date.
139 dirmgr: Arc<dyn tor_dirmgr::DirProvider>,
140 /// Bridge descriptor manager
141 ///
142 /// None until we have bootstrapped.
143 ///
144 /// Lock hierarchy: don't acquire this before dormant
145 //
146 // TODO: after or as part of https://gitlab.torproject.org/tpo/core/arti/-/issues/634
147 // this can be bridge_desc_mgr: BridgeDescMgr<R>>
148 // since BridgeDescMgr is Clone and all its methods take `&self` (it has a lock inside)
149 // Or maybe BridgeDescMgr should not be Clone, since we want to make Weaks of it,
150 // which we can't do when the Arc is inside.
151 #[cfg(feature = "bridge-client")]
152 bridge_desc_mgr: Arc<Mutex<Option<Arc<BridgeDescMgr<R>>>>>,
153 /// Pluggable transport manager.
154 #[cfg(feature = "pt-client")]
155 pt_mgr: Arc<tor_ptmgr::PtMgr<R>>,
156 /// HS client connector
157 #[cfg(feature = "onion-service-client")]
158 hsclient: HsClientConnector<R>,
159 /// Circuit pool for providing onion services with circuits.
160 #[cfg(any(feature = "onion-service-client", feature = "onion-service-service"))]
161 hs_circ_pool: Arc<tor_circmgr::hspool::HsCircPool<R>>,
162 /// A handle to this client's [`InertTorClient`].
163 ///
164 /// Used for accessing the key manager and other persistent state.
165 inert_client: InertTorClient,
166 /// Guard manager
167 #[cfg_attr(not(feature = "bridge-client"), allow(dead_code))]
168 guardmgr: GuardMgr<R>,
169 /// Location on disk where we store persistent data containing both location and Mistrust information.
170 ///
171 ///
172 /// This path is configured via `[storage]` in the config but is not used directly as a
173 /// StateDirectory in most places. Instead, its path and Mistrust information are copied
174 /// to subsystems like `dirmgr`, `keymgr`, and `statemgr` during `TorClient` creation.
175 #[cfg(feature = "onion-service-service")]
176 state_directory: StateDirectory,
177 /// Location on disk where we store persistent data (cooked state manager).
178 statemgr: UsingStateMgr,
179 /// Client address configuration
180 addrcfg: Arc<MutCfg<ClientAddrConfig>>,
181 /// Client DNS configuration
182 timeoutcfg: Arc<MutCfg<StreamTimeoutConfig>>,
183 /// Software status configuration.
184 software_status_cfg: Arc<MutCfg<SoftwareStatusOverrideConfig>>,
185 /// Mutex used to serialize concurrent attempts to reconfigure a TorClient.
186 ///
187 /// See [`TorClient::reconfigure`] for more information on its use.
188 reconfigure_lock: Arc<Mutex<()>>,
189
190 /// A stream of bootstrap messages that we can clone when a client asks for
191 /// it.
192 ///
193 /// (We don't need to observe this stream ourselves, since it drops each
194 /// unobserved status change when the next status change occurs.)
195 status_receiver: status::BootstrapEvents,
196
197 /// mutex used to prevent two tasks from trying to bootstrap at once.
198 bootstrap_in_progress: Arc<AsyncMutex<()>>,
199
200 /// Whether or not we should call `bootstrap` before doing things that require
201 /// bootstrapping. If this is `false`, we will just call `wait_for_bootstrap`
202 /// instead.
203 should_bootstrap: BootstrapBehavior,
204
205 /// Shared boolean for whether we're currently in "dormant mode" or not.
206 //
207 // The sent value is `Option`, so that `None` is sent when the sender, here,
208 // is dropped,. That shuts down the monitoring task.
209 dormant: Arc<Mutex<DropNotifyWatchSender<Option<DormantMode>>>>,
210
211 /// The path resolver given to us by a [`TorClientConfig`].
212 ///
213 /// We must not add our own variables to it since `TorClientConfig` uses it to perform its own
214 /// path expansions. If we added our own variables, it would introduce an inconsistency where
215 /// paths expanded by the `TorClientConfig` would expand differently than when expanded by us.
216 // This is an Arc so that we can make cheap clones of it.
217 path_resolver: Arc<tor_config_path::CfgPathResolver>,
218}
219
220/// A Tor client that is not runnable.
221///
222/// Can be used to access the state that would be used by a running [`TorClient`].
223///
224/// An `InertTorClient` never connects to the network.
225#[derive(Clone)]
226pub struct InertTorClient {
227 /// The key manager.
228 ///
229 /// This is used for retrieving private keys, certificates, and other sensitive data (for
230 /// example, for retrieving the keys necessary for connecting to hidden services that are
231 /// running in restricted discovery mode).
232 ///
233 /// If this crate is compiled _with_ the `keymgr` feature, [`TorClient`] will use a functional
234 /// key manager implementation.
235 ///
236 /// If this crate is compiled _without_ the `keymgr` feature, then [`TorClient`] will use a
237 /// no-op key manager implementation instead.
238 ///
239 /// See the [`KeyMgr`] documentation for more details.
240 keymgr: Option<Arc<KeyMgr>>,
241}
242
243impl InertTorClient {
244 /// Create an `InertTorClient` from a `TorClientConfig`.
245 pub(crate) fn new(config: &TorClientConfig) -> StdResult<Self, ErrorDetail> {
246 let keymgr = Self::create_keymgr(config)?;
247
248 Ok(Self { keymgr })
249 }
250
251 /// Create a [`KeyMgr`] using the specified configuration.
252 ///
253 /// Returns `Ok(None)` if keystore use is disabled.
254 fn create_keymgr(config: &TorClientConfig) -> StdResult<Option<Arc<KeyMgr>>, ErrorDetail> {
255 let keystore = config.storage.keystore();
256 let permissions = config.storage.permissions();
257 let primary_store: Box<dyn Keystore> = match keystore.primary_kind() {
258 Some(ArtiKeystoreKind::Native) => {
259 let (state_dir, _mistrust) = config.state_dir()?;
260 let key_store_dir = state_dir.join("keystore");
261
262 let native_store =
263 ArtiNativeKeystore::from_path_and_mistrust(&key_store_dir, permissions)?;
264 // Should only log fs paths at debug level or lower,
265 // unless they're part of a diagnostic message.
266 debug!("Using keystore from {key_store_dir:?}");
267
268 Box::new(native_store)
269 }
270 #[cfg(feature = "ephemeral-keystore")]
271 Some(ArtiKeystoreKind::Ephemeral) => {
272 // TODO: make the keystore ID somehow configurable
273 let ephemeral_store: ArtiEphemeralKeystore =
274 ArtiEphemeralKeystore::new("ephemeral".to_string());
275 Box::new(ephemeral_store)
276 }
277 None => {
278 info!("Running without a keystore");
279 return Ok(None);
280 }
281 ty => return Err(internal!("unrecognized keystore type {ty:?}").into()),
282 };
283
284 let mut builder = KeyMgrBuilder::default().primary_store(primary_store);
285
286 #[cfg(feature = "ctor-keystore")]
287 for config in config.storage.keystore().ctor_svc_stores() {
288 let store: Box<dyn Keystore> = Box::new(CTorServiceKeystore::from_path_and_mistrust(
289 config.path(),
290 permissions,
291 config.id().clone(),
292 // TODO: these nicknames should be cross-checked with configured
293 // svc nicknames as part of config validation!!!
294 config.nickname().clone(),
295 )?);
296
297 builder.secondary_stores().push(store);
298 }
299
300 #[cfg(feature = "ctor-keystore")]
301 for config in config.storage.keystore().ctor_client_stores() {
302 let store: Box<dyn Keystore> = Box::new(CTorClientKeystore::from_path_and_mistrust(
303 config.path(),
304 permissions,
305 config.id().clone(),
306 )?);
307
308 builder.secondary_stores().push(store);
309 }
310
311 let keymgr = builder
312 .build()
313 .map_err(|_| internal!("failed to build keymgr"))?;
314 Ok(Some(Arc::new(keymgr)))
315 }
316
317 /// Generate a service discovery keypair for connecting to a hidden service running in
318 /// "restricted discovery" mode.
319 ///
320 /// See [`TorClient::generate_service_discovery_key`].
321 //
322 // TODO: decide whether this should use get_or_generate before making it
323 // non-experimental
324 #[cfg(all(
325 feature = "onion-service-client",
326 feature = "experimental-api",
327 feature = "keymgr"
328 ))]
329 #[cfg_attr(
330 docsrs,
331 doc(cfg(all(
332 feature = "onion-service-client",
333 feature = "experimental-api",
334 feature = "keymgr"
335 )))
336 )]
337 pub fn generate_service_discovery_key(
338 &self,
339 selector: KeystoreSelector,
340 hsid: HsId,
341 ) -> crate::Result<HsClientDescEncKey> {
342 let mut rng = tor_llcrypto::rng::CautiousRng;
343 let spec = HsClientDescEncKeypairSpecifier::new(hsid);
344 let key = self
345 .keymgr
346 .as_ref()
347 .ok_or(ErrorDetail::KeystoreRequired {
348 action: "generate client service discovery key",
349 })?
350 .generate::<HsClientDescEncKeypair>(
351 &spec, selector, &mut rng, false, /* overwrite */
352 )?;
353
354 Ok(key.public().clone())
355 }
356
357 /// Rotate the service discovery keypair for connecting to a hidden service running in
358 /// "restricted discovery" mode.
359 ///
360 /// See [`TorClient::rotate_service_discovery_key`].
361 #[cfg(all(
362 feature = "onion-service-client",
363 feature = "experimental-api",
364 feature = "keymgr"
365 ))]
366 pub fn rotate_service_discovery_key(
367 &self,
368 selector: KeystoreSelector,
369 hsid: HsId,
370 ) -> crate::Result<HsClientDescEncKey> {
371 let mut rng = tor_llcrypto::rng::CautiousRng;
372 let spec = HsClientDescEncKeypairSpecifier::new(hsid);
373 let key = self
374 .keymgr
375 .as_ref()
376 .ok_or(ErrorDetail::KeystoreRequired {
377 action: "rotate client service discovery key",
378 })?
379 .generate::<HsClientDescEncKeypair>(
380 &spec, selector, &mut rng, true, /* overwrite */
381 )?;
382
383 Ok(key.public().clone())
384 }
385
386 /// Insert a service discovery secret key for connecting to a hidden service running in
387 /// "restricted discovery" mode
388 ///
389 /// See [`TorClient::insert_service_discovery_key`].
390 #[cfg(all(
391 feature = "onion-service-client",
392 feature = "experimental-api",
393 feature = "keymgr"
394 ))]
395 #[cfg_attr(
396 docsrs,
397 doc(cfg(all(
398 feature = "onion-service-client",
399 feature = "experimental-api",
400 feature = "keymgr"
401 )))
402 )]
403 pub fn insert_service_discovery_key(
404 &self,
405 selector: KeystoreSelector,
406 hsid: HsId,
407 hs_client_desc_enc_secret_key: HsClientDescEncSecretKey,
408 ) -> crate::Result<HsClientDescEncKey> {
409 let spec = HsClientDescEncKeypairSpecifier::new(hsid);
410 let client_desc_enc_key = HsClientDescEncKey::from(&hs_client_desc_enc_secret_key);
411 let client_desc_enc_keypair =
412 HsClientDescEncKeypair::new(client_desc_enc_key.clone(), hs_client_desc_enc_secret_key);
413 let _key = self
414 .keymgr
415 .as_ref()
416 .ok_or(ErrorDetail::KeystoreRequired {
417 action: "insert client service discovery key",
418 })?
419 .insert::<HsClientDescEncKeypair>(client_desc_enc_keypair, &spec, selector, false)?;
420 Ok(client_desc_enc_key)
421 }
422
423 /// Return the service discovery public key for the service with the specified `hsid`.
424 ///
425 /// See [`TorClient::get_service_discovery_key`].
426 #[cfg(all(feature = "onion-service-client", feature = "experimental-api"))]
427 #[cfg_attr(
428 docsrs,
429 doc(cfg(all(feature = "onion-service-client", feature = "experimental-api")))
430 )]
431 pub fn get_service_discovery_key(
432 &self,
433 hsid: HsId,
434 ) -> crate::Result<Option<HsClientDescEncKey>> {
435 let spec = HsClientDescEncKeypairSpecifier::new(hsid);
436 let key = self
437 .keymgr
438 .as_ref()
439 .ok_or(ErrorDetail::KeystoreRequired {
440 action: "get client service discovery key",
441 })?
442 .get::<HsClientDescEncKeypair>(&spec)?
443 .map(|key| key.public().clone());
444
445 Ok(key)
446 }
447
448 /// Removes the service discovery keypair for the service with the specified `hsid`.
449 ///
450 /// See [`TorClient::remove_service_discovery_key`].
451 #[cfg(all(
452 feature = "onion-service-client",
453 feature = "experimental-api",
454 feature = "keymgr"
455 ))]
456 #[cfg_attr(
457 docsrs,
458 doc(cfg(all(
459 feature = "onion-service-client",
460 feature = "experimental-api",
461 feature = "keymgr"
462 )))
463 )]
464 pub fn remove_service_discovery_key(
465 &self,
466 selector: KeystoreSelector,
467 hsid: HsId,
468 ) -> crate::Result<Option<()>> {
469 let spec = HsClientDescEncKeypairSpecifier::new(hsid);
470 let result = self
471 .keymgr
472 .as_ref()
473 .ok_or(ErrorDetail::KeystoreRequired {
474 action: "remove client service discovery key",
475 })?
476 .remove::<HsClientDescEncKeypair>(&spec, selector)?;
477 match result {
478 Some(_) => Ok(Some(())),
479 None => Ok(None),
480 }
481 }
482
483 /// Getter for keymgr.
484 #[cfg(feature = "onion-service-cli-extra")]
485 pub fn keymgr(&self) -> crate::Result<&KeyMgr> {
486 Ok(self.keymgr.as_ref().ok_or(ErrorDetail::KeystoreRequired {
487 action: "get key manager handle",
488 })?)
489 }
490
491 /// Create (but do not launch) a new
492 /// [`OnionService`](tor_hsservice::OnionService)
493 /// using the given configuration.
494 ///
495 /// See [`TorClient::create_onion_service`].
496 #[cfg(feature = "onion-service-service")]
497 #[instrument(skip_all, level = "trace")]
498 pub fn create_onion_service(
499 &self,
500 config: &TorClientConfig,
501 svc_config: tor_hsservice::OnionServiceConfig,
502 ) -> crate::Result<tor_hsservice::OnionService> {
503 let keymgr = self.keymgr.as_ref().ok_or(ErrorDetail::KeystoreRequired {
504 action: "create onion service",
505 })?;
506
507 let (state_dir, mistrust) = config.state_dir()?;
508 let state_dir =
509 self::StateDirectory::new(state_dir, mistrust).map_err(ErrorDetail::StateAccess)?;
510
511 Ok(tor_hsservice::OnionService::builder()
512 .config(svc_config)
513 .keymgr(keymgr.clone())
514 .state_dir(state_dir)
515 .build()
516 .map_err(ErrorDetail::OnionServiceSetup)?)
517 }
518}
519
520/// Preferences for whether a [`TorClient`] should bootstrap on its own or not.
521#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
522#[non_exhaustive]
523pub enum BootstrapBehavior {
524 /// Bootstrap the client automatically when requests are made that require the client to be
525 /// bootstrapped.
526 #[default]
527 OnDemand,
528 /// Make no attempts to automatically bootstrap. [`TorClient::bootstrap`] must be manually
529 /// invoked in order for the [`TorClient`] to become useful.
530 ///
531 /// Attempts to use the client (e.g. by creating connections or resolving hosts over the Tor
532 /// network) before calling [`bootstrap`](TorClient::bootstrap) will fail, and
533 /// return an error that has kind [`ErrorKind::BootstrapRequired`](crate::ErrorKind::BootstrapRequired).
534 Manual,
535}
536
537/// What level of sleep to put a Tor client into.
538#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
539#[non_exhaustive]
540pub enum DormantMode {
541 /// The client functions as normal, and background tasks run periodically.
542 #[default]
543 Normal,
544 /// Background tasks are suspended, conserving CPU usage. Attempts to use the client will
545 /// wake it back up again.
546 Soft,
547}
548
549/// Preferences for how to route a stream over the Tor network.
550#[derive(Debug, Default, Clone)]
551pub struct StreamPrefs {
552 /// What kind of IPv6/IPv4 we'd prefer, and how strongly.
553 ip_ver_pref: IpVersionPreference,
554 /// How should we isolate connection(s)?
555 isolation: StreamIsolationPreference,
556 /// Whether to return the stream optimistically.
557 optimistic_stream: bool,
558 // TODO GEOIP Ideally this would be unconditional, with CountryCode maybe being Void
559 // This probably applies in many other places, so probably: git grep 'cfg.*geoip'
560 // and consider each one with a view to making it unconditional. Background:
561 // https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/1537#note_2935256
562 // https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/1537#note_2942214
563 #[cfg(feature = "geoip")]
564 /// A country to restrict the exit relay's location to.
565 country_code: Option<CountryCode>,
566 /// Whether to try to make connections to onion services.
567 ///
568 /// `Auto` means to use the client configuration.
569 #[cfg(feature = "onion-service-client")]
570 pub(crate) connect_to_onion_services: BoolOrAuto,
571}
572
573/// Record of how we are isolating connections
574#[derive(Debug, Default, Clone)]
575enum StreamIsolationPreference {
576 /// No additional isolation
577 #[default]
578 None,
579 /// Isolation parameter to use for connections
580 Explicit(Box<dyn Isolation>),
581 /// Isolate every connection!
582 EveryStream,
583}
584
585impl From<DormantMode> for tor_chanmgr::Dormancy {
586 fn from(dormant: DormantMode) -> tor_chanmgr::Dormancy {
587 match dormant {
588 DormantMode::Normal => tor_chanmgr::Dormancy::Active,
589 DormantMode::Soft => tor_chanmgr::Dormancy::Dormant,
590 }
591 }
592}
593#[cfg(feature = "bridge-client")]
594impl From<DormantMode> for tor_dirmgr::bridgedesc::Dormancy {
595 fn from(dormant: DormantMode) -> tor_dirmgr::bridgedesc::Dormancy {
596 match dormant {
597 DormantMode::Normal => tor_dirmgr::bridgedesc::Dormancy::Active,
598 DormantMode::Soft => tor_dirmgr::bridgedesc::Dormancy::Dormant,
599 }
600 }
601}
602
603impl StreamPrefs {
604 /// Construct a new StreamPrefs.
605 pub fn new() -> Self {
606 Self::default()
607 }
608
609 /// Indicate that a stream may be made over IPv4 or IPv6, but that
610 /// we'd prefer IPv6.
611 pub fn ipv6_preferred(&mut self) -> &mut Self {
612 self.ip_ver_pref = IpVersionPreference::Ipv6Preferred;
613 self
614 }
615
616 /// Indicate that a stream may only be made over IPv6.
617 ///
618 /// When this option is set, we will only pick exit relays that
619 /// support IPv6, and we will tell them to only give us IPv6
620 /// connections.
621 pub fn ipv6_only(&mut self) -> &mut Self {
622 self.ip_ver_pref = IpVersionPreference::Ipv6Only;
623 self
624 }
625
626 /// Indicate that a stream may be made over IPv4 or IPv6, but that
627 /// we'd prefer IPv4.
628 ///
629 /// This is the default.
630 pub fn ipv4_preferred(&mut self) -> &mut Self {
631 self.ip_ver_pref = IpVersionPreference::Ipv4Preferred;
632 self
633 }
634
635 /// Indicate that a stream may only be made over IPv4.
636 ///
637 /// When this option is set, we will only pick exit relays that
638 /// support IPv4, and we will tell them to only give us IPv4
639 /// connections.
640 pub fn ipv4_only(&mut self) -> &mut Self {
641 self.ip_ver_pref = IpVersionPreference::Ipv4Only;
642 self
643 }
644
645 /// Indicate that a stream should appear to come from the given country.
646 ///
647 /// When this option is set, we will only pick exit relays that
648 /// have an IP address that matches the country in our GeoIP database.
649 #[cfg(feature = "geoip")]
650 pub fn exit_country(&mut self, country_code: CountryCode) -> &mut Self {
651 self.country_code = Some(country_code);
652 self
653 }
654
655 /// Indicate that we don't care which country a stream appears to come from.
656 ///
657 /// This is available even in the case where GeoIP support is compiled out,
658 /// to make things easier.
659 pub fn any_exit_country(&mut self) -> &mut Self {
660 #[cfg(feature = "geoip")]
661 {
662 self.country_code = None;
663 }
664 self
665 }
666
667 /// Indicate that the stream should be opened "optimistically".
668 ///
669 /// By default, streams are not "optimistic". When you call
670 /// [`TorClient::connect()`], it won't give you a stream until the
671 /// exit node has confirmed that it has successfully opened a
672 /// connection to your target address. It's safer to wait in this
673 /// way, but it is slower: it takes an entire round trip to get
674 /// your confirmation.
675 ///
676 /// If a stream _is_ configured to be "optimistic", on the other
677 /// hand, then `TorClient::connect()` will return the stream
678 /// immediately, without waiting for an answer from the exit. You
679 /// can start sending data on the stream right away, though of
680 /// course this data will be lost if the connection is not
681 /// actually successful.
682 pub fn optimistic(&mut self) -> &mut Self {
683 self.optimistic_stream = true;
684 self
685 }
686
687 /// Return true if this stream has been configured as "optimistic".
688 ///
689 /// See [`StreamPrefs::optimistic`] for more info.
690 pub fn is_optimistic(&self) -> bool {
691 self.optimistic_stream
692 }
693
694 /// Indicate whether connection to a hidden service (`.onion` service) should be allowed
695 ///
696 /// If `Explicit(false)`, attempts to connect to Onion Services will be forced to fail with
697 /// an error of kind [`InvalidStreamTarget`](crate::ErrorKind::InvalidStreamTarget).
698 ///
699 /// If `Explicit(true)`, Onion Service connections are enabled.
700 ///
701 /// If `Auto`, the behaviour depends on the `address_filter.allow_onion_addrs`
702 /// configuration option, which is in turn enabled by default.
703 #[cfg(feature = "onion-service-client")]
704 pub fn connect_to_onion_services(
705 &mut self,
706 connect_to_onion_services: BoolOrAuto,
707 ) -> &mut Self {
708 self.connect_to_onion_services = connect_to_onion_services;
709 self
710 }
711 /// Return a TargetPort to describe what kind of exit policy our
712 /// target circuit needs to support.
713 fn wrap_target_port(&self, port: u16) -> TargetPort {
714 match self.ip_ver_pref {
715 IpVersionPreference::Ipv6Only => TargetPort::ipv6(port),
716 _ => TargetPort::ipv4(port),
717 }
718 }
719
720 /// Return a new StreamParameters based on this configuration.
721 fn stream_parameters(&self) -> StreamParameters {
722 let mut params = StreamParameters::default();
723 params
724 .ip_version(self.ip_ver_pref)
725 .optimistic(self.optimistic_stream);
726 params
727 }
728
729 /// Indicate that connections with these preferences should have their own isolation group
730 ///
731 /// This is a convenience method which creates a fresh [`IsolationToken`]
732 /// and sets it for these preferences.
733 ///
734 /// This connection preference is orthogonal to isolation established by
735 /// [`TorClient::isolated_client`]. Connections made with an `isolated_client` (and its
736 /// clones) will not share circuits with the original client, even if the same
737 /// `isolation` is specified via the `ConnectionPrefs` in force.
738 pub fn new_isolation_group(&mut self) -> &mut Self {
739 self.isolation = StreamIsolationPreference::Explicit(Box::new(IsolationToken::new()));
740 self
741 }
742
743 /// Indicate which other connections might use the same circuit
744 /// as this one.
745 ///
746 /// By default all connections made on all clones of a `TorClient` may share connections.
747 /// Connections made with a particular `isolation` may share circuits with each other.
748 ///
749 /// This connection preference is orthogonal to isolation established by
750 /// [`TorClient::isolated_client`]. Connections made with an `isolated_client` (and its
751 /// clones) will not share circuits with the original client, even if the same
752 /// `isolation` is specified via the `ConnectionPrefs` in force.
753 pub fn set_isolation<T>(&mut self, isolation: T) -> &mut Self
754 where
755 T: Into<Box<dyn Isolation>>,
756 {
757 self.isolation = StreamIsolationPreference::Explicit(isolation.into());
758 self
759 }
760
761 /// Indicate that no connection should share a circuit with any other.
762 ///
763 /// **Use with care:** This is likely to have poor performance, and imposes a much greater load
764 /// on the Tor network. Use this option only to make small numbers of connections each of
765 /// which needs to be isolated from all other connections.
766 ///
767 /// (Don't just use this as a "get more privacy!!" method: the circuits
768 /// that it put connections on will have no more privacy than any other
769 /// circuits. The only benefit is that these circuits will not be shared
770 /// by multiple streams.)
771 ///
772 /// This can be undone by calling `set_isolation` or `new_isolation_group` on these
773 /// preferences.
774 pub fn isolate_every_stream(&mut self) -> &mut Self {
775 self.isolation = StreamIsolationPreference::EveryStream;
776 self
777 }
778
779 /// Return an [`Isolation`] which separates according to these `StreamPrefs` (only)
780 ///
781 /// This describes which connections or operations might use
782 /// the same circuit(s) as this one.
783 ///
784 /// Since this doesn't have access to the `TorClient`,
785 /// it doesn't separate streams which ought to be separated because of
786 /// the way their `TorClient`s are isolated.
787 /// For that, use [`TorClient::isolation`].
788 fn prefs_isolation(&self) -> Option<Box<dyn Isolation>> {
789 use StreamIsolationPreference as SIP;
790 match self.isolation {
791 SIP::None => None,
792 SIP::Explicit(ref ig) => Some(ig.clone()),
793 SIP::EveryStream => Some(Box::new(IsolationToken::new())),
794 }
795 }
796
797 // TODO: Add some way to be IPFlexible, and require exit to support both.
798}
799
800#[cfg(all(
801 any(feature = "native-tls", feature = "rustls"),
802 any(feature = "async-std", feature = "tokio")
803))]
804impl TorClient<PreferredRuntime> {
805 /// Bootstrap a connection to the Tor network, using the provided `config`.
806 ///
807 /// Returns a client once there is enough directory material to
808 /// connect safely over the Tor network.
809 ///
810 /// Consider using [`TorClient::builder`] for more fine-grained control.
811 ///
812 /// # Panics
813 ///
814 /// If Tokio is being used (the default), panics if created outside the context of a currently
815 /// running Tokio runtime. See the documentation for [`PreferredRuntime::current`] for
816 /// more information.
817 ///
818 /// If using `async-std`, either take care to ensure Arti is not compiled with Tokio support,
819 /// or manually create an `async-std` runtime using [`tor_rtcompat`] and use it with
820 /// [`TorClient::with_runtime`].
821 ///
822 /// # Do not fork
823 ///
824 /// The process [**may not fork**](tor_rtcompat#do-not-fork)
825 /// (except, very carefully, before exec)
826 /// after calling this function, because it creates a [`PreferredRuntime`].
827 pub async fn create_bootstrapped(config: TorClientConfig) -> crate::Result<Self> {
828 let runtime = PreferredRuntime::current()
829 .expect("TorClient could not get an asynchronous runtime; are you running in the right context?");
830
831 Self::with_runtime(runtime)
832 .config(config)
833 .create_bootstrapped()
834 .await
835 }
836
837 /// Return a new builder for creating TorClient objects.
838 ///
839 /// If you want to make a [`TorClient`] synchronously, this is what you want; call
840 /// `TorClientBuilder::create_unbootstrapped` on the returned builder.
841 ///
842 /// # Panics
843 ///
844 /// If Tokio is being used (the default), panics if created outside the context of a currently
845 /// running Tokio runtime. See the documentation for `tokio::runtime::Handle::current` for
846 /// more information.
847 ///
848 /// If using `async-std`, either take care to ensure Arti is not compiled with Tokio support,
849 /// or manually create an `async-std` runtime using [`tor_rtcompat`] and use it with
850 /// [`TorClient::with_runtime`].
851 ///
852 /// # Do not fork
853 ///
854 /// The process [**may not fork**](tor_rtcompat#do-not-fork)
855 /// (except, very carefully, before exec)
856 /// after calling this function, because it creates a [`PreferredRuntime`].
857 pub fn builder() -> TorClientBuilder<PreferredRuntime> {
858 let runtime = PreferredRuntime::current()
859 .expect("TorClient could not get an asynchronous runtime; are you running in the right context?");
860
861 TorClientBuilder::new(runtime)
862 }
863}
864
865impl<R: Runtime> TorClient<R> {
866 /// Return a new builder for creating TorClient objects, with a custom provided [`Runtime`].
867 ///
868 /// See the [`tor_rtcompat`] crate for more information on custom runtimes.
869 pub fn with_runtime(runtime: R) -> TorClientBuilder<R> {
870 TorClientBuilder::new(runtime)
871 }
872
873 /// Implementation of `create_unbootstrapped`, split out in order to avoid manually specifying
874 /// double error conversions.
875 #[instrument(skip_all, level = "trace")]
876 pub(crate) fn create_inner(
877 runtime: R,
878 config: &TorClientConfig,
879 autobootstrap: BootstrapBehavior,
880 dirmgr_builder: &dyn crate::builder::DirProviderBuilder<R>,
881 dirmgr_extensions: tor_dirmgr::config::DirMgrExtensions,
882 ) -> StdResult<Self, ErrorDetail> {
883 if crate::util::running_as_setuid() {
884 return Err(tor_error::bad_api_usage!(
885 "Arti does not support running in a setuid or setgid context."
886 )
887 .into());
888 }
889
890 let memquota = MemoryQuotaTracker::new(&runtime, config.system.memory.clone())?;
891
892 let path_resolver = Arc::new(config.path_resolver.clone());
893
894 let (state_dir, mistrust) = config.state_dir()?;
895 #[cfg(feature = "onion-service-service")]
896 let state_directory =
897 StateDirectory::new(&state_dir, mistrust).map_err(ErrorDetail::StateAccess)?;
898
899 let dormant = DormantMode::Normal;
900 let dir_cfg = {
901 let mut c: tor_dirmgr::DirMgrConfig = config.dir_mgr_config()?;
902 c.extensions = dirmgr_extensions;
903 c
904 };
905
906 let statemgr = Self::statemgr_from_config(config)?;
907
908 // Try to take state ownership early, so we'll know if we have it.
909 // Note that this `try_lock()` may return `Ok` even if we can't acquire the lock.
910 // (At this point we don't yet care if we have it.)
911 let _ignore_status = statemgr.try_lock().map_err(ErrorDetail::StateMgrSetup)?;
912
913 let addr_cfg = config.address_filter.clone();
914
915 let (status_sender, status_receiver) = postage::watch::channel();
916 let status_receiver = status::BootstrapEvents {
917 inner: status_receiver,
918 };
919 let chanmgr = Arc::new(
920 tor_chanmgr::ChanMgr::new(
921 runtime.clone(),
922 ChanMgrConfig::new(config.channel.clone()),
923 dormant.into(),
924 &NetParameters::from_map(&config.override_net_params),
925 memquota.clone(),
926 )
927 .map_err(ErrorDetail::ChanMgrSetup)?,
928 );
929 let guardmgr = tor_guardmgr::GuardMgr::new(runtime.clone(), statemgr.clone(), config)
930 .map_err(ErrorDetail::GuardMgrSetup)?;
931
932 #[cfg(feature = "pt-client")]
933 let pt_mgr = {
934 let pt_state_dir = state_dir.as_path().join("pt_state");
935 config.storage.permissions().make_directory(&pt_state_dir)?;
936
937 let mgr = Arc::new(tor_ptmgr::PtMgr::new(
938 config.bridges.transports.clone(),
939 pt_state_dir,
940 Arc::clone(&path_resolver),
941 runtime.clone(),
942 )?);
943
944 chanmgr.set_pt_mgr(mgr.clone());
945
946 mgr
947 };
948
949 let circmgr = Arc::new(
950 tor_circmgr::CircMgr::new(
951 config,
952 statemgr.clone(),
953 &runtime,
954 Arc::clone(&chanmgr),
955 &guardmgr,
956 )
957 .map_err(ErrorDetail::CircMgrSetup)?,
958 );
959
960 let timeout_cfg = config.stream_timeouts.clone();
961
962 let dirmgr_store =
963 DirMgrStore::new(&dir_cfg, runtime.clone(), false).map_err(ErrorDetail::DirMgrSetup)?;
964 let dirmgr = dirmgr_builder
965 .build(
966 runtime.clone(),
967 dirmgr_store.clone(),
968 Arc::clone(&circmgr),
969 dir_cfg,
970 )
971 .map_err(crate::Error::into_detail)?;
972
973 let software_status_cfg = Arc::new(MutCfg::new(config.use_obsolete_software.clone()));
974 let rtclone = runtime.clone();
975 #[allow(clippy::print_stderr)]
976 crate::protostatus::enforce_protocol_recommendations(
977 &runtime,
978 Arc::clone(&dirmgr),
979 crate::software_release_date(),
980 crate::supported_protocols(),
981 Arc::clone(&software_status_cfg),
982 // TODO #1932: It would be nice to have a cleaner shutdown mechanism here,
983 // but that will take some work.
984 |fatal| async move {
985 use tor_error::ErrorReport as _;
986 // We already logged this error, but let's tell stderr too.
987 eprintln!(
988 "Shutting down because of unsupported software version.\nError was:\n{}",
989 fatal.report(),
990 );
991 if let Some(hint) = crate::err::Error::from(fatal).hint() {
992 eprintln!("{}", hint);
993 }
994 // Give the tracing module a while to flush everything, since it has no built-in
995 // flush function.
996 rtclone.sleep(std::time::Duration::new(5, 0)).await;
997 std::process::exit(1);
998 },
999 )?;
1000
1001 let mut periodic_task_handles = circmgr
1002 .launch_background_tasks(&runtime, &dirmgr, statemgr.clone())
1003 .map_err(ErrorDetail::CircMgrSetup)?;
1004 periodic_task_handles.extend(dirmgr.download_task_handle());
1005
1006 periodic_task_handles.extend(
1007 chanmgr
1008 .launch_background_tasks(&runtime, dirmgr.clone().upcast_arc())
1009 .map_err(ErrorDetail::ChanMgrSetup)?,
1010 );
1011
1012 let (dormant_send, dormant_recv) = postage::watch::channel_with(Some(dormant));
1013 let dormant_send = DropNotifyWatchSender::new(dormant_send);
1014 #[cfg(feature = "bridge-client")]
1015 let bridge_desc_mgr = Arc::new(Mutex::new(None));
1016
1017 #[cfg(any(feature = "onion-service-client", feature = "onion-service-service"))]
1018 let hs_circ_pool = {
1019 let circpool = Arc::new(tor_circmgr::hspool::HsCircPool::new(&circmgr));
1020 circpool
1021 .launch_background_tasks(&runtime, &dirmgr.clone().upcast_arc())
1022 .map_err(ErrorDetail::CircMgrSetup)?;
1023 circpool
1024 };
1025
1026 #[cfg(feature = "onion-service-client")]
1027 let hsclient = {
1028 // Prompt the hs connector to do its data housekeeping when we get a new consensus.
1029 // That's a time we're doing a bunch of thinking anyway, and it's not very frequent.
1030 let housekeeping = dirmgr.events().filter_map(|event| async move {
1031 match event {
1032 DirEvent::NewConsensus => Some(()),
1033 _ => None,
1034 }
1035 });
1036 let housekeeping = Box::pin(housekeeping);
1037
1038 HsClientConnector::new(runtime.clone(), hs_circ_pool.clone(), config, housekeeping)?
1039 };
1040
1041 runtime
1042 .spawn(tasks_monitor_dormant(
1043 dormant_recv,
1044 dirmgr.clone().upcast_arc(),
1045 chanmgr.clone(),
1046 #[cfg(feature = "bridge-client")]
1047 bridge_desc_mgr.clone(),
1048 periodic_task_handles,
1049 ))
1050 .map_err(|e| ErrorDetail::from_spawn("periodic task dormant monitor", e))?;
1051
1052 let conn_status = chanmgr.bootstrap_events();
1053 let dir_status = dirmgr.bootstrap_events();
1054 let skew_status = circmgr.skew_events();
1055 runtime
1056 .spawn(status::report_status(
1057 status_sender,
1058 conn_status,
1059 dir_status,
1060 skew_status,
1061 ))
1062 .map_err(|e| ErrorDetail::from_spawn("top-level status reporter", e))?;
1063
1064 let client_isolation = IsolationToken::new();
1065 let inert_client = InertTorClient::new(config)?;
1066
1067 Ok(TorClient {
1068 runtime,
1069 client_isolation,
1070 connect_prefs: Default::default(),
1071 memquota,
1072 chanmgr,
1073 circmgr,
1074 dirmgr_store,
1075 dirmgr,
1076 #[cfg(feature = "bridge-client")]
1077 bridge_desc_mgr,
1078 #[cfg(feature = "pt-client")]
1079 pt_mgr,
1080 #[cfg(feature = "onion-service-client")]
1081 hsclient,
1082 #[cfg(any(feature = "onion-service-client", feature = "onion-service-service"))]
1083 hs_circ_pool,
1084 inert_client,
1085 guardmgr,
1086 statemgr,
1087 addrcfg: Arc::new(addr_cfg.into()),
1088 timeoutcfg: Arc::new(timeout_cfg.into()),
1089 reconfigure_lock: Arc::new(Mutex::new(())),
1090 status_receiver,
1091 bootstrap_in_progress: Arc::new(AsyncMutex::new(())),
1092 should_bootstrap: autobootstrap,
1093 dormant: Arc::new(Mutex::new(dormant_send)),
1094 #[cfg(feature = "onion-service-service")]
1095 state_directory,
1096 path_resolver,
1097 software_status_cfg,
1098 })
1099 }
1100
1101 /// Construct a state manager from the client configuration.
1102 fn statemgr_from_config(config: &TorClientConfig) -> Result<UsingStateMgr, ErrorDetail> {
1103 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
1104 {
1105 use tor_persist::FsStateMgr;
1106
1107 let (state_dir, mistrust) = config.state_dir()?;
1108 FsStateMgr::from_path_and_mistrust(state_dir, mistrust)
1109 .map_err(ErrorDetail::StateMgrSetup)
1110 }
1111 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
1112 {
1113 unimplemented!()
1114 }
1115 }
1116
1117 /// Bootstrap a connection to the Tor network, with a client created by `create_unbootstrapped`.
1118 ///
1119 /// Since cloned copies of a `TorClient` share internal state, you can bootstrap a client by
1120 /// cloning it and running this function in a background task (or similar). This function
1121 /// only needs to be called on one client in order to bootstrap all of its clones.
1122 ///
1123 /// Returns once there is enough directory material to connect safely over the Tor network.
1124 /// If the client or one of its clones has already been bootstrapped, returns immediately with
1125 /// success. If a bootstrap is in progress, waits for it to finish, then retries it if it
1126 /// failed (returning success if it succeeded).
1127 ///
1128 /// Bootstrap progress can be tracked by listening to the event receiver returned by
1129 /// [`bootstrap_events`](TorClient::bootstrap_events).
1130 ///
1131 /// # Failures
1132 ///
1133 /// If the bootstrapping process fails, returns an error. This function can safely be called
1134 /// again later to attempt to bootstrap another time.
1135 #[instrument(skip_all, level = "trace")]
1136 pub async fn bootstrap(&self) -> crate::Result<()> {
1137 self.bootstrap_inner().await.map_err(ErrorDetail::into)
1138 }
1139
1140 /// Implementation of `bootstrap`, split out in order to avoid manually specifying
1141 /// double error conversions.
1142 async fn bootstrap_inner(&self) -> StdResult<(), ErrorDetail> {
1143 // Make sure we have a bridge descriptor manager, which is active iff required
1144 #[cfg(feature = "bridge-client")]
1145 {
1146 let mut dormant = self.dormant.lock().expect("dormant lock poisoned");
1147 let dormant = dormant.borrow();
1148 let dormant = dormant.ok_or_else(|| internal!("dormant dropped"))?.into();
1149
1150 let mut bdm = self.bridge_desc_mgr.lock().expect("bdm lock poisoned");
1151 if bdm.is_none() {
1152 let new_bdm = Arc::new(BridgeDescMgr::new(
1153 &Default::default(),
1154 self.runtime.clone(),
1155 self.dirmgr_store.clone(),
1156 self.circmgr.clone(),
1157 dormant,
1158 )?);
1159 self.guardmgr
1160 .install_bridge_desc_provider(&(new_bdm.clone() as _))
1161 .map_err(ErrorDetail::GuardMgrSetup)?;
1162 // If ^ that fails, we drop the BridgeDescMgr again. It may do some
1163 // work but will hopefully eventually quit.
1164 *bdm = Some(new_bdm);
1165 }
1166 }
1167
1168 // Wait for an existing bootstrap attempt to finish first.
1169 //
1170 // This is a futures::lock::Mutex, so it's okay to await while we hold it.
1171 let _bootstrap_lock = self.bootstrap_in_progress.lock().await;
1172
1173 if self
1174 .statemgr
1175 .try_lock()
1176 .map_err(ErrorDetail::StateAccess)?
1177 .held()
1178 {
1179 debug!("It appears we have the lock on our state files.");
1180 } else {
1181 info!(
1182 "Another process has the lock on our state files. We'll proceed in read-only mode."
1183 );
1184 }
1185
1186 // If we fail to bootstrap (i.e. we return before the disarm() point below), attempt to
1187 // unlock the state files.
1188 let unlock_guard = util::StateMgrUnlockGuard::new(&self.statemgr);
1189
1190 self.dirmgr
1191 .bootstrap()
1192 .await
1193 .map_err(ErrorDetail::DirMgrBootstrap)?;
1194
1195 // Since we succeeded, disarm the unlock guard.
1196 unlock_guard.disarm();
1197
1198 Ok(())
1199 }
1200
1201 /// ## For `BootstrapBehavior::OnDemand` clients
1202 ///
1203 /// Initiate a bootstrap by calling `bootstrap` (which is idempotent, so attempts to
1204 /// bootstrap twice will just do nothing).
1205 ///
1206 /// ## For `BootstrapBehavior::Manual` clients
1207 ///
1208 /// Check whether a bootstrap is in progress; if one is, wait until it finishes
1209 /// and then return. (Otherwise, return immediately.)
1210 #[instrument(skip_all, level = "trace")]
1211 async fn wait_for_bootstrap(&self) -> StdResult<(), ErrorDetail> {
1212 match self.should_bootstrap {
1213 BootstrapBehavior::OnDemand => {
1214 self.bootstrap_inner().await?;
1215 }
1216 BootstrapBehavior::Manual => {
1217 // Grab the lock, and immediately release it. That will ensure that nobody else is trying to bootstrap.
1218 self.bootstrap_in_progress.lock().await;
1219 }
1220 }
1221 self.dormant
1222 .lock()
1223 .map_err(|_| internal!("dormant poisoned"))?
1224 .try_maybe_send(|dormant| {
1225 Ok::<_, Bug>(Some({
1226 match dormant.ok_or_else(|| internal!("dormant dropped"))? {
1227 DormantMode::Soft => DormantMode::Normal,
1228 other @ DormantMode::Normal => other,
1229 }
1230 }))
1231 })?;
1232 Ok(())
1233 }
1234
1235 /// Change the configuration of this TorClient to `new_config`.
1236 ///
1237 /// The `how` describes whether to perform an all-or-nothing
1238 /// reconfiguration: either all of the configuration changes will be
1239 /// applied, or none will. If you have disabled all-or-nothing changes, then
1240 /// only fatal errors will be reported in this function's return value.
1241 ///
1242 /// This function applies its changes to **all** TorClient instances derived
1243 /// from the same call to `TorClient::create_*`: even ones whose circuits
1244 /// are isolated from this handle.
1245 ///
1246 /// # Limitations
1247 ///
1248 /// Although most options are reconfigurable, there are some whose values
1249 /// can't be changed on an a running TorClient. Those options (or their
1250 /// sections) are explicitly documented not to be changeable.
1251 /// NOTE: Currently, not all of these non-reconfigurable options are
1252 /// documented. See [arti#1721][arti-1721].
1253 ///
1254 /// [arti-1721]: https://gitlab.torproject.org/tpo/core/arti/-/issues/1721
1255 ///
1256 /// Changing some options do not take effect immediately on all open streams
1257 /// and circuits, but rather affect only future streams and circuits. Those
1258 /// are also explicitly documented.
1259 #[instrument(skip_all, level = "trace")]
1260 pub fn reconfigure(
1261 &self,
1262 new_config: &TorClientConfig,
1263 how: tor_config::Reconfigure,
1264 ) -> crate::Result<()> {
1265 // We need to hold this lock while we're reconfiguring the client: even
1266 // though the individual fields have their own synchronization, we can't
1267 // safely let two threads change them at once. If we did, then we'd
1268 // introduce time-of-check/time-of-use bugs in checking our configuration,
1269 // deciding how to change it, then applying the changes.
1270 let guard = self.reconfigure_lock.lock().expect("Poisoned lock");
1271
1272 match how {
1273 tor_config::Reconfigure::AllOrNothing => {
1274 // We have to check before we make any changes.
1275 self.reconfigure_inner(
1276 new_config,
1277 tor_config::Reconfigure::CheckAllOrNothing,
1278 &guard,
1279 )?;
1280 }
1281 tor_config::Reconfigure::CheckAllOrNothing => {}
1282 tor_config::Reconfigure::WarnOnFailures => {}
1283 _ => {}
1284 }
1285
1286 // Actually reconfigure
1287 self.reconfigure_inner(new_config, how, &guard)?;
1288
1289 Ok(())
1290 }
1291
1292 /// This is split out from `reconfigure` so we can do the all-or-nothing
1293 /// check without recursion. the caller to this method must hold the
1294 /// `reconfigure_lock`.
1295 #[instrument(level = "trace", skip_all)]
1296 fn reconfigure_inner(
1297 &self,
1298 new_config: &TorClientConfig,
1299 how: tor_config::Reconfigure,
1300 _reconfigure_lock_guard: &std::sync::MutexGuard<'_, ()>,
1301 ) -> crate::Result<()> {
1302 // We ignore 'new_config.path_resolver' here since CfgPathResolver does not impl PartialEq
1303 // and we have no way to compare them, but this field is explicitly documented as being
1304 // non-reconfigurable anyways.
1305
1306 let dir_cfg = new_config.dir_mgr_config().map_err(wrap_err)?;
1307 let state_cfg = new_config
1308 .storage
1309 .expand_state_dir(&self.path_resolver)
1310 .map_err(wrap_err)?;
1311 let addr_cfg = &new_config.address_filter;
1312 let timeout_cfg = &new_config.stream_timeouts;
1313
1314 // TODO wasm: This ins't really how things should be long term,
1315 // but once we have a more generic notion of configuring storage
1316 // we can change this to comply with it.
1317 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
1318 if state_cfg != self.statemgr.path() {
1319 how.cannot_change("storage.state_dir").map_err(wrap_err)?;
1320 }
1321
1322 self.memquota
1323 .reconfigure(new_config.system.memory.clone(), how)
1324 .map_err(wrap_err)?;
1325
1326 let retire_circuits = self
1327 .circmgr
1328 .reconfigure(new_config, how)
1329 .map_err(wrap_err)?;
1330
1331 #[cfg(any(feature = "onion-service-client", feature = "onion-service-service"))]
1332 if retire_circuits != RetireCircuits::None {
1333 self.hs_circ_pool.retire_all_circuits().map_err(wrap_err)?;
1334 }
1335
1336 self.dirmgr.reconfigure(&dir_cfg, how).map_err(wrap_err)?;
1337
1338 let netparams = self.dirmgr.params();
1339
1340 self.chanmgr
1341 .reconfigure(&new_config.channel, how, netparams)
1342 .map_err(wrap_err)?;
1343
1344 #[cfg(feature = "pt-client")]
1345 self.pt_mgr
1346 .reconfigure(how, new_config.bridges.transports.clone())
1347 .map_err(wrap_err)?;
1348
1349 if how == tor_config::Reconfigure::CheckAllOrNothing {
1350 return Ok(());
1351 }
1352
1353 self.addrcfg.replace(addr_cfg.clone());
1354 self.timeoutcfg.replace(timeout_cfg.clone());
1355 self.software_status_cfg
1356 .replace(new_config.use_obsolete_software.clone());
1357
1358 Ok(())
1359 }
1360
1361 /// Return a new isolated `TorClient` handle.
1362 ///
1363 /// The two `TorClient`s will share internal state and configuration, but
1364 /// their streams will never share circuits with one another.
1365 ///
1366 /// Use this function when you want separate parts of your program to
1367 /// each have a TorClient handle, but where you don't want their
1368 /// activities to be linkable to one another over the Tor network.
1369 ///
1370 /// Calling this function is usually preferable to creating a
1371 /// completely separate TorClient instance, since it can share its
1372 /// internals with the existing `TorClient`.
1373 ///
1374 /// (Connections made with clones of the returned `TorClient` may
1375 /// share circuits with each other.)
1376 #[must_use]
1377 pub fn isolated_client(&self) -> TorClient<R> {
1378 let mut result = self.clone();
1379 result.client_isolation = IsolationToken::new();
1380 result
1381 }
1382
1383 /// Launch an anonymized connection to the provided address and port over
1384 /// the Tor network.
1385 ///
1386 /// Note that because Tor prefers to do DNS resolution on the remote side of
1387 /// the network, this function takes its address as a string:
1388 ///
1389 /// ```no_run
1390 /// # use arti_client::*;use tor_rtcompat::Runtime;
1391 /// # async fn ex<R:Runtime>(tor_client: TorClient<R>) -> Result<()> {
1392 /// // The most usual way to connect is via an address-port tuple.
1393 /// let socket = tor_client.connect(("www.example.com", 443)).await?;
1394 ///
1395 /// // You can also specify an address and port as a colon-separated string.
1396 /// let socket = tor_client.connect("www.example.com:443").await?;
1397 /// # Ok(())
1398 /// # }
1399 /// ```
1400 ///
1401 /// Hostnames are _strongly_ preferred here: if this function allowed the
1402 /// caller here to provide an IPAddr or [`IpAddr`] or
1403 /// [`SocketAddr`](std::net::SocketAddr) address, then
1404 ///
1405 /// ```no_run
1406 /// # use arti_client::*; use tor_rtcompat::Runtime;
1407 /// # async fn ex<R:Runtime>(tor_client: TorClient<R>) -> Result<()> {
1408 /// # use std::net::ToSocketAddrs;
1409 /// // BAD: We're about to leak our target address to the local resolver!
1410 /// let address = "www.example.com:443".to_socket_addrs().unwrap().next().unwrap();
1411 /// // 🤯 Oh no! Now any eavesdropper can tell where we're about to connect! 🤯
1412 ///
1413 /// // Fortunately, this won't compile, since SocketAddr doesn't implement IntoTorAddr.
1414 /// // let socket = tor_client.connect(address).await?;
1415 /// // ^^^^^^^ the trait `IntoTorAddr` is not implemented for `std::net::SocketAddr`
1416 /// # Ok(())
1417 /// # }
1418 /// ```
1419 ///
1420 /// If you really do need to connect to an IP address rather than a
1421 /// hostname, and if you're **sure** that the IP address came from a safe
1422 /// location, there are a few ways to do so.
1423 ///
1424 /// ```no_run
1425 /// # use arti_client::{TorClient,Result};use tor_rtcompat::Runtime;
1426 /// # use std::net::{SocketAddr,IpAddr};
1427 /// # async fn ex<R:Runtime>(tor_client: TorClient<R>) -> Result<()> {
1428 /// # use std::net::ToSocketAddrs;
1429 /// // ⚠️This is risky code!⚠️
1430 /// // (Make sure your addresses came from somewhere safe...)
1431 ///
1432 /// // If we have a fixed address, we can just provide it as a string.
1433 /// let socket = tor_client.connect("192.0.2.22:443").await?;
1434 /// let socket = tor_client.connect(("192.0.2.22", 443)).await?;
1435 ///
1436 /// // If we have a SocketAddr or an IpAddr, we can use the
1437 /// // DangerouslyIntoTorAddr trait.
1438 /// use arti_client::DangerouslyIntoTorAddr;
1439 /// let sockaddr = SocketAddr::from(([192, 0, 2, 22], 443));
1440 /// let ipaddr = IpAddr::from([192, 0, 2, 22]);
1441 /// let socket = tor_client.connect(sockaddr.into_tor_addr_dangerously().unwrap()).await?;
1442 /// let socket = tor_client.connect((ipaddr, 443).into_tor_addr_dangerously().unwrap()).await?;
1443 /// # Ok(())
1444 /// # }
1445 /// ```
1446 #[instrument(skip_all, level = "trace")]
1447 pub async fn connect<A: IntoTorAddr>(&self, target: A) -> crate::Result<DataStream> {
1448 self.connect_with_prefs(target, &self.connect_prefs).await
1449 }
1450
1451 /// Launch an anonymized connection to the provided address and
1452 /// port over the Tor network, with explicit connection preferences.
1453 ///
1454 /// Note that because Tor prefers to do DNS resolution on the remote
1455 /// side of the network, this function takes its address as a string.
1456 /// (See [`TorClient::connect()`] for more information.)
1457 #[instrument(skip_all, level = "trace")]
1458 pub async fn connect_with_prefs<A: IntoTorAddr>(
1459 &self,
1460 target: A,
1461 prefs: &StreamPrefs,
1462 ) -> crate::Result<DataStream> {
1463 let addr = target.into_tor_addr().map_err(wrap_err)?;
1464 let mut stream_parameters = prefs.stream_parameters();
1465 // This macro helps prevent code duplication in the match below.
1466 //
1467 // Ideally, the match should resolve to a tuple consisting of the
1468 // tunnel, and the address, port and stream params,
1469 // but that's not currently possible because
1470 // the Exit and Hs branches use different tunnel types.
1471 //
1472 // TODO: replace with an async closure (when our MSRV allows it),
1473 // or with a more elegant approach.
1474 macro_rules! begin_stream {
1475 ($tunnel:expr, $addr:expr, $port:expr, $stream_params:expr) => {{
1476 let fut = $tunnel.begin_stream($addr, $port, $stream_params);
1477 self.runtime
1478 .timeout(self.timeoutcfg.get().connect_timeout, fut)
1479 .await
1480 .map_err(|_| ErrorDetail::ExitTimeout)?
1481 .map_err(|cause| ErrorDetail::StreamFailed {
1482 cause,
1483 kind: "data",
1484 })
1485 }};
1486 }
1487
1488 let stream = match addr.into_stream_instructions(&self.addrcfg.get(), prefs)? {
1489 StreamInstructions::Exit {
1490 hostname: addr,
1491 port,
1492 } => {
1493 let exit_ports = [prefs.wrap_target_port(port)];
1494 let tunnel = self
1495 .get_or_launch_exit_tunnel(&exit_ports, prefs)
1496 .await
1497 .map_err(wrap_err)?;
1498 debug!("Got a circuit for {}:{}", sensitive(&addr), port);
1499 begin_stream!(tunnel, &addr, port, Some(stream_parameters))
1500 }
1501
1502 #[cfg(not(feature = "onion-service-client"))]
1503 #[allow(unused_variables)] // for hostname and port
1504 StreamInstructions::Hs {
1505 hsid,
1506 hostname,
1507 port,
1508 } => void::unreachable(hsid.0),
1509
1510 #[cfg(feature = "onion-service-client")]
1511 StreamInstructions::Hs {
1512 hsid,
1513 hostname,
1514 port,
1515 } => {
1516 use safelog::DisplayRedacted as _;
1517
1518 self.wait_for_bootstrap().await?;
1519 let netdir = self.netdir(Timeliness::Timely, "connect to a hidden service")?;
1520
1521 let mut hs_client_secret_keys_builder = HsClientSecretKeysBuilder::default();
1522
1523 if let Some(keymgr) = &self.inert_client.keymgr {
1524 let desc_enc_key_spec = HsClientDescEncKeypairSpecifier::new(hsid);
1525
1526 let ks_hsc_desc_enc =
1527 keymgr.get::<HsClientDescEncKeypair>(&desc_enc_key_spec)?;
1528
1529 if let Some(ks_hsc_desc_enc) = ks_hsc_desc_enc {
1530 debug!(
1531 "Found descriptor decryption key for {}",
1532 hsid.display_redacted()
1533 );
1534 hs_client_secret_keys_builder.ks_hsc_desc_enc(ks_hsc_desc_enc);
1535 }
1536 };
1537
1538 let hs_client_secret_keys = hs_client_secret_keys_builder
1539 .build()
1540 .map_err(ErrorDetail::Configuration)?;
1541
1542 let tunnel = self
1543 .hsclient
1544 .get_or_launch_tunnel(
1545 &netdir,
1546 hsid,
1547 hs_client_secret_keys,
1548 self.isolation(prefs),
1549 )
1550 .await
1551 .map_err(|cause| ErrorDetail::ObtainHsCircuit { cause, hsid })?;
1552 // On connections to onion services, we have to suppress
1553 // everything except the port from the BEGIN message. We also
1554 // disable optimistic data.
1555 stream_parameters
1556 .suppress_hostname()
1557 .suppress_begin_flags()
1558 .optimistic(false);
1559
1560 begin_stream!(tunnel, &hostname, port, Some(stream_parameters))
1561 }
1562 };
1563
1564 Ok(stream?)
1565 }
1566
1567 /// Sets the default preferences for future connections made with this client.
1568 ///
1569 /// The preferences set with this function will be inherited by clones of this client, but
1570 /// updates to the preferences in those clones will not propagate back to the original. I.e.,
1571 /// the preferences are copied by `clone`.
1572 ///
1573 /// Connection preferences always override configuration, even configuration set later
1574 /// (eg, by a config reload).
1575 pub fn set_stream_prefs(&mut self, connect_prefs: StreamPrefs) {
1576 self.connect_prefs = connect_prefs;
1577 }
1578
1579 /// Provides a new handle on this client, but with adjusted default preferences.
1580 ///
1581 /// Connections made with e.g. [`connect`](TorClient::connect) on the returned handle will use
1582 /// `connect_prefs`. This is a convenience wrapper for `clone` and `set_connect_prefs`.
1583 #[must_use]
1584 pub fn clone_with_prefs(&self, connect_prefs: StreamPrefs) -> Self {
1585 let mut result = self.clone();
1586 result.set_stream_prefs(connect_prefs);
1587 result
1588 }
1589
1590 /// On success, return a list of IP addresses.
1591 #[instrument(skip_all, level = "trace")]
1592 pub async fn resolve(&self, hostname: &str) -> crate::Result<Vec<IpAddr>> {
1593 self.resolve_with_prefs(hostname, &self.connect_prefs).await
1594 }
1595
1596 /// On success, return a list of IP addresses, but use prefs.
1597 #[instrument(skip_all, level = "trace")]
1598 pub async fn resolve_with_prefs(
1599 &self,
1600 hostname: &str,
1601 prefs: &StreamPrefs,
1602 ) -> crate::Result<Vec<IpAddr>> {
1603 // TODO This dummy port is only because `address::Host` is not pub(crate),
1604 // but I see no reason why it shouldn't be? Then `into_resolve_instructions`
1605 // should be a method on `Host`, not `TorAddr`. -Diziet.
1606 let addr = (hostname, 1).into_tor_addr().map_err(wrap_err)?;
1607
1608 match addr.into_resolve_instructions(&self.addrcfg.get(), prefs)? {
1609 ResolveInstructions::Exit(hostname) => {
1610 let circ = self.get_or_launch_exit_tunnel(&[], prefs).await?;
1611
1612 let resolve_future = circ.resolve(&hostname);
1613 let addrs = self
1614 .runtime
1615 .timeout(self.timeoutcfg.get().resolve_timeout, resolve_future)
1616 .await
1617 .map_err(|_| ErrorDetail::ExitTimeout)?
1618 .map_err(|cause| ErrorDetail::StreamFailed {
1619 cause,
1620 kind: "DNS lookup",
1621 })?;
1622
1623 Ok(addrs)
1624 }
1625 ResolveInstructions::Return(addrs) => Ok(addrs),
1626 }
1627 }
1628
1629 /// Perform a remote DNS reverse lookup with the provided IP address.
1630 ///
1631 /// On success, return a list of hostnames.
1632 #[instrument(skip_all, level = "trace")]
1633 pub async fn resolve_ptr(&self, addr: IpAddr) -> crate::Result<Vec<String>> {
1634 self.resolve_ptr_with_prefs(addr, &self.connect_prefs).await
1635 }
1636
1637 /// Perform a remote DNS reverse lookup with the provided IP address.
1638 ///
1639 /// On success, return a list of hostnames.
1640 #[instrument(level = "trace", skip_all)]
1641 pub async fn resolve_ptr_with_prefs(
1642 &self,
1643 addr: IpAddr,
1644 prefs: &StreamPrefs,
1645 ) -> crate::Result<Vec<String>> {
1646 let circ = self.get_or_launch_exit_tunnel(&[], prefs).await?;
1647
1648 let resolve_ptr_future = circ.resolve_ptr(addr);
1649 let hostnames = self
1650 .runtime
1651 .timeout(
1652 self.timeoutcfg.get().resolve_ptr_timeout,
1653 resolve_ptr_future,
1654 )
1655 .await
1656 .map_err(|_| ErrorDetail::ExitTimeout)?
1657 .map_err(|cause| ErrorDetail::StreamFailed {
1658 cause,
1659 kind: "reverse DNS lookup",
1660 })?;
1661
1662 Ok(hostnames)
1663 }
1664
1665 /// Return a reference to this client's directory manager.
1666 ///
1667 /// This function is unstable. It is only enabled if the crate was
1668 /// built with the `experimental-api` feature.
1669 #[cfg(feature = "experimental-api")]
1670 pub fn dirmgr(&self) -> &Arc<dyn tor_dirmgr::DirProvider> {
1671 &self.dirmgr
1672 }
1673
1674 /// Return a reference to this client's circuit manager.
1675 ///
1676 /// This function is unstable. It is only enabled if the crate was
1677 /// built with the `experimental-api` feature.
1678 #[cfg(feature = "experimental-api")]
1679 pub fn circmgr(&self) -> &Arc<tor_circmgr::CircMgr<R>> {
1680 &self.circmgr
1681 }
1682
1683 /// Return a reference to this client's channel manager.
1684 ///
1685 /// This function is unstable. It is only enabled if the crate was
1686 /// built with the `experimental-api` feature.
1687 #[cfg(feature = "experimental-api")]
1688 pub fn chanmgr(&self) -> &Arc<tor_chanmgr::ChanMgr<R>> {
1689 &self.chanmgr
1690 }
1691
1692 /// Return a reference to this client's circuit pool.
1693 ///
1694 /// This function is unstable. It is only enabled if the crate was
1695 /// built with the `experimental-api` feature and any of `onion-service-client`
1696 /// or `onion-service-service` features. This method is required to invoke
1697 /// tor_hsservice::OnionService::launch()
1698 #[cfg(all(
1699 feature = "experimental-api",
1700 any(feature = "onion-service-client", feature = "onion-service-service")
1701 ))]
1702 pub fn hs_circ_pool(&self) -> &Arc<tor_circmgr::hspool::HsCircPool<R>> {
1703 &self.hs_circ_pool
1704 }
1705
1706 /// Return a reference to the runtime being used by this client.
1707 //
1708 // This API is not a hostage to fortune since we already require that R: Clone,
1709 // and necessarily a TorClient must have a clone of it.
1710 //
1711 // We provide it simply to save callers who have a TorClient from
1712 // having to separately keep their own handle,
1713 pub fn runtime(&self) -> &R {
1714 &self.runtime
1715 }
1716
1717 /// Return a netdir that is timely according to the rules of `timeliness`.
1718 ///
1719 /// The `action` string is a description of what we wanted to do with the
1720 /// directory, to be put into the error message if we couldn't find a directory.
1721 fn netdir(
1722 &self,
1723 timeliness: Timeliness,
1724 action: &'static str,
1725 ) -> StdResult<Arc<tor_netdir::NetDir>, ErrorDetail> {
1726 use tor_netdir::Error as E;
1727 match self.dirmgr.netdir(timeliness) {
1728 Ok(netdir) => Ok(netdir),
1729 Err(E::NoInfo) | Err(E::NotEnoughInfo) => {
1730 Err(ErrorDetail::BootstrapRequired { action })
1731 }
1732 Err(error) => Err(ErrorDetail::NoDir { error, action }),
1733 }
1734 }
1735
1736 /// Get or launch an exit-suitable circuit with a given set of
1737 /// exit ports.
1738 #[instrument(skip_all, level = "trace")]
1739 async fn get_or_launch_exit_tunnel(
1740 &self,
1741 exit_ports: &[TargetPort],
1742 prefs: &StreamPrefs,
1743 ) -> StdResult<ClientDataTunnel, ErrorDetail> {
1744 // TODO HS probably this netdir ought to be made in connect_with_prefs
1745 // like for StreamInstructions::Hs.
1746 self.wait_for_bootstrap().await?;
1747 let dir = self.netdir(Timeliness::Timely, "build a circuit")?;
1748
1749 let tunnel = self
1750 .circmgr
1751 .get_or_launch_exit(
1752 dir.as_ref().into(),
1753 exit_ports,
1754 self.isolation(prefs),
1755 #[cfg(feature = "geoip")]
1756 prefs.country_code,
1757 )
1758 .await
1759 .map_err(|cause| ErrorDetail::ObtainExitCircuit {
1760 cause,
1761 exit_ports: Sensitive::new(exit_ports.into()),
1762 })?;
1763 drop(dir); // This decreases the refcount on the netdir.
1764
1765 Ok(tunnel)
1766 }
1767
1768 /// Return an overall [`Isolation`] for this `TorClient` and a `StreamPrefs`.
1769 ///
1770 /// This describes which operations might use
1771 /// circuit(s) with this one.
1772 ///
1773 /// This combines isolation information from
1774 /// [`StreamPrefs::prefs_isolation`]
1775 /// and the `TorClient`'s isolation (eg from [`TorClient::isolated_client`]).
1776 fn isolation(&self, prefs: &StreamPrefs) -> StreamIsolation {
1777 let mut b = StreamIsolationBuilder::new();
1778 // Always consider our client_isolation.
1779 b.owner_token(self.client_isolation);
1780 // Consider stream isolation too, if it's set.
1781 if let Some(tok) = prefs.prefs_isolation() {
1782 b.stream_isolation(tok);
1783 }
1784 // Failure should be impossible with this builder.
1785 b.build().expect("Failed to construct StreamIsolation")
1786 }
1787
1788 /// Try to launch an onion service with a given configuration.
1789 ///
1790 /// Returns `Ok(None)` if the service specified is disabled in the config.
1791 ///
1792 /// This onion service will not actually handle any requests on its own: you
1793 /// will need to
1794 /// pull [`RendRequest`](tor_hsservice::RendRequest) objects from the returned stream,
1795 /// [`accept`](tor_hsservice::RendRequest::accept) the ones that you want to
1796 /// answer, and then wait for them to give you [`StreamRequest`](tor_hsservice::StreamRequest)s.
1797 ///
1798 /// You may find the [`tor_hsservice::handle_rend_requests`] API helpful for
1799 /// translating `RendRequest`s into `StreamRequest`s.
1800 ///
1801 /// If you want to forward all the requests from an onion service to a set
1802 /// of local ports, you may want to use the `tor-hsrproxy` crate.
1803 #[cfg(feature = "onion-service-service")]
1804 #[instrument(skip_all, level = "trace")]
1805 pub fn launch_onion_service(
1806 &self,
1807 config: tor_hsservice::OnionServiceConfig,
1808 ) -> crate::Result<
1809 Option<(
1810 Arc<tor_hsservice::RunningOnionService>,
1811 impl futures::Stream<Item = tor_hsservice::RendRequest> + use<R>,
1812 )>,
1813 > {
1814 let nickname = config.nickname();
1815
1816 if !config.enabled() {
1817 info!(
1818 nickname=%nickname,
1819 "Skipping onion service because it was disabled in the config"
1820 );
1821 return Ok(None);
1822 }
1823
1824 let keymgr = self
1825 .inert_client
1826 .keymgr
1827 .as_ref()
1828 .ok_or(ErrorDetail::KeystoreRequired {
1829 action: "launch onion service",
1830 })?
1831 .clone();
1832 let state_dir = self.state_directory.clone();
1833
1834 let service = tor_hsservice::OnionService::builder()
1835 .config(config) // TODO #1186: Allow override of KeyMgr for "ephemeral" operation?
1836 .keymgr(keymgr)
1837 // TODO #1186: Allow override of StateMgr for "ephemeral" operation?
1838 .state_dir(state_dir)
1839 .build()
1840 .map_err(ErrorDetail::LaunchOnionService)?;
1841 Ok(service
1842 .launch(
1843 self.runtime.clone(),
1844 self.dirmgr.clone().upcast_arc(),
1845 self.hs_circ_pool.clone(),
1846 Arc::clone(&self.path_resolver),
1847 )
1848 .map_err(ErrorDetail::LaunchOnionService)?)
1849 }
1850
1851 /// Try to launch an onion service with a given configuration and provided
1852 /// [`HsIdKeypair`]. If an onion service with the given nickname already has an
1853 /// associated `HsIdKeypair` in this `TorClient`'s `KeyMgr`, then this operation
1854 /// fails rather than overwriting the existing key.
1855 ///
1856 /// Returns `Ok(None)` if the service specified is disabled in the config.
1857 ///
1858 /// The specified `HsIdKeypair` will be inserted in the primary keystore.
1859 ///
1860 /// **Important**: depending on the configuration of your
1861 /// [primary keystore](tor_keymgr::config::PrimaryKeystoreConfig),
1862 /// the `HsIdKeypair` **may** get persisted to disk.
1863 /// By default, Arti's primary keystore is the [native](ArtiKeystoreKind::Native),
1864 /// disk-based keystore.
1865 ///
1866 /// This onion service will not actually handle any requests on its own: you
1867 /// will need to
1868 /// pull [`RendRequest`](tor_hsservice::RendRequest) objects from the returned stream,
1869 /// [`accept`](tor_hsservice::RendRequest::accept) the ones that you want to
1870 /// answer, and then wait for them to give you [`StreamRequest`](tor_hsservice::StreamRequest)s.
1871 ///
1872 /// You may find the [`tor_hsservice::handle_rend_requests`] API helpful for
1873 /// translating `RendRequest`s into `StreamRequest`s.
1874 ///
1875 /// If you want to forward all the requests from an onion service to a set
1876 /// of local ports, you may want to use the `tor-hsrproxy` crate.
1877 #[cfg(all(feature = "onion-service-service", feature = "experimental-api"))]
1878 #[instrument(skip_all, level = "trace")]
1879 pub fn launch_onion_service_with_hsid(
1880 &self,
1881 config: tor_hsservice::OnionServiceConfig,
1882 id_keypair: HsIdKeypair,
1883 ) -> crate::Result<
1884 Option<(
1885 Arc<tor_hsservice::RunningOnionService>,
1886 impl futures::Stream<Item = tor_hsservice::RendRequest> + use<R>,
1887 )>,
1888 > {
1889 let nickname = config.nickname();
1890 let hsid_spec = HsIdKeypairSpecifier::new(nickname.clone());
1891 let selector = KeystoreSelector::Primary;
1892
1893 let _kp = self
1894 .inert_client
1895 .keymgr
1896 .as_ref()
1897 .ok_or(ErrorDetail::KeystoreRequired {
1898 action: "launch onion service ex",
1899 })?
1900 .insert::<HsIdKeypair>(id_keypair, &hsid_spec, selector, false)?;
1901
1902 self.launch_onion_service(config)
1903 }
1904
1905 /// Generate a service discovery keypair for connecting to a hidden service running in
1906 /// "restricted discovery" mode.
1907 ///
1908 /// The `selector` argument is used for choosing the keystore in which to generate the keypair.
1909 /// While most users will want to write to the [`Primary`](KeystoreSelector::Primary), if you
1910 /// have configured this `TorClient` with a non-default keystore and wish to generate the
1911 /// keypair in it, you can do so by calling this function with a [KeystoreSelector::Id]
1912 /// specifying the keystore ID of your keystore.
1913 ///
1914 // Note: the selector argument exists for future-proofing reasons. We don't currently support
1915 // configuring custom or non-default keystores (see #1106).
1916 ///
1917 /// Returns an error if the key already exists in the specified key store.
1918 ///
1919 /// Important: the public part of the generated keypair must be shared with the service, and
1920 /// the service needs to be configured to allow the owner of its private counterpart to
1921 /// discover its introduction points. The caller is responsible for sharing the public part of
1922 /// the key with the hidden service.
1923 ///
1924 /// This function does not require the `TorClient` to be running or bootstrapped.
1925 //
1926 // TODO: decide whether this should use get_or_generate before making it
1927 // non-experimental
1928 #[cfg(all(
1929 feature = "onion-service-client",
1930 feature = "experimental-api",
1931 feature = "keymgr"
1932 ))]
1933 pub fn generate_service_discovery_key(
1934 &self,
1935 selector: KeystoreSelector,
1936 hsid: HsId,
1937 ) -> crate::Result<HsClientDescEncKey> {
1938 self.inert_client
1939 .generate_service_discovery_key(selector, hsid)
1940 }
1941
1942 /// Rotate the service discovery keypair for connecting to a hidden service running in
1943 /// "restricted discovery" mode.
1944 ///
1945 /// **If the specified keystore already contains a restricted discovery keypair
1946 /// for the service, it will be overwritten.** Otherwise, a new keypair is generated.
1947 ///
1948 /// The `selector` argument is used for choosing the keystore in which to generate the keypair.
1949 /// While most users will want to write to the [`Primary`](KeystoreSelector::Primary), if you
1950 /// have configured this `TorClient` with a non-default keystore and wish to generate the
1951 /// keypair in it, you can do so by calling this function with a [KeystoreSelector::Id]
1952 /// specifying the keystore ID of your keystore.
1953 ///
1954 // Note: the selector argument exists for future-proofing reasons. We don't currently support
1955 // configuring custom or non-default keystores (see #1106).
1956 ///
1957 /// Important: the public part of the generated keypair must be shared with the service, and
1958 /// the service needs to be configured to allow the owner of its private counterpart to
1959 /// discover its introduction points. The caller is responsible for sharing the public part of
1960 /// the key with the hidden service.
1961 ///
1962 /// This function does not require the `TorClient` to be running or bootstrapped.
1963 #[cfg(all(
1964 feature = "onion-service-client",
1965 feature = "experimental-api",
1966 feature = "keymgr"
1967 ))]
1968 #[cfg_attr(
1969 docsrs,
1970 doc(cfg(all(
1971 feature = "onion-service-client",
1972 feature = "experimental-api",
1973 feature = "keymgr"
1974 )))
1975 )]
1976 pub fn rotate_service_discovery_key(
1977 &self,
1978 selector: KeystoreSelector,
1979 hsid: HsId,
1980 ) -> crate::Result<HsClientDescEncKey> {
1981 self.inert_client
1982 .rotate_service_discovery_key(selector, hsid)
1983 }
1984
1985 /// Insert a service discovery secret key for connecting to a hidden service running in
1986 /// "restricted discovery" mode
1987 ///
1988 /// The `selector` argument is used for choosing the keystore in which to generate the keypair.
1989 /// While most users will want to write to the [`Primary`](KeystoreSelector::Primary), if you
1990 /// have configured this `TorClient` with a non-default keystore and wish to insert the
1991 /// key in it, you can do so by calling this function with a [KeystoreSelector::Id]
1992 ///
1993 // Note: the selector argument exists for future-proofing reasons. We don't currently support
1994 // configuring custom or non-default keystores (see #1106).
1995 ///
1996 /// Returns an error if the key already exists in the specified key store.
1997 ///
1998 /// Important: the public part of the generated keypair must be shared with the service, and
1999 /// the service needs to be configured to allow the owner of its private counterpart to
2000 /// discover its introduction points. The caller is responsible for sharing the public part of
2001 /// the key with the hidden service.
2002 ///
2003 /// This function does not require the `TorClient` to be running or bootstrapped.
2004 #[cfg(all(
2005 feature = "onion-service-client",
2006 feature = "experimental-api",
2007 feature = "keymgr"
2008 ))]
2009 #[cfg_attr(
2010 docsrs,
2011 doc(cfg(all(
2012 feature = "onion-service-client",
2013 feature = "experimental-api",
2014 feature = "keymgr"
2015 )))
2016 )]
2017 pub fn insert_service_discovery_key(
2018 &self,
2019 selector: KeystoreSelector,
2020 hsid: HsId,
2021 hs_client_desc_enc_secret_key: HsClientDescEncSecretKey,
2022 ) -> crate::Result<HsClientDescEncKey> {
2023 self.inert_client.insert_service_discovery_key(
2024 selector,
2025 hsid,
2026 hs_client_desc_enc_secret_key,
2027 )
2028 }
2029
2030 /// Return the service discovery public key for the service with the specified `hsid`.
2031 ///
2032 /// Returns `Ok(None)` if no such key exists.
2033 ///
2034 /// This function does not require the `TorClient` to be running or bootstrapped.
2035 #[cfg(all(feature = "onion-service-client", feature = "experimental-api"))]
2036 #[cfg_attr(
2037 docsrs,
2038 doc(cfg(all(feature = "onion-service-client", feature = "experimental-api")))
2039 )]
2040 pub fn get_service_discovery_key(
2041 &self,
2042 hsid: HsId,
2043 ) -> crate::Result<Option<HsClientDescEncKey>> {
2044 self.inert_client.get_service_discovery_key(hsid)
2045 }
2046
2047 /// Removes the service discovery keypair for the service with the specified `hsid`.
2048 ///
2049 /// Returns an error if the selected keystore is not the default keystore or one of the
2050 /// configured secondary stores.
2051 ///
2052 /// Returns `Ok(None)` if no such keypair exists whereas `Ok(Some()) means the keypair was successfully removed.
2053 ///
2054 /// Returns `Err` if an error occurred while trying to remove the key.
2055 #[cfg(all(
2056 feature = "onion-service-client",
2057 feature = "experimental-api",
2058 feature = "keymgr"
2059 ))]
2060 #[cfg_attr(
2061 docsrs,
2062 doc(cfg(all(
2063 feature = "onion-service-client",
2064 feature = "experimental-api",
2065 feature = "keymgr"
2066 )))
2067 )]
2068 pub fn remove_service_discovery_key(
2069 &self,
2070 selector: KeystoreSelector,
2071 hsid: HsId,
2072 ) -> crate::Result<Option<()>> {
2073 self.inert_client
2074 .remove_service_discovery_key(selector, hsid)
2075 }
2076
2077 /// Create (but do not launch) a new
2078 /// [`OnionService`](tor_hsservice::OnionService)
2079 /// using the given configuration.
2080 ///
2081 /// This is useful for managing an onion service without needing to start a `TorClient` or the
2082 /// onion service itself.
2083 /// If you only wish to run the onion service, see
2084 /// [`TorClient::launch_onion_service()`]
2085 /// which allows you to launch an onion service from a running `TorClient`.
2086 ///
2087 /// The returned `OnionService` can be launched using
2088 /// [`OnionService::launch()`](tor_hsservice::OnionService::launch).
2089 /// Note that `launch()` requires a [`NetDirProvider`],
2090 /// [`HsCircPool`](tor_circmgr::hspool::HsCircPool), etc,
2091 /// which you should obtain from a running `TorClient`.
2092 /// But these are only accessible from a `TorClient` if the "experimental-api" feature is
2093 /// enabled.
2094 /// The behaviour is not specified if you create the `OnionService` with
2095 /// `create_onion_service()` using one [`TorClientConfig`],
2096 /// but launch it using a `TorClient` generated from a different `TorClientConfig`.
2097 // TODO #2249: Look into this behaviour more, and possibly error if there is a different config.
2098 #[cfg(feature = "onion-service-service")]
2099 #[instrument(skip_all, level = "trace")]
2100 pub fn create_onion_service(
2101 config: &TorClientConfig,
2102 svc_config: tor_hsservice::OnionServiceConfig,
2103 ) -> crate::Result<tor_hsservice::OnionService> {
2104 let inert_client = InertTorClient::new(config)?;
2105 inert_client.create_onion_service(config, svc_config)
2106 }
2107
2108 /// Return a current [`status::BootstrapStatus`] describing how close this client
2109 /// is to being ready for user traffic.
2110 pub fn bootstrap_status(&self) -> status::BootstrapStatus {
2111 self.status_receiver.inner.borrow().clone()
2112 }
2113
2114 /// Return a stream of [`status::BootstrapStatus`] events that will be updated
2115 /// whenever the client's status changes.
2116 ///
2117 /// The receiver might not receive every update sent to this stream, though
2118 /// when it does poll the stream it should get the most recent one.
2119 //
2120 // TODO(nickm): will this also need to implement Send and 'static?
2121 pub fn bootstrap_events(&self) -> status::BootstrapEvents {
2122 self.status_receiver.clone()
2123 }
2124
2125 /// Change the client's current dormant mode, putting background tasks to sleep
2126 /// or waking them up as appropriate.
2127 ///
2128 /// This can be used to conserve CPU usage if you aren't planning on using the
2129 /// client for a while, especially on mobile platforms.
2130 ///
2131 /// See the [`DormantMode`] documentation for more details.
2132 pub fn set_dormant(&self, mode: DormantMode) {
2133 *self
2134 .dormant
2135 .lock()
2136 .expect("dormant lock poisoned")
2137 .borrow_mut() = Some(mode);
2138 }
2139
2140 /// Return a [`Future`] which resolves
2141 /// once this TorClient has stopped.
2142 #[cfg(feature = "experimental-api")]
2143 #[instrument(skip_all, level = "trace")]
2144 pub fn wait_for_stop(
2145 &self,
2146 ) -> impl futures::Future<Output = ()> + Send + Sync + 'static + use<R> {
2147 // We defer to the "wait for unlock" handle on our statemgr.
2148 //
2149 // The statemgr won't actually be unlocked until it is finally
2150 // dropped, which will happen when this TorClient is
2151 // dropped—which is what we want.
2152 self.statemgr.wait_for_unlock()
2153 }
2154
2155 /// Getter for keymgr.
2156 #[cfg(feature = "onion-service-cli-extra")]
2157 pub fn keymgr(&self) -> crate::Result<&KeyMgr> {
2158 self.inert_client.keymgr()
2159 }
2160}
2161
2162/// Monitor `dormant_mode` and enable/disable periodic tasks as applicable
2163///
2164/// This function is spawned as a task during client construction.
2165// TODO should this perhaps be done by each TaskHandle?
2166async fn tasks_monitor_dormant<R: Runtime>(
2167 mut dormant_rx: postage::watch::Receiver<Option<DormantMode>>,
2168 netdir: Arc<dyn NetDirProvider>,
2169 chanmgr: Arc<tor_chanmgr::ChanMgr<R>>,
2170 #[cfg(feature = "bridge-client")] bridge_desc_mgr: Arc<Mutex<Option<Arc<BridgeDescMgr<R>>>>>,
2171 periodic_task_handles: Vec<TaskHandle>,
2172) {
2173 while let Some(Some(mode)) = dormant_rx.next().await {
2174 let netparams = netdir.params();
2175
2176 chanmgr
2177 .set_dormancy(mode.into(), netparams)
2178 .unwrap_or_else(|e| error_report!(e, "couldn't set dormancy"));
2179
2180 // IEFI simplifies handling of exceptional cases, as "never mind, then".
2181 #[cfg(feature = "bridge-client")]
2182 (|| {
2183 let mut bdm = bridge_desc_mgr.lock().ok()?;
2184 let bdm = bdm.as_mut()?;
2185 bdm.set_dormancy(mode.into());
2186 Some(())
2187 })();
2188
2189 let is_dormant = matches!(mode, DormantMode::Soft);
2190
2191 for task in periodic_task_handles.iter() {
2192 if is_dormant {
2193 task.cancel();
2194 } else {
2195 task.fire();
2196 }
2197 }
2198 }
2199}
2200
2201/// Alias for TorError::from(Error)
2202pub(crate) fn wrap_err<T>(err: T) -> crate::Error
2203where
2204 ErrorDetail: From<T>,
2205{
2206 ErrorDetail::from(err).into()
2207}
2208
2209#[cfg(test)]
2210mod test {
2211 // @@ begin test lint list maintained by maint/add_warning @@
2212 #![allow(clippy::bool_assert_comparison)]
2213 #![allow(clippy::clone_on_copy)]
2214 #![allow(clippy::dbg_macro)]
2215 #![allow(clippy::mixed_attributes_style)]
2216 #![allow(clippy::print_stderr)]
2217 #![allow(clippy::print_stdout)]
2218 #![allow(clippy::single_char_pattern)]
2219 #![allow(clippy::unwrap_used)]
2220 #![allow(clippy::unchecked_time_subtraction)]
2221 #![allow(clippy::useless_vec)]
2222 #![allow(clippy::needless_pass_by_value)]
2223 //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
2224
2225 use tor_config::Reconfigure;
2226
2227 use super::*;
2228 use crate::config::TorClientConfigBuilder;
2229 use crate::{ErrorKind, HasKind};
2230
2231 #[test]
2232 fn create_unbootstrapped() {
2233 tor_rtcompat::test_with_one_runtime!(|rt| async {
2234 let state_dir = tempfile::tempdir().unwrap();
2235 let cache_dir = tempfile::tempdir().unwrap();
2236 let cfg = TorClientConfigBuilder::from_directories(state_dir, cache_dir)
2237 .build()
2238 .unwrap();
2239 let _ = TorClient::with_runtime(rt)
2240 .config(cfg)
2241 .bootstrap_behavior(BootstrapBehavior::Manual)
2242 .create_unbootstrapped()
2243 .unwrap();
2244 });
2245 tor_rtcompat::test_with_one_runtime!(|rt| async {
2246 let state_dir = tempfile::tempdir().unwrap();
2247 let cache_dir = tempfile::tempdir().unwrap();
2248 let cfg = TorClientConfigBuilder::from_directories(state_dir, cache_dir)
2249 .build()
2250 .unwrap();
2251 let _ = TorClient::with_runtime(rt)
2252 .config(cfg)
2253 .bootstrap_behavior(BootstrapBehavior::Manual)
2254 .create_unbootstrapped_async()
2255 .await
2256 .unwrap();
2257 });
2258 }
2259
2260 #[test]
2261 fn unbootstrapped_client_unusable() {
2262 tor_rtcompat::test_with_one_runtime!(|rt| async {
2263 let state_dir = tempfile::tempdir().unwrap();
2264 let cache_dir = tempfile::tempdir().unwrap();
2265 let cfg = TorClientConfigBuilder::from_directories(state_dir, cache_dir)
2266 .build()
2267 .unwrap();
2268 // Test sync
2269 let client = TorClient::with_runtime(rt)
2270 .config(cfg)
2271 .bootstrap_behavior(BootstrapBehavior::Manual)
2272 .create_unbootstrapped()
2273 .unwrap();
2274 let result = client.connect("example.com:80").await;
2275 assert!(result.is_err());
2276 assert_eq!(result.err().unwrap().kind(), ErrorKind::BootstrapRequired);
2277 });
2278 // Need a separate test for async because Runtime and TorClientConfig are consumed by the
2279 // builder
2280 tor_rtcompat::test_with_one_runtime!(|rt| async {
2281 let state_dir = tempfile::tempdir().unwrap();
2282 let cache_dir = tempfile::tempdir().unwrap();
2283 let cfg = TorClientConfigBuilder::from_directories(state_dir, cache_dir)
2284 .build()
2285 .unwrap();
2286 // Test sync
2287 let client = TorClient::with_runtime(rt)
2288 .config(cfg)
2289 .bootstrap_behavior(BootstrapBehavior::Manual)
2290 .create_unbootstrapped_async()
2291 .await
2292 .unwrap();
2293 let result = client.connect("example.com:80").await;
2294 assert!(result.is_err());
2295 assert_eq!(result.err().unwrap().kind(), ErrorKind::BootstrapRequired);
2296 });
2297 }
2298
2299 #[test]
2300 fn streamprefs_isolate_every_stream() {
2301 let mut observed = StreamPrefs::new();
2302 observed.isolate_every_stream();
2303 match observed.isolation {
2304 StreamIsolationPreference::EveryStream => (),
2305 _ => panic!("unexpected isolation: {:?}", observed.isolation),
2306 };
2307 }
2308
2309 #[test]
2310 fn streamprefs_new_has_expected_defaults() {
2311 let observed = StreamPrefs::new();
2312 assert_eq!(observed.ip_ver_pref, IpVersionPreference::Ipv4Preferred);
2313 assert!(!observed.optimistic_stream);
2314 // StreamIsolationPreference does not implement Eq, check manually.
2315 match observed.isolation {
2316 StreamIsolationPreference::None => (),
2317 _ => panic!("unexpected isolation: {:?}", observed.isolation),
2318 };
2319 }
2320
2321 #[test]
2322 fn streamprefs_new_isolation_group() {
2323 let mut observed = StreamPrefs::new();
2324 observed.new_isolation_group();
2325 match observed.isolation {
2326 StreamIsolationPreference::Explicit(_) => (),
2327 _ => panic!("unexpected isolation: {:?}", observed.isolation),
2328 };
2329 }
2330
2331 #[test]
2332 fn streamprefs_ipv6_only() {
2333 let mut observed = StreamPrefs::new();
2334 observed.ipv6_only();
2335 assert_eq!(observed.ip_ver_pref, IpVersionPreference::Ipv6Only);
2336 }
2337
2338 #[test]
2339 fn streamprefs_ipv6_preferred() {
2340 let mut observed = StreamPrefs::new();
2341 observed.ipv6_preferred();
2342 assert_eq!(observed.ip_ver_pref, IpVersionPreference::Ipv6Preferred);
2343 }
2344
2345 #[test]
2346 fn streamprefs_ipv4_only() {
2347 let mut observed = StreamPrefs::new();
2348 observed.ipv4_only();
2349 assert_eq!(observed.ip_ver_pref, IpVersionPreference::Ipv4Only);
2350 }
2351
2352 #[test]
2353 fn streamprefs_ipv4_preferred() {
2354 let mut observed = StreamPrefs::new();
2355 observed.ipv4_preferred();
2356 assert_eq!(observed.ip_ver_pref, IpVersionPreference::Ipv4Preferred);
2357 }
2358
2359 #[test]
2360 fn streamprefs_optimistic() {
2361 let mut observed = StreamPrefs::new();
2362 observed.optimistic();
2363 assert!(observed.optimistic_stream);
2364 }
2365
2366 #[test]
2367 fn streamprefs_set_isolation() {
2368 let mut observed = StreamPrefs::new();
2369 observed.set_isolation(IsolationToken::new());
2370 match observed.isolation {
2371 StreamIsolationPreference::Explicit(_) => (),
2372 _ => panic!("unexpected isolation: {:?}", observed.isolation),
2373 };
2374 }
2375
2376 #[test]
2377 fn reconfigure_all_or_nothing() {
2378 tor_rtcompat::test_with_one_runtime!(|rt| async {
2379 let state_dir = tempfile::tempdir().unwrap();
2380 let cache_dir = tempfile::tempdir().unwrap();
2381 let cfg = TorClientConfigBuilder::from_directories(state_dir, cache_dir)
2382 .build()
2383 .unwrap();
2384 let tor_client = TorClient::with_runtime(rt)
2385 .config(cfg.clone())
2386 .bootstrap_behavior(BootstrapBehavior::Manual)
2387 .create_unbootstrapped()
2388 .unwrap();
2389 tor_client
2390 .reconfigure(&cfg, Reconfigure::AllOrNothing)
2391 .unwrap();
2392 });
2393 tor_rtcompat::test_with_one_runtime!(|rt| async {
2394 let state_dir = tempfile::tempdir().unwrap();
2395 let cache_dir = tempfile::tempdir().unwrap();
2396 let cfg = TorClientConfigBuilder::from_directories(state_dir, cache_dir)
2397 .build()
2398 .unwrap();
2399 let tor_client = TorClient::with_runtime(rt)
2400 .config(cfg.clone())
2401 .bootstrap_behavior(BootstrapBehavior::Manual)
2402 .create_unbootstrapped_async()
2403 .await
2404 .unwrap();
2405 tor_client
2406 .reconfigure(&cfg, Reconfigure::AllOrNothing)
2407 .unwrap();
2408 });
2409 }
2410}