Skip to main content

tor_dircommon/
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 std::time::Duration;
12
13use derive_builder::Builder;
14use getset::{CopyGetters, Getters};
15use serde::{Deserialize, Serialize};
16use tor_checkable::timed::TimerangeBound;
17use tor_config::{ConfigBuildError, define_list_builder_accessors, impl_standard_builder};
18use tor_netdoc::doc::netstatus::Lifetime;
19
20use crate::{
21    authority::{AuthorityContacts, AuthorityContactsBuilder},
22    fallback::{FallbackDirBuilder, FallbackList, FallbackListBuilder},
23    retry::{DownloadSchedule, DownloadScheduleBuilder},
24};
25
26/// Configuration information about the Tor network itself; used as
27/// part of Arti's configuration.
28///
29/// This type is immutable once constructed. To make one, use
30/// [`NetworkConfigBuilder`], or deserialize it from a string.
31//
32// TODO: We should move this type around, since the fallbacks part will no longer be used in
33// dirmgr, but only in guardmgr.  Probably this type belongs in `arti-client`.
34#[derive(Debug, Clone, Builder, Eq, PartialEq, Getters)]
35#[builder(build_fn(validate = "Self::validate", error = "ConfigBuildError"))]
36#[builder(derive(Debug, Serialize, Deserialize))]
37#[non_exhaustive]
38pub struct NetworkConfig {
39    /// List of locations to look in when downloading directory information, if
40    /// we don't actually have a directory yet.
41    ///
42    /// (If we do have a cached directory, we use directory caches listed there
43    /// instead.)
44    ///
45    /// This section can be changed in a running Arti client.  Doing so will
46    /// affect future download attempts only.
47    ///
48    /// The default is to use a set of compiled-in fallback directories,
49    /// whose addresses and public keys are shipped as part of the Arti source code.
50    #[builder(sub_builder, setter(custom))]
51    #[getset(get = "pub")]
52    fallback_caches: FallbackList,
53
54    /// List of directory authorities which we expect to perform various operations
55    /// affecting the overall Tor network.
56    ///
57    /// (If none are specified, we use a default list of authorities shipped
58    /// with Arti.)
59    ///
60    /// This section cannot be changed in a running Arti client.
61    ///
62    /// The default is to use a set of compiled-in authorities,
63    /// whose identities and public keys are shipped as part of the Arti source code.
64    #[builder(sub_builder)]
65    #[builder_field_attr(serde(default))]
66    #[getset(get = "pub")]
67    authorities: AuthorityContacts,
68}
69
70impl_standard_builder! { NetworkConfig }
71
72define_list_builder_accessors! {
73    struct NetworkConfigBuilder {
74        pub fallback_caches: [FallbackDirBuilder],
75    }
76}
77
78impl NetworkConfigBuilder {
79    /// Check that this builder will give a reasonable network.
80    fn validate(&self) -> std::result::Result<(), ConfigBuildError> {
81        if self.authorities.opt_v3idents().is_some() && self.opt_fallback_caches().is_none() {
82            return Err(ConfigBuildError::Inconsistent {
83                fields: vec!["authorities".to_owned(), "fallbacks".to_owned()],
84                problem: "Non-default authorities are use, but the fallback list is not overridden"
85                    .to_owned(),
86            });
87        }
88
89        Ok(())
90    }
91}
92
93/// Configuration information for how exactly we download documents from the
94/// Tor directory caches.
95///
96/// This type is immutable once constructed. To make one, use
97/// [`DownloadScheduleConfigBuilder`], or deserialize it from a string.
98#[derive(Debug, Clone, Builder, Eq, PartialEq, Getters, CopyGetters)]
99#[builder(build_fn(error = "ConfigBuildError"))]
100#[builder(derive(Debug, Serialize, Deserialize))]
101#[non_exhaustive]
102pub struct DownloadScheduleConfig {
103    /// Top-level configuration for how to retry our initial bootstrap attempt.
104    #[builder(
105        sub_builder,
106        field(build = "self.retry_bootstrap.build_retry_bootstrap()?")
107    )]
108    #[builder_field_attr(serde(default))]
109    #[getset(get_copy = "pub")]
110    retry_bootstrap: DownloadSchedule,
111
112    /// Configuration for how to retry a consensus download.
113    #[builder(sub_builder)]
114    #[builder_field_attr(serde(default))]
115    #[getset(get_copy = "pub")]
116    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    #[getset(get_copy = "pub")]
122    retry_certs: DownloadSchedule,
123
124    /// Configuration for how to retry a microdescriptor download.
125    #[builder(
126        sub_builder,
127        field(build = "self.retry_microdescs.build_retry_microdescs()?")
128    )]
129    #[builder_field_attr(serde(default))]
130    #[getset(get_copy = "pub")]
131    retry_microdescs: DownloadSchedule,
132}
133
134impl_standard_builder! { DownloadScheduleConfig }
135
136/// Configuration for how much much to extend the official tolerances of our
137/// directory information.
138///
139/// Because of possible clock skew, and because we want to tolerate possible
140/// failures of the directory authorities to reach a consensus, we want to
141/// consider a directory to be valid for a while before and after its official
142/// range of validity.
143///
144/// TODO: Remove the [`Default`] because it is too tightly bound to a client.
145#[derive(Debug, Clone, Builder, Eq, PartialEq, Getters, CopyGetters)]
146#[builder(derive(Debug, Serialize, Deserialize))]
147#[builder(build_fn(error = "ConfigBuildError"))]
148#[non_exhaustive]
149pub struct DirTolerance {
150    /// For how long before a directory document is valid should we accept it?
151    ///
152    /// Having a nonzero value here allows us to tolerate a little clock skew.
153    ///
154    /// Defaults to 1 day.
155    #[builder(default = "Duration::from_secs(24 * 60 * 60)")]
156    #[builder_field_attr(serde(default, with = "humantime_serde::option"))]
157    #[getset(get_copy = "pub")]
158    pre_valid_tolerance: Duration,
159
160    /// For how long after a directory document is valid should we consider it
161    /// usable?
162    ///
163    /// Having a nonzero value here allows us to tolerate a little clock skew,
164    /// and makes us more robust to temporary failures for the directory
165    /// authorities to reach consensus.
166    ///
167    /// Defaults to 3 days (per [prop212]).
168    ///
169    /// [prop212]:
170    ///     https://gitlab.torproject.org/tpo/core/torspec/-/blob/main/proposals/212-using-old-consensus.txt
171    #[builder(default = "Duration::from_secs(3 * 24 * 60 * 60)")]
172    #[builder_field_attr(serde(default, with = "humantime_serde::option"))]
173    #[getset(get_copy = "pub")]
174    post_valid_tolerance: Duration,
175}
176
177impl_standard_builder! { DirTolerance }
178
179impl DirTolerance {
180    /// Return a new [`TimerangeBound`] that extends the validity interval of
181    /// `timebound` according to this configuration.
182    pub fn extend_tolerance<B>(&self, timebound: TimerangeBound<B>) -> TimerangeBound<B> {
183        timebound
184            .extend_tolerance(self.post_valid_tolerance)
185            .extend_pre_tolerance(self.pre_valid_tolerance)
186    }
187
188    /// Return a new consensus [`Lifetime`] that extends the validity intervals
189    /// of `lifetime` according to this configuration.
190    pub fn extend_lifetime(&self, lifetime: &Lifetime) -> Lifetime {
191        Lifetime::new(
192            lifetime.valid_after() - self.pre_valid_tolerance,
193            lifetime.fresh_until(),
194            lifetime.valid_until() + self.post_valid_tolerance,
195        )
196        .expect("Logic error when constructing lifetime")
197    }
198}
199
200#[cfg(test)]
201mod test {
202    // @@ begin test lint list maintained by maint/add_warning @@
203    #![allow(clippy::bool_assert_comparison)]
204    #![allow(clippy::clone_on_copy)]
205    #![allow(clippy::dbg_macro)]
206    #![allow(clippy::mixed_attributes_style)]
207    #![allow(clippy::print_stderr)]
208    #![allow(clippy::print_stdout)]
209    #![allow(clippy::single_char_pattern)]
210    #![allow(clippy::unwrap_used)]
211    #![allow(clippy::unchecked_time_subtraction)]
212    #![allow(clippy::useless_vec)]
213    #![allow(clippy::needless_pass_by_value)]
214    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
215    #![allow(clippy::unnecessary_wraps)]
216
217    use tor_llcrypto::pk::rsa::RsaIdentity;
218
219    use crate::fallback::FallbackDir;
220
221    use super::*;
222
223    #[test]
224    fn build_network() {
225        let dflt = NetworkConfig::default();
226
227        // with nothing set, we get the default.
228        let mut bld = NetworkConfig::builder();
229        let cfg = bld.build().unwrap();
230        assert_eq!(
231            cfg.authorities.v3idents().len(),
232            dflt.authorities.v3idents().len()
233        );
234        assert_eq!(cfg.fallback_caches.len(), dflt.fallback_caches.len());
235
236        // with any authorities set, the fallback list _must_ be set
237        // or the build fails.
238        bld.authorities
239            .set_v3idents(vec![[b'?'; 20].into(), [b'!'; 20].into()]);
240        assert!(bld.build().is_err());
241
242        bld.set_fallback_caches(vec![{
243            let mut bld = FallbackDir::builder();
244            bld.rsa_identity([b'x'; 20].into())
245                .ed_identity([b'y'; 32].into());
246            bld.orports().push("127.0.0.1:99".parse().unwrap());
247            bld.orports().push("[::]:99".parse().unwrap());
248            bld
249        }]);
250        let cfg = bld.build().unwrap();
251        assert_eq!(cfg.authorities.v3idents().len(), 2);
252        assert_eq!(cfg.fallback_caches.len(), 1);
253    }
254
255    #[test]
256    fn deserialize() {
257        let mut netcfg_prop330: NetworkConfigBuilder = toml::from_str(
258            "
259        [authorities]
260        v3idents = [
261            \"911F7C74212214823DDBDE3044B5B1AF3EFB98A0\",
262            \"46C4A4492D103A8C5CA544AC653B51C7B9AC8692\",
263            \"28D4680EA9C3660D1028FC40BACAC1319414581E\",
264            \"3817C9EB7E41C957594D0D9BCD6C7D7D718479C2\",
265        ]",
266        )
267        .unwrap();
268
269        assert_eq!(netcfg_prop330.authorities.v3idents().len(), 4);
270        assert_eq!(
271            *netcfg_prop330.authorities.v3idents(),
272            vec![
273                RsaIdentity::from_hex("911F7C74212214823DDBDE3044B5B1AF3EFB98A0").unwrap(),
274                RsaIdentity::from_hex("46C4A4492D103A8C5CA544AC653B51C7B9AC8692").unwrap(),
275                RsaIdentity::from_hex("28D4680EA9C3660D1028FC40BACAC1319414581E").unwrap(),
276                RsaIdentity::from_hex("3817C9EB7E41C957594D0D9BCD6C7D7D718479C2").unwrap(),
277            ]
278        );
279    }
280}