1
#![cfg_attr(docsrs, feature(doc_cfg))]
2
#![doc = include_str!("../README.md")]
3
// @@ begin lint list maintained by maint/add_warning @@
4
#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
5
#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
6
#![warn(missing_docs)]
7
#![warn(noop_method_call)]
8
#![warn(unreachable_pub)]
9
#![warn(clippy::all)]
10
#![deny(clippy::await_holding_lock)]
11
#![deny(clippy::cargo_common_metadata)]
12
#![deny(clippy::cast_lossless)]
13
#![deny(clippy::checked_conversions)]
14
#![allow(clippy::cognitive_complexity)] // See arti#2556
15
#![deny(clippy::debug_assert_with_mut_call)]
16
#![deny(clippy::exhaustive_enums)]
17
#![deny(clippy::exhaustive_structs)]
18
#![deny(clippy::expl_impl_clone_on_copy)]
19
#![deny(clippy::fallible_impl_from)]
20
#![deny(clippy::implicit_clone)]
21
#![deny(clippy::large_stack_arrays)]
22
#![warn(clippy::manual_ok_or)]
23
#![deny(clippy::missing_docs_in_private_items)]
24
#![warn(clippy::needless_borrow)]
25
#![warn(clippy::needless_pass_by_value)]
26
#![warn(clippy::option_option)]
27
#![warn(clippy::or_fun_call)]
28
#![deny(clippy::print_stderr)]
29
#![deny(clippy::print_stdout)]
30
#![warn(clippy::rc_buffer)]
31
#![deny(clippy::ref_option_ref)]
32
#![warn(clippy::semicolon_if_nothing_returned)]
33
#![warn(clippy::trait_duplication_in_bounds)]
34
#![deny(clippy::unchecked_time_subtraction)]
35
#![deny(clippy::unnecessary_wraps)]
36
#![warn(clippy::unseparated_literal_suffix)]
37
#![deny(clippy::unwrap_used)]
38
#![deny(clippy::mod_module_files)]
39
#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
40
#![allow(clippy::uninlined_format_args)]
41
#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
42
#![allow(clippy::result_large_err)] // temporary workaround for arti#587
43
#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
44
#![allow(clippy::needless_lifetimes)] // See arti#1765
45
#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
46
#![allow(clippy::collapsible_if)] // See arti#2342
47
#![deny(clippy::unused_async)]
48
#![deny(clippy::string_slice)] // See arti#2571
49
#![allow(recursion_depth_exceeding_limit)] // arti#2715, rust/issues/159228
50
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
51

            
52
// TODO #1645 (either remove this, or decide to have it everywhere)
53
#![cfg_attr(not(all(feature = "full", feature = "experimental")), allow(unused))]
54

            
55
use build::TunnelBuilder;
56
use mgr::{AbstractTunnel, AbstractTunnelBuilder};
57
use tor_basic_utils::retry::RetryDelay;
58
use tor_chanmgr::ChanMgr;
59
use tor_dircommon::fallback::FallbackList;
60
use tor_error::{error_report, warn_report};
61
use tor_guardmgr::RetireCircuits;
62
use tor_linkspec::ChanTarget;
63
use tor_netdir::{DirEvent, NetDir, NetDirProvider, Timeliness};
64
use tor_proto::circuit::UniqId;
65
use tor_proto::client::circuit::CircParameters;
66
use tor_rtcompat::Runtime;
67

            
68
#[cfg(any(feature = "specific-relay", feature = "hs-common"))]
69
use tor_linkspec::IntoOwnedChanTarget;
70

            
71
use futures::StreamExt;
72
use std::sync::{Arc, Mutex, Weak};
73
use std::time::Duration;
74
use tor_rtcompat::SpawnExt;
75
use tracing::{debug, info, instrument, trace, warn};
76

            
77
#[cfg(feature = "testing")]
78
pub use config::test_config::TestConfig;
79

            
80
pub mod build;
81
mod config;
82
mod err;
83
#[cfg(feature = "hs-common")]
84
pub mod hspool;
85
mod impls;
86
pub mod isolation;
87
mod mgr;
88
#[cfg(test)]
89
mod mocks;
90
mod preemptive;
91
pub mod timeouts;
92
mod tunnel;
93
mod usage;
94

            
95
// Can't apply `visibility` to modules.
96
cfg_if::cfg_if! {
97
    if #[cfg(feature = "experimental-api")] {
98
        pub mod path;
99
    } else {
100
        pub(crate) mod path;
101
    }
102
}
103

            
104
pub use err::Error;
105
pub use isolation::IsolationToken;
106
pub use tor_guardmgr::{ClockSkewEvents, GuardMgrConfig, SkewEstimate};
107
pub use tunnel::{
108
    ClientDataTunnel, ClientDirTunnel, ClientOnionServiceDataTunnel, ClientOnionServiceDirTunnel,
109
    ClientOnionServiceIntroTunnel, ServiceOnionServiceDataTunnel, ServiceOnionServiceDirTunnel,
110
    ServiceOnionServiceIntroTunnel,
111
};
112
#[cfg(feature = "conflux")]
113
pub use tunnel::{
114
    ClientMultiPathDataTunnel, ClientMultiPathOnionServiceDataTunnel,
115
    ServiceMultiPathOnionServiceDataTunnel,
116
};
117
pub use usage::{TargetPort, TargetPorts};
118

            
119
pub use config::{
120
    CircMgrConfig, CircuitTiming, CircuitTimingBuilder, PathConfig, PathConfigBuilder,
121
    PreemptiveCircuitConfig, PreemptiveCircuitConfigBuilder,
122
};
123

            
124
use crate::isolation::StreamIsolation;
125
use crate::mgr::TunnelProvenance;
126
use crate::preemptive::PreemptiveCircuitPredictor;
127
use usage::TargetTunnelUsage;
128

            
129
use safelog::sensitive as sv;
130
#[cfg(feature = "geoip")]
131
use tor_geoip::CountryCode;
132
pub use tor_guardmgr::{ExternalActivity, FirstHopId};
133
use tor_persist::StateMgr;
134
use tor_rtcompat::scheduler::{TaskHandle, TaskSchedule};
135

            
136
#[cfg(feature = "hs-common")]
137
use crate::hspool::{HsCircKind, HsCircStemKind};
138
#[cfg(all(feature = "vanguards", feature = "hs-common"))]
139
use tor_guardmgr::vanguards::VanguardMgr;
140

            
141
/// A Result type as returned from this crate.
142
pub type Result<T> = std::result::Result<T, Error>;
143

            
144
/// Type alias for dynamic StorageHandle that can handle our timeout state.
145
type TimeoutStateHandle = tor_persist::DynStorageHandle<timeouts::pareto::ParetoTimeoutState>;
146

            
147
/// Key used to load timeout state information.
148
const PARETO_TIMEOUT_DATA_KEY: &str = "circuit_timeouts";
149

            
150
/// Represents what we know about the Tor network.
151
///
152
/// This can either be a complete directory, or a list of fallbacks.
153
///
154
/// Not every DirInfo can be used to build every kind of circuit:
155
/// if you try to build a path with an inadequate DirInfo, you'll get a
156
/// NeedConsensus error.
157
#[derive(Debug, Copy, Clone)]
158
#[non_exhaustive]
159
pub enum DirInfo<'a> {
160
    /// A list of fallbacks, for use when we don't know a network directory.
