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::retry::DownloadSchedule;
12
use crate::storage::DynStore;
13
use crate::{Authority, Result};
14
use tor_config::ConfigBuildError;
15
use tor_netdir::fallback::FallbackDir;
16
use tor_netdoc::doc::netstatus;
17

            
18
use derive_builder::Builder;
19
use std::path::PathBuf;
20

            
21
use serde::Deserialize;
22

            
23
/// Configuration information about the Tor network itself; used as
24
/// part of Arti's configuration.
25
///
26
/// This type is immutable once constructed. To make one, use
27
/// [`NetworkConfigBuilder`], or deserialize it from a string.
28
140
#[derive(Deserialize, Debug, Clone, Builder, Eq, PartialEq)]
29
#[serde(deny_unknown_fields)]
30
#[builder(build_fn(validate = "Self::validate", error = "ConfigBuildError"))]
31
#[builder(derive(Deserialize))]
32
pub struct NetworkConfig {
33
    /// List of locations to look in when downloading directory information, if
34
    /// we don't actually have a directory yet.
35
    ///
36
    /// (If we do have a cached directory, we use directory caches listed there
37
    /// instead.)
38
    ///
39
    /// This section can be changed in a running Arti client.  Doing so will
40
    /// affect future download attempts only.
41
    #[serde(default = "fallbacks::default_fallbacks")]
42
    #[builder(default = "fallbacks::default_fallbacks()")]
43
    fallback_caches: Vec<FallbackDir>,
44

            
45
    /// List of directory authorities which we expect to sign consensus
46
    /// documents.
47
    ///
48
    /// (If none are specified, we use a default list of authorities shipped
49
    /// with Arti.)
50
    ///
51
    /// This section cannot be changed in a running Arti client.
52
    #[serde(default = "crate::authority::default_authorities")]
53
    #[builder(default = "crate::authority::default_authorities()")]
54
    authorities: Vec<Authority>,
55
}
56

            
57
impl Default for NetworkConfig {
58
10
    fn default() -> Self {
59
10
        NetworkConfig {
60
10
            fallback_caches: fallbacks::default_fallbacks(),
61
10
            authorities: crate::authority::default_authorities(),
62
10
        }
63
10
    }
64
}
65

            
66
impl NetworkConfig {
67
    /// Return a new builder to construct a NetworkConfig.
68
8
    pub fn builder() -> NetworkConfigBuilder {
69
8
        NetworkConfigBuilder::default()
70
8
    }
71
    /// Return the configured directory authorities
72
10
    pub(crate) fn authorities(&self) -> &[Authority] {
73
10
        &self.authorities[..]
74
10
    }
75
    /// Return the configured fallback directories
76
3
    pub(crate) fn fallbacks(&self) -> &[FallbackDir] {
77
3
        &self.fallback_caches[..]
78
3
    }
79
}
80

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

            
92
139
        Ok(())
