tor_dirmgr/
config.rs

1//! Types for managing directory configuration.
2//!
3//! Directory configuration tells us where to load and store directory
4//! information, where to fetch it from, and how to validate it.
5//!
6//! # Semver note
7//!
8//! The types in this module are re-exported from `arti-client`: any changes
9//! here must be reflected in the version of `arti-client`.
10
11use crate::authority::{Authority, AuthorityBuilder, AuthorityList, AuthorityListBuilder};
12use crate::retry::{DownloadSchedule, DownloadScheduleBuilder};
13use crate::storage::DynStore;
14use crate::Result;
15use tor_checkable::timed::TimerangeBound;
16use tor_config::{define_list_builder_accessors, impl_standard_builder, ConfigBuildError};
17use tor_guardmgr::fallback::FallbackDirBuilder;
18use tor_netdoc::doc::netstatus::{self, Lifetime};
19
20use derive_builder::Builder;
21use serde::{Deserialize, Serialize};
22use std::path::PathBuf;
23use std::time::Duration;
24
25/// Configuration information about the Tor network itself; used as
26/// part of Arti's configuration.
27///
28/// This type is immutable once constructed. To make one, use
29/// [`NetworkConfigBuilder`], or deserialize it from a string.
30//
31// TODO: We should move this type around, since the fallbacks part will no longer be used in
32// dirmgr, but only in guardmgr.  Probably this type belongs in `arti-client`.
33#[derive(Debug, Clone, Builder, Eq, PartialEq)]
34#[builder(build_fn(validate = "Self::validate", error = "ConfigBuildError"))]
35#[builder(derive(Debug, Serialize, Deserialize))]
36pub struct NetworkConfig {
37    /// List of locations to look in when downloading directory information, if
38    /// we don't actually have a directory yet.
39    ///
40    /// (If we do have a cached directory, we use directory caches listed there
41    /// instead.)
42    ///
43    /// This section can be changed in a running Arti client.  Doing so will
44    /// affect future download attempts only.
45    ///
46    /// The default is to use a set of compiled-in fallback directories,
47    /// whose addresses and public keys are shipped as part of the Arti source code.
48    #[builder(sub_builder, setter(custom))]
49    pub(crate) fallback_caches: tor_guardmgr::fallback::FallbackList,
50
51    /// List of directory authorities which we expect to sign consensus
52    /// documents.
53    ///
54    /// (If none are specified, we use a default list of authorities shipped
55    /// with Arti.)
56    ///
57    /// This section cannot be changed in a running Arti client.
58    ///
59    /// The default is to use a set of compiled-in authorities,
60    /// whose identities and public keys are shipped as part of the Arti source code.
61    #[builder(sub_builder, setter(custom))]
62    pub(crate) authorities: AuthorityList,
63}
64
65impl_standard_builder! { NetworkConfig }
66
67define_list_builder_accessors! {
68    struct NetworkConfigBuilder {
69        pub fallback_caches: [FallbackDirBuilder],
70        pub authorities: [AuthorityBuilder],
71    }
72}
73
74impl NetworkConfig {
75    /// Return the list of fallback directory caches from this configuration.
76    pub fn fallback_caches(&self) -> &tor_guardmgr::fallback::FallbackList {
77        &self.fallback_caches
78    }
79}
80
81impl NetworkConfigBuilder {
82    /// Check that this builder will give a reasonable network.
83    fn validate(&self) -> std::result::Result<(), ConfigBuildError> {
84        if self.opt_authorities().is_some() && self.opt_fallback_caches().is_none() {
85            return Err(ConfigBuildError::Inconsistent {
86                fields: vec!["authorities".to_owned(), "fallbacks".to_owned()],
87                problem: "Non-default authorities are use, but the fallback list is not overridden"
88                    .to_owned(),
89            });
90        }
91
92        Ok(())
93    }
94}
95
96/// Configuration information for how exactly we download documents from the
97/// Tor directory caches.
98///
99/// This type is immutable once constructed. To make one, use
100/// [`DownloadScheduleConfigBuilder`], or deserialize it from a string.
101#[derive(Debug, Clone, Builder, Eq, PartialEq)]
102#[builder(build_fn(error = "ConfigBuildError"))]
103#[builder(derive(Debug, Serialize, Deserialize))]
104pub struct DownloadScheduleConfig {
105    /// Top-level configuration for how to retry our initial bootstrap attempt.
106    #[builder(
107        sub_builder,
108        field(build = "self.retry_bootstrap.build_retry_bootstrap()?")
109    )]
110    #[builder_field_attr(serde(default))]
111    pub(crate) retry_bootstrap: DownloadSchedule,
112
113    /// Configuration for how to retry a consensus download.
114    #[builder(sub_builder)]
115    #[builder_field_attr(serde(default))]
116    pub(crate) retry_consensus: DownloadSchedule,
117
118    /// Configuration for how to retry an authority cert download.
119    #[builder(sub_builder)]
120    #[builder_field_attr(serde(default))]
121    pub(crate) retry_certs: DownloadSchedule,
122
123    /// Configuration for how to retry a microdescriptor download.
124    #[builder(
125        sub_builder,
126        field(build = "self.retry_microdescs.build_retry_microdescs()?")
127    )]
128    #[builder_field_attr(serde(default))]
129    pub(crate) retry_microdescs: DownloadSchedule,
130}
131
132impl_standard_builder! { DownloadScheduleConfig }
133
134/// Configuration for how much much to extend the official tolerances of our
135/// directory information.
136///
137/// Because of possible clock skew, and because we want to tolerate possible
138/// failures of the directory authorities to reach a consensus, we want to
139/// consider a directory to be valid for a while before and after its official
140/// range of validity.
141#[derive(Debug, Clone, Builder, Eq, PartialEq)]
142#[builder(derive(Debug, Serialize, Deserialize))]
143#[builder(build_fn(error = "ConfigBuildError"))]
144pub struct DirTolerance {
145    /// For how long before a directory document is valid should we accept it?
146    ///
147    /// Having a nonzero value here allows us to tolerate a little clock skew.
148    ///
149    /// Defaults to 1 day.
150    #[builder(default = "Duration::from_secs(24 * 60 * 60)")]
151    #[builder_field_attr(serde(default, with = "humantime_serde::option"))]
152    pub(crate) pre_valid_tolerance: Duration,
153
154    /// For how long after a directory document is valid should we consider it
155    /// usable?
156    ///
157    /// Having a nonzero value here allows us to tolerate a little clock skew,
158    /// and makes us more robust to temporary failures for the directory
159    /// authorities to reach consensus.
160    ///
161    /// Defaults to 3 days (per [prop212]).
162    ///
163    /// [prop212]:
164    ///     https://gitlab.torproject.org/tpo/core/torspec/-/blob/main/proposals/212-using-old-consensus.txt
165    #[builder(default = "Duration::from_secs(3 * 24 * 60 * 60)")]
166    #[builder_field_attr(serde(default, with = "humantime_serde::option"))]
167    pub(crate) post_valid_tolerance: Duration,
168}
169
170impl_standard_builder! { DirTolerance }
171
172impl DirTolerance {
173    /// Return a new [`TimerangeBound`] that extends the validity interval of
174    /// `timebound` according to this configuration.
175    pub(crate) fn extend_tolerance<B>(&self, timebound: TimerangeBound<B>) -> TimerangeBound<B> {
176        timebound
177            .extend_tolerance(self.post_valid_tolerance)
178            .extend_pre_tolerance(self.pre_valid_tolerance)
179    }
180
181    /// Return a new consensus [`Lifetime`] that extends the validity intervals
182    /// of `lifetime` according to this configuration.
183    pub(crate) fn extend_lifetime(&self, lifetime: &Lifetime) -> Lifetime {
184        Lifetime::new(
185            lifetime.valid_after() - self.pre_valid_tolerance,
186            lifetime.fresh_until(),
187            lifetime.valid_until() + self.post_valid_tolerance,
188        )
189        .expect("Logic error when constructing lifetime")
190    }
191}
192
193/// Configuration type for network directory operations.
194///
195/// If the directory manager gains new configurabilities, this structure will gain additional
196/// supertraits, as an API break.
197///
198/// Prefer to use `TorClientConfig`, which can be converted to this struct via
199/// the `dir_mgr_config` method.
200//
201// We do not use a builder here.  Instead, additions or changes here are API breaks.
202//
203// Rationale:
204//
205// The purpose of using a builder is to allow the code to continue to
206// compile when new fields are added to the built struct.
207//
208// However, here, the DirMgrConfig is just a subset of the fields of a
209// TorClientConfig, and it is important that all its fields are
210// initialized by arti-client.
211//
212// If it grows a field, arti-client ought not to compile any more.
213#[derive(Debug, Clone)]
214#[cfg_attr(test, derive(Default))]
215#[allow(clippy::exhaustive_structs)]
216pub struct DirMgrConfig {
217    /// Location to use for storing and reading current-format
218    /// directory information.
219    ///
220    /// Cannot be changed on a running Arti client.
221    pub cache_dir: PathBuf,
222
223    /// Rules for whether to trust the permissions on the cache_path.
224    pub cache_trust: fs_mistrust::Mistrust,
225
226    /// Configuration information about the network.
227    pub network: NetworkConfig,
228
229    /// Configuration information about when we download things.
230    ///
231    /// This can be replaced on a running Arti client. Doing so affects _future_
232    /// download attempts, but has no effect on attempts that are currently in
233    /// progress or being retried.
234    ///
235    /// (The above is a limitation: we would like it to someday have an effect
236    /// on in-progress attempts as well, at least at the top level.  Users
237    /// should _not_ assume that the effect of changing this option will always
238    /// be delayed.)
239    pub schedule: DownloadScheduleConfig,
240
241    /// How much skew do we tolerate in directory validity times?
242    pub tolerance: DirTolerance,
243
244    /// A map of network parameters that we're overriding from their settings in
245    /// the consensus.
246    ///
247    /// This can be replaced on a running Arti client.  Doing so will take
248    /// effect the next time a consensus is downloaded.
249    ///
250    /// (The above is a limitation: we would like it to someday take effect
251    /// immediately. Users should _not_ assume that the effect of changing this
252    /// option will always be delayed.)
253    pub override_net_params: netstatus::NetParams<i32>,
254
255    /// Extra fields for extension purposes.
256    ///
257    /// These are kept in a separate type so that the type can be marked as
258    /// `non_exhaustive` and used for optional features.
259    pub extensions: DirMgrExtensions,
260}
261
262impl DirMgrConfig {
263    /// Create a store from this configuration.
264    ///
265    /// Note that each time this is called, a new store object will be
266    /// created: you probably only want to call this once.
267    pub(crate) fn open_store(&self, readonly: bool) -> Result<DynStore> {
268        Ok(Box::new(
269            crate::storage::SqliteStore::from_path_and_mistrust(
270                &self.cache_dir,
271                &self.cache_trust,
272                readonly,
273            )?,
274        ))
275    }
276
277    /// Return a slice of the configured authorities
278    pub fn authorities(&self) -> &[Authority] {
279        &self.network.authorities
280    }
281
282    /// Return the configured set of fallback directories
283    pub fn fallbacks(&self) -> &tor_guardmgr::fallback::FallbackList {
284        &self.network.fallback_caches
285    }
286
287    /// Construct a new configuration object where all replaceable fields in
288    /// `self` are replaced with those from  `new_config`.
289    ///
290    /// Any fields which aren't allowed to change at runtime are copied from self.
291    pub(crate) fn update_from_config(&self, new_config: &DirMgrConfig) -> DirMgrConfig {
292        // NOTE: keep this in sync with the behaviour of `DirMgr::reconfigure`
293        DirMgrConfig {
294            cache_dir: self.cache_dir.clone(),
295            cache_trust: self.cache_trust.clone(),
296            network: NetworkConfig {
297                fallback_caches: new_config.network.fallback_caches.clone(),
298                authorities: self.network.authorities.clone(),
299            },
300            schedule: new_config.schedule.clone(),
301            tolerance: new_config.tolerance.clone(),
302            override_net_params: new_config.override_net_params.clone(),
303            extensions: new_config.extensions.clone(),
304        }
305    }
306
307    /// Construct a new configuration object where all replaceable fields in
308    /// `self` are replaced with those from  `new_config`.
309    ///
310    /// Any fields which aren't allowed to change at runtime are copied from self.
311    #[cfg(feature = "experimental-api")]
312    pub fn update_config(&self, new_config: &DirMgrConfig) -> DirMgrConfig {
313        self.update_from_config(new_config)
314    }
315}
316
317/// Optional extensions for configuring
318#[derive(Debug, Clone, Default)]
319#[non_exhaustive]
320pub struct DirMgrExtensions {
321    /// A filter to be used when installing new directory objects.
322    #[cfg(feature = "dirfilter")]
323    pub filter: crate::filter::FilterConfig,
324}
325
326#[cfg(test)]
327mod test {
328    // @@ begin test lint list maintained by maint/add_warning @@
329    #![allow(clippy::bool_assert_comparison)]
330    #![allow(clippy::clone_on_copy)]
331    #![allow(clippy::dbg_macro)]
332    #![allow(clippy::mixed_attributes_style)]
333    #![allow(clippy::print_stderr)]
334    #![allow(clippy::print_stdout)]
335    #![allow(clippy::single_char_pattern)]
336    #![allow(clippy::unwrap_used)]
337    #![allow(clippy::unchecked_duration_subtraction)]
338    #![allow(clippy::useless_vec)]
339    #![allow(clippy::needless_pass_by_value)]
340    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
341    #![allow(clippy::unnecessary_wraps)]
342    use super::*;
343    use tempfile::tempdir;
344
345    #[test]
346    fn simplest_config() -> Result<()> {
347        let tmp = tempdir().unwrap();
348
349        let dir = DirMgrConfig {
350            cache_dir: tmp.path().into(),
351            ..Default::default()
352        };
353
354        assert!(dir.authorities().len() >= 3);
355        assert!(dir.fallbacks().len() >= 3);
356
357        // TODO: verify other defaults.
358
359        Ok(())
360    }
361
362    #[test]
363    fn build_network() -> Result<()> {
364        use tor_guardmgr::fallback::FallbackDir;
365
366        let dflt = NetworkConfig::default();
367
368        // with nothing set, we get the default.
369        let mut bld = NetworkConfig::builder();
370        let cfg = bld.build().unwrap();
371        assert_eq!(cfg.authorities.len(), dflt.authorities.len());
372        assert_eq!(cfg.fallback_caches.len(), dflt.fallback_caches.len());
373
374        // with any authorities set, the fallback list _must_ be set
375        // or the build fails.
376        bld.set_authorities(vec![
377            Authority::builder()
378                .name("Hello")
379                .v3ident([b'?'; 20].into())
380                .clone(),
381            Authority::builder()
382                .name("world")
383                .v3ident([b'!'; 20].into())
384                .clone(),
385        ]);
386        assert!(bld.build().is_err());
387
388        bld.set_fallback_caches(vec![{
389            let mut bld = FallbackDir::builder();
390            bld.rsa_identity([b'x'; 20].into())
391                .ed_identity([b'y'; 32].into());
392            bld.orports().push("127.0.0.1:99".parse().unwrap());
393            bld.orports().push("[::]:99".parse().unwrap());
394            bld
395        }]);
396        let cfg = bld.build().unwrap();
397        assert_eq!(cfg.authorities.len(), 2);
398        assert_eq!(cfg.fallback_caches.len(), 1);
399
400        Ok(())
401    }
402
403    #[test]
404    fn build_schedule() -> Result<()> {
405        use std::time::Duration;
406        let mut bld = DownloadScheduleConfig::builder();
407
408        let cfg = bld.build().unwrap();
409        assert_eq!(cfg.retry_microdescs.parallelism(), 4);
410        assert_eq!(cfg.retry_microdescs.n_attempts(), 3);
411        assert_eq!(cfg.retry_bootstrap.n_attempts(), 128);
412
413        bld.retry_consensus().attempts(7);
414        bld.retry_consensus().initial_delay(Duration::new(86400, 0));
415        bld.retry_consensus().parallelism(1);
416        bld.retry_bootstrap().attempts(4);
417        bld.retry_bootstrap().initial_delay(Duration::new(3600, 0));
418        bld.retry_bootstrap().parallelism(1);
419
420        bld.retry_certs().attempts(5);
421        bld.retry_certs().initial_delay(Duration::new(3600, 0));
422        bld.retry_certs().parallelism(1);
423        bld.retry_microdescs().attempts(6);
424        bld.retry_microdescs().initial_delay(Duration::new(3600, 0));
425        bld.retry_microdescs().parallelism(1);
426
427        let cfg = bld.build().unwrap();
428        assert_eq!(cfg.retry_microdescs.parallelism(), 1);
429        assert_eq!(cfg.retry_microdescs.n_attempts(), 6);
430        assert_eq!(cfg.retry_bootstrap.n_attempts(), 4);
431        assert_eq!(cfg.retry_consensus.n_attempts(), 7);
432        assert_eq!(cfg.retry_certs.n_attempts(), 5);
433
434        Ok(())
435    }
436
437    #[test]
438    fn build_dirmgrcfg() -> Result<()> {
439        let mut bld = DirMgrConfig::default();
440        let tmp = tempdir().unwrap();
441
442        bld.override_net_params.set("circwindow".into(), 999);
443        bld.cache_dir = tmp.path().into();
444
445        assert_eq!(bld.override_net_params.get("circwindow").unwrap(), &999);
446
447        Ok(())
448    }
449}