161
    Fallbacks(&'a FallbackList),
162
    /// A complete network directory
163
    Directory(&'a NetDir),
164
    /// No information: we can only build one-hop paths: and that, only if the
165
    /// guard manager knows some guards or fallbacks.
166
    Nothing,
167
}
168

            
169
impl<'a> From<&'a FallbackList> for DirInfo<'a> {
170
239
    fn from(v: &'a FallbackList) -> DirInfo<'a> {
171
239
        DirInfo::Fallbacks(v)
172
239
    }
173
}
174
impl<'a> From<&'a NetDir> for DirInfo<'a> {
175
270
    fn from(v: &'a NetDir) -> DirInfo<'a> {
176
270
        DirInfo::Directory(v)
177
270
    }
178
}
179
impl<'a> DirInfo<'a> {
180
    /// Return a set of circuit parameters for this DirInfo.
181
3
    fn circ_params(&self, usage: &TargetTunnelUsage) -> Result<CircParameters> {
182
        use tor_netdir::params::NetParameters;
183
        // We use a common function for both cases here to be sure that
184
        // we look at the defaults from NetParameters code.
185
3
        let defaults = NetParameters::default();
186
3
        let net_params = match self {
187
2
            DirInfo::Directory(d) => d.params(),
188
1
            _ => &defaults,
189
        };
190
3
        match usage {
191
            #[cfg(feature = "hs-common")]
192
            TargetTunnelUsage::HsCircBase { .. } => {
193
                build::onion_circparams_from_netparams(net_params)
194
            }
195
3
            _ => build::exit_circparams_from_netparams(net_params),
196
        }
197
3
    }
198
}
199

            
200
/// A Circuit Manager (CircMgr) manages a set of circuits, returning them
201
/// when they're suitable, and launching them if they don't already exist.
202
///
203
/// Right now, its notion of "suitable" is quite rudimentary: it just
204
/// believes in two kinds of circuits: Exit circuits, and directory
205
/// circuits.  Exit circuits are ones that were created to connect to
206
/// a set of ports; directory circuits were made to talk to directory caches.
207
///
208
/// This is a "handle"; clones of it share state.
209
pub struct CircMgr<R: Runtime>(Arc<CircMgrInner<build::TunnelBuilder<R>, R>>);
210

            
211
impl<R: Runtime> CircMgr<R> {
212
    /// Construct a new circuit manager.
213
    ///
214
    /// # Usage note
215
    ///
216
    /// For the manager to work properly, you will need to call `CircMgr::launch_background_tasks`.
217
4
    pub fn new<SM, CFG: CircMgrConfig>(
218
4
        config: &CFG,
219
4
        storage: SM,
220
4
        runtime: &R,
221
4
        chanmgr: Arc<ChanMgr<R>>,
222
4
        guardmgr: &tor_guardmgr::GuardMgr<R>,
223
4
    ) -> Result<Self>
224
4
    where
225
4
        SM: tor_persist::StateMgr + Clone + Send + Sync + 'static,
226
    {
227
4
        Ok(Self(Arc::new(CircMgrInner::new(
228
4
            config, storage, runtime, chanmgr, guardmgr,
229
        )?)))
230
4
    }
231

            
232
    /// Return a circuit suitable for sending one-hop BEGINDIR streams,
233
    /// launching it if necessary.
234
    #[instrument(level = "trace", skip_all)]
235
    pub async fn get_or_launch_dir(&self, netdir: DirInfo<'_>) -> Result<ClientDirTunnel> {
236
        let tunnel = self.0.get_or_launch_dir(netdir).await?;
237
        Ok(tunnel.into())
238
    }
239

            
240
    /// Return a circuit suitable for exiting to all of the provided
241
    /// `ports`, launching it if necessary.
242
    ///
243
    /// If the list of ports is empty, then the chosen circuit will
244
    /// still end at _some_ exit.
245
    #[instrument(level = "trace", skip_all)]
246
    pub async fn get_or_launch_exit(
247
        &self,
248
        netdir: DirInfo<'_>, // TODO: This has to be a NetDir.
249
        ports: &[TargetPort],
250
        isolation: StreamIsolation,
251
        // TODO GEOIP: this cannot be stabilised like this, since Cargo features need to be
252
        //             additive. The function should be refactored to be builder-like.
253
        #[cfg(feature = "geoip")] country_code: Option<CountryCode>,
254
    ) -> Result<ClientDataTunnel> {
255
        let tunnel = self
256
            .0
257
            .get_or_launch_exit(
258
                netdir,
259
                ports,
260
                isolation,
261
                #[cfg(feature = "geoip")]
262
                country_code,
263
            )
264
            .await?;
265
        Ok(tunnel.into())
266
    }
267

            
268
    /// Return a circuit to a specific relay, suitable for using for direct
269
    /// (one-hop) directory downloads.
270
    ///
271
    /// This could be used, for example, to download a descriptor for a bridge.
272
    #[cfg(feature = "specific-relay")]
273
    #[instrument(level = "trace", skip_all)]
274
    pub async fn get_or_launch_dir_specific<T: IntoOwnedChanTarget>(
275
        &self,
276
        target: T,
277
    ) -> Result<ClientDirTunnel> {
278
        let tunnel = self.0.get_or_launch_dir_specific(target).await?;
279
        Ok(tunnel.into())
280
    }
281

            
282
    /// Launch the periodic daemon tasks required by the manager to function properly.
283
    ///
284
    /// Returns a set of [`TaskHandle`]s that can be used to manage the daemon tasks.
285
    //
286
    // NOTE(eta): The ?Sized on D is so we can pass a trait object in.
287
    #[instrument(level = "trace", skip_all)]
288
    pub fn launch_background_tasks<D, S>(
289
        self: &Arc<Self>,
290
        runtime: &R,
291
        dir_provider: &Arc<D>,
292
        state_mgr: S,
293
    ) -> Result<Vec<TaskHandle>>
294
    where
295
        D: NetDirProvider + 'static + ?Sized,
296
        S: StateMgr + std::marker::Send + 'static,
297
    {
298
        CircMgrInner::launch_background_tasks(&self.0.clone(), runtime, dir_provider, state_mgr)
299
    }
300

            
301
    /// Return true if `netdir` has enough information to be used for this
302
    /// circuit manager.
303
    ///
304
    /// (This will check whether the netdir is missing any primary guard
305
    /// microdescriptors)
306
    pub fn netdir_is_sufficient(&self, netdir: &NetDir) -> bool {
307
        self.0.netdir_is_sufficient(netdir)
308
    }
309

            
310
    /// If `circ_id` is the unique identifier for a circuit that we're
311
    /// keeping track of, don't give it out for any future requests.
312
    pub fn retire_circ(&self, circ_id: &UniqId) {
313
        self.0.retire_circ(circ_id);
314
    }
315

            
316
    /// Record that a failure occurred on a circuit with a given guard, in a way
317
    /// that makes us unwilling to use that guard for future circuits.
318
    ///
319
    pub fn note_external_failure(
320
        &self,
321
        target: &impl ChanTarget,
322
        external_failure: ExternalActivity,
323
    ) {
324
        self.0.note_external_failure(target, external_failure);
325
    }
326

            
327
    /// Record that a success occurred on a circuit with a given guard, in a way
328
    /// that makes us possibly willing to use that guard for future circuits.
329
    pub fn note_external_success(
330
        &self,
331
        target: &impl ChanTarget,
332
        external_activity: ExternalActivity,
333
    ) {
334
        self.0.note_external_success(target, external_activity);
335
    }
336

            
337
    /// Return a stream of events about our estimated clock skew; these events
338
    /// are `None` when we don't have enough information to make an estimate,
339
    /// and `Some(`[`SkewEstimate`]`)` otherwise.
340
    ///
341
    /// Note that this stream can be lossy: if the estimate changes more than
342
    /// one before you read from the stream, you might only get the most recent
343
    /// update.
344
    pub fn skew_events(&self) -> ClockSkewEvents {
345
        self.0.skew_events()
346
    }
347

            
348
    /// Try to change our configuration settings to `new_config`.
349
    ///
350
    /// The actual behavior here will depend on the value of `how`.
351
    ///
352
    /// Returns whether any of the circuit pools should be cleared.
353
    #[instrument(level = "trace", skip_all)]
354
    pub fn reconfigure<CFG: CircMgrConfig>(
355
        &self,
356
        new_config: &CFG,
357
        how: tor_config::Reconfigure,
358
    ) -> std::result::Result<RetireCircuits, tor_config::ReconfigureError> {
359
        self.0.reconfigure(new_config, how)
360
    }
361

            
362
    /// Return an estimate-based delay for how long a given
363
    /// [`Action`](timeouts::Action) should be allowed to complete.
364
    ///
365
    /// Note that **you do not need to use this function** in order to get
366
    /// reasonable timeouts for the circuit-building operations provided by the
367
    /// `tor-circmgr` crate: those, unless specifically noted, always use these
368
    /// timeouts to cancel circuit operations that have taken too long.
369
    ///
370
    /// Instead, you should only use this function when you need to estimate how
371
    /// long some _other_ operation should take to complete.  For example, if
372
    /// you are sending a request over a 3-hop circuit and waiting for a reply,
373
    /// you might choose to wait for `estimate_timeout(Action::RoundTrip {
374
    /// length: 3 })`.
375
    ///
376
    /// Note also that this function returns a _timeout_ that the operation
377
    /// should be permitted to complete, not an estimated Duration that the
378
    /// operation _will_ take to complete. Timeouts are chosen to ensure that
379
    /// most operations will complete, but very slow ones will not.  So even if
380
    /// we expect that a circuit will complete in (say) 3 seconds, we might
381
    /// still allow a timeout of 4.5 seconds, to ensure that most circuits can
382
    /// complete.
383
    ///
384
    /// Estimate-based timeouts may change over time, given observations on the
385
    /// actual amount of time needed for circuits to complete building.  If not
386
    /// enough information has been gathered, a reasonable default will be used.
387
    pub fn estimate_timeout(&self, timeout_action: &timeouts::Action) -> std::time::Duration {
388
        self.0.estimate_timeout(timeout_action)
389
    }
390

            
391
    /// Return a reference to the associated CircuitBuilder that this CircMgr
392
    /// will use to create its circuits.
393
    #[cfg(feature = "experimental-api")]
394
    pub fn builder(&self) -> &TunnelBuilder<R> {
395
        CircMgrInner::builder(&self.0)
396
    }
397
}
398

            
399
/// Internal object used to implement CircMgr, which allows for mocking.
400
#[derive(Clone)]
401
pub(crate) struct CircMgrInner<B: AbstractTunnelBuilder<R> + 'static, R: Runtime> {
402
    /// The underlying circuit manager object that implements our behavior.
403
    mgr: Arc<mgr::AbstractTunnelMgr<B, R>>,
404
    /// A preemptive circuit predictor, for, uh, building circuits preemptively.
405
    predictor: Arc<Mutex<PreemptiveCircuitPredictor>>,
406
}
407

            
408
impl<R: Runtime> CircMgrInner<TunnelBuilder<R>, R> {
409
    /// Construct a new circuit manager.
410
    ///
411
    /// # Usage note
412
    ///
413
    /// For the manager to work properly, you will need to call `CircMgr::launch_background_tasks`.
414
    #[allow(clippy::unnecessary_wraps)]
415
10
    pub(crate) fn new<SM, CFG: CircMgrConfig>(
416
10
        config: &CFG,
417
10
        storage: SM,
418
10
        runtime: &R,
419
10
        chanmgr: Arc<ChanMgr<R>>,
420
10
        guardmgr: &tor_guardmgr::GuardMgr<R>,
421
10
    ) -> Result<Self>
422
10
    where
423
10
        SM: tor_persist::StateMgr + Clone + Send + Sync + 'static,
424
    {
425
        #[cfg(all(feature = "vanguards", feature = "hs-common"))]
426
10
        let vanguardmgr = {
427
            // TODO(#1382): we need a way of checking if this arti instance
428
            // is running an onion service or not.
429
            //
430
            // Perhaps this information should be provided by CircMgrConfig.
431
10
            let has_onion_svc = false;
432
10
            VanguardMgr::new(
433
10
                config.vanguard_config(),
434
10
                runtime.clone(),
435
10
                storage.clone(),
436
10
                has_onion_svc,
437
            )?
438
        };
439

            
440
10
        let storage_handle = storage.create_handle(PARETO_TIMEOUT_DATA_KEY);
441

            
442
10
        let builder = build::TunnelBuilder::new(
443
10
            runtime.clone(),
444
10
            chanmgr,
445
10
            config.path_rules().clone(),
446
10
            storage_handle,
447
10
            guardmgr.clone(),
448
            #[cfg(all(feature = "vanguards", feature = "hs-common"))]
449
10
            vanguardmgr,
450
        );
451

            
452
10
        Ok(Self::new_generic(config, runtime, guardmgr, builder))
453
10
    }
454
}
455

            
456
impl<B: AbstractTunnelBuilder<R> + 'static, R: Runtime> CircMgrInner<B, R> {
457
    /// Generic implementation for [`CircMgrInner::new`]
