1
//! Configuration logic for launching a circuit manager.
2
//!
3
//! # Semver note
4
//!
5
//! Most types in this module are re-exported by `arti-client`.
6

            
7
use derive_deftly::Deftly;
8
use tor_basic_utils::define_accessor_trait;
9
use tor_guardmgr::{GuardFilter, GuardMgrConfig, VanguardConfig};
10
use tor_netdoc::types::policy::AddrPortPattern;
11
use tor_relay_selection::RelaySelectionConfig;
12

            
13
use tor_config::derive::prelude::*;
14

            
15
use std::collections::HashSet;
16
use std::time::Duration;
17

            
18
/// Rules for building paths over the network.
19
///
20
/// This type is immutable once constructed.  To build one, use
21
/// [`PathConfigBuilder`], or deserialize it from a string.
22
///
23
/// You may change the PathConfig on a running Arti client.  Doing so changes
24
/// paths that are constructed in the future, and prevents requests from being
25
/// attached to existing circuits, if the configuration has become more
26
/// restrictive.
27
#[derive(Debug, Clone, Eq, PartialEq, Deftly)]
28
#[derive_deftly(TorConfig)]
29
pub struct PathConfig {
30
    /// Set the length of a bit-prefix for a default IPv4 subnet-family.
31
    ///
32
    /// Any two relays will be considered to belong to the same family if their
33
    /// IPv4 addresses share at least this many initial bits.
34
    #[deftly(tor_config(default = "ipv4_prefix_default()"))]
35
    ipv4_subnet_family_prefix: u8,
36

            
37
    /// Set the length of a bit-prefix for a default IPv6 subnet-family.
38
    ///
39
    /// Any two relays will be considered to belong to the same family if their
40
    /// IPv6 addresses share at least this many initial bits.
41
    #[deftly(tor_config(default = "ipv6_prefix_default()"))]
42
    ipv6_subnet_family_prefix: u8,
43

            
44
    /// A set of ports that need to be sent over Stable circuits.
45
    #[deftly(tor_config(list(element(clone)), default = "long_lived_ports_default()"))]
46
    pub(crate) long_lived_ports: HashSet<u16>,
47

            
48
    /// The set of addresses to which we're willing to make direct connections.
49
    #[deftly(tor_config(list(element(clone)), default = "default_reachable_addrs()"))]
50
    pub(crate) reachable_addrs: Vec<AddrPortPattern>,
51
}
52

            
53
/// Return the default list of reachable addresses (namely, "*:*")
54
6990
fn default_reachable_addrs() -> Vec<AddrPortPattern> {
55
6990
    vec![AddrPortPattern::new_all()]
56
6990
}
57

            
58
/// Default value for ipv4_subnet_family_prefix.
59
6946
fn ipv4_prefix_default() -> u8 {
60
6946
    16
61
6946
}
62
/// Default value for ipv6_subnet_family_prefix.
63
6946
fn ipv6_prefix_default() -> u8 {
64
6946
    32
65
6946
}
66
/// Default value for long_lived_ports.
67
7032
fn long_lived_ports_default() -> Vec<u16> {
68
7032
    vec![
69
        21, 22, 706, 1863, 5050, 5190, 5222, 5223, 6523, 6667, 6697, 8300,
70
    ]
71
7032
}
72

            
73
impl PathConfig {
74
    /// Return a subnet configuration based on these rules.
75
49432
    pub fn subnet_config(&self) -> tor_netdir::SubnetConfig {
76
49432
        tor_netdir::SubnetConfig::new(
77
49432
            self.ipv4_subnet_family_prefix,
78
49432
            self.ipv6_subnet_family_prefix,
79
        )
80
49432
    }
81

            
82
    /// Return true if this configuration is at least as permissive as `other`.
83
    ///
84
    /// In other words, in other words, return true if every circuit permitted
85
    /// by `other` would also be permitted by this configuration.
86
    ///
87
    /// We use this function to decide when circuits must be discarded.
88
    /// Therefore, it is okay to return "false" inaccurately, but we should
89
    /// never return "true" inaccurately.
90
96
    pub(crate) fn at_least_as_permissive_as(&self, other: &Self) -> bool {
91
96
        self.ipv4_subnet_family_prefix >= other.ipv4_subnet_family_prefix
92
92
            && self.ipv6_subnet_family_prefix >= other.ipv6_subnet_family_prefix
93
90
            && self.reachable_addrs == other.reachable_addrs
94
96
    }
95

            
96
    /// Return a new [`GuardFilter`] reflecting the rules in this configuration.
97
646
    pub(crate) fn build_guard_filter(&self) -> GuardFilter {
98
646
        let mut filt = GuardFilter::default();
99
646
        filt.push_reachable_addresses(self.reachable_addrs.clone());
100
646
        filt
101
646
    }
102

            
103
    /// Return a new [`RelaySelectionConfig`] reflecting the rules in this
104
    /// configuration.
105
49432
    pub(crate) fn relay_selection_config(&self) -> RelaySelectionConfig<'_> {
106
49432
        RelaySelectionConfig {
107
49432
            long_lived_ports: &self.long_lived_ports,
108
49432
            subnet_config: self.subnet_config(),
109
49432
        }
110
49432
    }
