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