458
12
    pub(crate) fn new_generic<CFG: CircMgrConfig>(
459
12
        config: &CFG,
460
12
        runtime: &R,
461
12
        guardmgr: &tor_guardmgr::GuardMgr<R>,
462
12
        builder: B,
463
12
    ) -> Self {
464
12
        let preemptive = Arc::new(Mutex::new(PreemptiveCircuitPredictor::new(
465
12
            config.preemptive_circuits().clone(),
466
        )));
467

            
468
12
        guardmgr.set_filter(config.path_rules().build_guard_filter());
469

            
470
12
        let mgr =
471
12
            mgr::AbstractTunnelMgr::new(builder, runtime.clone(), config.circuit_timing().clone());
472

            
473
12
        CircMgrInner {
474
12
            mgr: Arc::new(mgr),
475
12
            predictor: preemptive,
476
12
        }
477
12
    }
478

            
479
    /// Launch the periodic daemon tasks required by the manager to function properly.
480
    ///
481
    /// Returns a set of [`TaskHandle`]s that can be used to manage the daemon tasks.
482
    //
483
    // NOTE(eta): The ?Sized on D is so we can pass a trait object in.
484
    #[instrument(level = "trace", skip_all)]
485
2
    pub(crate) fn launch_background_tasks<D, S>(
486
2
        self: &Arc<Self>,
487
2
        runtime: &R,
488
2
        dir_provider: &Arc<D>,
489
2
        state_mgr: S,
490
2
    ) -> Result<Vec<TaskHandle>>
