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 std::time::Duration;
12

            
13
use derive_deftly::Deftly;
14
use getset::{CopyGetters, Getters};
15
use tor_checkable::timed::TimerangeBound;
16
use tor_config::derive::prelude::*;
17
use tor_config::{ConfigBuildError, define_list_builder_accessors};
18
use tor_netdoc::doc::netstatus::Lifetime;
19

            
20
use 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, Deftly, Eq, PartialEq, Getters)]
35
#[derive_deftly(TorConfig)]
36
#[deftly(tor_config(pre_build = "Self::validate"))]
37
#[non_exhaustive]
38
pub 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
    #[deftly(tor_config(sub_builder, setter(skip)))]
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
    #[deftly(tor_config(sub_builder))]
65
    #[getset(get = "pub")]
66
    authorities: AuthorityContacts,
67
}
68

            
69
// TODO #2297: There ought to be a cleaner way to do this: the trouble is that
70
// we are explicitly creating a list-builder object elsewhere, but exposing its accessors here.
71
define_list_builder_accessors! {
72
    struct NetworkConfigBuilder {
73
        pub fallback_caches: [FallbackDirBuilder],
74
    }
75
}
76

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

            
88
6707
        Ok(())
89
6709
    }
90
}
91

            
92
/// Configuration information for how exactly we download documents from the
93
/// Tor directory caches.
94
///
95
/// This type is immutable once constructed. To make one, use
96
/// [`DownloadScheduleConfigBuilder`], or deserialize it from a string.
97
#[derive(Debug, Clone, Deftly, Eq, PartialEq, Getters, CopyGetters)]
98
#[derive_deftly(TorConfig)]
99
#[non_exhaustive]
100
pub struct DownloadScheduleConfig {
101
    /// Top-level configuration for how to retry our initial bootstrap attempt.
102
    #[deftly(tor_config(sub_builder(build_fn = "build_retry_bootstrap")))]
103
    #[getset(get_copy = "pub")]
104
    retry_bootstrap: DownloadSchedule,
105

            
106
    /// Configuration for how to retry a consensus download.
107
    #[deftly(tor_config(sub_builder))]
108
    #[getset(get_copy = "pub")]
109
    retry_consensus: DownloadSchedule,
110

            
111
    /// Configuration for how to retry an authority cert download.
112
    #[deftly(tor_config(sub_builder))]
113
    #[getset(get_copy = "pub")]
114
    retry_certs: DownloadSchedule,
115

            
116
    /// Configuration for how to retry a microdescriptor download.
117
    #[deftly(tor_config(sub_builder(build_fn = "build_retry_microdescs")))]
118
    #[getset(get_copy = "pub")]
119
    retry_microdescs: DownloadSchedule,
120
}
121

            
122
/// Configuration for how much much to extend the official tolerances of our
123
/// directory information.
124
///
125
/// Because of possible clock skew, and because we want to tolerate possible
126
/// failures of the directory authorities to reach a consensus, we want to
127
/// consider a directory to be valid for a while before and after its official
128
/// range of validity.
129
///
130
/// TODO: Remove the [`Default`] because it is too tightly bound to a client.
131
#[derive(Debug, Clone, Deftly, Eq, PartialEq, Getters, CopyGetters)]
132
#[derive_deftly(TorConfig)]
133
#[non_exhaustive]
134
pub struct DirTolerance {
135
    /// For how long before a directory document is valid should we accept it?
136
    ///
137
    /// Having a nonzero value here allows us to tolerate a little clock skew.
138
    ///
139
    /// Defaults to 1 day.
140
    #[deftly(tor_config(default = "Duration::from_secs(24 * 60 * 60)"))]
141
    #[getset(get_copy = "pub")]
142
    pre_valid_tolerance: Duration,
143

            
144
    /// For how long after a directory document is valid should we consider it
145
    /// usable?
146
    ///
147
    /// Having a nonzero value here allows us to tolerate a little clock skew,
148
    /// and makes us more robust to temporary failures for the directory
149
    /// authorities to reach consensus.
150
    ///
151
    /// Defaults to 3 days (per [prop212]).
152
    ///
153
    /// [prop212]:
154
    ///     https://gitlab.torproject.org/tpo/core/torspec/-/blob/main/proposals/212-using-old-consensus.txt
155
    #[deftly(tor_config(default = "Duration::from_secs(3 * 24 * 60 * 60)"))]
156
    #[getset(get_copy = "pub")]
157
    post_valid_tolerance: Duration,
158
}
159

            
160
impl DirTolerance {
161
    /// Return a new [`TimerangeBound`] that extends the validity interval of
162
    /// `timebound` according to this configuration.
163
16
    pub fn extend_tolerance<B>(&self, timebound: TimerangeBound<B>) -> TimerangeBound<B> {
164
16
        timebound
165
16
            .extend_tolerance(self.post_valid_tolerance)
166
16
            .extend_pre_tolerance(self.pre_valid_tolerance)
167
16
    }
168

            
169
    /// Return a new consensus [`Lifetime`] that extends the validity intervals
170
    /// of `lifetime` according to this configuration.
171
148
    pub fn extend_lifetime(&self, lifetime: &Lifetime) -> Lifetime {
172
148
        Lifetime::new(
173
148
            lifetime.valid_after() - self.pre_valid_tolerance,
174
148
            lifetime.fresh_until(),
175
148
            lifetime.valid_until() + self.post_valid_tolerance,
176
        )
177
148
        .expect("Logic error when constructing lifetime")
178
148
    }
179
}
180

            
181
#[cfg(test)]
182
mod test {
183
    // @@ begin test lint list maintained by maint/add_warning @@
184
    #![allow(clippy::bool_assert_comparison)]
185
    #![allow(clippy::clone_on_copy)]
186
    #![allow(clippy::dbg_macro)]
187
    #![allow(clippy::mixed_attributes_style)]
188
    #![allow(clippy::print_stderr)]
189
    #![allow(clippy::print_stdout)]
190
    #![allow(clippy::single_char_pattern)]
191
    #![allow(clippy::unwrap_used)]
192
    #![allow(clippy::unchecked_time_subtraction)]
193
    #![allow(clippy::useless_vec)]
194
    #![allow(clippy::needless_pass_by_value)]
195
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
196
    #![allow(clippy::unnecessary_wraps)]
197

            
198
    use tor_llcrypto::pk::rsa::RsaIdentity;
199

            
200
    use crate::fallback::FallbackDir;
201

            
202
    use super::*;
203

            
204
    #[test]
205
    fn build_network() {
206
        let dflt = NetworkConfig::default();
207

            
208
        // with nothing set, we get the default.
209
        let mut bld = NetworkConfig::builder();
210
        let cfg = bld.build().unwrap();
211
        assert_eq!(
212
            cfg.authorities.v3idents().len(),
213
            dflt.authorities.v3idents().len()
214
        );
215
        assert_eq!(cfg.fallback_caches.len(), dflt.fallback_caches.len());
216

            
217
        // with any authorities set, the fallback list _must_ be set
218
        // or the build fails.
219
        bld.authorities
220
            .set_v3idents(vec![[b'?'; 20].into(), [b'!'; 20].into()]);
221
        assert!(bld.build().is_err());
222

            
223
        bld.set_fallback_caches(vec![{
224
            let mut bld = FallbackDir::builder();
225
            bld.rsa_identity([b'x'; 20].into())
226
                .ed_identity([b'y'; 32].into());
227
            bld.orports().push("127.0.0.1:99".parse().unwrap());
228
            bld.orports().push("[::]:99".parse().unwrap());
229
            bld
230
        }]);
231
        let cfg = bld.build().unwrap();
232
        assert_eq!(cfg.authorities.v3idents().len(), 2);
233
        assert_eq!(cfg.fallback_caches.len(), 1);
234
    }
235

            
236
    #[test]
237
    fn deserialize() {
238
        let mut netcfg_prop330: NetworkConfigBuilder = toml::from_str(
239
            "
240
        [authorities]
241
        v3idents = [
242
            \"911F7C74212214823DDBDE3044B5B1AF3EFB98A0\",
243
            \"46C4A4492D103A8C5CA544AC653B51C7B9AC8692\",
244
            \"28D4680EA9C3660D1028FC40BACAC1319414581E\",
245
            \"3817C9EB7E41C957594D0D9BCD6C7D7D718479C2\",
246
        ]",
247
        )
248
        .unwrap();
249

            
250
        assert_eq!(netcfg_prop330.authorities.v3idents().len(), 4);
251
        assert_eq!(
252
            *netcfg_prop330.authorities.v3idents(),
253
            vec![
254
                RsaIdentity::from_hex("911F7C74212214823DDBDE3044B5B1AF3EFB98A0").unwrap(),
255
                RsaIdentity::from_hex("46C4A4492D103A8C5CA544AC653B51C7B9AC8692").unwrap(),
256
                RsaIdentity::from_hex("28D4680EA9C3660D1028FC40BACAC1319414581E").unwrap(),
257
                RsaIdentity::from_hex("3817C9EB7E41C957594D0D9BCD6C7D7D718479C2").unwrap(),
258
            ]
259
        );
260
    }
261
}