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`.
67use tor_basic_utils::define_accessor_trait;
8use tor_config::impl_standard_builder;
9use tor_config::{define_list_builder_accessors, define_list_builder_helper, ConfigBuildError};
10use tor_guardmgr::{GuardFilter, GuardMgrConfig};
1112use derive_builder::Builder;
13use serde::{Deserialize, Serialize};
14use tor_netdoc::types::policy::AddrPortPattern;
15use tor_relay_selection::RelaySelectionConfig;
1617use std::collections::HashSet;
18use std::time::Duration;
1920/// Rules for building paths over the network.
21///
22/// This type is immutable once constructed. To build one, use
23/// [`PathConfigBuilder`], or deserialize it from a string.
24///
25/// You may change the PathConfig on a running Arti client. Doing so changes
26/// paths that are constructed in the future, and prevents requests from being
27/// attached to existing circuits, if the configuration has become more
28/// restrictive.
29#[derive(Debug, Clone, Builder, Eq, PartialEq)]
30#[builder(build_fn(error = "ConfigBuildError"))]
31#[builder(derive(Debug, Serialize, Deserialize))]
32pub struct PathConfig {
33/// Set the length of a bit-prefix for a default IPv4 subnet-family.
34 ///
35 /// Any two relays will be considered to belong to the same family if their
36 /// IPv4 addresses share at least this many initial bits.
37#[builder(default = "ipv4_prefix_default()")]
38ipv4_subnet_family_prefix: u8,
3940/// Set the length of a bit-prefix for a default IPv6 subnet-family.
41 ///
42 /// Any two relays will be considered to belong to the same family if their
43 /// IPv6 addresses share at least this many initial bits.
44#[builder(default = "ipv6_prefix_default()")]
45ipv6_subnet_family_prefix: u8,
4647/// A set of ports that need to be sent over Stable circuits.
48#[builder(sub_builder, setter(custom))]
49 #[builder_field_attr(serde(default))]
50pub(crate) long_lived_ports: LongLivedPorts,
5152/// The set of addresses to which we're willing to make direct connections.
53#[builder(sub_builder, setter(custom))]
54 #[builder_field_attr(serde(default))]
55pub(crate) reachable_addrs: ReachableAddrs,
56}
57impl_standard_builder! { PathConfig }
5859/// Type alias for a list of reachable addresses.
60type ReachableAddrs = Vec<AddrPortPattern>;
6162/// Return the default list of reachable addresses (namely, "*:*")
63fn default_reachable_addrs() -> ReachableAddrs {
64vec![AddrPortPattern::new_all()]
65}
6667define_list_builder_helper! {
68struct ReachableAddrsBuilder {
69pub(crate) patterns: [AddrPortPattern],
70 }
71 built: ReachableAddrs = patterns;
72 default = default_reachable_addrs();
73 item_build: |pat| Ok(pat.clone());
74}
7576define_list_builder_accessors! {
77struct PathConfigBuilder {
78pub reachable_addrs: [AddrPortPattern],
79 }
80}
8182/// Type alias to help define long_lived_ports.
83type LongLivedPorts = HashSet<u16>;
8485define_list_builder_helper! {
86pub struct LongLivedPortsBuilder {
87 long_lived_ports:[u16],
88 }
89 built: LongLivedPorts = long_lived_ports;
90 default = long_lived_ports_default();
91 item_build: |item| Ok(*item);
92}
9394define_list_builder_accessors! {
95struct PathConfigBuilder {
96pub long_lived_ports: [u16],
97 }
98}
99100/// Default value for ipv4_subnet_family_prefix.
101fn ipv4_prefix_default() -> u8 {
10216
103}
104/// Default value for ipv6_subnet_family_prefix.
105fn ipv6_prefix_default() -> u8 {
10632
107}
108/// Default value for long_lived_ports.
109fn long_lived_ports_default() -> Vec<u16> {
110vec![
11121, 22, 706, 1863, 5050, 5190, 5222, 5223, 6523, 6667, 6697, 8300,
112 ]
113}
114115impl PathConfig {
116/// Return a subnet configuration based on these rules.
117pub fn subnet_config(&self) -> tor_netdir::SubnetConfig {
118 tor_netdir::SubnetConfig::new(
119self.ipv4_subnet_family_prefix,
120self.ipv6_subnet_family_prefix,
121 )
122 }
123124/// Return true if this configuration is at least as permissive as `other`.
125 ///
126 /// In other words, in other words, return true if every circuit permitted
127 /// by `other` would also be permitted by this configuration.
128 ///
129 /// We use this function to decide when circuits must be discarded.
130 /// Therefore, it is okay to return "false" inaccurately, but we should
131 /// never return "true" inaccurately.
132pub(crate) fn at_least_as_permissive_as(&self, other: &Self) -> bool {
133self.ipv4_subnet_family_prefix >= other.ipv4_subnet_family_prefix
134 && self.ipv6_subnet_family_prefix >= other.ipv6_subnet_family_prefix
135 && self.reachable_addrs == other.reachable_addrs
136 }
137138/// Return a new [`GuardFilter`] reflecting the rules in this configuration.
139pub(crate) fn build_guard_filter(&self) -> GuardFilter {
140let mut filt = GuardFilter::default();
141 filt.push_reachable_addresses(self.reachable_addrs.clone());
142 filt
143 }
144145/// Return a new [`RelaySelectionConfig`] reflecting the rules in this
146 /// configuration.
147pub(crate) fn relay_selection_config(&self) -> RelaySelectionConfig<'_> {
148 RelaySelectionConfig {
149 long_lived_ports: &self.long_lived_ports,
150 subnet_config: self.subnet_config(),
151 }
152 }
153}
154155/// Configuration for preemptive circuits.
156///
157/// Preemptive circuits are built ahead of time, to anticipate client need. This
158/// object configures the way in which this demand is anticipated and in which
159/// these circuits are constructed.
160///
161/// This type is immutable once constructed. To create an object of this type,
162/// use [`PreemptiveCircuitConfigBuilder`].
163///
164/// Except as noted, this configuration can be changed on a running Arti client.
165#[derive(Debug, Clone, Builder, Eq, PartialEq)]
166#[builder(build_fn(error = "ConfigBuildError"))]
167#[builder(derive(Debug, Serialize, Deserialize))]
168pub struct PreemptiveCircuitConfig {
169/// If we have at least this many available circuits, we suspend
170 /// construction of preemptive circuits. whether our available circuits
171 /// support our predicted exit ports or not.
172#[builder(default = "default_preemptive_threshold()")]
173pub(crate) disable_at_threshold: usize,
174175/// At startup, which exit ports should we expect that the client will want?
176 ///
177 /// (Over time, new ports are added to the predicted list, in response to
178 /// what the client has actually requested.)
179 ///
180 /// This value cannot be changed on a running Arti client, because doing so
181 /// would be meaningless.
182 ///
183 /// The default is `[80, 443]`.
184#[builder(sub_builder, setter(custom))]
185pub(crate) initial_predicted_ports: PredictedPortsList,
186187/// After we see the client request a connection to a new port, how long
188 /// should we predict that the client will still want to have circuits
189 /// available for that port?
190#[builder(default = "default_preemptive_duration()")]
191 #[builder_field_attr(serde(default, with = "humantime_serde::option"))]
192pub(crate) prediction_lifetime: Duration,
193194/// How many available circuits should we try to have, at minimum, for each
195 /// predicted exit port?
196#[builder(default = "default_preemptive_min_exit_circs_for_port()")]
197pub(crate) min_exit_circs_for_port: usize,
198}
199impl_standard_builder! { PreemptiveCircuitConfig }
200201/// Configuration for circuit timeouts, expiration, and so on.
202///
203/// This type is immutable once constructed. To create an object of this type,
204/// use [`CircuitTimingBuilder`].
205///
206/// You can change the CircuitTiming on a running Arti client. Doing
207/// so _should_ affect the expiration times of all circuits that are
208/// not currently expired, and the request timing of all _future_
209/// requests. However, there are currently bugs: see bug
210/// [#263](https://gitlab.torproject.org/tpo/core/arti/-/issues/263).
211#[derive(Debug, Clone, Builder, Eq, PartialEq)]
212#[builder(build_fn(error = "ConfigBuildError"))]
213#[builder(derive(Debug, Serialize, Deserialize))]
214// TODO Use a getters derive macro which lets us only generate getters
215// for fields we explicitly request, rather than having to mark the rest with `skip`.
216// (amplify::Getters doesn't allow #[getter(skip)] at the type level)
217#[derive(amplify::Getters)]
218pub struct CircuitTiming {
219/// How long after a circuit has first been used should we give
220 /// it out for new requests?
221#[builder(default = "default_max_dirtiness()")]
222 #[builder_field_attr(serde(default, with = "humantime_serde::option"))]
223 #[getter(skip)]
224pub(crate) max_dirtiness: Duration,
225226/// When a circuit is requested, we stop retrying new circuits
227 /// after this much time.
228// TODO: Impose a maximum or minimum?
229#[builder(default = "default_request_timeout()")]
230 #[builder_field_attr(serde(default, with = "humantime_serde::option"))]
231 #[getter(skip)]
232pub(crate) request_timeout: Duration,
233234/// When a circuit is requested, we stop retrying new circuits after
235 /// this many attempts.
236// TODO: Impose a maximum or minimum?
237#[builder(default = "default_request_max_retries()")]
238 #[getter(skip)]
239pub(crate) request_max_retries: u32,
240241/// When waiting for requested circuits, wait at least this long
242 /// before using a suitable-looking circuit launched by some other
243 /// request.
244#[builder(default = "default_request_loyalty()")]
245 #[builder_field_attr(serde(default, with = "humantime_serde::option"))]
246 #[getter(skip)]
247pub(crate) request_loyalty: Duration,
248249/// When an HS connection is attempted, we stop trying more hsdirs after this many attempts
250//
251 // This parameter is honoured by tor-hsclient, not here.
252 // This is because the best configuration taxonomy isn't the same as the best code structure.
253 // This, and `hs_intro_rend_attempts`, fit rather well amongst the other tunings here.
254#[cfg(feature = "hs-client")]
255 #[builder(default = "default_hs_max_attempts()")]
256 #[getter(as_copy)]
257pub(crate) hs_desc_fetch_attempts: u32,
258259/// When an HS connection is attempted, we stop trying intro/rendezvous
260 /// after this many attempts
261//
262 // This parameter is honoured by tor-hsclient, not here.
263#[cfg(feature = "hs-client")]
264 #[builder(default = "default_hs_max_attempts()")]
265 #[getter(as_copy)]
266pub(crate) hs_intro_rend_attempts: u32,
267}
268impl_standard_builder! { CircuitTiming }
269270/// Return default threshold
271fn default_preemptive_threshold() -> usize {
27212
273}
274275/// Built list of configured preemptive ports
276type PredictedPortsList = Vec<u16>;
277278define_list_builder_helper! {
279struct PredictedPortsListBuilder {
280pub(crate) ports: [u16],
281 }
282 built: PredictedPortsList = ports;
283 default = default_preemptive_ports();
284 item_build: |&port| Ok(port);
285}
286287define_list_builder_accessors! {
288struct PreemptiveCircuitConfigBuilder {
289pub initial_predicted_ports: [u16],
290 }
291}
292293/// Return default target ports
294fn default_preemptive_ports() -> Vec<u16> {
295vec![80, 443]
296}
297298/// Return default duration
299fn default_preemptive_duration() -> Duration {
300 Duration::from_secs(60 * 60)
301}
302303/// Return minimum circuits for an exit port
304fn default_preemptive_min_exit_circs_for_port() -> usize {
3052
306}
307308/// Return the default value for `max_dirtiness`.
309fn default_max_dirtiness() -> Duration {
310 Duration::from_secs(60 * 10)
311}
312313/// Return the default value for `request_timeout`.
314fn default_request_timeout() -> Duration {
315 Duration::from_secs(60)
316}
317318/// Return the default value for `request_max_retries`.
319fn default_request_max_retries() -> u32 {
32016
321}
322323/// Return the default value for `request_max_retries`.
324#[cfg(feature = "hs-client")]
325fn default_hs_max_attempts() -> u32 {
326// TODO SPEC: Should HS retries be 6 even though the default request_max_retries is 16?
327 // Probably, because the HS may be missing or down, and we don't want to spend ages
328 // turning over every stone looking for it.
3296
330}
331332/// Return the default request loyalty timeout.
333fn default_request_loyalty() -> Duration {
334 Duration::from_millis(50)
335}
336337define_accessor_trait! {
338/// Configuration for a circuit manager
339 ///
340 /// If the circuit manager gains new configurabilities, this trait will gain additional
341 /// supertraits, as an API break.
342 ///
343 /// Prefer to use `TorClientConfig`, which will always implement this trait.
344//
345 // We do not use a builder here. Instead, additions or changes here are API breaks.
346 //
347 // Rationale:
348 //
349 // The purpose of using a builder is to allow the code to continue to
350 // compile when new fields are added to the built struct.
351 //
352 // However, here, the DirMgrConfig is just a subset of the fields of a
353 // TorClientConfig, and it is important that all its fields are
354 // initialised by arti-client.
355 //
356 // If it grows a field, arti-client ought not to compile any more.
357 //
358 // Indeed, we have already had a bug where a manually-written
359 // conversion function omitted to copy a config field from
360 // TorClientConfig into then-existing CircMgrConfigBuilder.
361 //
362 // We use this AsRef-based trait, so that we can pass a reference
363 // to the configuration when we build a new CircMgr, rather than
364 // cloning all the fields an extra time.
365pub trait CircMgrConfig: GuardMgrConfig {
366 path_rules: PathConfig,
367 circuit_timing: CircuitTiming,
368 preemptive_circuits: PreemptiveCircuitConfig,
369 +
370// Note: ideally this would be defined in the same way as `path_rules`,
371 // `circuit_timing`, etc., but define_accessor_trait unconditionally adds
372 // AsRef<VanguardsConfig> as a supertrait, which can't be cfg'd behind
373 // the vanguards feature.
374375/// Access the field
376#[cfg(all(feature = "vanguards", feature = "hs-common"))]
377fn vanguard_config(&self) -> &tor_guardmgr::VanguardConfig;
378 }
379}
380381/// Testing configuration, with public fields
382#[cfg(any(test, feature = "testing"))]
383pub(crate) mod test_config {
384use super::*;
385use crate::*;
386use tor_guardmgr::bridge::BridgeConfig;
387#[cfg(all(feature = "vanguards", feature = "hs-common"))]
388use tor_guardmgr::VanguardConfig;
389390/// Testing configuration, with public fields
391#[derive(Default, derive_more::AsRef)]
392 #[allow(clippy::exhaustive_structs)]
393 #[allow(missing_docs)]
394 #[cfg_attr(docsrs, doc(cfg(feature = "testing")))]
395pub struct TestConfig {
396pub path_rules: PathConfig,
397pub circuit_timing: CircuitTiming,
398pub preemptive_circuits: PreemptiveCircuitConfig,
399pub guardmgr: tor_guardmgr::TestConfig,
400#[cfg(all(feature = "vanguards", feature = "hs-common"))]
401pub vanguard_config: VanguardConfig,
402 }
403impl AsRef<[BridgeConfig]> for TestConfig {
404fn as_ref(&self) -> &[BridgeConfig] {
405&self.guardmgr.bridges
406 }
407 }
408impl AsRef<FallbackList> for TestConfig {
409fn as_ref(&self) -> &FallbackList {
410&self.guardmgr.fallbacks
411 }
412 }
413impl GuardMgrConfig for TestConfig {
414fn bridges_enabled(&self) -> bool {
415self.guardmgr.bridges_enabled()
416 }
417 }
418impl CircMgrConfig for TestConfig {
419fn path_rules(&self) -> &PathConfig {
420&self.path_rules
421 }
422fn circuit_timing(&self) -> &CircuitTiming {
423&self.circuit_timing
424 }
425fn preemptive_circuits(&self) -> &PreemptiveCircuitConfig {
426&self.preemptive_circuits
427 }
428#[cfg(all(feature = "vanguards", feature = "hs-common"))]
429fn vanguard_config(&self) -> &tor_guardmgr::VanguardConfig {
430&self.vanguard_config
431 }
432 }
433}
434435#[cfg(test)]
436mod test {
437// @@ begin test lint list maintained by maint/add_warning @@
438#![allow(clippy::bool_assert_comparison)]
439 #![allow(clippy::clone_on_copy)]
440 #![allow(clippy::dbg_macro)]
441 #![allow(clippy::mixed_attributes_style)]
442 #![allow(clippy::print_stderr)]
443 #![allow(clippy::print_stdout)]
444 #![allow(clippy::single_char_pattern)]
445 #![allow(clippy::unwrap_used)]
446 #![allow(clippy::unchecked_duration_subtraction)]
447 #![allow(clippy::useless_vec)]
448 #![allow(clippy::needless_pass_by_value)]
449//! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
450use super::*;
451452#[test]
453fn path_config() {
454let pc1 = PathConfig::default();
455// Because these configurations consider _fewer_ nodes to be in the same
456 // families, they are _more_ permissive about what circuits we can
457 // build.
458let pc2 = PathConfig::builder()
459 .ipv4_subnet_family_prefix(32)
460 .build()
461 .unwrap();
462let pc3 = PathConfig::builder()
463 .ipv6_subnet_family_prefix(128)
464 .build()
465 .unwrap();
466467assert!(pc2.at_least_as_permissive_as(&pc1));
468assert!(pc3.at_least_as_permissive_as(&pc1));
469assert!(pc1.at_least_as_permissive_as(&pc1));
470assert!(!pc1.at_least_as_permissive_as(&pc2));
471assert!(!pc1.at_least_as_permissive_as(&pc3));
472assert!(!pc3.at_least_as_permissive_as(&pc2));
473 }
474}