491
2
    where
492
2
        D: NetDirProvider + 'static + ?Sized,
493
2
        S: StateMgr + std::marker::Send + 'static,
494
    {
495
2
        let mut ret = vec![];
496

            
497
2
        runtime
498
2
            .spawn(Self::keep_circmgr_params_updated(
499
2
                dir_provider.events(),
500
2
                Arc::downgrade(self),
501
2
                Arc::downgrade(dir_provider),
502
            ))
503
2
            .map_err(|e| Error::from_spawn("circmgr parameter updater", e))?;
504

            
505
2
        let (sched, handle) = TaskSchedule::new(runtime.clone());
506
2
        ret.push(handle);
507

            
508
2
        runtime
509
2
            .spawn(Self::update_persistent_state(
510
2
                sched,
511
2
                Arc::downgrade(self),
512
2
                state_mgr,
513
            ))
514
2
            .map_err(|e| Error::from_spawn("persistent state updater", e))?;
515

            
516
2
        let (sched, handle) = TaskSchedule::new(runtime.clone());
517
2
        ret.push(handle);
518

            
519
2
        runtime
520
2
            .spawn(Self::continually_launch_timeout_testing_circuits(
521
2
                sched,
522
2
                Arc::downgrade(self),
523
2
                Arc::downgrade(dir_provider),
524
            ))
525
2
            .map_err(|e| Error::from_spawn("timeout-probe circuit launcher", e))?;
526

            
527
2
        let (sched, handle) = TaskSchedule::new(runtime.clone());
528
2
        ret.push(handle);
529

            
530
2
        runtime
531
2
            .spawn(Self::continually_preemptively_build_circuits(
532
2
                sched,
533
2
                Arc::downgrade(self),
534
2
                Arc::downgrade(dir_provider),
535
            ))
536
2
            .map_err(|e| Error::from_spawn("preemptive circuit launcher", e))?;
537

            
538
2
        self.mgr
539
2
            .peek_builder()
540
2
            .guardmgr()
541
2
            .install_netdir_provider(&dir_provider.clone().upcast_arc())?;
542

            
543
        #[cfg(all(feature = "vanguards", feature = "hs-common"))]
544
        {
545
2
            let () = self
546
2
                .mgr
547
2
                .peek_builder()
548
2
                .vanguardmgr()
549
2
                .launch_background_tasks(&dir_provider.clone().upcast_arc())?;
550
        }
551

            
552
2
        Ok(ret)
553
2
    }
554

            
555
    /// Return a circuit suitable for sending one-hop BEGINDIR streams,
556
    /// launching it if necessary.
557
    #[instrument(level = "trace", skip_all)]
558
    pub(crate) async fn get_or_launch_dir(&self, netdir: DirInfo<'_>) -> Result<Arc<B::Tunnel>> {
559
        self.expire_circuits().await;
560
        let usage = TargetTunnelUsage::Dir;
561
        self.mgr.get_or_launch(&usage, netdir).await.map(|(c, _)| c)
562
    }
563

            
564
    /// Return a circuit suitable for exiting to all of the provided
565
    /// `ports`, launching it if necessary.
566
    ///
567
    /// If the list of ports is empty, then the chosen circuit will
568
    /// still end at _some_ exit.
569
    #[instrument(level = "trace", skip_all)]
570
    pub(crate) async fn get_or_launch_exit(
571
        &self,
572
        netdir: DirInfo<'_>, // TODO: This has to be a NetDir.
573
        ports: &[TargetPort],
574
        isolation: StreamIsolation,
575
        // TODO GEOIP: this cannot be stabilised like this, since Cargo features need to be
576
        //             additive. The function should be refactored to be builder-like.
577
        #[cfg(feature = "geoip")] country_code: Option<CountryCode>,
578
    ) -> Result<Arc<B::Tunnel>> {
579
        self.expire_circuits().await;
580
        let time = self.mgr.peek_runtime().now();
581
        {
582
            let mut predictive = self.predictor.lock().expect("preemptive lock poisoned");
583
            if ports.is_empty() {
584
                predictive.note_usage(None, time);
585
            } else {
586
                for port in ports.iter() {
587
                    predictive.note_usage(Some(*port), time);
588
                }
589
            }
590
        }
591
        let require_stability = ports.iter().any(|p| {
592
            self.mgr
593
                .peek_builder()
594
                .path_config()
595
                .long_lived_ports
596
                .contains(&p.port)
597
        });
598
        let ports = ports.iter().map(Clone::clone).collect();
599
        #[cfg(not(feature = "geoip"))]
600
        let country_code = None;
601
        let usage = TargetTunnelUsage::Exit {
602
            ports,
603
            isolation,
604
            country_code,
605
            require_stability,
606
        };
607
        self.mgr.get_or_launch(&usage, netdir).await.map(|(c, _)| c)
608
    }
