Skip to main content

tor_circmgr/
config.rs

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
7use derive_deftly::Deftly;
8use tor_basic_utils::define_accessor_trait;
9use tor_guardmgr::{GuardFilter, GuardMgrConfig, VanguardConfig};
10use tor_netdoc::types::policy::AddrPortPattern;
11use tor_relay_selection::RelaySelectionConfig;
12
13use tor_config::derive::prelude::*;
14
15use std::collections::HashSet;
16use 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)]
29pub 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, "*:*")
54fn default_reachable_addrs() -> Vec<AddrPortPattern> {
55    vec![AddrPortPattern::new_all()]
56}
57
58/// Default value for ipv4_subnet_family_prefix.
59fn ipv4_prefix_default() -> u8 {
60    16
61}
62/// Default value for ipv6_subnet_family_prefix.
63fn ipv6_prefix_default() -> u8 {
64    32
65}
66/// Default value for long_lived_ports.
67fn long_lived_ports_default() -> Vec<u16> {
68    vec![
69        21, 22, 706, 1863, 5050, 5190, 5222, 5223, 6523, 6667, 6697, 8300,
70    ]
71}
72
73impl PathConfig {
74    /// Return a subnet configuration based on these rules.
75    pub fn subnet_config(&self) -> tor_netdir::SubnetConfig {
76        tor_netdir::SubnetConfig::new(
77            self.ipv4_subnet_family_prefix,
78            self.ipv6_subnet_family_prefix,
79        )
80    }
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    pub(crate) fn at_least_as_permissive_as(&self, other: &Self) -> bool {
91        self.ipv4_subnet_family_prefix >= other.ipv4_subnet_family_prefix
92            && self.ipv6_subnet_family_prefix >= other.ipv6_subnet_family_prefix
93            && self.reachable_addrs == other.reachable_addrs
94    }
95
96    /// Return a new [`GuardFilter`] reflecting the rules in this configuration.
97    pub(crate) fn build_guard_filter(&self) -> GuardFilter {
98        let mut filt = GuardFilter::default();
99        filt.push_reachable_addresses(self.reachable_addrs.clone());
100        filt
101    }
102
103    /// Return a new [`RelaySelectionConfig`] reflecting the rules in this
104    /// configuration.
105    pub(crate) fn relay_selection_config(&self) -> RelaySelectionConfig<'_> {
106        RelaySelectionConfig {
107            long_lived_ports: &self.long_lived_ports,
108            subnet_config: self.subnet_config(),
109        }
110    }
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)]
125pub 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)]
172pub 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    /// The period for which a hidden service directory cannot be queried for
235    /// the same descriptor ID again.
236    //
237    // This parameter is honoured by tor-hsclient, not here.
238    #[cfg(feature = "hs-client")]
239    #[deftly(tor_config(default = "default_hs_dir_requery_interval()"))]
240    #[getter(as_copy)]
241    pub(crate) hs_dir_requery_interval: Duration,
242}
243
244/// Return default threshold
245fn default_preemptive_threshold() -> usize {
246    12
247}
248
249/// Return default target ports
250fn default_preemptive_ports() -> Vec<u16> {
251    vec![80, 443]
252}
253
254/// Return default duration
255fn default_preemptive_duration() -> Duration {
256    Duration::from_secs(60 * 60)
257}
258
259/// Return minimum circuits for an exit port
260fn default_preemptive_min_exit_circs_for_port() -> usize {
261    2
262}
263
264/// Return the default value for `max_dirtiness`.
265fn default_max_dirtiness() -> Duration {
266    Duration::from_secs(60 * 10)
267}
268
269/// Return the default value for `disused_circuit_timeout`.
270fn default_disused_timeout() -> Duration {
271    Duration::from_secs(60 * 60)
272}
273
274/// Return the default value for `request_timeout`.
275fn default_request_timeout() -> Duration {
276    Duration::from_secs(60)
277}
278
279/// Return the default value for `request_max_retries`.
280fn default_request_max_retries() -> u32 {
281    16
282}
283
284/// Return the default value for `request_max_retries`.
285#[cfg(feature = "hs-client")]
286fn default_hs_max_attempts() -> u32 {
287    // TODO SPEC: Should HS retries be 6 even though the default request_max_retries is 16?
288    // Probably, because the HS may be missing or down, and we don't want to spend ages
289    // turning over every stone looking for it.
290    6
291}
292
293/// Return the default HsDir requery period.
294///
295// Corresponds to C Tor's REND_HID_SERV_DIR_REQUERY_PERIOD
296fn default_hs_dir_requery_interval() -> Duration {
297    Duration::from_secs(15 * 60)
298}
299
300/// Return the default request loyalty timeout.
301fn default_request_loyalty() -> Duration {
302    Duration::from_millis(50)
303}
304
305define_accessor_trait! {
306    /// Configuration for a circuit manager
307    ///
308    /// If the circuit manager gains new configurabilities, this trait will gain additional
309    /// supertraits, as an API break.
310    ///
311    /// Prefer to use `TorClientConfig`, which will always implement this trait.
312    //
313    // We do not use a builder here.  Instead, additions or changes here are API breaks.
314    //
315    // Rationale:
316    //
317    // The purpose of using a builder is to allow the code to continue to
318    // compile when new fields are added to the built struct.
319    //
320    // However, here, the DirMgrConfig is just a subset of the fields of a
321    // TorClientConfig, and it is important that all its fields are
322    // initialised by arti-client.
323    //
324    // If it grows a field, arti-client ought not to compile any more.
325    //
326    // Indeed, we have already had a bug where a manually-written
327    // conversion function omitted to copy a config field from
328    // TorClientConfig into then-existing CircMgrConfigBuilder.
329    //
330    // We use this AsRef-based trait, so that we can pass a reference
331    // to the configuration when we build a new CircMgr, rather than
332    // cloning all the fields an extra time.
333    pub trait CircMgrConfig: GuardMgrConfig {
334        path_rules: PathConfig,
335        vanguard_config: VanguardConfig,
336        circuit_timing: CircuitTiming,
337        preemptive_circuits: PreemptiveCircuitConfig,
338    }
339}
340
341/// Testing configuration, with public fields
342#[cfg(any(test, feature = "testing"))]
343pub(crate) mod test_config {
344    use super::*;
345    use crate::*;
346    use tor_guardmgr::VanguardConfig;
347    use tor_guardmgr::bridge::BridgeConfig;
348
349    /// Testing configuration, with public fields
350    #[derive(Default, derive_more::AsRef)]
351    #[allow(clippy::exhaustive_structs)]
352    #[allow(missing_docs)]
353    #[cfg_attr(docsrs, doc(cfg(feature = "testing")))]
354    pub struct TestConfig {
355        pub path_rules: PathConfig,
356        pub circuit_timing: CircuitTiming,
357        pub preemptive_circuits: PreemptiveCircuitConfig,
358        pub guardmgr: tor_guardmgr::TestConfig,
359        pub vanguard_config: VanguardConfig,
360    }
361    impl AsRef<[BridgeConfig]> for TestConfig {
362        fn as_ref(&self) -> &[BridgeConfig] {
363            &self.guardmgr.bridges
364        }
365    }
366    impl AsRef<FallbackList> for TestConfig {
367        fn as_ref(&self) -> &FallbackList {
368            &self.guardmgr.fallbacks
369        }
370    }
371    impl GuardMgrConfig for TestConfig {
372        fn bridges_enabled(&self) -> bool {
373            self.guardmgr.bridges_enabled()
374        }
375    }
376    impl CircMgrConfig for TestConfig {
377        fn path_rules(&self) -> &PathConfig {
378            &self.path_rules
379        }
380        fn circuit_timing(&self) -> &CircuitTiming {
381            &self.circuit_timing
382        }
383        fn preemptive_circuits(&self) -> &PreemptiveCircuitConfig {
384            &self.preemptive_circuits
385        }
386        fn vanguard_config(&self) -> &tor_guardmgr::VanguardConfig {
387            &self.vanguard_config
388        }
389    }
390}
391
392#[cfg(test)]
393mod test {
394    // @@ begin test lint list maintained by maint/add_warning @@
395    #![allow(clippy::bool_assert_comparison)]
396    #![allow(clippy::clone_on_copy)]
397    #![allow(clippy::dbg_macro)]
398    #![allow(clippy::mixed_attributes_style)]
399    #![allow(clippy::print_stderr)]
400    #![allow(clippy::print_stdout)]
401    #![allow(clippy::single_char_pattern)]
402    #![allow(clippy::unwrap_used)]
403    #![allow(clippy::unchecked_time_subtraction)]
404    #![allow(clippy::useless_vec)]
405    #![allow(clippy::needless_pass_by_value)]
406    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
407    use super::*;
408
409    #[test]
410    fn path_config() {
411        let pc1 = PathConfig::default();
412        // Because these configurations consider _fewer_ nodes to be in the same
413        // families, they are _more_ permissive about what circuits we can
414        // build.
415        let pc2 = PathConfig::builder()
416            .ipv4_subnet_family_prefix(32)
417            .build()
418            .unwrap();
419        let pc3 = PathConfig::builder()
420            .ipv6_subnet_family_prefix(128)
421            .build()
422            .unwrap();
423
424        assert!(pc2.at_least_as_permissive_as(&pc1));
425        assert!(pc3.at_least_as_permissive_as(&pc1));
426        assert!(pc1.at_least_as_permissive_as(&pc1));
427        assert!(!pc1.at_least_as_permissive_as(&pc2));
428        assert!(!pc1.at_least_as_permissive_as(&pc3));
429        assert!(!pc3.at_least_as_permissive_as(&pc2));
430    }
431}