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::sqlite::SqliteStore;
13 use crate::Authority;
14 use crate::Result;
15 use tor_config::ConfigBuildError;
16 use tor_netdir::fallback::FallbackDir;
17 use tor_netdoc::doc::netstatus;
18 
19 use derive_builder::Builder;
20 use std::path::PathBuf;
21 
22 use serde::Deserialize;
23 
24 /// Configuration information about the Tor network itself; used as
25 /// part of Arti's configuration.
26 ///
27 /// This type is immutable once constructed. To make one, use
28 /// [`NetworkConfigBuilder`], or deserialize it from a string.
29 #[derive(Deserialize, Debug, Clone, Builder, Eq, PartialEq)]
30 #[serde(deny_unknown_fields)]
31 #[builder(build_fn(validate = "Self::validate", error = "ConfigBuildError"))]
32 pub struct NetworkConfig {
33     /// List of locations to look in when downloading directory information,
34     /// if we don't actually have a directory yet.
35     ///
36     /// (If we do have a cached directory, we use directory caches
37     /// listed there instead.)
38     #[serde(default = "fallbacks::default_fallbacks")]
39     #[builder(default = "fallbacks::default_fallbacks()")]
40     fallback_caches: Vec<FallbackDir>,
41 
42     /// List of directory authorities which we expect to sign
43     /// consensus documents.
44     ///
45     /// (If none are specified, we use a default list of authorities
46     /// shipped with Arti.)
47     #[serde(default = "crate::authority::default_authorities")]
48     #[builder(default = "crate::authority::default_authorities()")]
49     authorities: Vec<Authority>,
50 }
51 
52 impl Default for NetworkConfig {
default() -> Self53     fn default() -> Self {
54         NetworkConfig {
55             fallback_caches: fallbacks::default_fallbacks(),
56             authorities: crate::authority::default_authorities(),
57         }
58     }
59 }
60 
61 impl From<NetworkConfig> for NetworkConfigBuilder {
from(cfg: NetworkConfig) -> NetworkConfigBuilder62     fn from(cfg: NetworkConfig) -> NetworkConfigBuilder {
63         let mut builder = NetworkConfigBuilder::default();
64         builder
65             .fallback_caches(cfg.fallback_caches)
66             .authorities(cfg.authorities);
67         builder
68     }
69 }
70 
71 impl NetworkConfig {
72     /// Return a new builder to construct a NetworkConfig.
builder() -> NetworkConfigBuilder73     pub fn builder() -> NetworkConfigBuilder {
74         NetworkConfigBuilder::default()
75     }
76     /// Return the configured directory authorities
authorities(&self) -> &[Authority]77     pub(crate) fn authorities(&self) -> &[Authority] {
78         &self.authorities[..]
79     }
80     /// Return the configured fallback directories
fallbacks(&self) -> &[FallbackDir]81     pub(crate) fn fallbacks(&self) -> &[FallbackDir] {
82         &self.fallback_caches[..]
83     }
84 }
85 
86 impl NetworkConfigBuilder {
87     /// Check that this builder will give a reasonable network.
validate(&self) -> std::result::Result<(), ConfigBuildError>88     fn validate(&self) -> std::result::Result<(), ConfigBuildError> {
89         if self.authorities.is_some() && self.fallback_caches.is_none() {
90             return Err(ConfigBuildError::Inconsistent {
91                 fields: vec!["authorities".to_owned(), "fallbacks".to_owned()],
92                 problem: "Non-default authorities are use, but the fallback list is not overridden"
93                     .to_owned(),
94             });
95         }
96 
97         Ok(())
98     }
99 }
100 
101 /// Configuration information for how exactly we download documents from the
102 /// Tor directory caches.
103 ///
104 /// This type is immutable once constructed. To make one, use
105 /// [`DownloadScheduleConfigBuilder`], or deserialize it from a string.
106 #[derive(Deserialize, Debug, Clone, Builder, Eq, PartialEq)]
107 #[serde(deny_unknown_fields)]
108 #[builder(build_fn(error = "ConfigBuildError"))]
109 pub struct DownloadScheduleConfig {
110     /// Top-level configuration for how to retry our initial bootstrap attempt.
111     #[serde(default = "default_retry_bootstrap")]
112     #[builder(default = "default_retry_bootstrap()")]
113     retry_bootstrap: DownloadSchedule,
114 
115     /// Configuration for how to retry a consensus download.
116     #[serde(default)]
117     #[builder(default)]
118     retry_consensus: DownloadSchedule,
119 
120     /// Configuration for how to retry an authority cert download.
121     #[serde(default)]
122     #[builder(default)]
123     retry_certs: DownloadSchedule,
124 
125     /// Configuration for how to retry a microdescriptor download.
126     #[serde(default = "default_microdesc_schedule")]
127     #[builder(default = "default_microdesc_schedule()")]
128     retry_microdescs: DownloadSchedule,
129 }
130 
131 /// Default value for retry_bootstrap in DownloadScheduleConfig.
default_retry_bootstrap() -> DownloadSchedule132 fn default_retry_bootstrap() -> DownloadSchedule {
133     DownloadSchedule::new(128, std::time::Duration::new(1, 0), 1)
134 }
135 
136 /// Default value for microdesc_bootstrap in DownloadScheduleConfig.
default_microdesc_schedule() -> DownloadSchedule137 fn default_microdesc_schedule() -> DownloadSchedule {
138     DownloadSchedule::new(3, std::time::Duration::new(1, 0), 4)
139 }
140 
141 impl Default for DownloadScheduleConfig {
default() -> Self142     fn default() -> Self {
143         Self::builder()
144             .build()
145             .expect("default builder setting didn't work")
146     }
147 }
148 
149 impl DownloadScheduleConfig {
150     /// Return a new builder to make a [`DownloadScheduleConfig`]
builder() -> DownloadScheduleConfigBuilder151     pub fn builder() -> DownloadScheduleConfigBuilder {
152         DownloadScheduleConfigBuilder::default()
153     }
154 }
155 
156 impl From<DownloadScheduleConfig> for DownloadScheduleConfigBuilder {
from(cfg: DownloadScheduleConfig) -> DownloadScheduleConfigBuilder157     fn from(cfg: DownloadScheduleConfig) -> DownloadScheduleConfigBuilder {
158         let mut builder = DownloadScheduleConfigBuilder::default();
159         builder
160             .retry_bootstrap(cfg.retry_bootstrap)
161             .retry_consensus(cfg.retry_consensus)
162             .retry_certs(cfg.retry_certs)
163             .retry_microdescs(cfg.retry_microdescs);
164         builder
165     }
166 }
167 
168 /// Configuration type for network directory operations.
169 ///
170 /// This type is immutable once constructed.
171 ///
172 /// To create an object of this type, use [`DirMgrConfigBuilder`], or
173 /// deserialize it from a string. (Arti generally uses Toml for
174 /// configuration, but you can use other formats if you prefer.)
175 #[derive(Debug, Clone, Builder, Eq, PartialEq)]
176 #[builder(build_fn(error = "ConfigBuildError"))]
177 pub struct DirMgrConfig {
178     /// Location to use for storing and reading current-format
179     /// directory information.
180     #[builder(setter(into))]
181     cache_path: PathBuf,
182 
183     /// Configuration information about the network.
184     #[builder(default)]
185     network_config: NetworkConfig,
186 
187     /// Configuration information about when we download things.
188     #[builder(default)]
189     schedule_config: DownloadScheduleConfig,
190 
191     /// A map of network parameters that we're overriding from their
192     /// settings in the consensus.
193     #[builder(default)]
194     override_net_params: netstatus::NetParams<i32>,
195 }
196 
197 impl DirMgrConfigBuilder {
198     /// Overrides the network consensus parameter named `param` with a
199     /// new value.
200     ///
201     /// If the new value is out of range, it will be clamped to the
202     /// acceptable range.
203     ///
204     /// If the parameter is not recognized by Arti, it will be
205     /// ignored, and a warning will be produced when we try to apply
206     /// it to the consensus.
207     ///
208     /// By default no parameters will be overridden.
override_net_param(&mut self, param: String, value: i32) -> &mut Self209     pub fn override_net_param(&mut self, param: String, value: i32) -> &mut Self {
210         self.override_net_params
211             .get_or_insert_with(netstatus::NetParams::default)
212             .set(param, value);
213         self
214     }
215 }
216 
217 impl DirMgrConfig {
218     /// Return a new builder to construct a DirMgrConfig.
builder() -> DirMgrConfigBuilder219     pub fn builder() -> DirMgrConfigBuilder {
220         DirMgrConfigBuilder::default()
221     }
222 
223     /// Create a SqliteStore from this configuration.
224     ///
225     /// Note that each time this is called, a new store object will be
226     /// created: you probably only want to call this once.
227     ///
228     /// The `readonly` argument is as for [`SqliteStore::from_path`]
open_sqlite_store(&self, readonly: bool) -> Result<SqliteStore>229     pub(crate) fn open_sqlite_store(&self, readonly: bool) -> Result<SqliteStore> {
230         SqliteStore::from_path(&self.cache_path, readonly)
231     }
232 
233     /// Return a slice of the configured authorities
authorities(&self) -> &[Authority]234     pub(crate) fn authorities(&self) -> &[Authority] {
235         self.network_config.authorities()
236     }
237 
238     /// Return the configured set of fallback directories
fallbacks(&self) -> &[FallbackDir]239     pub(crate) fn fallbacks(&self) -> &[FallbackDir] {
240         self.network_config.fallbacks()
241     }
242 
243     /// Return set of configured networkstatus parameter overrides.
override_net_params(&self) -> &netstatus::NetParams<i32>244     pub(crate) fn override_net_params(&self) -> &netstatus::NetParams<i32> {
245         &self.override_net_params
246     }
247 
248     /// Return the schedule configuration we should use to decide when to
249     /// attempt and retry downloads.
schedule(&self) -> &DownloadScheduleConfig250     pub(crate) fn schedule(&self) -> &DownloadScheduleConfig {
251         &self.schedule_config
252     }
253 }
254 
255 impl DownloadScheduleConfig {
256     /// Return configuration for retrying our entire bootstrap
257     /// operation at startup.
retry_bootstrap(&self) -> &DownloadSchedule258     pub(crate) fn retry_bootstrap(&self) -> &DownloadSchedule {
259         &self.retry_bootstrap
260     }
261 
262     /// Return configuration for retrying a consensus download.
retry_consensus(&self) -> &DownloadSchedule263     pub(crate) fn retry_consensus(&self) -> &DownloadSchedule {
264         &self.retry_consensus
265     }
266 
267     /// Return configuration for retrying an authority certificate download
retry_certs(&self) -> &DownloadSchedule268     pub(crate) fn retry_certs(&self) -> &DownloadSchedule {
269         &self.retry_certs
270     }
271 
272     /// Return configuration for retrying an authority certificate download
retry_microdescs(&self) -> &DownloadSchedule273     pub(crate) fn retry_microdescs(&self) -> &DownloadSchedule {
274         &self.retry_microdescs
275     }
276 }
277 
278 /// Helpers for initializing the fallback list.
279 mod fallbacks {
280     use tor_llcrypto::pk::{ed25519::Ed25519Identity, rsa::RsaIdentity};
281     use tor_netdir::fallback::FallbackDir;
282     /// Return a list of the default fallback directories shipped with
283     /// arti.
default_fallbacks() -> Vec<super::FallbackDir>284     pub(crate) fn default_fallbacks() -> Vec<super::FallbackDir> {
285         /// Build a fallback directory; panic if input is bad.
286         fn fallback(rsa: &str, ed: &str, ports: &[&str]) -> FallbackDir {
287             let rsa = hex::decode(rsa).expect("Bad hex in built-in fallback list");
288             let rsa =
289                 RsaIdentity::from_bytes(&rsa).expect("Wrong length in built-in fallback list");
290             let ed = base64::decode_config(ed, base64::STANDARD_NO_PAD)
291                 .expect("Bad hex in built-in fallback list");
292             let ed =
293                 Ed25519Identity::from_bytes(&ed).expect("Wrong length in built-in fallback list");
294             let mut bld = FallbackDir::builder();
295             bld.rsa_identity(rsa).ed_identity(ed);
296 
297             ports
298                 .iter()
299                 .map(|s| s.parse().expect("Bad socket address in fallbacklist"))
300                 .for_each(|p| {
301                     bld.orport(p);
302                 });
303 
304             bld.build()
305                 .expect("Unable to build default fallback directory!?")
306         }
307         include!("fallback_dirs.inc")
308     }
309 }
310 
311 #[cfg(test)]
312 mod test {
313     #![allow(clippy::unwrap_used)]
314     #![allow(clippy::unnecessary_wraps)]
315     use super::*;
316     use tempfile::tempdir;
317 
318     #[test]
simplest_config() -> Result<()>319     fn simplest_config() -> Result<()> {
320         let tmp = tempdir().unwrap();
321 
322         let dir = DirMgrConfigBuilder::default()
323             .cache_path(tmp.path().to_path_buf())
324             .build()
325             .unwrap();
326 
327         assert!(dir.authorities().len() >= 3);
328         assert!(dir.fallbacks().len() >= 3);
329 
330         // TODO: verify other defaults.
331 
332         Ok(())
333     }
334 
335     #[test]
build_network() -> Result<()>336     fn build_network() -> Result<()> {
337         let dflt = NetworkConfig::default();
338 
339         // with nothing set, we get the default.
340         let mut bld = NetworkConfig::builder();
341         let cfg = bld.build().unwrap();
342         assert_eq!(cfg.authorities().len(), dflt.authorities.len());
343         assert_eq!(cfg.fallbacks().len(), dflt.fallback_caches.len());
344 
345         // with any authorities set, the fallback list _must_ be set
346         // or the build fails.
347         bld.authorities(vec![
348             Authority::builder()
349                 .name("Hello")
350                 .v3ident([b'?'; 20].into())
351                 .build()
352                 .unwrap(),
353             Authority::builder()
354                 .name("world")
355                 .v3ident([b'!'; 20].into())
356                 .build()
357                 .unwrap(),
358         ]);
359         assert!(bld.build().is_err());
360 
361         bld.fallback_caches(vec![FallbackDir::builder()
362             .rsa_identity([b'x'; 20].into())
363             .ed_identity([b'y'; 32].into())
364             .orport("127.0.0.1:99".parse().unwrap())
365             .orport("[::]:99".parse().unwrap())
366             .build()
367             .unwrap()]);
368         let cfg = bld.build().unwrap();
369         assert_eq!(cfg.authorities().len(), 2);
370         assert_eq!(cfg.fallbacks().len(), 1);
371 
372         Ok(())
373     }
374 
375     #[test]
build_schedule() -> Result<()>376     fn build_schedule() -> Result<()> {
377         use std::time::Duration;
378         let mut bld = DownloadScheduleConfig::builder();
379 
380         let cfg = bld.build().unwrap();
381         assert_eq!(cfg.retry_microdescs().parallelism(), 4);
382         assert_eq!(cfg.retry_microdescs().n_attempts(), 3);
383         assert_eq!(cfg.retry_bootstrap().n_attempts(), 128);
384 
385         bld.retry_consensus(DownloadSchedule::new(7, Duration::new(86400, 0), 1))
386             .retry_bootstrap(DownloadSchedule::new(4, Duration::new(3600, 0), 1))
387             .retry_certs(DownloadSchedule::new(5, Duration::new(3600, 0), 1))
388             .retry_microdescs(DownloadSchedule::new(6, Duration::new(3600, 0), 0));
389 
390         let cfg = bld.build().unwrap();
391         assert_eq!(cfg.retry_microdescs().parallelism(), 1); // gets clamped
392         assert_eq!(cfg.retry_microdescs().n_attempts(), 6);
393         assert_eq!(cfg.retry_bootstrap().n_attempts(), 4);
394         assert_eq!(cfg.retry_consensus().n_attempts(), 7);
395         assert_eq!(cfg.retry_certs().n_attempts(), 5);
396 
397         Ok(())
398     }
399 
400     #[test]
build_dirmgrcfg() -> Result<()>401     fn build_dirmgrcfg() -> Result<()> {
402         let mut bld = DirMgrConfig::builder();
403         let tmp = tempdir().unwrap();
404 
405         let cfg = bld
406             .override_net_param("circwindow".into(), 999)
407             .cache_path(tmp.path())
408             .network_config(NetworkConfig::default())
409             .schedule_config(DownloadScheduleConfig::default())
410             .build()
411             .unwrap();
412 
413         assert_eq!(cfg.override_net_params().get("circwindow").unwrap(), &999);
414 
415         Ok(())
416     }
417 }
418