609

            
610
    /// Return a circuit to a specific relay, suitable for using for direct
611
    /// (one-hop) directory downloads.
612
    ///
613
    /// This could be used, for example, to download a descriptor for a bridge.
614
    #[cfg(feature = "specific-relay")]
615
    #[instrument(level = "trace", skip_all)]
616
    pub(crate) async fn get_or_launch_dir_specific<T: IntoOwnedChanTarget>(
617
        &self,
618
        target: T,
619
    ) -> Result<Arc<B::Tunnel>> {
620
        self.expire_circuits().await;
621
        let usage = TargetTunnelUsage::DirSpecificTarget(target.to_owned());
622
        self.mgr
623
            .get_or_launch(&usage, DirInfo::Nothing)
624
            .await
625
            .map(|(c, _)| c)
626
    }
627

            
628
    /// Try to change our configuration settings to `new_config`.
629
    ///
630
    /// The actual behavior here will depend on the value of `how`.
631
    ///
632
    /// Returns whether any of the circuit pools should be cleared.
633
    #[instrument(level = "trace", skip_all)]
634
    pub(crate) fn reconfigure<CFG: CircMgrConfig>(
635
        &self,
636
        new_config: &CFG,
637
        how: tor_config::Reconfigure,
638
    ) -> std::result::Result<RetireCircuits, tor_config::ReconfigureError> {
639
        let old_path_rules = self.mgr.peek_builder().path_config();
640
        let predictor = self.predictor.lock().expect("poisoned lock");
641
        let preemptive_circuits = predictor.config();
642
        if preemptive_circuits.initial_predicted_ports
643
            != new_config.preemptive_circuits().initial_predicted_ports
644
        {
645
            // This change has no effect, since the list of ports was _initial_.
646
            how.cannot_change("preemptive_circuits.initial_predicted_ports")?;
647
        }
648

            
649
        if how == tor_config::Reconfigure::CheckAllOrNothing {
650
            return Ok(RetireCircuits::None);
651
        }
652

            
653
        let retire_because_of_guardmgr =
654
            self.mgr.peek_builder().guardmgr().reconfigure(new_config)?;
655

            
656
        #[cfg(all(feature = "vanguards", feature = "hs-common"))]
657
        let retire_because_of_vanguardmgr = self
658
            .mgr
659
            .peek_builder()
660
            .vanguardmgr()
661
            .reconfigure(new_config.vanguard_config())?;
662

            
663
        let new_reachable = &new_config.path_rules().reachable_addrs;
664
        if new_reachable != &old_path_rules.reachable_addrs {
665
            let filter = new_config.path_rules().build_guard_filter();
666
            self.mgr.peek_builder().guardmgr().set_filter(filter);
667
        }
668

            
669
        let discard_all_circuits = !new_config
670
            .path_rules()
671
            .at_least_as_permissive_as(&old_path_rules)
672
            || retire_because_of_guardmgr != tor_guardmgr::RetireCircuits::None;
673

            
674
        #[cfg(all(feature = "vanguards", feature = "hs-common"))]
675
        let discard_all_circuits = discard_all_circuits
676
            || retire_because_of_vanguardmgr != tor_guardmgr::RetireCircuits::None;
677

            
678
        self.mgr
679
            .peek_builder()
680
            .set_path_config(new_config.path_rules().clone());
681
        self.mgr
682
            .set_circuit_timing(new_config.circuit_timing().clone());
683
        predictor.set_config(new_config.preemptive_circuits().clone());
684

            
685
        if discard_all_circuits {
686
            // TODO(nickm): Someday, we might want to take a more lenient approach, and only
687
            // retire those circuits that do not conform to the new path rules,
688
            // or do not conform to the new guard configuration.
689
            info!("Path configuration has become more restrictive: retiring existing circuits.");
690
            self.retire_all_circuits();
691
            return Ok(RetireCircuits::All);
692
        }
693
        Ok(RetireCircuits::None)
694
    }
695

            
696
    /// Whenever a [`DirEvent::NewConsensus`] arrives on `events`, update
697
    /// `circmgr` with the consensus parameters from `dirmgr`.
698
    ///
699
    /// Exit when `events` is closed, or one of `circmgr` or `dirmgr` becomes
700
    /// dangling.
701
    ///
702
    /// This is a daemon task: it runs indefinitely in the background.
703
    #[instrument(level = "trace", skip_all)]
704
2
    async fn keep_circmgr_params_updated<D>(
705
2
        mut events: impl futures::Stream<Item = DirEvent> + Unpin,
706
2
        circmgr: Weak<Self>,
707
2
        dirmgr: Weak<D>,
708
2
    ) where
709
2
        D: NetDirProvider + 'static + ?Sized,
710
2
    {
711
        use DirEvent::*;
712
        while let Some(event) = events.next().await {
713
            if matches!(event, NewConsensus) {
714
                if let (Some(cm), Some(dm)) = (Weak::upgrade(&circmgr), Weak::upgrade(&dirmgr)) {
715
                    if let Ok(netdir) = dm.netdir(Timeliness::Timely) {
716
                        cm.update_network_parameters(netdir.params());
717
                    }
718
                } else {
719
                    debug!("Circmgr or dirmgr has disappeared; task exiting.");
720
                    break;
721
                }
722
            }
723
        }
724
2
    }
725

            
726
    /// Reconfigure this circuit manager using the latest set of
727
    /// network parameters.
728
    fn update_network_parameters(&self, p: &tor_netdir::params::NetParameters) {
729
        self.mgr.update_network_parameters(p);
730
        self.mgr.peek_builder().update_network_parameters(p);
731
    }
732

            
733
    /// Run indefinitely, launching circuits as needed to get a good
734
    /// estimate for our circuit build timeouts.
735
    ///
736
    /// Exit when we notice that `circmgr` or `dirmgr` has been dropped.
737
    ///
738
    /// This is a daemon task: it runs indefinitely in the background.
739
    #[instrument(level = "trace", skip_all)]
740
2
    async fn continually_launch_timeout_testing_circuits<D>(
741
2
        mut sched: TaskSchedule<R>,
742
2
        circmgr: Weak<Self>,
743
2
        dirmgr: Weak<D>,
744
2
    ) where
745
2
        D: NetDirProvider + 'static + ?Sized,