111
}
112

            
113
/// Configuration for preemptive circuits.
114
///
115
/// Preemptive circuits are built ahead of time, to anticipate client need. This
116
/// object configures the way in which this demand is anticipated and in which
117
/// these circuits are constructed.
118
///
119
/// This type is immutable once constructed. To create an object of this type,
120
/// use [`PreemptiveCircuitConfigBuilder`].
121
///
122
/// Except as noted, this configuration can be changed on a running Arti client.
123
#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
124
#[derive_deftly(TorConfig)]
125
pub struct PreemptiveCircuitConfig {
126
    /// If we have at least this many available circuits, we suspend
127
    /// construction of preemptive circuits. whether our available circuits
128
    /// support our predicted exit ports or not.
129
    #[deftly(tor_config(default = "default_preemptive_threshold()"))]
130
    pub(crate) disable_at_threshold: usize,
131

            
132
    /// At startup, which exit ports should we expect that the client will want?
133
    ///
134
    /// (Over time, new ports are added to the predicted list, in response to
135
    /// what the client has actually requested.)
136
    ///
137
    /// This value cannot be changed on a running Arti client, because doing so
138
    /// would be meaningless.
139
    ///
140
    /// The default is `[80, 443]`.
141
    #[deftly(tor_config(list(element(clone)), default = "default_preemptive_ports()"))]
142
    pub(crate) initial_predicted_ports: Vec<u16>,
143

            
144
    /// After we see the client request a connection to a new port, how long
145
    /// should we predict that the client will still want to have circuits
146
    /// available for that port?
147
    #[deftly(tor_config(default = "default_preemptive_duration()"))]
148
    pub(crate) prediction_lifetime: Duration,
149

            
150
    /// How many available circuits should we try to have, at minimum, for each
151
    /// predicted exit port?
152
    #[deftly(tor_config(default = "default_preemptive_min_exit_circs_for_port()"))]
153
    pub(crate) min_exit_circs_for_port: usize,
154
}
155

            
156
/// Configuration for circuit timeouts, expiration, and so on.
157
///
158
/// This type is immutable once constructed. To create an object of this type,
159
/// use [`CircuitTimingBuilder`].
160
///
161
/// You can change the CircuitTiming on a running Arti client.  Doing
162
/// so _should_ affect the expiration times of all circuits that are
163
/// not currently expired, and the request timing of all _future_
164
/// requests.  However, there are currently bugs: see bug
165
/// [#263](https://gitlab.torproject.org/tpo/core/arti/-/issues/263).
166
#[derive(Debug, Clone, Eq, PartialEq, Deftly)]
167
// TODO Use a getters derive macro which lets us only generate getters
168
// for fields we explicitly request, rather than having to mark the rest with `skip`.
169
// (amplify::Getters doesn't allow #[getter(skip)] at the type level)
170
#[derive(amplify::Getters)]
171
#[derive_deftly(TorConfig)]
172
pub struct CircuitTiming {
173
    /// How long after a circuit has first been used should we give
174
    /// it out for new requests?
175
    ///
176
    /// This setting applies to circuits without strong isolation.
177
    /// See also [`disused_circuit_timeout`](Self::disused_circuit_timeout)
178
    #[deftly(tor_config(default = "default_max_dirtiness()"))]
179
    #[getter(skip)]
180
    pub(crate) max_dirtiness: Duration,
181

            
182
    /// How long after a circuit has become disused should we discard it?
183
    ///
184
    /// This setting applies to circuits _with_ strong isolation.
185
    /// See also [`max_dirtiness`](Self::max_dirtiness)
186
    // TODO: Impose a maximum or minimum?
187
    #[deftly(tor_config(default = "default_disused_timeout()"))]
188
    #[getter(skip)]
189
    pub(crate) disused_circuit_timeout: Duration,
190

            
191
    /// When a circuit is requested, we stop retrying new circuits
192
    /// after this much time.
193
    // TODO: Impose a maximum or minimum?
194
    #[deftly(tor_config(default = "default_request_timeout()"))]
195
    #[getter(skip)]
196
    pub(crate) request_timeout: Duration,
197

            
198
    /// When a circuit is requested, we stop retrying new circuits after
199
    /// this many attempts.
200
    // TODO: Impose a maximum or minimum?
201
    #[deftly(tor_config(default = "default_request_max_retries()"))]
202
    #[getter(skip)]
203
    pub(crate) request_max_retries: u32,
204

            
205
    /// When waiting for requested circuits, wait at least this long
206
    /// before using a suitable-looking circuit launched by some other
207
    /// request.
208
    #[deftly(tor_config(default = "default_request_loyalty()"))]
209
    #[getter(skip)]
210
    pub(crate) request_loyalty: Duration,
211

            
212
    /// When an HS connection is attempted, we stop trying more hsdirs after this many attempts
213
    //
214
    // This parameter is honoured by tor-hsclient, not here.
215
    // This is because the best configuration taxonomy isn't the same as the best code structure.
216
    // This, and `hs_intro_rend_attempts`, fit rather well amongst the other tunings here.
217
    //
218
    // NOTE: we could use #[deftly(tor_config(cfg = ))] rather than #[cfg] here, if we wanted to give
219
    // a warning when the user provided a setting for this value while hs-client was compiled out.
220
    #[cfg(feature = "hs-client")]
221
    #[deftly(tor_config(default = "default_hs_max_attempts()"))]
222
    #[getter(as_copy)]
223
    pub(crate) hs_desc_fetch_attempts: u32,
224

            
225
    /// When an HS connection is attempted, we stop trying intro/rendezvous
226
    /// after this many attempts
227
    //
228
    // This parameter is honoured by tor-hsclient, not here.
229
    #[cfg(feature = "hs-client")]
230
    #[deftly(tor_config(default = "default_hs_max_attempts()"))]
231
    #[getter(as_copy)]
232
    pub(crate) hs_intro_rend_attempts: u32,
233
}
234

            
235
/// Return default threshold
236
6454
fn default_preemptive_threshold() -> usize {
237
6454
    12
238
6454
}
239

            
240
/// Return default target ports
241
6446
fn default_preemptive_ports() -> Vec<u16> {
242
6446
    vec![80, 443]
243
6446
}
244

            
245
/// Return default duration
246
6446
fn default_preemptive_duration() -> Duration {
247
6446
    Duration::from_secs(60 * 60)
248
6446
}
249

            
250
/// Return minimum circuits for an exit port
251
6454
fn default_preemptive_min_exit_circs_for_port() -> usize {
252
6454
    2
253
6454
}
254

            
255
/// Return the default value for `max_dirtiness`.
256
6654
fn default_max_dirtiness() -> Duration {
257
6654
    Duration::from_secs(60 * 10)
258
6654
}
259

            
260
/// Return the default value for `disused_circuit_timeout`.
261
6742
fn default_disused_timeout() -> Duration {
262
6742
    Duration::from_secs(60 * 60)
263
6742
}
264

            
265
/// Return the default value for `request_timeout`.
266
6658
fn default_request_timeout() -> Duration {
267
6658
    Duration::from_secs(60)
268
6658
}
269

            
270
/// Return the default value for `request_max_retries`.
271
6658
fn default_request_max_retries() -> u32 {
272
6658
    16
273
6658
}
274

            
275
/// Return the default value for `request_max_retries`.
276
#[cfg(feature = "hs-client")]
277
13484
fn default_hs_max_attempts() -> u32 {
278
    // TODO SPEC: Should HS retries be 6 even though the default request_max_retries is 16?
279
    // Probably, because the HS may be missing or down, and we don't want to spend ages
280
    // turning over every stone looking for it.
281
13484
    6
282
13484
}
283

            
284
/// Return the default request loyalty timeout.
285
6658
fn default_request_loyalty() -> Duration {
286
6658
    Duration::from_millis(50)
287
6658
}
288

            
289
define_accessor_trait! {
290
    /// Configuration for a circuit manager
291
    ///
292
    /// If the circuit manager gains new configurabilities, this trait will gain additional
293
    /// supertraits, as an API break.
294
    ///
295
    /// Prefer to use `TorClientConfig`, which will always implement this trait.
296
    //
297
    // We do not use a builder here.  Instead, additions or changes here are API breaks.
298
    //
299
    // Rationale:
300
    //
301
    // The purpose of using a builder is to allow the code to continue to
302
    // compile when new fields are added to the built struct.
303
    //
304
    // However, here, the DirMgrConfig is just a subset of the fields of a
305
    // TorClientConfig, and it is important that all its fields are
306
    // initialised by arti-client.
307
    //
308
    // If it grows a field, arti-client ought not to compile any more.
309
    //
310
    // Indeed, we have already had a bug where a manually-written
311
    // conversion function omitted to copy a config field from
312
    // TorClientConfig into then-existing CircMgrConfigBuilder.
313
    //
314
    // We use this AsRef-based trait, so that we can pass a reference
315
    // to the configuration when we build a new CircMgr, rather than
316
    // cloning all the fields an extra time.
317
    pub trait CircMgrConfig: GuardMgrConfig {
318
        path_rules: PathConfig,
319
        vanguard_config: VanguardConfig,
320
        circuit_timing: CircuitTiming,
321
        preemptive_circuits: PreemptiveCircuitConfig,
322
    }
323
}
324

            
325
/// Testing configuration, with public fields
326
#[cfg(any(test, feature = "testing"))]
327
pub(crate) mod test_config {
328
    use super::*;
329
    use crate::*;
330
    use tor_guardmgr::VanguardConfig;
331
    use tor_guardmgr::bridge::BridgeConfig;
332

            
333
    /// Testing configuration, with public fields
334
    #[derive(Default, derive_more::AsRef)]
335
    #[allow(clippy::exhaustive_structs)]
336
    #[allow(missing_docs)]
337
    #[cfg_attr(docsrs, doc(cfg(feature = "testing")))]
338
    pub struct TestConfig {
339
        pub path_rules: PathConfig,
340
        pub circuit_timing: CircuitTiming,
341
        pub preemptive_circuits: PreemptiveCircuitConfig,
342
        pub guardmgr: tor_guardmgr::TestConfig,
343
        pub vanguard_config: VanguardConfig,
344
    }
345
    impl AsRef<[BridgeConfig]> for TestConfig {
346
        fn as_ref(&self) -> &[BridgeConfig] {
347
            &self.guardmgr.bridges
348
        }
349
    }
350
    impl AsRef<FallbackList> for TestConfig {
351
        fn as_ref(&self) -> &FallbackList {
352
            &self.guardmgr.fallbacks
353
        }
354
    }
355
    impl GuardMgrConfig for TestConfig {
356
        fn bridges_enabled(&self) -> bool {
357
            self.guardmgr.bridges_enabled()
358
        }
359
    }
360
    impl CircMgrConfig for TestConfig {
361
        fn path_rules(&self) -> &PathConfig {
362
            &self.path_rules
363
        }
364
        fn circuit_timing(&self) -> &CircuitTiming {
365
            &self.circuit_timing
366
        }
367
        fn preemptive_circuits(&self) -> &PreemptiveCircuitConfig {
368
            &self.preemptive_circuits
369
        }
370
        fn vanguard_config(&self) -> &tor_guardmgr::VanguardConfig {
371
            &self.vanguard_config
372
        }
373
    }
374
}
375

            
376
#[cfg(test)]
377
mod test {
378
    // @@ begin test lint list maintained by maint/add_warning @@
379
    #![allow(clippy::bool_assert_comparison)]
380
    #![allow(clippy::clone_on_copy)]
381
    #![allow(clippy::dbg_macro)]
382
    #![allow(clippy::mixed_attributes_style)]
383
    #![allow(clippy::print_stderr)]
384
    #![allow(clippy::print_stdout)]
385
    #![allow(clippy::single_char_pattern)]
386
    #![allow(clippy::unwrap_used)]
387
    #![allow(clippy::unchecked_time_subtraction)]
388
    #![allow(clippy::useless_vec)]
389
    #![allow(clippy::needless_pass_by_value)]
390
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
391
    use super::*;
392

            
393
    #[test]
394
    fn path_config() {
395
        let pc1 = PathConfig::default();
396
        // Because these configurations consider _fewer_ nodes to be in the same
397
        // families, they are _more_ permissive about what circuits we can
398
        // build.
399
        let pc2 = PathConfig::builder()
400
            .ipv4_subnet_family_prefix(32)
401
            .build()
402
            .unwrap();
403
        let pc3 = PathConfig::builder()
404
            .ipv6_subnet_family_prefix(128)
405
            .build()
406
            .unwrap();
407

            
408
        assert!(pc2.at_least_as_permissive_as(&pc1));
409
        assert!(pc3.at_least_as_permissive_as(&pc1));
410
        assert!(pc1.at_least_as_permissive_as(&pc1));
411
        assert!(!pc1.at_least_as_permissive_as(&pc2));
412
        assert!(!pc1.at_least_as_permissive_as(&pc3));
413
        assert!(!pc3.at_least_as_permissive_as(&pc2));
414
    }
415
}