tor_netdir/
details.rs

1//! Functionality for exposing details about a relay that most users should avoid.
2//!
3//! ## Design notes
4//!
5//! These types aren't meant to be a dumping grounds
6//! for every function in `Relay` or `UncheckedRelay`:
7//! instead, they are for methods that are easy to misuse or misunderstand
8//! if applied out-of-context.
9//!
10//! For example, it's generally wrong in most contexts
11//! to check for a specific relay flag.
12//! Instead, we should be checking whether the relay is suitable
13//! for some particular _usage_,
14//! which will itself depend on a combination of flags.
15//!
16//! Therefore, this module should be used for checking properties only when:
17//! - The property is one that is usually subsumed
18//!   in a higher-level check.
19//! - Using the lower-level property on its own poses a risk
20//!   of accidentally forgetting to check other important properties.
21//!
22//! If you find that your code is using this module, you should ask yourself
23//! - whether the actual thing that you're testing
24//!   is something that _any other piece of code_ might want to test
25//! - whether the collection of properties that you're testing
26//!   creates a risk of leaving out some other properties
27//!   that should also be tested,
28//!   for example in the future, if new relay flags or properties are introduced
29//!   that are supposed to influence relay selection or reuse.
30//!
31//! If you answer "yes" to either of these, it's better to define a higher-level property,
32//! and have your code use that instead.
33
34use std::sync::Arc;
35
36use tor_linkspec::HasRelayIds;
37use tor_netdoc::{doc::netstatus, types::policy::PortPolicy};
38
39use crate::{FamilyRules, Relay, SubnetConfig};
40
41/// A view for lower-level details about a [`Relay`].
42///
43/// Most callers should avoid using this structure;
44/// they should instead call higher-level functions
45/// like those in the `tor-relay-selection` crate.
46#[derive(Clone)]
47pub struct RelayDetails<'a>(pub(crate) &'a super::Relay<'a>);
48
49impl<'a> RelayDetails<'a> {
50    /// Return true if this relay allows exiting to `port` on IPv4.
51    pub fn supports_exit_port_ipv4(&self, port: u16) -> bool {
52        self.ipv4_policy().allows_port(port)
53    }
54    /// Return true if this relay allows exiting to `port` on IPv6.
55    pub fn supports_exit_port_ipv6(&self, port: u16) -> bool {
56        self.ipv6_policy().allows_port(port)
57    }
58    /// Return true if this relay is suitable for use as a directory
59    /// cache.
60    pub fn is_dir_cache(&self) -> bool {
61        rs_is_dir_cache(self.0.rs)
62    }
63    /// Return true if this relay has the "Fast" flag.
64    ///
65    /// Most relays have this flag.  It indicates that the relay is suitable for
66    /// circuits that need more than a minimal amount of bandwidth.
67    pub fn is_flagged_fast(&self) -> bool {
68        self.0.rs.is_flagged_fast()
69    }
70    /// Return true if this relay has the "Stable" flag.
71    ///
72    /// Most relays have this flag. It indicates that the relay is suitable for
73    /// long-lived circuits.
74    pub fn is_flagged_stable(&self) -> bool {
75        self.0.rs.is_flagged_stable()
76    }
77    /// Return true if this relay is a potential HS introduction point
78    pub fn is_hs_intro_point(&self) -> bool {
79        self.is_flagged_fast()
80            && self.0.rs.is_flagged_stable()
81            && !self.0.rs.is_flagged_middle_only()
82    }
83    /// Return true if this relay is a potential HS rendezvous point
84    pub fn is_hs_rend_point(&self) -> bool {
85        self.is_flagged_fast()
86            && self.0.rs.is_flagged_stable()
87            && !self.0.rs.is_flagged_middle_only()
88    }
89    /// Return true if this relay is suitable for use as a newly sampled guard,
90    /// or for continuing to use as a guard.
91    pub fn is_suitable_as_guard(&self) -> bool {
92        self.0.rs.is_flagged_guard() && self.is_flagged_fast() && self.is_flagged_stable()
93    }
94    /// Return true if both relays are in the same subnet, as configured by
95    /// `subnet_config`.
96    ///
97    /// Two relays are considered to be in the same subnet if they
98    /// have IPv4 addresses with the same `subnets_family_v4`-bit
99    /// prefix, or if they have IPv6 addresses with the same
100    /// `subnets_family_v6`-bit prefix.
101    pub fn in_same_subnet(&self, other: &Relay<'_>, subnet_config: &SubnetConfig) -> bool {
102        subnet_config.any_addrs_in_same_subnet(self.0, other)
103    }
104    /// Return true if both relays are in the same family.
105    ///
106    /// (Every relay is considered to be in the same family as itself.)
107    pub fn in_same_family(&self, other: &Relay<'_>, family_rules: FamilyRules) -> bool {
108        #![allow(clippy::collapsible_if)] // I prefer this style here.
109        if self.0.same_relay_ids(other) {
110            return true;
111        }
112        if family_rules.use_family_lists {
113            if self.0.md.family().contains(other.rsa_id())
114                && other.md.family().contains(self.0.rsa_id())
115            {
116                return true;
117            }
118        }
119        if family_rules.use_family_ids {
120            let my_ids = self.0.md.family_ids();
121            let their_ids = other.md.family_ids();
122            if my_ids.iter().any(|my_id| their_ids.contains(my_id)) {
123                return true;
124            }
125        }
126
127        false
128    }
129
130    /// Return true if there are any ports for which this Relay can be
131    /// used for exit traffic.
132    ///
133    /// (Returns false if this relay doesn't allow exit traffic, or if it
134    /// has been flagged as a bad exit.)
135    pub fn policies_allow_some_port(&self) -> bool {
136        if self.0.rs.is_flagged_bad_exit() {
137            return false;
138        }
139
140        self.0.md.ipv4_policy().allows_some_port() || self.0.md.ipv6_policy().allows_some_port()
141    }
142
143    /// Return the IPv4 exit policy for this relay. If the relay has been marked BadExit, return an
144    /// empty policy
145    pub fn ipv4_policy(&self) -> Arc<PortPolicy> {
146        if !self.0.rs.is_flagged_bad_exit() {
147            Arc::clone(self.0.md.ipv4_policy())
148        } else {
149            Arc::new(PortPolicy::new_reject_all())
150        }
151    }
152    /// Return the IPv6 exit policy for this relay. If the relay has been marked BadExit, return an
153    /// empty policy
154    pub fn ipv6_policy(&self) -> Arc<PortPolicy> {
155        if !self.0.rs.is_flagged_bad_exit() {
156            Arc::clone(self.0.md.ipv6_policy())
157        } else {
158            Arc::new(PortPolicy::new_reject_all())
159        }
160    }
161    /// Return the IPv4 exit policy declared by this relay.
162    ///
163    /// In contrast to [`RelayDetails::ipv4_policy`],
164    /// this does not verify if the relay is marked BadExit.
165    pub fn ipv4_declared_policy(&self) -> &Arc<PortPolicy> {
166        self.0.md.ipv4_policy()
167    }
168    /// Return the IPv6 exit policy declared by this relay.
169    ///
170    /// In contrast to [`RelayDetails::ipv6_policy`],
171    /// this does not verify if the relay is marked BadExit.
172    pub fn ipv6_declared_policy(&self) -> &Arc<PortPolicy> {
173        self.0.md.ipv6_policy()
174    }
175}
176
177/// A view for lower-level details about a [`UncheckedRelay`](crate::UncheckedRelay).
178///
179/// Most callers should avoid using this structure;
180/// they should instead call higher-level functions
181/// like those in the `tor-relay-selection` crate.
182#[derive(Debug, Clone)]
183pub struct UncheckedRelayDetails<'a>(pub(crate) &'a super::UncheckedRelay<'a>);
184
185impl<'a> UncheckedRelayDetails<'a> {
186    /// Return true if this relay is suitable for use as a newly sampled guard,
187    /// or for continuing to use as a guard.
188    pub fn is_suitable_as_guard(&self) -> bool {
189        self.0.rs.is_flagged_guard() && self.0.rs.is_flagged_fast() && self.0.rs.is_flagged_stable()
190    }
191    /// Return true if this relay is a potential directory cache.
192    pub fn is_dir_cache(&self) -> bool {
193        rs_is_dir_cache(self.0.rs)
194    }
195}
196
197/// Return true if `rs` is usable as a directory cache.
198fn rs_is_dir_cache(rs: &netstatus::MdConsensusRouterStatus) -> bool {
199    use tor_protover::named::DIRCACHE_CONSDIFF;
200    rs.is_flagged_v2dir() && rs.protovers().supports_named_subver(DIRCACHE_CONSDIFF)
201}