746
2
    {
747
        while sched.next().await.is_some() {
748
            if let (Some(cm), Some(dm)) = (Weak::upgrade(&circmgr), Weak::upgrade(&dirmgr)) {
749
                if let Ok(netdir) = dm.netdir(Timeliness::Unchecked) {
750
                    if let Err(e) = cm
751
                        .launch_timeout_testing_circuit_if_appropriate(&netdir)
752
                        .await
753
                    {
754
                        warn_report!(e, "Problem launching a timeout testing circuit");
755
                    }
756
                    let delay = netdir
757
                        .params()
758
                        .cbt_testing_delay
759
                        .try_into()
760
                        .expect("Out-of-bounds value from BoundedInt32");
761

            
762
                    drop((cm, dm));
763
                    sched.fire_in(delay);
764
                } else {
765
                    // wait for the provider to announce some event, which will probably be
766
                    // NewConsensus; this is therefore a decent yardstick for rechecking
767
                    let _ = dm.events().next().await;
768
                    sched.fire();
769
                }
770
            } else {
771
                return;
772
            }
773
        }
774
2
    }
775

            
776
    /// If we need to launch a testing circuit to judge our circuit
777
    /// build timeouts timeouts, do so.
778
    ///
779
    /// # Note
780
    ///
781
    /// This function is invoked periodically from
782
    /// `continually_launch_timeout_testing_circuits`.
783
    async fn launch_timeout_testing_circuit_if_appropriate(&self, netdir: &NetDir) -> Result<()> {
784
        if !self.mgr.peek_builder().learning_timeouts() {
785
            return Ok(());
786
        }
787
        // We expire any too-old circuits here, so they don't get
788
        // counted towards max_circs.
789
        self.expire_circuits().await;
790
        let max_circs: u64 = netdir
791
            .params()
792
            .cbt_max_open_circuits_for_testing
793
            .try_into()
794
            .expect("Out-of-bounds result from BoundedInt32");
795
        if (self.mgr.n_tunnels() as u64) < max_circs {
796
            // Actually launch the circuit!
797
            let usage = TargetTunnelUsage::TimeoutTesting;
798
            let dirinfo = netdir.into();
799
            let mgr = Arc::clone(&self.mgr);
800
            debug!("Launching a circuit to test build times.");
801
            let receiver = mgr.launch_by_usage(&usage, dirinfo)?;
802
            // We don't actually care when this circuit is done,
803
            // so it's okay to drop the Receiver without awaiting it.
804
            drop(receiver);
805
        }
806

            
807
        Ok(())
808
    }
809

            
810
    /// Run forever, periodically telling `circmgr` to update its persistent
811
    /// state.
812
    ///
813
    /// Exit when we notice that `circmgr` has been dropped.
814
    ///
815
    /// This is a daemon task: it runs indefinitely in the background.
816
    #[instrument(level = "trace", skip_all)]
817
2
    async fn update_persistent_state<S>(
818
2
        mut sched: TaskSchedule<R>,
819
2
        circmgr: Weak<Self>,
820
2
        statemgr: S,
821
2
    ) where
822
2
        S: StateMgr + std::marker::Send,
823
2
    {
824
        while sched.next().await.is_some() {
825
            if let Some(circmgr) = Weak::upgrade(&circmgr) {
826
                use tor_persist::LockStatus::*;
827

            
828
                match statemgr.try_lock() {
829
                    Err(e) => {
830
                        error_report!(e, "Problem with state lock file");
831
                        break;
832
                    }
833
                    Ok(NewlyAcquired) => {
834
                        info!("We now own the lock on our state files.");
835
                        if let Err(e) = circmgr.upgrade_to_owned_persistent_state() {
836
                            error_report!(e, "Unable to upgrade to owned state files");
837
                            break;
838
                        }
839
                    }
840
                    Ok(AlreadyHeld) => {
841
                        if let Err(e) = circmgr.store_persistent_state() {
842
                            error_report!(e, "Unable to flush circmgr state");
843
                            break;
844
                        }
845
                    }
846
                    Ok(NoLock) => {
847
                        if let Err(e) = circmgr.reload_persistent_state() {
848
                            error_report!(e, "Unable to reload circmgr state");
849
                            break;
850
                        }
851
                    }
852
                }
853
            } else {
854
                debug!("Circmgr has disappeared; task exiting.");
855
                return;
856
            }
857
            // TODO(nickm): This delay is probably too small.
858
            //
859
            // Also, we probably don't even want a fixed delay here.  Instead,
860
            // we should be updating more frequently when the data is volatile
861
            // or has important info to save, and not at all when there are no
862
            // changes.
863
            sched.fire_in(Duration::from_secs(60));
864
        }
865

            
866
        debug!("State update task exiting (potentially due to handle drop).");
867
2
    }
868

            
869
    /// Switch from having an unowned persistent state to having an owned one.
870
    ///
871
    /// Requires that we hold the lock on the state files.
872
    pub(crate) fn upgrade_to_owned_persistent_state(&self) -> Result<()> {
873
        self.mgr.peek_builder().upgrade_to_owned_state()?;
874
        Ok(())
875
    }
876

            
877
    /// Reload state from the state manager.
878
    ///
879
    /// We only call this method if we _don't_ have the lock on the state
880
    /// files.  If we have the lock, we only want to save.
881
    pub(crate) fn reload_persistent_state(&self) -> Result<()> {
882
        self.mgr.peek_builder().reload_state()?;
883
        Ok(())
884
    }
885

            
886
    /// Run indefinitely, launching circuits where the preemptive circuit
887
    /// predictor thinks it'd be a good idea to have them.
888
    ///
889
    /// Exit when we notice that `circmgr` or `dirmgr` has been dropped.
890
    ///
891
    /// This is a daemon task: it runs indefinitely in the background.
892
    ///
893
    /// # Note
894
    ///
895
    /// This would be better handled entirely within `tor-circmgr`, like
896
    /// other daemon tasks.
897
    #[instrument(level = "trace", skip_all)]
898
2
    async fn continually_preemptively_build_circuits<D>(
899
2
        mut sched: TaskSchedule<R>,
900
2
        circmgr: Weak<Self>,
901
2
        dirmgr: Weak<D>,
902
2
    ) where
903
2
        D: NetDirProvider + 'static + ?Sized,