93
140
    }
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
151
#[derive(Deserialize, Debug, Clone, Builder, Eq, PartialEq)]
102
#[serde(deny_unknown_fields)]
103
#[builder(build_fn(error = "ConfigBuildError"))]
104
9
#[builder(derive(Deserialize))]
105
pub struct DownloadScheduleConfig {
106
    /// Top-level configuration for how to retry our initial bootstrap attempt.
107
    #[serde(default = "default_retry_bootstrap")]
108
    #[builder(default = "default_retry_bootstrap()")]
109
    retry_bootstrap: DownloadSchedule,
110

            
111
    /// Configuration for how to retry a consensus download.
112
    #[serde(default)]
113
    #[builder(default)]
114
    retry_consensus: DownloadSchedule,
115

            
116
    /// Configuration for how to retry an authority cert download.
117
    #[serde(default)]
118
    #[builder(default)]
119
    retry_certs: DownloadSchedule,
120

            
121
    /// Configuration for how to retry a microdescriptor download.
122
    #[serde(default = "default_microdesc_schedule")]
123
    #[builder(default = "default_microdesc_schedule()")]
124
    retry_microdescs: DownloadSchedule,
125
}
126

            
127
/// Default value for retry_bootstrap in DownloadScheduleConfig.
128
140
fn default_retry_bootstrap() -> DownloadSchedule {
129
140
    DownloadSchedule::new(128, std::time::Duration::new(1, 0), 1)
130
140
}
131

            
132
/// Default value for microdesc_bootstrap in DownloadScheduleConfig.
133
120
fn default_microdesc_schedule() -> DownloadSchedule {
134
120
    DownloadSchedule::new(3, std::time::Duration::new(1, 0), 4)
135
120
}
136

            
137
impl Default for DownloadScheduleConfig {
138
19
    fn default() -> Self {
139
19
        Self::builder()
140
19
            .build()
141
19
            .expect("default builder setting didn't work")
142
19
    }
143
}
144

            
145
impl DownloadScheduleConfig {
146
    /// Return a new builder to make a [`DownloadScheduleConfig`]
147
20
    pub fn builder() -> DownloadScheduleConfigBuilder {
148
20
        DownloadScheduleConfigBuilder::default()
149
20
    }
150
}
151

            
152
/// Configuration type for network directory operations.
153
///
154
/// This type is immutable once constructed.
155
///
156
/// To create an object of this type, use [`DirMgrConfigBuilder`], or
157
/// deserialize it from a string. (Arti generally uses Toml for configuration,
158
/// but you can use other formats if you prefer.)
159
///
160
/// Many members of this type can be replaced with a new configuration on a
161
/// running Arti client. Those that cannot are documented.
162
36
#[derive(Debug, Clone, Builder, Eq, PartialEq)]
163
#[builder(build_fn(error = "ConfigBuildError"))]
164
#[builder(derive(Deserialize))]
165
pub struct DirMgrConfig {
166
    /// Location to use for storing and reading current-format
167
    /// directory information.
168
    ///
169
    /// Cannot be changed on a running Arti client.
170
    #[builder(setter(into))]
171
    cache_path: PathBuf,
172

            
173
    /// Configuration information about the network.
174
    #[builder(default)]
175
    network_config: NetworkConfig,
176

            
177
    /// Configuration information about when we download things.
178
    ///
179
    /// This can be replaced on a running Arti client. Doing so affects _future_
180
    /// download attempts, but has no effect on attempts that are currently in
181
    /// progress or being retried.
182
    ///
183
    /// (The above is a limitation: we would like it to someday have an effect
184
    /// on in-progress attempts as well, at least at the top level.  Users
185
    /// should _not_ assume that the effect of changing this option will always
186
    /// be delayed.)
187
    #[builder(default)]
188
    schedule_config: DownloadScheduleConfig,
189

            
190
    /// A map of network parameters that we're overriding from their settings in
191
    /// the consensus.
192
    ///
193
    /// This can be replaced on a running Arti client.  Doing so will take
194
    /// effect the next time a consensus is downloaded.
195
    ///
196
    /// (The above is a limitation: we would like it to someday take effect
197
    /// immediately. Users should _not_ assume that the effect of changing this
198
    /// option will always be delayed.)
199
    #[builder(default)]
200
    override_net_params: netstatus::NetParams<i32>,
201
}
202

            
203
impl DirMgrConfigBuilder {
204
    /// Overrides the network consensus parameter named `param` with a
205
    /// new value.
206
    ///
207
    /// If the new value is out of range, it will be clamped to the
208
    /// acceptable range.
209
    ///
210
    /// If the parameter is not recognized by Arti, it will be
211
    /// ignored, and a warning will be produced when we try to apply
212
    /// it to the consensus.
213
    ///
214
    /// By default no parameters will be overridden.
215
1
    pub fn override_net_param(&mut self, param: String, value: i32) -> &mut Self {
216
1
        self.override_net_params
217
1
            .get_or_insert_with(netstatus::NetParams::default)
218
1
            .set(param, value);
219
1
        self
220
1
    }
221
}
222

            
223
impl DirMgrConfig {
224
    /// Return a new builder to construct a DirMgrConfig.
225
15
    pub fn builder() -> DirMgrConfigBuilder {
226
15
        DirMgrConfigBuilder::default()
227
15
    }
228

            
229
    /// Create a store from this configuration.
230
    ///
231
    /// Note that each time this is called, a new store object will be
232
    /// created: you probably only want to call this once.
233
27
    pub(crate) fn open_store(&self, readonly: bool) -> Result<DynStore> {
234
27
        Ok(Box::new(crate::storage::SqliteStore::from_path(
235
27
            &self.cache_path,
236
27
            readonly,
237
27
        )?))
238
27
    }
239

            
240
    /// Return the configured cache path.
241
    pub fn cache_path(&self) -> &std::path::Path {
242
        self.cache_path.as_ref()
243
    }
244

            
245
    /// Return a slice of the configured authorities
246
8
    pub fn authorities(&self) -> &[Authority] {
247
8
        self.network_config.authorities()
248
8
    }
249

            
250
    /// Return the configured set of fallback directories
251
1
    pub fn fallbacks(&self) -> &[FallbackDir] {
252
1
        self.network_config.fallbacks()
253
1
    }
254

            
255
    /// Return set of configured networkstatus parameter overrides.
256
5
    pub fn override_net_params(&self) -> &netstatus::NetParams<i32> {
257
5
        &self.override_net_params
258
5
    }
259

            
260
    /// Return the schedule configuration we should use to decide when to
261
    /// attempt and retry downloads.
262
3
    pub fn schedule(&self) -> &DownloadScheduleConfig {
263
3
        &self.schedule_config
264
3
    }
265

            
266
    /// Construct a new configuration object where all replaceable fields in
267
    /// `self` are replaced with those from  `new_config`.
268
    ///
269
    /// Any fields which aren't allowed to change at runtime are copied from self.
270
    pub(crate) fn update_from_config(&self, new_config: &DirMgrConfig) -> DirMgrConfig {
271
        DirMgrConfig {
272
            cache_path: self.cache_path.clone(),
273
            network_config: NetworkConfig {
274
                fallback_caches: new_config.network_config.fallback_caches.clone(),
275
                authorities: self.network_config.authorities.clone(),
276
            },
277
            schedule_config: new_config.schedule_config.clone(),
278
            override_net_params: new_config.override_net_params.clone(),
279
        }
280
    }
281

            
282
    /// Construct a new configuration object where all replaceable fields in
283
    /// `self` are replaced with those from  `new_config`.
284
    ///
285
    /// Any fields which aren't allowed to change at runtime are copied from self.
286
    #[cfg(feature = "experimental-api")]
287
    pub fn update_config(&self, new_config: &DirMgrConfig) -> DirMgrConfig {
288
        self.update_from_config(new_config)
289
    }
290
}
291

            
292
impl DownloadScheduleConfig {
293
    /// Return configuration for retrying our entire bootstrap
294
    /// operation at startup.
295
2
    pub(crate) fn retry_bootstrap(&self) -> &DownloadSchedule {
296
2
        &self.retry_bootstrap
297
2
    }
298

            
299
    /// Return configuration for retrying a consensus download.
300
3
    pub(crate) fn retry_consensus(&self) -> &DownloadSchedule {
301
3
        &self.retry_consensus
302
3
    }
303

            
304
    /// Return configuration for retrying an authority certificate download
305
3
    pub(crate) fn retry_certs(&self) -> &DownloadSchedule {
306
3
        &self.retry_certs
307
3
    }
308

            
309
    /// Return configuration for retrying an authority certificate download
310
6
    pub(crate) fn retry_microdescs(&self) -> &DownloadSchedule {
311
6
        &self.retry_microdescs
312
6
    }
313
}
314

            
315
/// Helpers for initializing the fallback list.
316
mod fallbacks {
317
    use tor_llcrypto::pk::{ed25519::Ed25519Identity, rsa::RsaIdentity};
318
    use tor_netdir::fallback::FallbackDir;
319
    /// Return a list of the default fallback directories shipped with
320
    /// arti.
321
121
    pub(crate) fn default_fallbacks() -> Vec<super::FallbackDir> {
322
121
        /// Build a fallback directory; panic if input is bad.
323
23919
        fn fallback(rsa: &str, ed: &str, ports: &[&str]) -> FallbackDir {
324
23919
            let rsa = RsaIdentity::from_hex(rsa).expect("Bad hex in built-in fallback list");
325
23919
            let ed = base64::decode_config(ed, base64::STANDARD_NO_PAD)
326
23919
                .expect("Bad hex in built-in fallback list");
327
23919
            let ed =
328
23919
                Ed25519Identity::from_bytes(&ed).expect("Wrong length in built-in fallback list");
329
23919
            let mut bld = FallbackDir::builder();
330
23919
            bld.rsa_identity(rsa).ed_identity(ed);
331
23919

            
332
23919
            ports
333
23919
                .iter()
334
34803
                .map(|s| s.parse().expect("Bad socket address in fallbacklist"))
335
34936
                .for_each(|p| {
336
34936
                    bld.orport(p);
337
34936
                });
338
23919

            
339
23919
            bld.build()
340
23919
                .expect("Unable to build default fallback directory!?")
341
23919
        }
342
121
        include!("fallback_dirs.inc")
343
121
    }
344
}
345

            
346
#[cfg(test)]
347
mod test {
348
    #![allow(clippy::unwrap_used)]
349
    #![allow(clippy::unnecessary_wraps)]
350
    use super::*;
351
    use tempfile::tempdir;
352

            
353
    #[test]
354
    fn simplest_config() -> Result<()> {
355
        let tmp = tempdir().unwrap();
356

            
357
        let dir = DirMgrConfigBuilder::default()
358
            .cache_path(tmp.path().to_path_buf())
359
            .build()
360
            .unwrap();
361

            
362
        assert!(dir.authorities().len() >= 3);
363
        assert!(dir.fallbacks().len() >= 3);
364

            
365
        // TODO: verify other defaults.
366

            
367
        Ok(())
368
    }
369

            
370
    #[test]
371
    fn build_network() -> Result<()> {
372
        let dflt = NetworkConfig::default();
373

            
374
        // with nothing set, we get the default.
375
        let mut bld = NetworkConfig::builder();
376
        let cfg = bld.build().unwrap();
377
        assert_eq!(cfg.authorities().len(), dflt.authorities.len());
378
        assert_eq!(cfg.fallbacks().len(), dflt.fallback_caches.len());
379

            
380
        // with any authorities set, the fallback list _must_ be set
381
        // or the build fails.
382
        bld.authorities(vec![
383
            Authority::builder()
384
                .name("Hello")
385
                .v3ident([b'?'; 20].into())
386
                .build()
387
                .unwrap(),
388
            Authority::builder()
389
                .name("world")
390
                .v3ident([b'!'; 20].into())
391
                .build()
392
                .unwrap(),
393
        ]);
394
        assert!(bld.build().is_err());
395

            
396
        bld.fallback_caches(vec![FallbackDir::builder()
397
            .rsa_identity([b'x'; 20].into())
398
            .ed_identity([b'y'; 32].into())
399
            .orport("127.0.0.1:99".parse().unwrap())
400
            .orport("[::]:99".parse().unwrap())
401
            .build()
402
            .unwrap()]);
403
        let cfg = bld.build().unwrap();
404
        assert_eq!(cfg.authorities().len(), 2);
405
        assert_eq!(cfg.fallbacks().len(), 1);
406

            
407
        Ok(())
408
    }
409

            
410
    #[test]
411
    fn build_schedule() -> Result<()> {
412
        use std::time::Duration;
413
        let mut bld = DownloadScheduleConfig::builder();
414

            
415
        let cfg = bld.build().unwrap();
416
        assert_eq!(cfg.retry_microdescs().parallelism(), 4);
417
        assert_eq!(cfg.retry_microdescs().n_attempts(), 3);
418
        assert_eq!(cfg.retry_bootstrap().n_attempts(), 128);
419

            
420
        bld.retry_consensus(DownloadSchedule::new(7, Duration::new(86400, 0), 1))
421
            .retry_bootstrap(DownloadSchedule::new(4, Duration::new(3600, 0), 1))
422
            .retry_certs(DownloadSchedule::new(5, Duration::new(3600, 0), 1))
423
            .retry_microdescs(DownloadSchedule::new(6, Duration::new(3600, 0), 0));
424

            
425
        let cfg = bld.build().unwrap();
426
        assert_eq!(cfg.retry_microdescs().parallelism(), 1); // gets clamped
427
        assert_eq!(cfg.retry_microdescs().n_attempts(), 6);
428
        assert_eq!(cfg.retry_bootstrap().n_attempts(), 4);
429
        assert_eq!(cfg.retry_consensus().n_attempts(), 7);
430
        assert_eq!(cfg.retry_certs().n_attempts(), 5);
431

            
432
        Ok(())
433
    }
434

            
435
    #[test]
436
    fn build_dirmgrcfg() -> Result<()> {
437
        let mut bld = DirMgrConfig::builder();
438
        let tmp = tempdir().unwrap();
439

            
440
        let cfg = bld
441
            .override_net_param("circwindow".into(), 999)
442
            .cache_path(tmp.path())
443
            .network_config(NetworkConfig::default())
444
            .schedule_config(DownloadScheduleConfig::default())
445
            .build()
446
            .unwrap();
447

            
448
        assert_eq!(cfg.override_net_params().get("circwindow").unwrap(), &999);
449

            
450
        Ok(())
451
    }
452
}