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!(
1499 tunnel_id = %tunnel.unique_id(),
1500 "Got a circuit for {}:{}", sensitive(&addr), port);
1501
1502 begin_stream!(tunnel, &addr, port, Some(stream_parameters))
1503 }
1504
1505 #[cfg(not(feature = "onion-service-client"))]
1506 #[allow(unused_variables)] // for hostname and port
1507 StreamInstructions::Hs {
1508 hsid,
1509 hostname,
1510 port,
1511 } => void::unreachable(hsid.0),
1512
1513 #[cfg(feature = "onion-service-client")]
1514 StreamInstructions::Hs {
1515 hsid,
1516 hostname,
1517 port,
1518 } => {
1519 use safelog::DisplayRedacted as _;
1520
1521 self.wait_for_bootstrap().await?;
1522 let netdir = self.netdir(Timeliness::Timely, "connect to a hidden service")?;
1523
1524 let mut hs_client_secret_keys_builder = HsClientSecretKeysBuilder::default();
1525
1526 if let Some(keymgr) = &self.inert_client.keymgr {
1527 let desc_enc_key_spec = HsClientDescEncKeypairSpecifier::new(hsid);
1528
1529 let ks_hsc_desc_enc =
1530 keymgr.get::<HsClientDescEncKeypair>(&desc_enc_key_spec)?;
1531
1532 if let Some(ks_hsc_desc_enc) = ks_hsc_desc_enc {
1533 debug!(
1534 "Found descriptor decryption key for {}",
1535 hsid.display_redacted()
1536 );
1537 hs_client_secret_keys_builder.ks_hsc_desc_enc(ks_hsc_desc_enc);
1538 }
1539 };
1540
1541 let hs_client_secret_keys = hs_client_secret_keys_builder
1542 .build()
1543 .map_err(ErrorDetail::Configuration)?;
1544
1545 let tunnel = self
1546 .hsclient
1547 .get_or_launch_tunnel(
1548 &netdir,
1549 hsid,
1550 hs_client_secret_keys,
1551 self.isolation(prefs),
1552 )
1553 .await
1554 .map_err(|cause| ErrorDetail::ObtainHsCircuit { cause, hsid })?;
1555 // On connections to onion services, we have to suppress
1556 // everything except the port from the BEGIN message. We also
1557 // disable optimistic data.
1558 stream_parameters
1559 .suppress_hostname()
1560 .suppress_begin_flags()
1561 .optimistic(false);
1562
1563 begin_stream!(tunnel, &hostname, port, Some(stream_parameters))
1564 }
1565 };
1566
1567 Ok(stream?)
1568 }
1569
1570 /// Sets the default preferences for future connections made with this client.
1571 ///
1572 /// The preferences set with this function will be inherited by clones of this client, but
1573 /// updates to the preferences in those clones will not propagate back to the original. I.e.,
1574 /// the preferences are copied by `clone`.
1575 ///
1576 /// Connection preferences always override configuration, even configuration set later
1577 /// (eg, by a config reload).
1578 pub fn set_stream_prefs(&mut self, connect_prefs: StreamPrefs) {
1579 self.connect_prefs = connect_prefs;
1580 }
1581
1582 /// Provides a new handle on this client, but with adjusted default preferences.
1583 ///
1584 /// Connections made with e.g. [`connect`](TorClient::connect) on the returned handle will use
1585 /// `connect_prefs`. This is a convenience wrapper for `clone` and `set_connect_prefs`.
1586 #[must_use]
1587 pub fn clone_with_prefs(&self, connect_prefs: StreamPrefs) -> Self {
1588 let mut result = self.clone();
1589 result.set_stream_prefs(connect_prefs);
1590 result
1591 }
1592
1593 /// On success, return a list of IP addresses.
1594 #[instrument(skip_all, level = "trace")]
1595 pub async fn resolve(&self, hostname: &str) -> crate::Result<Vec<IpAddr>> {
1596 self.resolve_with_prefs(hostname, &self.connect_prefs).await
1597 }
1598
1599 /// On success, return a list of IP addresses, but use prefs.
1600 #[instrument(skip_all, level = "trace")]
1601 pub async fn resolve_with_prefs(
1602 &self,
1603 hostname: &str,
1604 prefs: &StreamPrefs,
1605 ) -> crate::Result<Vec<IpAddr>> {
1606 // TODO This dummy port is only because `address::Host` is not pub(crate),
1607 // but I see no reason why it shouldn't be? Then `into_resolve_instructions`
1608 // should be a method on `Host`, not `TorAddr`. -Diziet.
1609 let addr = (hostname, 1).into_tor_addr().map_err(wrap_err)?;
1610
1611 match addr.into_resolve_instructions(&self.addrcfg.get(), prefs)? {
1612 ResolveInstructions::Exit(hostname) => {
1613 let circ = self.get_or_launch_exit_tunnel(&[], prefs).await?;
1614
1615 let resolve_future = circ.resolve(&hostname);
1616 let addrs = self
1617 .runtime
1618 .timeout(self.timeoutcfg.get().resolve_timeout, resolve_future)
1619 .await
1620 .map_err(|_| ErrorDetail::ExitTimeout)?
1621 .map_err(|cause| ErrorDetail::StreamFailed {
1622 cause,
1623 kind: "DNS lookup",
1624 })?;
1625
1626 Ok(addrs)
1627 }
1628 ResolveInstructions::Return(addrs) => Ok(addrs),
1629 }
1630 }
1631
1632 /// Perform a remote DNS reverse lookup with the provided IP address.
1633 ///
1634 /// On success, return a list of hostnames.
1635 #[instrument(skip_all, level = "trace")]
1636 pub async fn resolve_ptr(&self, addr: IpAddr) -> crate::Result<Vec<String>> {
1637 self.resolve_ptr_with_prefs(addr, &self.connect_prefs).await
1638 }
1639
1640 /// Perform a remote DNS reverse lookup with the provided IP address.
1641 ///
1642 /// On success, return a list of hostnames.
1643 #[instrument(level = "trace", skip_all)]
1644 pub async fn resolve_ptr_with_prefs(
1645 &self,
1646 addr: IpAddr,
1647 prefs: &StreamPrefs,
1648 ) -> crate::Result<Vec<String>> {
1649 let circ = self.get_or_launch_exit_tunnel(&[], prefs).await?;
1650
1651 let resolve_ptr_future = circ.resolve_ptr(addr);
1652 let hostnames = self
1653 .runtime
1654 .timeout(
1655 self.timeoutcfg.get().resolve_ptr_timeout,
1656 resolve_ptr_future,
1657 )
1658 .await
1659 .map_err(|_| ErrorDetail::ExitTimeout)?
1660 .map_err(|cause| ErrorDetail::StreamFailed {
1661 cause,
1662 kind: "reverse DNS lookup",
1663 })?;
1664
1665 Ok(hostnames)
1666 }
1667
1668 /// Return a reference to this client's directory manager.
1669 ///
1670 /// This function is unstable. It is only enabled if the crate was
1671 /// built with the `experimental-api` feature.
1672 #[cfg(feature = "experimental-api")]
1673 pub fn dirmgr(&self) -> &Arc<dyn tor_dirmgr::DirProvider> {
1674 &self.dirmgr
1675 }
1676
1677 /// Return a reference to this client's circuit manager.
1678 ///
1679 /// This function is unstable. It is only enabled if the crate was
1680 /// built with the `experimental-api` feature.
1681 #[cfg(feature = "experimental-api")]
1682 pub fn circmgr(&self) -> &Arc<tor_circmgr::CircMgr<R>> {
1683 &self.circmgr
1684 }
1685
1686 /// Return a reference to this client's channel manager.
1687 ///
1688 /// This function is unstable. It is only enabled if the crate was
1689 /// built with the `experimental-api` feature.
1690 #[cfg(feature = "experimental-api")]
1691 pub fn chanmgr(&self) -> &Arc<tor_chanmgr::ChanMgr<R>> {
1692 &self.chanmgr
1693 }
1694
1695 /// Return a reference to this client's circuit pool.
1696 ///
1697 /// This function is unstable. It is only enabled if the crate was
1698 /// built with the `experimental-api` feature and any of `onion-service-client`
1699 /// or `onion-service-service` features. This method is required to invoke
1700 /// tor_hsservice::OnionService::launch()
1701 #[cfg(all(
1702 feature = "experimental-api",
1703 any(feature = "onion-service-client", feature = "onion-service-service")
1704 ))]
1705 pub fn hs_circ_pool(&self) -> &Arc<tor_circmgr::hspool::HsCircPool<R>> {
1706 &self.hs_circ_pool
1707 }
1708
1709 /// Return a reference to the runtime being used by this client.
1710 //
1711 // This API is not a hostage to fortune since we already require that R: Clone,
1712 // and necessarily a TorClient must have a clone of it.
1713 //
1714 // We provide it simply to save callers who have a TorClient from
1715 // having to separately keep their own handle,
1716 pub fn runtime(&self) -> &R {
1717 &self.runtime
1718 }
1719
1720 /// Return a netdir that is timely according to the rules of `timeliness`.
1721 ///
1722 /// The `action` string is a description of what we wanted to do with the
1723 /// directory, to be put into the error message if we couldn't find a directory.
1724 fn netdir(
1725 &self,
1726 timeliness: Timeliness,
1727 action: &'static str,
1728 ) -> StdResult<Arc<tor_netdir::NetDir>, ErrorDetail> {
1729 use tor_netdir::Error as E;
1730 match self.dirmgr.netdir(timeliness) {
1731 Ok(netdir) => Ok(netdir),
1732 Err(E::NoInfo) | Err(E::NotEnoughInfo) => {
1733 Err(ErrorDetail::BootstrapRequired { action })
1734 }
1735 Err(error) => Err(ErrorDetail::NoDir { error, action }),
1736 }
1737 }
1738
1739 /// Get or launch an exit-suitable circuit with a given set of
1740 /// exit ports.
1741 #[instrument(skip_all, level = "trace")]
1742 async fn get_or_launch_exit_tunnel(
1743 &self,
1744 exit_ports: &[TargetPort],
1745 prefs: &StreamPrefs,
1746 ) -> StdResult<ClientDataTunnel, ErrorDetail> {
1747 // TODO HS probably this netdir ought to be made in connect_with_prefs
1748 // like for StreamInstructions::Hs.
1749 self.wait_for_bootstrap().await?;
1750 let dir = self.netdir(Timeliness::Timely, "build a circuit")?;
1751
1752 let tunnel = self
1753 .circmgr
1754 .get_or_launch_exit(
1755 dir.as_ref().into(),
1756 exit_ports,
1757 self.isolation(prefs),
1758 #[cfg(feature = "geoip")]
1759 prefs.country_code,
1760 )
1761 .await
1762 .map_err(|cause| ErrorDetail::ObtainExitCircuit {
1763 cause,
1764 exit_ports: Sensitive::new(exit_ports.into()),
1765 })?;
1766 drop(dir); // This decreases the refcount on the netdir.
1767
1768 Ok(tunnel)
1769 }
1770
1771 /// Return an overall [`Isolation`] for this `TorClient` and a `StreamPrefs`.
1772 ///
1773 /// This describes which operations might use
1774 /// circuit(s) with this one.
1775 ///
1776 /// This combines isolation information from
1777 /// [`StreamPrefs::prefs_isolation`]
1778 /// and the `TorClient`'s isolation (eg from [`TorClient::isolated_client`]).
1779 fn isolation(&self, prefs: &StreamPrefs) -> StreamIsolation {
1780 let mut b = StreamIsolationBuilder::new();
1781 // Always consider our client_isolation.
1782 b.owner_token(self.client_isolation);
1783 // Consider stream isolation too, if it's set.
1784 if let Some(tok) = prefs.prefs_isolation() {
1785 b.stream_isolation(tok);
1786 }
1787 // Failure should be impossible with this builder.
1788 b.build().expect("Failed to construct StreamIsolation")
1789 }
1790
1791 /// Try to launch an onion service with a given configuration.
1792 ///
1793 /// Returns `Ok(None)` if the service specified is disabled in the config.
1794 ///
1795 /// This onion service will not actually handle any requests on its own: you
1796 /// will need to
1797 /// pull [`RendRequest`](tor_hsservice::RendRequest) objects from the returned stream,
1798 /// [`accept`](tor_hsservice::RendRequest::accept) the ones that you want to
1799 /// answer, and then wait for them to give you [`StreamRequest`](tor_hsservice::StreamRequest)s.
1800 ///
1801 /// You may find the [`tor_hsservice::handle_rend_requests`] API helpful for
1802 /// translating `RendRequest`s into `StreamRequest`s.
1803 ///
1804 /// If you want to forward all the requests from an onion service to a set
1805 /// of local ports, you may want to use the `tor-hsrproxy` crate.
1806 #[cfg(feature = "onion-service-service")]
1807 #[instrument(skip_all, level = "trace")]
1808 pub fn launch_onion_service(
1809 &self,
1810 config: tor_hsservice::OnionServiceConfig,
1811 ) -> crate::Result<
1812 Option<(
1813 Arc<tor_hsservice::RunningOnionService>,
1814 impl futures::Stream<Item = tor_hsservice::RendRequest> + use<R>,
1815 )>,
1816 > {
1817 let nickname = config.nickname();
1818
1819 if !config.enabled() {
1820 info!(
1821 nickname=%nickname,
1822 "Skipping onion service because it was disabled in the config"
1823 );
1824 return Ok(None);
1825 }
1826
1827 let keymgr = self
1828 .inert_client
1829 .keymgr
1830 .as_ref()
1831 .ok_or(ErrorDetail::KeystoreRequired {
1832 action: "launch onion service",
1833 })?
1834 .clone();
1835 let state_dir = self.state_directory.clone();
1836
1837 let service = tor_hsservice::OnionService::builder()
1838 .config(config) // TODO #1186: Allow override of KeyMgr for "ephemeral" operation?
1839 .keymgr(keymgr)
1840 // TODO #1186: Allow override of StateMgr for "ephemeral" operation?
1841 .state_dir(state_dir)
1842 .build()
1843 .map_err(ErrorDetail::LaunchOnionService)?;
1844 Ok(service
1845 .launch(
1846 self.runtime.clone(),
1847 self.dirmgr.clone().upcast_arc(),
1848 self.hs_circ_pool.clone(),
1849 Arc::clone(&self.path_resolver),
1850 )
1851 .map_err(ErrorDetail::LaunchOnionService)?)
1852 }
1853
1854 /// Try to launch an onion service with a given configuration and provided
1855 /// [`HsIdKeypair`]. If an onion service with the given nickname already has an
1856 /// associated `HsIdKeypair` in this `TorClient`'s `KeyMgr`, then this operation
1857 /// fails rather than overwriting the existing key.
1858 ///
1859 /// Returns `Ok(None)` if the service specified is disabled in the config.
1860 ///
1861 /// The specified `HsIdKeypair` will be inserted in the primary keystore.
1862 ///
1863 /// **Important**: depending on the configuration of your
1864 /// [primary keystore](tor_keymgr::config::PrimaryKeystoreConfig),
1865 /// the `HsIdKeypair` **may** get persisted to disk.
1866 /// By default, Arti's primary keystore is the [native](ArtiKeystoreKind::Native),
1867 /// disk-based keystore.
1868 ///
1869 /// This onion service will not actually handle any requests on its own: you
1870 /// will need to
1871 /// pull [`RendRequest`](tor_hsservice::RendRequest) objects from the returned stream,
1872 /// [`accept`](tor_hsservice::RendRequest::accept) the ones that you want to
1873 /// answer, and then wait for them to give you [`StreamRequest`](tor_hsservice::StreamRequest)s.
1874 ///
1875 /// You may find the [`tor_hsservice::handle_rend_requests`] API helpful for
1876 /// translating `RendRequest`s into `StreamRequest`s.
1877 ///
1878 /// If you want to forward all the requests from an onion service to a set
1879 /// of local ports, you may want to use the `tor-hsrproxy` crate.
1880 #[cfg(all(feature = "onion-service-service", feature = "experimental-api"))]
1881 #[instrument(skip_all, level = "trace")]
1882 pub fn launch_onion_service_with_hsid(
1883 &self,
1884 config: tor_hsservice::OnionServiceConfig,
1885 id_keypair: HsIdKeypair,
1886 ) -> crate::Result<
1887 Option<(
1888 Arc<tor_hsservice::RunningOnionService>,
1889 impl futures::Stream<Item = tor_hsservice::RendRequest> + use<R>,
1890 )>,
1891 > {
1892 let nickname = config.nickname();
1893 let hsid_spec = HsIdKeypairSpecifier::new(nickname.clone());
1894 let selector = KeystoreSelector::Primary;
1895
1896 let _kp = self
1897 .inert_client
1898 .keymgr
1899 .as_ref()
1900 .ok_or(ErrorDetail::KeystoreRequired {
1901 action: "launch onion service ex",
1902 })?
1903 .insert::<HsIdKeypair>(id_keypair, &hsid_spec, selector, false)?;
1904
1905 self.launch_onion_service(config)
1906 }
1907
1908 /// Generate a service discovery keypair for connecting to a hidden service running in
1909 /// "restricted discovery" mode.
1910 ///
1911 /// The `selector` argument is used for choosing the keystore in which to generate the keypair.
1912 /// While most users will want to write to the [`Primary`](KeystoreSelector::Primary), if you
1913 /// have configured this `TorClient` with a non-default keystore and wish to generate the
1914 /// keypair in it, you can do so by calling this function with a [KeystoreSelector::Id]
1915 /// specifying the keystore ID of your keystore.
1916 ///
1917 // Note: the selector argument exists for future-proofing reasons. We don't currently support
1918 // configuring custom or non-default keystores (see #1106).
1919 ///
1920 /// Returns an error if the key already exists in the specified key store.
1921 ///
1922 /// Important: the public part of the generated keypair must be shared with the service, and
1923 /// the service needs to be configured to allow the owner of its private counterpart to
1924 /// discover its introduction points. The caller is responsible for sharing the public part of
1925 /// the key with the hidden service.
1926 ///
1927 /// This function does not require the `TorClient` to be running or bootstrapped.
1928 //
1929 // TODO: decide whether this should use get_or_generate before making it
1930 // non-experimental
1931 #[cfg(all(
1932 feature = "onion-service-client",
1933 feature = "experimental-api",
1934 feature = "keymgr"
1935 ))]
1936 pub fn generate_service_discovery_key(
1937 &self,
1938 selector: KeystoreSelector,
1939 hsid: HsId,
1940 ) -> crate::Result<HsClientDescEncKey> {
1941 self.inert_client
1942 .generate_service_discovery_key(selector, hsid)
1943 }
1944
1945 /// Rotate the service discovery keypair for connecting to a hidden service running in
1946 /// "restricted discovery" mode.
1947 ///
1948 /// **If the specified keystore already contains a restricted discovery keypair
1949 /// for the service, it will be overwritten.** Otherwise, a new keypair is generated.
1950 ///
1951 /// The `selector` argument is used for choosing the keystore in which to generate the keypair.
1952 /// While most users will want to write to the [`Primary`](KeystoreSelector::Primary), if you
1953 /// have configured this `TorClient` with a non-default keystore and wish to generate the
1954 /// keypair in it, you can do so by calling this function with a [KeystoreSelector::Id]
1955 /// specifying the keystore ID of your keystore.
1956 ///
1957 // Note: the selector argument exists for future-proofing reasons. We don't currently support
1958 // configuring custom or non-default keystores (see #1106).
1959 ///
1960 /// Important: the public part of the generated keypair must be shared with the service, and
1961 /// the service needs to be configured to allow the owner of its private counterpart to
1962 /// discover its introduction points. The caller is responsible for sharing the public part of
1963 /// the key with the hidden service.
1964 ///
1965 /// This function does not require the `TorClient` to be running or bootstrapped.
1966 #[cfg(all(
1967 feature = "onion-service-client",
1968 feature = "experimental-api",
1969 feature = "keymgr"
1970 ))]
1971 #[cfg_attr(
1972 docsrs,
1973 doc(cfg(all(
1974 feature = "onion-service-client",
1975 feature = "experimental-api",
1976 feature = "keymgr"
1977 )))
1978 )]
1979 pub fn rotate_service_discovery_key(
1980 &self,
1981 selector: KeystoreSelector,
1982 hsid: HsId,
1983 ) -> crate::Result<HsClientDescEncKey> {
1984 self.inert_client
1985 .rotate_service_discovery_key(selector, hsid)
1986 }
1987
1988 /// Insert a service discovery secret key for connecting to a hidden service running in
1989 /// "restricted discovery" mode
1990 ///
1991 /// The `selector` argument is used for choosing the keystore in which to generate the keypair.
1992 /// While most users will want to write to the [`Primary`](KeystoreSelector::Primary), if you
1993 /// have configured this `TorClient` with a non-default keystore and wish to insert the
1994 /// key in it, you can do so by calling this function with a [KeystoreSelector::Id]
1995 ///
1996 // Note: the selector argument exists for future-proofing reasons. We don't currently support
1997 // configuring custom or non-default keystores (see #1106).
1998 ///
1999 /// Returns an error if the key already exists in the specified key store.
2000 ///
2001 /// Important: the public part of the generated keypair must be shared with the service, and
2002 /// the service needs to be configured to allow the owner of its private counterpart to
2003 /// discover its introduction points. The caller is responsible for sharing the public part of
2004 /// the key with the hidden service.
2005 ///
2006 /// This function does not require the `TorClient` to be running or bootstrapped.
2007 #[cfg(all(
2008 feature = "onion-service-client",
2009 feature = "experimental-api",
2010 feature = "keymgr"
2011 ))]
2012 #[cfg_attr(
2013 docsrs,
2014 doc(cfg(all(
2015 feature = "onion-service-client",
2016 feature = "experimental-api",
2017 feature = "keymgr"
2018 )))
2019 )]
2020 pub fn insert_service_discovery_key(
2021 &self,
2022 selector: KeystoreSelector,
2023 hsid: HsId,
2024 hs_client_desc_enc_secret_key: HsClientDescEncSecretKey,
2025 ) -> crate::Result<HsClientDescEncKey> {
2026 self.inert_client.insert_service_discovery_key(
2027 selector,
2028 hsid,
2029 hs_client_desc_enc_secret_key,
2030 )
2031 }
2032
2033 /// Return the service discovery public key for the service with the specified `hsid`.
2034 ///
2035 /// Returns `Ok(None)` if no such key exists.
2036 ///
2037 /// This function does not require the `TorClient` to be running or bootstrapped.
2038 #[cfg(all(feature = "onion-service-client", feature = "experimental-api"))]
2039 #[cfg_attr(
2040 docsrs,
2041 doc(cfg(all(feature = "onion-service-client", feature = "experimental-api")))
2042 )]
2043 pub fn get_service_discovery_key(
2044 &self,
2045 hsid: HsId,
2046 ) -> crate::Result<Option<HsClientDescEncKey>> {
2047 self.inert_client.get_service_discovery_key(hsid)
2048 }
2049
2050 /// Removes the service discovery keypair for the service with the specified `hsid`.
2051 ///
2052 /// Returns an error if the selected keystore is not the default keystore or one of the
2053 /// configured secondary stores.
2054 ///
2055 /// Returns `Ok(None)` if no such keypair exists whereas `Ok(Some()) means the keypair was successfully removed.
2056 ///
2057 /// Returns `Err` if an error occurred while trying to remove the key.
2058 #[cfg(all(
2059 feature = "onion-service-client",
2060 feature = "experimental-api",
2061 feature = "keymgr"
2062 ))]
2063 #[cfg_attr(
2064 docsrs,
2065 doc(cfg(all(
2066 feature = "onion-service-client",
2067 feature = "experimental-api",
2068 feature = "keymgr"
2069 )))
2070 )]
2071 pub fn remove_service_discovery_key(
2072 &self,
2073 selector: KeystoreSelector,
2074 hsid: HsId,
2075 ) -> crate::Result<Option<()>> {
2076 self.inert_client
2077 .remove_service_discovery_key(selector, hsid)
2078 }
2079
2080 /// Create (but do not launch) a new
2081 /// [`OnionService`](tor_hsservice::OnionService)
2082 /// using the given configuration.
2083 ///
2084 /// This is useful for managing an onion service without needing to start a `TorClient` or the
2085 /// onion service itself.
2086 /// If you only wish to run the onion service, see
2087 /// [`TorClient::launch_onion_service()`]
2088 /// which allows you to launch an onion service from a running `TorClient`.
2089 ///
2090 /// The returned `OnionService` can be launched using
2091 /// [`OnionService::launch()`](tor_hsservice::OnionService::launch).
2092 /// Note that `launch()` requires a [`NetDirProvider`],
2093 /// [`HsCircPool`](tor_circmgr::hspool::HsCircPool), etc,
2094 /// which you should obtain from a running `TorClient`.
2095 /// But these are only accessible from a `TorClient` if the "experimental-api" feature is
2096 /// enabled.
2097 /// The behaviour is not specified if you create the `OnionService` with
2098 /// `create_onion_service()` using one [`TorClientConfig`],
2099 /// but launch it using a `TorClient` generated from a different `TorClientConfig`.
2100 // TODO #2249: Look into this behaviour more, and possibly error if there is a different config.
2101 #[cfg(feature = "onion-service-service")]
2102 #[instrument(skip_all, level = "trace")]
2103 pub fn create_onion_service(
2104 config: &TorClientConfig,
2105 svc_config: tor_hsservice::OnionServiceConfig,
2106 ) -> crate::Result<tor_hsservice::OnionService> {
2107 let inert_client = InertTorClient::new(config)?;
2108 inert_client.create_onion_service(config, svc_config)
2109 }
2110
2111 /// Return a current [`status::BootstrapStatus`] describing how close this client
2112 /// is to being ready for user traffic.
2113 pub fn bootstrap_status(&self) -> status::BootstrapStatus {
2114 self.status_receiver.inner.borrow().clone()
2115 }
2116
2117 /// Return a stream of [`status::BootstrapStatus`] events that will be updated
2118 /// whenever the client's status changes.
2119 ///
2120 /// The receiver might not receive every update sent to this stream, though
2121 /// when it does poll the stream it should get the most recent one.
2122 //
2123 // TODO(nickm): will this also need to implement Send and 'static?
2124 pub fn bootstrap_events(&self) -> status::BootstrapEvents {
2125 self.status_receiver.clone()
2126 }
2127
2128 /// Change the client's current dormant mode, putting background tasks to sleep
2129 /// or waking them up as appropriate.
2130 ///
2131 /// This can be used to conserve CPU usage if you aren't planning on using the
2132 /// client for a while, especially on mobile platforms.
2133 ///
2134 /// See the [`DormantMode`] documentation for more details.
2135 pub fn set_dormant(&self, mode: DormantMode) {
2136 *self
2137 .dormant
2138 .lock()
2139 .expect("dormant lock poisoned")
2140 .borrow_mut() = Some(mode);
2141 }
2142
2143 /// Return a [`Future`] which resolves
2144 /// once this TorClient has stopped.
2145 #[cfg(feature = "experimental-api")]
2146 #[instrument(skip_all, level = "trace")]
2147 pub fn wait_for_stop(
2148 &self,
2149 ) -> impl futures::Future<Output = ()> + Send + Sync + 'static + use<R> {
2150 // We defer to the "wait for unlock" handle on our statemgr.
2151 //
2152 // The statemgr won't actually be unlocked until it is finally
2153 // dropped, which will happen when this TorClient is
2154 // dropped—which is what we want.
2155 self.statemgr.wait_for_unlock()
2156 }
2157
2158 /// Getter for keymgr.
2159 #[cfg(feature = "onion-service-cli-extra")]
2160 pub fn keymgr(&self) -> crate::Result<&KeyMgr> {
2161 self.inert_client.keymgr()
2162 }
2163}
2164
2165/// Monitor `dormant_mode` and enable/disable periodic tasks as applicable
2166///
2167/// This function is spawned as a task during client construction.
2168// TODO should this perhaps be done by each TaskHandle?
2169async fn tasks_monitor_dormant<R: Runtime>(
2170 mut dormant_rx: postage::watch::Receiver<Option<DormantMode>>,
2171 netdir: Arc<dyn NetDirProvider>,
2172 chanmgr: Arc<tor_chanmgr::ChanMgr<R>>,
2173 #[cfg(feature = "bridge-client")] bridge_desc_mgr: Arc<Mutex<Option<Arc<BridgeDescMgr<R>>>>>,
2174 periodic_task_handles: Vec<TaskHandle>,
2175) {
2176 while let Some(Some(mode)) = dormant_rx.next().await {
2177 let netparams = netdir.params();
2178
2179 chanmgr
2180 .set_dormancy(mode.into(), netparams)
2181 .unwrap_or_else(|e| error_report!(e, "couldn't set dormancy"));
2182
2183 // IEFI simplifies handling of exceptional cases, as "never mind, then".
2184 #[cfg(feature = "bridge-client")]
2185 (|| {
2186 let mut bdm = bridge_desc_mgr.lock().ok()?;
2187 let bdm = bdm.as_mut()?;
2188 bdm.set_dormancy(mode.into());
2189 Some(())
2190 })();
2191
2192 let is_dormant = matches!(mode, DormantMode::Soft);
2193
2194 for task in periodic_task_handles.iter() {
2195 if is_dormant {
2196 task.cancel();
2197 } else {
2198 task.fire();
2199 }
2200 }
2201 }
2202}
2203
2204/// Alias for TorError::from(Error)
2205pub(crate) fn wrap_err<T>(err: T) -> crate::Error
2206where
2207 ErrorDetail: From<T>,
2208{
2209 ErrorDetail::from(err).into()
2210}
2211
2212#[cfg(test)]
2213mod test {
2214 // @@ begin test lint list maintained by maint/add_warning @@
2215 #![allow(clippy::bool_assert_comparison)]
2216 #![allow(clippy::clone_on_copy)]
2217 #![allow(clippy::dbg_macro)]
2218 #![allow(clippy::mixed_attributes_style)]
2219 #![allow(clippy::print_stderr)]
2220 #![allow(clippy::print_stdout)]
2221 #![allow(clippy::single_char_pattern)]
2222 #![allow(clippy::unwrap_used)]
2223 #![allow(clippy::unchecked_time_subtraction)]
2224 #![allow(clippy::useless_vec)]
2225 #![allow(clippy::needless_pass_by_value)]
2226 //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
2227
2228 use tor_config::Reconfigure;
2229
2230 use super::*;
2231 use crate::config::TorClientConfigBuilder;
2232 use crate::{ErrorKind, HasKind};
2233
2234 #[test]
2235 fn create_unbootstrapped() {
2236 tor_rtcompat::test_with_one_runtime!(|rt| async {
2237 let state_dir = tempfile::tempdir().unwrap();
2238 let cache_dir = tempfile::tempdir().unwrap();
2239 let cfg = TorClientConfigBuilder::from_directories(state_dir, cache_dir)
2240 .build()
2241 .unwrap();
2242 let _ = TorClient::with_runtime(rt)
2243 .config(cfg)
2244 .bootstrap_behavior(BootstrapBehavior::Manual)
2245 .create_unbootstrapped()
2246 .unwrap();
2247 });
2248 tor_rtcompat::test_with_one_runtime!(|rt| async {
2249 let state_dir = tempfile::tempdir().unwrap();
2250 let cache_dir = tempfile::tempdir().unwrap();
2251 let cfg = TorClientConfigBuilder::from_directories(state_dir, cache_dir)
2252 .build()
2253 .unwrap();
2254 let _ = TorClient::with_runtime(rt)
2255 .config(cfg)
2256 .bootstrap_behavior(BootstrapBehavior::Manual)
2257 .create_unbootstrapped_async()
2258 .await
2259 .unwrap();
2260 });
2261 }
2262
2263 #[test]
2264 fn unbootstrapped_client_unusable() {
2265 tor_rtcompat::test_with_one_runtime!(|rt| async {
2266 let state_dir = tempfile::tempdir().unwrap();
2267 let cache_dir = tempfile::tempdir().unwrap();
2268 let cfg = TorClientConfigBuilder::from_directories(state_dir, cache_dir)
2269 .build()
2270 .unwrap();
2271 // Test sync
2272 let client = TorClient::with_runtime(rt)
2273 .config(cfg)
2274 .bootstrap_behavior(BootstrapBehavior::Manual)
2275 .create_unbootstrapped()
2276 .unwrap();
2277 let result = client.connect("example.com:80").await;
2278 assert!(result.is_err());
2279 assert_eq!(result.err().unwrap().kind(), ErrorKind::BootstrapRequired);
2280 });
2281 // Need a separate test for async because Runtime and TorClientConfig are consumed by the
2282 // builder
2283 tor_rtcompat::test_with_one_runtime!(|rt| async {
2284 let state_dir = tempfile::tempdir().unwrap();
2285 let cache_dir = tempfile::tempdir().unwrap();
2286 let cfg = TorClientConfigBuilder::from_directories(state_dir, cache_dir)
2287 .build()
2288 .unwrap();
2289 // Test sync
2290 let client = TorClient::with_runtime(rt)
2291 .config(cfg)
2292 .bootstrap_behavior(BootstrapBehavior::Manual)
2293 .create_unbootstrapped_async()
2294 .await
2295 .unwrap();
2296 let result = client.connect("example.com:80").await;
2297 assert!(result.is_err());
2298 assert_eq!(result.err().unwrap().kind(), ErrorKind::BootstrapRequired);
2299 });
2300 }
2301
2302 #[test]
2303 fn streamprefs_isolate_every_stream() {
2304 let mut observed = StreamPrefs::new();
2305 observed.isolate_every_stream();
2306 match observed.isolation {
2307 StreamIsolationPreference::EveryStream => (),
2308 _ => panic!("unexpected isolation: {:?}", observed.isolation),
2309 };
2310 }
2311
2312 #[test]
2313 fn streamprefs_new_has_expected_defaults() {
2314 let observed = StreamPrefs::new();
2315 assert_eq!(observed.ip_ver_pref, IpVersionPreference::Ipv4Preferred);
2316 assert!(!observed.optimistic_stream);
2317 // StreamIsolationPreference does not implement Eq, check manually.
2318 match observed.isolation {
2319 StreamIsolationPreference::None => (),
2320 _ => panic!("unexpected isolation: {:?}", observed.isolation),
2321 };
2322 }
2323
2324 #[test]
2325 fn streamprefs_new_isolation_group() {
2326 let mut observed = StreamPrefs::new();
2327 observed.new_isolation_group();
2328 match observed.isolation {
2329 StreamIsolationPreference::Explicit(_) => (),
2330 _ => panic!("unexpected isolation: {:?}", observed.isolation),
2331 };
2332 }
2333
2334 #[test]
2335 fn streamprefs_ipv6_only() {
2336 let mut observed = StreamPrefs::new();
2337 observed.ipv6_only();
2338 assert_eq!(observed.ip_ver_pref, IpVersionPreference::Ipv6Only);
2339 }
2340
2341 #[test]
2342 fn streamprefs_ipv6_preferred() {
2343 let mut observed = StreamPrefs::new();
2344 observed.ipv6_preferred();
2345 assert_eq!(observed.ip_ver_pref, IpVersionPreference::Ipv6Preferred);
2346 }
2347
2348 #[test]
2349 fn streamprefs_ipv4_only() {
2350 let mut observed = StreamPrefs::new();
2351 observed.ipv4_only();
2352 assert_eq!(observed.ip_ver_pref, IpVersionPreference::Ipv4Only);
2353 }
2354
2355 #[test]
2356 fn streamprefs_ipv4_preferred() {
2357 let mut observed = StreamPrefs::new();
2358 observed.ipv4_preferred();
2359 assert_eq!(observed.ip_ver_pref, IpVersionPreference::Ipv4Preferred);
2360 }
2361
2362 #[test]
2363 fn streamprefs_optimistic() {
2364 let mut observed = StreamPrefs::new();
2365 observed.optimistic();
2366 assert!(observed.optimistic_stream);
2367 }
2368
2369 #[test]
2370 fn streamprefs_set_isolation() {
2371 let mut observed = StreamPrefs::new();
2372 observed.set_isolation(IsolationToken::new());
2373 match observed.isolation {
2374 StreamIsolationPreference::Explicit(_) => (),
2375 _ => panic!("unexpected isolation: {:?}", observed.isolation),
2376 };
2377 }
2378
2379 #[test]
2380 fn reconfigure_all_or_nothing() {
2381 tor_rtcompat::test_with_one_runtime!(|rt| async {
2382 let state_dir = tempfile::tempdir().unwrap();
2383 let cache_dir = tempfile::tempdir().unwrap();
2384 let cfg = TorClientConfigBuilder::from_directories(state_dir, cache_dir)
2385 .build()
2386 .unwrap();
2387 let tor_client = TorClient::with_runtime(rt)
2388 .config(cfg.clone())
2389 .bootstrap_behavior(BootstrapBehavior::Manual)
2390 .create_unbootstrapped()
2391 .unwrap();
2392 tor_client
2393 .reconfigure(&cfg, Reconfigure::AllOrNothing)
2394 .unwrap();
2395 });
2396 tor_rtcompat::test_with_one_runtime!(|rt| async {
2397 let state_dir = tempfile::tempdir().unwrap();
2398 let cache_dir = tempfile::tempdir().unwrap();
2399 let cfg = TorClientConfigBuilder::from_directories(state_dir, cache_dir)
2400 .build()
2401 .unwrap();
2402 let tor_client = TorClient::with_runtime(rt)
2403 .config(cfg.clone())
2404 .bootstrap_behavior(BootstrapBehavior::Manual)
2405 .create_unbootstrapped_async()
2406 .await
2407 .unwrap();
2408 tor_client
2409 .reconfigure(&cfg, Reconfigure::AllOrNothing)
2410 .unwrap();
2411 });
2412 }
2413}