904
2
    {
905
        let base_delay = Duration::from_secs(10);
906
        let mut retry = RetryDelay::from_duration(base_delay);
907

            
908
        while sched.next().await.is_some() {
909
            if let (Some(cm), Some(dm)) = (Weak::upgrade(&circmgr), Weak::upgrade(&dirmgr)) {
910
                if let Ok(netdir) = dm.netdir(Timeliness::Timely) {
911
                    let result = cm
912
                        .launch_circuits_preemptively(DirInfo::Directory(&netdir))
913
                        .await;
914

            
915
                    let delay = match result {
916
                        Ok(()) => {
917
                            retry.reset();
918
                            base_delay
919
                        }
920
                        Err(_) => retry.next_delay(&mut rand::rng()),
921
                    };
922

            
923
                    sched.fire_in(delay);
924
                } else {
925
                    // wait for the provider to announce some event, which will probably be
926
                    // NewConsensus; this is therefore a decent yardstick for rechecking
927
                    let _ = dm.events().next().await;
928
                    sched.fire();
929
                }
930
            } else {
931
                return;
932
            }
933
        }
934
2
    }
935

            
936
    /// Launch circuits preemptively, using the preemptive circuit predictor's
937
    /// predictions.
938
    ///
939
    /// # Note
940
    ///
941
    /// This function is invoked periodically from
942
    /// `continually_preemptively_build_circuits()`.
943
    #[instrument(level = "trace", skip_all)]
944
    async fn launch_circuits_preemptively(
945
        &self,
946
        netdir: DirInfo<'_>,
947
    ) -> std::result::Result<(), err::PreemptiveCircError> {
948
        trace!("Checking preemptive circuit predictions.");
949
        let (circs, threshold) = {
950
            let path_config = self.mgr.peek_builder().path_config();
951
            let preemptive = self.predictor.lock().expect("preemptive lock poisoned");
952
            let threshold = preemptive.config().disable_at_threshold;
953
            (preemptive.predict(&path_config), threshold)
954
        };
955

            
956
        if self.mgr.n_tunnels() >= threshold {
957
            return Ok(());
958
        }
959
        let mut n_created = 0_usize;
960
        let mut n_errors = 0_usize;
961

            
962
        let futures = circs
963
            .iter()
964
            .map(|usage| self.mgr.get_or_launch(usage, netdir));
965
        let results = futures::future::join_all(futures).await;
966
        for (i, result) in results.into_iter().enumerate() {
967
            match result {
968
                Ok((t, TunnelProvenance::NewlyCreated)) => {
969
                    debug!(
970
                        tunnel_id=%t.unique_id(),
971
                        "Preemptive circuit was created for {:?}", circs[i]);
972
                    n_created += 1;
973
                }
974
                Ok((t, TunnelProvenance::Preexisting)) => {
975
                    trace!(
976
                        tunnel_id=%t.unique_id(),
977
                        "Circuit already existed created for {:?}", circs[i]);
978
                }
979
                Err(e) => {
980
                    warn_report!(e, "Failed to build preemptive circuit {:?}", sv(&circs[i]));
981
                    n_errors += 1;
982
                }
983
            }
984
        }
985

            
986
        if n_created > 0 || n_errors == 0 {
987
            // Either we successfully made a circuit, or we didn't have any
988
            // failures while looking for preexisting circuits.  Progress was
989
            // made, so there's no need to back off.
990
            Ok(())
991
        } else {
992
            // We didn't build any circuits and we hit at least one error:
993
            // We'll call this unsuccessful.
994
            Err(err::PreemptiveCircError)
995
        }
996
    }
997

            
998
    /// Create and return a new (typically anonymous) onion circuit stem
999
    /// of type `stem_kind`.
    ///
    /// If `circ_kind` is provided, we apply additional rules to make sure
    /// that this will be usable as a stem for the given kind of onion service circuit.
    /// Otherwise, we pick a stem that will probably be useful in general.
    ///
    /// This circuit is guaranteed not to have been used for any traffic
    /// previously, and it will not be given out for any other requests in the
    /// future unless explicitly re-registered with a circuit manager.
    ///
    /// If `planned_target` is provided, then the circuit will be built so that
    /// it does not share any family members with the provided target.  (The
    /// circuit _will not be_ extended to that target itself!)
    ///
    /// Used to implement onion service clients and services.
    #[cfg(feature = "hs-common")]
    #[instrument(level = "trace", skip_all)]
2
    pub(crate) async fn launch_hs_unmanaged<T>(
2
        &self,
2
        planned_target: Option<T>,
2
        dir: &NetDir,
2
        stem_kind: HsCircStemKind,
2
        circ_kind: Option<HsCircKind>,
2
    ) -> Result<B::Tunnel>
2
    where
2
        T: IntoOwnedChanTarget,
2
    {
        let usage = TargetTunnelUsage::HsCircBase {
            compatible_with_target: planned_target.map(IntoOwnedChanTarget::to_owned),
            stem_kind,
            circ_kind,
        };
        let (_, client_circ) = self.mgr.launch_unmanaged(&usage, dir.into()).await?;
        Ok(client_circ)
2
    }
    /// Return true if `netdir` has enough information to be used for this
    /// circuit manager.
    ///
    /// (This will check whether the netdir is missing any primary guard
    /// microdescriptors)
    pub(crate) fn netdir_is_sufficient(&self, netdir: &NetDir) -> bool {
        self.mgr
            .peek_builder()
            .guardmgr()
            .netdir_is_sufficient(netdir)
    }
    /// Internal implementation for [`CircMgr::estimate_timeout`].
    pub(crate) fn estimate_timeout(
        &self,
        timeout_action: &timeouts::Action,
    ) -> std::time::Duration {
        let (timeout, _abandon) = self.mgr.peek_builder().estimator().timeouts(timeout_action);
        timeout
    }
    /// Internal implementation for [`CircMgr::builder`].
    pub(crate) fn builder(&self) -> &B {
        self.mgr.peek_builder()
    }
    /// Flush state to the state manager, if there is any unsaved state and
    /// we have the lock.
    ///
    /// Return true if we saved something; false if we didn't have the lock.
