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

            
11
use crate::authority::{Authority, AuthorityBuilder, AuthorityList, AuthorityListBuilder};
12
use crate::retry::{DownloadSchedule, DownloadScheduleBuilder};
13
use crate::storage::DynStore;
14
use crate::Result;
15
use tor_checkable::timed::TimerangeBound;
16
use tor_config::{define_list_builder_accessors, impl_standard_builder, ConfigBuildError};
17
use tor_guardmgr::fallback::FallbackDirBuilder;
18
use tor_netdoc::doc::netstatus::{self, Lifetime};
19

            
20
use derive_builder::Builder;
21
use serde::{Deserialize, Serialize};
22
use std::path::PathBuf;
23
use 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
4876
#[derive(Debug, Clone, Builder, Eq, PartialEq)]
34
#[builder(build_fn(validate = "Self::validate", error = "ConfigBuildError"))]
35
#[builder(derive(Debug, Serialize, Deserialize))]
36
pub 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

            
65
impl_standard_builder! { NetworkConfig }
66

            
67
define_list_builder_accessors! {
68
    struct NetworkConfigBuilder {
69
        pub fallback_caches: [FallbackDirBuilder],
70
        pub authorities: [AuthorityBuilder],
71
    }
72
}
73

            
74
impl NetworkConfig {
75
    /// Return the list of fallback directory caches from this configuration.
76
175
    pub fn fallback_caches(&self) -> &tor_guardmgr::fallback::FallbackList {
77
175
        &self.fallback_caches
78
175
    }
79
}
80

            
81
impl NetworkConfigBuilder {
82
    /// Check that this builder will give a reasonable network.
83
2479
    fn validate(&self) -> std::result::Result<(), ConfigBuildError> {
84
2479
        if self.opt_authorities().is_some() && self.opt_fallback_caches().is_none() {
85
2
            return Err(ConfigBuildError::Inconsistent {
86
2
                fields: vec!["authorities".to_owned(), "fallbacks".to_owned()],
87
2
                problem: "Non-default authorities are use, but the fallback list is not overridden"
88
2
                    .to_owned(),
89
2
            });
90
2477
        }
91
2477

            
92
2477
        Ok(())
93
2479
    }
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
5300
#[derive(Debug, Clone, Builder, Eq, PartialEq)]
102
#[builder(build_fn(error = "ConfigBuildError"))]
103
#[builder(derive(Debug, Serialize, Deserialize))]
104
pub 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

            
132
impl_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
4848
#[derive(Debug, Clone, Builder, Eq, PartialEq)]
142
#[builder(derive(Debug, Serialize, Deserialize))]
143
#[builder(build_fn(error = "ConfigBuildError"))]
144
pub 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

            
170
impl_standard_builder! { DirTolerance }
171

            
172
impl DirTolerance {
173
    /// Return a new [`TimerangeBound`] that extends the validity interval of
174
    /// `timebound` according to this configuration.
175
16
    pub(crate) fn extend_tolerance<B>(&self, timebound: TimerangeBound<B>) -> TimerangeBound<B> {
176
16
        timebound
177
16
            .extend_tolerance(self.post_valid_tolerance)
178
16
            .extend_pre_tolerance(self.pre_valid_tolerance)
179
16
    }
180

            
181
    /// Return a new consensus [`Lifetime`] that extends the validity intervals
182
    /// of `lifetime` according to this configuration.
183
8
    pub(crate) fn extend_lifetime(&self, lifetime: &Lifetime) -> Lifetime {
184
8
        Lifetime::new(
185
8
            lifetime.valid_after() - self.pre_valid_tolerance,
186
8
            lifetime.fresh_until(),
187
8
            lifetime.valid_until() + self.post_valid_tolerance,
188
8
        )
189
8
        .expect("Logic error when constructing lifetime")
190
8
    }
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)]
216
pub 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

            
262
impl 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
154
    pub(crate) fn open_store(&self, readonly: bool) -> Result<DynStore> {
268
154
        Ok(Box::new(
269
154
            crate::storage::SqliteStore::from_path_and_mistrust(
270
154
                &self.cache_dir,
271
154
                &self.cache_trust,
272
154
                readonly,
273
154
            )?,
274
        ))
275
154
    }
276

            
277
    /// Return a slice of the configured authorities
278
156
    pub fn authorities(&self) -> &[Authority] {
279
156
        &self.network.authorities
280
156
    }
281

            
282
    /// Return the configured set of fallback directories
283
2
    pub fn fallbacks(&self) -> &tor_guardmgr::fallback::FallbackList {
284
2
        &self.network.fallback_caches
285
2
    }
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
35
    pub(crate) fn update_from_config(&self, new_config: &DirMgrConfig) -> DirMgrConfig {
292
35
        // NOTE: keep this in sync with the behaviour of `DirMgr::reconfigure`
293
35
        DirMgrConfig {
294
35
            cache_dir: self.cache_dir.clone(),
295
35
            cache_trust: self.cache_trust.clone(),
296
35
            network: NetworkConfig {
297
35
                fallback_caches: new_config.network.fallback_caches.clone(),
298
35
                authorities: self.network.authorities.clone(),
299
35
            },
300
35
            schedule: new_config.schedule.clone(),
301
35
            tolerance: new_config.tolerance.clone(),
302
35
            override_net_params: new_config.override_net_params.clone(),
303
35
            extensions: new_config.extensions.clone(),
304
35
        }
305
35
    }
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]
320
pub 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)]
327
mod 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
}