Skip to main content

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