8
    pub(crate) fn store_persistent_state(&self) -> Result<bool> {
8
        self.mgr.peek_builder().save_state()
8
    }
    /// Expire every circuit that has been dirty for too long.
    ///
    /// Expired circuits are not closed while they still have users,
    /// but they are no longer given out for new requests.
    async fn expire_circuits(&self) {
        // TODO: I would prefer not to call this at every request, but
        // it should be fine for now.  (At some point we may no longer
        // need this, or might not need to call it so often, now that
        // our circuit expiration runs on scheduled timers via
        // spawn_expiration_task.)
        let now = self.mgr.peek_runtime().now();
        // TODO: Use return value here to implement the above TODO.
        let _next_expiration = self.mgr.expire_tunnels(now).await;
    }
    /// Mark every circuit that we have launched so far as unsuitable for
    /// any future requests.  This won't close existing circuits that have
    /// streams attached to them, but it will prevent any future streams from
    /// being attached.
    ///
    /// TODO: we may want to expose this eventually.  If we do, we should
    /// be very clear that you don't want to use it haphazardly.
    pub(crate) fn retire_all_circuits(&self) {
        self.mgr.retire_all_tunnels();
    }
    /// If `circ_id` is the unique identifier for a circuit that we're
    /// keeping track of, don't give it out for any future requests.
    pub(crate) fn retire_circ(&self, circ_id: &<B::Tunnel as AbstractTunnel>::Id) {
        let _ = self.mgr.take_tunnel(circ_id);
    }
    /// Return a stream of events about our estimated clock skew; these events
    /// are `None` when we don't have enough information to make an estimate,
    /// and `Some(`[`SkewEstimate`]`)` otherwise.
    ///
    /// Note that this stream can be lossy: if the estimate changes more than
    /// one before you read from the stream, you might only get the most recent
    /// update.
    pub(crate) fn skew_events(&self) -> ClockSkewEvents {
        self.mgr.peek_builder().guardmgr().skew_events()
    }
    /// Record that a failure occurred on a circuit with a given guard, in a way
    /// that makes us unwilling to use that guard for future circuits.
    ///
    pub(crate) fn note_external_failure(
        &self,
        target: &impl ChanTarget,
        external_failure: ExternalActivity,
    ) {
        self.mgr
            .peek_builder()
            .guardmgr()
            .note_external_failure(target, external_failure);
    }
    /// Record that a success occurred on a circuit with a given guard, in a way
    /// that makes us possibly willing to use that guard for future circuits.
    pub(crate) fn note_external_success(
        &self,
        target: &impl ChanTarget,
        external_activity: ExternalActivity,
    ) {
        self.mgr
            .peek_builder()
            .guardmgr()
            .note_external_success(target, external_activity);
    }
}
impl<B: AbstractTunnelBuilder<R> + 'static, R: Runtime> Drop for CircMgrInner<B, R> {
8
    fn drop(&mut self) {
8
        match self.store_persistent_state() {
2
            Ok(true) => info!("Flushed persistent state at exit."),
6
            Ok(false) => debug!("Lock not held; no state to flush."),
            Err(e) => error_report!(e, "Unable to flush state on circuit manager drop"),
        }
8
    }
}
#[cfg(test)]
mod test {
    // @@ begin test lint list maintained by maint/add_warning @@
    #![allow(clippy::bool_assert_comparison)]
    #![allow(clippy::clone_on_copy)]
    #![allow(clippy::dbg_macro)]
    #![allow(clippy::mixed_attributes_style)]
    #![allow(clippy::print_stderr)]
    #![allow(clippy::print_stdout)]
    #![allow(clippy::single_char_pattern)]
    #![allow(clippy::unwrap_used)]
    #![allow(clippy::unchecked_time_subtraction)]
    #![allow(clippy::useless_vec)]
    #![allow(clippy::needless_pass_by_value)]
    #![allow(clippy::string_slice)] // See arti#2571
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
    use mocks::FakeBuilder;
    use tor_guardmgr::GuardMgr;
    use tor_linkspec::OwnedChanTarget;
    use tor_netdir::testprovider::TestNetDirProvider;
    use tor_persist::TestingStateMgr;
    use super::*;
    #[test]
    fn get_params() {
        use tor_netdir::{MdReceiver, PartialNetDir};
        use tor_netdoc::doc::netstatus::NetParams;
        // If it's just fallbackdir, we get the default parameters.
        let fb = FallbackList::from([]);
        let di: DirInfo<'_> = (&fb).into();
        let p1 = di.circ_params(&TargetTunnelUsage::Dir).unwrap();
        assert!(!p1.extend_by_ed25519_id);
        // Now try with a directory and configured parameters.
        let (consensus, microdescs) = tor_netdir::testnet::construct_network().unwrap();
        let mut params = NetParams::default();
        params.set("circwindow".into(), 100);
        params.set("ExtendByEd25519ID".into(), 1);
        let mut dir = PartialNetDir::new(consensus, Some(&params));
        for m in microdescs {
            dir.add_microdesc(m);
        }
        let netdir = dir.unwrap_if_sufficient().unwrap();
        let di: DirInfo<'_> = (&netdir).into();
        let p2 = di.circ_params(&TargetTunnelUsage::Dir).unwrap();
        assert!(p2.extend_by_ed25519_id);
        // Now try with a bogus circwindow value.
        let (consensus, microdescs) = tor_netdir::testnet::construct_network().unwrap();
        let mut params = NetParams::default();
        params.set("circwindow".into(), 100_000);
        params.set("ExtendByEd25519ID".into(), 1);
        let mut dir = PartialNetDir::new(consensus, Some(&params));
        for m in microdescs {
            dir.add_microdesc(m);
        }
        let netdir = dir.unwrap_if_sufficient().unwrap();
        let di: DirInfo<'_> = (&netdir).into();
        let p2 = di.circ_params(&TargetTunnelUsage::Dir).unwrap();
        assert!(p2.extend_by_ed25519_id);
    }
    fn make_circmgr<R: Runtime>(runtime: R) -> Arc<CircMgrInner<FakeBuilder<R>, R>> {
        let config = crate::config::test_config::TestConfig::default();
        let statemgr = TestingStateMgr::new();
        let guardmgr =
            GuardMgr::new(runtime.clone(), statemgr.clone(), &config).expect("Create GuardMgr");
        let builder = FakeBuilder::new(
            &runtime,
            statemgr.clone(),
            &tor_guardmgr::TestConfig::default(),
        );
        let circmgr = Arc::new(CircMgrInner::new_generic(
            &config, &runtime, &guardmgr, builder,
        ));
        let netdir = Arc::new(TestNetDirProvider::new());
        CircMgrInner::launch_background_tasks(&circmgr, &runtime, &netdir, statemgr)
            .expect("launch CircMgrInner background tasks");
        circmgr
    }
    #[test]
    #[cfg(feature = "hs-common")]
    fn test_launch_hs_unmanaged() {
        tor_rtmock::MockRuntime::test_with_various(|runtime| async move {
            let circmgr = make_circmgr(runtime.clone());
            let netdir = tor_netdir::testnet::construct_netdir()
                .unwrap_if_sufficient()
                .unwrap();
            let (ret_tx, ret_rx) = tor_async_utils::oneshot::channel();
            runtime.spawn_identified("launch_hs_unamanged", async move {
                ret_tx
                    .send(
                        circmgr
                            .launch_hs_unmanaged::<OwnedChanTarget>(
                                None,
                                &netdir,
                                HsCircStemKind::Naive,
                                None,
                            )
                            .await,
                    )
                    .unwrap();
            });
            runtime.advance_by(Duration::from_millis(60)).await;
            ret_rx.await.unwrap().unwrap();
        });
    }
}