Skip to main content

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}