tor_relay_selection/
usage.rs

1//! Define a type describing how we're going to use a relay.
2
3use crate::{LowLevelRelayPredicate, RelaySelectionConfig, TargetPort};
4use tor_netdir::{Relay, WeightRole};
5
6/// Description for how we plan to use a single relay.
7#[derive(Clone, Debug)]
8pub struct RelayUsage {
9    /// Interior enumeration to describe the particular usage.
10    inner: RelayUsageInner,
11    /// Does this usage require the `Stable` flag?
12    ///
13    /// This is derived when we construct the RelayUsage, since it may require
14    /// access to the config, and since it's cheaper to pre-compute.
15    need_stable: bool,
16}
17
18/// Implementation type for RelayUsage.
19///
20/// This is a separate type so that we can hide its variants.
21#[derive(Clone, Debug)]
22enum RelayUsageInner {
23    /// Allow any relay that exits to any port.
24    AnyExit,
25    /// Require that the relay can exit to every port in `TargetPort`.
26    ExitToAllPorts(Vec<TargetPort>),
27    /// Require that the relay can exit to at least one port in a given set.
28    ///
29    /// (We split the ports into those that require Stability and those that do
30    /// not, for efficiency.)
31    ExitToAnyPort {
32        /// The desired ports that require the Stable flag.
33        stable_ports: Vec<TargetPort>,
34        /// The desired ports that do not require the Stable flag.
35        unstable_ports: Vec<TargetPort>,
36    },
37    /// Allow any relay that's suitable as a middle-point.
38    Middle,
39    /// Allow any relay that's suitable as a newly selected introduction point.
40    NewIntroPoint,
41    /// Allow any relay that's suitable for continued use as a pre-existing
42    /// introduction point.
43    ContinuingIntroPoint,
44    /// Allow any relay that's suitable as a newly selected guard.
45    NewGuard,
46    /// Allow any relay that's suitable for continued use as a pre-existing
47    /// guard.
48    ContinuingGuard,
49    /// Allow any relay that's suitable as a vanguard.
50    #[cfg(feature = "vanguards")]
51    Vanguard,
52    /// Allow any relay that's suitable as a one-hop directory cache.
53    DirectoryCache,
54}
55
56impl RelayUsage {
57    /// Require a relay that exits to at least one port.
58    ///
59    /// This usage is generally suitable as the final relay for a testing
60    /// circuit of some kind, or for a circuit that needs to _look_ like an
61    /// exit circuit without actually being useful for any exit in particular.
62    pub fn any_exit(_cfg: &RelaySelectionConfig) -> Self {
63        // TODO: properly, we ought to make sure that this does not select
64        // relays that only exit to long_lived ports, unless they have the
65        // Stable flag.
66        //
67        // C tor doesn't make this distinction, however, and so neither do we.
68        RelayUsage {
69            inner: RelayUsageInner::AnyExit,
70            need_stable: false,
71        }
72    }
73
74    /// Require a relay that exits to every port in a given list.
75    pub fn exit_to_all_ports(cfg: &RelaySelectionConfig, ports: Vec<TargetPort>) -> Self {
76        let need_stable = ports.iter().any(|p| cfg.port_requires_stable_flag(p.port));
77        RelayUsage {
78            inner: RelayUsageInner::ExitToAllPorts(ports),
79            need_stable,
80        }
81    }
82
83    /// Require a relay that exits to at least one port in a given list.
84    pub fn exit_to_any_port(cfg: &RelaySelectionConfig, ports: Vec<TargetPort>) -> Self {
85        let (stable_ports, unstable_ports): (Vec<_>, Vec<_>) = ports
86            .into_iter()
87            .partition(|p| cfg.port_requires_stable_flag(p.port));
88        let need_stable = unstable_ports.is_empty() && !stable_ports.is_empty();
89        RelayUsage {
90            inner: RelayUsageInner::ExitToAnyPort {
91                stable_ports,
92                unstable_ports,
93            },
94            need_stable,
95        }
96    }
97
98    /// Require a relay that is suitable for a middle relay.
99    ///
100    /// If `known_final_hop_usage` is provided, then the middle relay must support any
101    /// additional properties needed in order to build a circuit for the usage
102    /// of the final hop.
103    ///
104    /// If `known_final_hop_usage` is *not* provided, then the middle relay must
105    /// support all possible such additional properties.
106    ///
107    /// (Note that providing a `known_final_hop_usage` can only _weaken_ the
108    /// requirements of this usage.)
109    pub fn middle_relay(known_final_hop_usage: Option<&RelayUsage>) -> Self {
110        let need_stable = known_final_hop_usage.map(|u| u.need_stable).unwrap_or(true);
111        RelayUsage {
112            inner: RelayUsageInner::Middle,
113            need_stable,
114        }
115    }
116
117    /// Require a relay that is suitable as a newly selected introduction point.
118    ///
119    /// This usage is suitable for selecting _new_ introduction points for an
120    /// onion service.  When deciding whether to _keep_ an introduction point,
121    /// use [`RelayUsage::continuing_intro_point`].
122    pub fn new_intro_point() -> Self {
123        RelayUsage {
124            inner: RelayUsageInner::NewIntroPoint,
125            need_stable: true,
126        }
127    }
128
129    /// Require a relay that is suitable to keep using as a pre-existing introduction point.
130    pub fn continuing_intro_point() -> Self {
131        RelayUsage {
132            inner: RelayUsageInner::ContinuingIntroPoint,
133            need_stable: true,
134        }
135    }
136
137    /// Require a relay that is suitable as a newly selected guard.
138    ///
139    /// This usage is suitable for selecting _new_ guards.
140    /// When deciding whether to _keep_ a guard,
141    /// use [`RelayUsage::continuing_guard`].
142    pub fn new_guard() -> Self {
143        RelayUsage {
144            inner: RelayUsageInner::NewGuard,
145            need_stable: true,
146        }
147    }
148
149    /// Require a relay that is suitable to keep using as a pre-existing guard.
150    pub fn continuing_guard() -> Self {
151        RelayUsage {
152            inner: RelayUsageInner::ContinuingGuard,
153            need_stable: true,
154        }
155    }
156
157    /// Require a relay that is suitable as a vanguard.
158    #[cfg(feature = "vanguards")]
159    pub fn vanguard() -> Self {
160        RelayUsage {
161            inner: RelayUsageInner::Vanguard,
162            // Vanguards must have the Fast, Stable, and Valid flags.
163            need_stable: true,
164        }
165    }
166
167    /// Require a relay that is suitable to use for a directory request.
168    ///
169    /// Note that this usage is suitable for fetching consensuses, authority certificates,
170    /// descriptors and microdescriptors.  It is _not_ suitable for use with the
171    /// HsDir system.
172    pub fn directory_cache() -> Self {
173        RelayUsage {
174            inner: RelayUsageInner::DirectoryCache,
175            need_stable: false,
176        }
177    }
178
179    /// Return the [`WeightRole`] to use when picking a relay for this usage.
180    pub(crate) fn selection_weight_role(&self) -> WeightRole {
181        use RelayUsageInner::*;
182
183        match &self.inner {
184            AnyExit | ExitToAllPorts(_) | ExitToAnyPort { .. } => WeightRole::Exit,
185            Middle => WeightRole::Middle,
186            NewIntroPoint | ContinuingIntroPoint => WeightRole::HsIntro,
187            NewGuard | ContinuingGuard => WeightRole::Guard,
188            #[cfg(feature = "vanguards")]
189            Vanguard => WeightRole::Middle,
190            DirectoryCache => WeightRole::BeginDir,
191        }
192    }
193
194    /// Return a string describing why we rejected the relays that _don't_ match
195    /// this usage.
196    pub(crate) fn rejection_description(&self) -> &'static str {
197        use RelayUsageInner::*;
198        match &self.inner {
199            AnyExit => "non-exit",
200            ExitToAllPorts(_) => "not exiting to desired ports",
201            ExitToAnyPort { .. } => "not exiting to any desired port",
202            Middle => "useless for middle relay",
203            NewIntroPoint | ContinuingIntroPoint => "not introduction point",
204            NewGuard | ContinuingGuard => "not guard",
205            #[cfg(feature = "vanguards")]
206            Vanguard => "not usable as vanguard",
207            DirectoryCache => "not directory cache",
208        }
209    }
210}
211
212impl LowLevelRelayPredicate for RelayUsage {
213    fn low_level_predicate_permits_relay(&self, relay_in: &Relay<'_>) -> bool {
214        use RelayUsageInner::*;
215        let relay = relay_in.low_level_details();
216        if !relay.is_flagged_fast() {
217            return false;
218        }
219        if self.need_stable && !relay.is_flagged_stable() {
220            return false;
221        }
222        match &self.inner {
223            AnyExit => relay.policies_allow_some_port(),
224            ExitToAllPorts(ports) => ports.iter().all(|p| p.is_supported_by(&relay)),
225            ExitToAnyPort {
226                stable_ports,
227                unstable_ports,
228            } => {
229                if relay.is_flagged_stable()
230                    && stable_ports.iter().any(|p| p.is_supported_by(&relay))
231                {
232                    return true;
233                }
234                unstable_ports.iter().any(|p| p.is_supported_by(&relay))
235            }
236            Middle => true,
237            // TODO: Is there a distinction we should implement?
238            // TODO: Move is_hs_intro_point logic here.
239            NewIntroPoint | ContinuingIntroPoint => relay.is_hs_intro_point(),
240            // TODO: Is there a distinction we should implement?
241            // TODO: Move is_suitable_as_guard logic here.
242            NewGuard | ContinuingGuard => relay.is_suitable_as_guard() && relay.is_dir_cache(),
243            #[cfg(feature = "vanguards")]
244            Vanguard => {
245                // TODO: we might want to impose additional restrictions here
246                true
247            }
248            DirectoryCache => relay.is_dir_cache(),
249        }
250    }
251}
252
253#[cfg(test)]
254mod test {
255    // @@ begin test lint list maintained by maint/add_warning @@
256    #![allow(clippy::bool_assert_comparison)]
257    #![allow(clippy::clone_on_copy)]
258    #![allow(clippy::dbg_macro)]
259    #![allow(clippy::mixed_attributes_style)]
260    #![allow(clippy::print_stderr)]
261    #![allow(clippy::print_stdout)]
262    #![allow(clippy::single_char_pattern)]
263    #![allow(clippy::unwrap_used)]
264    #![allow(clippy::unchecked_duration_subtraction)]
265    #![allow(clippy::useless_vec)]
266    #![allow(clippy::needless_pass_by_value)]
267    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
268
269    use super::*;
270    use crate::testing::{cfg, split_netdir, testnet};
271
272    #[test]
273    fn any_exits() {
274        let nd = testnet();
275
276        let (yes, no) = split_netdir(&nd, &RelayUsage::any_exit(&cfg()));
277
278        let p = |r: &Relay<'_>| {
279            r.low_level_details().is_flagged_fast()
280                && r.low_level_details().policies_allow_some_port()
281        };
282        assert!(yes.iter().all(p));
283        assert!(no.iter().all(|r| !p(r)));
284    }
285
286    #[test]
287    fn all_ports() {
288        let nd = testnet();
289        let ports_stable = vec![TargetPort::ipv4(22), TargetPort::ipv4(80)];
290        let usage_stable = RelayUsage::exit_to_all_ports(&cfg(), ports_stable);
291        assert!(usage_stable.need_stable);
292
293        let p1 = |relay: &Relay<'_>| {
294            let r = relay.low_level_details();
295            r.is_flagged_fast()
296                && r.is_flagged_stable()
297                && r.ipv4_policy().allows_port(22)
298                && r.ipv4_policy().allows_port(80)
299        };
300
301        let (yes, no) = split_netdir(&nd, &usage_stable);
302        assert!(yes.iter().all(p1));
303        assert!(no.iter().all(|r| !p1(r)));
304
305        let ports_not_stable = vec![TargetPort::ipv4(80)];
306        let usage_not_stable = RelayUsage::exit_to_all_ports(&cfg(), ports_not_stable);
307
308        let p2 = |relay: &Relay<'_>| {
309            let r = relay.low_level_details();
310            r.is_flagged_fast() && r.ipv4_policy().allows_port(80)
311        };
312        let (yes, no) = split_netdir(&nd, &usage_not_stable);
313        assert!(yes.iter().all(p2));
314        assert!(no.iter().all(|r| !p2(r)));
315    }
316
317    #[test]
318    fn any_port() {
319        let nd = testnet();
320        let ports = vec![TargetPort::ipv4(22), TargetPort::ipv4(80)];
321        let usage = RelayUsage::exit_to_any_port(&cfg(), ports);
322        assert!(!usage.need_stable);
323        match &usage.inner {
324            RelayUsageInner::ExitToAnyPort {
325                stable_ports,
326                unstable_ports,
327            } => {
328                assert_eq!(&stable_ports[..], &[TargetPort::ipv4(22)]);
329                assert_eq!(&unstable_ports[..], &[TargetPort::ipv4(80)]);
330            }
331            _ => {
332                panic!("Wrong kind of usage.");
333            }
334        }
335
336        let p = |relay: &Relay<'_>| {
337            let r = relay.low_level_details();
338            let port_22 = r.is_flagged_stable() && r.ipv4_policy().allows_port(22);
339            let port_80 = r.ipv4_policy().allows_port(80);
340            r.is_flagged_fast() && (port_22 || port_80)
341        };
342
343        let (yes, no) = split_netdir(&nd, &usage);
344        assert!(yes.iter().all(p));
345        assert!(no.iter().all(|r| !p(r)));
346    }
347
348    #[test]
349    fn middle() {
350        let nd = testnet();
351
352        let u_unstable = RelayUsage::any_exit(&cfg());
353        let u_stable = RelayUsage::new_guard();
354        let mid_stable = RelayUsage::middle_relay(Some(&u_stable));
355        let mid_unstable = RelayUsage::middle_relay(Some(&u_unstable));
356        let mid_default = RelayUsage::middle_relay(None);
357        assert!(mid_stable.need_stable);
358        assert!(!mid_unstable.need_stable);
359        assert!(mid_default.need_stable);
360
361        let (yes, no) = split_netdir(&nd, &mid_unstable);
362        let p1 = |relay: &Relay<'_>| {
363            let r = relay.low_level_details();
364            r.is_flagged_fast()
365        };
366        assert!(yes.iter().all(p1));
367        assert!(no.iter().all(|r| !p1(r)));
368
369        let (yes, no) = split_netdir(&nd, &mid_stable);
370        let p2 = |relay: &Relay<'_>| {
371            let r = relay.low_level_details();
372            r.is_flagged_fast() && r.is_flagged_stable()
373        };
374        assert!(yes.iter().all(p2));
375        assert!(no.iter().all(|r| !p2(r)));
376    }
377
378    #[test]
379    fn intro() {
380        let nd = testnet();
381        let usage = RelayUsage::new_intro_point();
382
383        let (yes, no) = split_netdir(&nd, &usage);
384        let p1 = |relay: &Relay<'_>| {
385            let r = relay.low_level_details();
386            r.is_flagged_fast() && r.is_flagged_stable()
387        };
388        assert!(yes.iter().all(p1));
389        assert!(no.iter().all(|r| !p1(r)));
390    }
391
392    #[test]
393    fn guard() {
394        let nd = testnet();
395        let usage = RelayUsage::new_guard();
396
397        let (yes, no) = split_netdir(&nd, &usage);
398        let p1 = |relay: &Relay<'_>| {
399            let r = relay.low_level_details();
400            r.is_flagged_fast()
401                && r.is_flagged_stable()
402                && r.is_suitable_as_guard()
403                && r.is_dir_cache()
404        };
405        assert!(yes.iter().all(p1));
406        assert!(no.iter().all(|r| !p1(r)));
407    }
408
409    #[test]
410    fn cache() {
411        let nd = testnet();
412        let usage = RelayUsage::directory_cache();
413
414        let (yes, no) = split_netdir(&nd, &usage);
415        let p1 = |relay: &Relay<'_>| {
416            let r = relay.low_level_details();
417            r.is_flagged_fast() && r.is_dir_cache()
418        };
419        assert!(yes.iter().all(p1));
420        assert!(no.iter().all(|r| !p1(r)));
421    }
422}