tor_relay_selection/
selector.rs

1//! Logic for selecting relays from a network directory,
2//! and reporting the outcome of such a selection.
3
4use crate::{LowLevelRelayPredicate, RelayExclusion, RelayRestriction, RelayUsage};
5use tor_basic_utils::iter::FilterCount;
6use tor_netdir::{NetDir, Relay, WeightRole};
7
8use std::fmt;
9
10/// Description of the requirements that a relay must implement in order to be selected.
11///
12/// This object is used to pick a [`Relay`] from a [`NetDir`], or to ensure that a
13/// previously selected `Relay` still meets its requirements.
14///
15/// The requirements on a relay can be _strict_ or _flexible_.
16/// If any restriction is flexible, and relay selection fails at first,
17/// we _relax_ the `RelaySelector` by removing that restriction,
18/// and trying again,
19/// before we give up completely.
20#[derive(Clone, Debug)]
21pub struct RelaySelector<'a> {
22    /// A usage that the relay must support.
23    ///
24    /// Invariant: This is a RelayUsage.
25    usage: Restr<'a>,
26
27    /// An excludion that the relay must obey.
28    ///
29    /// Invariant: This a RelayExclusion.
30    exclusion: Restr<'a>,
31
32    /// Other restrictions that a Relay must obey in order to be selected.
33    other_restrictions: Vec<Restr<'a>>,
34}
35
36/// A single restriction, along with a flag about whether it's strict.
37#[derive(Clone, Debug)]
38struct Restr<'a> {
39    /// The underlying restriction.
40    restriction: RelayRestriction<'a>,
41    /// Is the restriction strict or flexible?
42    strict: bool,
43}
44
45impl<'a> Restr<'a> {
46    /// Try relaxing this restriction.
47    ///
48    /// (If this can't be relaxed, just return a copy of it.)
49    fn maybe_relax(&self) -> Self {
50        if self.strict {
51            self.clone()
52        } else {
53            Self {
54                restriction: self.restriction.relax(),
55                // The new restriction is always strict, since we don't want to
56                // relax it any further.
57                strict: true,
58            }
59        }
60    }
61}
62
63/// Information about how a given selection was generated.
64///
65/// Records the specifics of how many relays were excluded by each
66/// requirement,
67/// whether we had to relax the selector, and so on.
68///
69/// The caller should typically decide whether an error or warning is necessary,
70/// and if so use this to generate a formattable report about what went wrong.
71#[derive(Debug, Clone)]
72pub struct SelectionInfo<'a> {
73    /// Outcome of our first attempt to pick a relay.
74    first_try: FilterCounts,
75
76    /// Present if we tried again with a relaxed version of our
77    /// flexible members.
78    relaxed_try: Option<FilterCounts>,
79
80    /// True if we eventually succeeded in picking a relay.
81    succeeded: bool,
82
83    /// The `RelaySelector` that was used.
84    ///
85    /// Used to produce information about which restriction is which.
86    in_selection: &'a RelaySelector<'a>,
87}
88
89impl<'a> SelectionInfo<'a> {
90    /// Return true if we eventually picked at least one relay.
91    ///
92    /// (We report success on `pick_n_relays` if we returned a nonzero
93    /// number of relays, even if it is smaller than the requested number.)
94    pub fn success(&self) -> bool {
95        self.succeeded
96    }
97
98    /// Return true if picked at least one relay,
99    /// but only after relaxing our initial selector.
100    pub fn result_is_relaxed_success(&self) -> bool {
101        self.relaxed_try.is_some() && self.succeeded
102    }
103}
104
105impl<'a> fmt::Display for SelectionInfo<'a> {
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        match (self.succeeded, &self.relaxed_try) {
108            (true, None) => write!(f, "Success: {}", FcDisp(&self.first_try, self.in_selection))?,
109            (false, None) => write!(f, "Failed: {}", FcDisp(&self.first_try, self.in_selection))?,
110            (true, Some(retry)) => write!(
111                f,
112                "Failed at first, then succeeded. At first, {}. After relaxing requirements, {}",
113                FcDisp(&self.first_try, self.in_selection),
114                FcDisp(retry, self.in_selection)
115            )?,
116            (false, Some(retry)) => write!(
117                f,
118                "Failed even after relaxing requirement. At first, {}. After relaxing requirements, {}",
119                FcDisp(&self.first_try, self.in_selection),
120                FcDisp(retry, self.in_selection)
121            )?,
122        };
123        Ok(())
124    }
125}
126
127/// A list of [`FilterCount`], associated with a [`RelaySelector`].
128#[derive(Debug, Clone)]
129struct FilterCounts {
130    /// The [`FilterCount`] created by each restriction.
131    ///
132    /// This `Vec` has the same length as the list of restrictions; its items
133    /// refer to them one by one.
134    ///
135    /// Because restrictions are applied as a set of filters, each successive
136    /// count will only include the relays not excluded by the previous filters.
137    counts: Vec<FilterCount>,
138}
139
140impl FilterCounts {
141    /// Create a new empty `FilterCounts`.
142    fn new(selector: &RelaySelector) -> Self {
143        let counts = vec![FilterCount::default(); selector.n_restrictions()];
144        FilterCounts { counts }
145    }
146}
147
148/// Helper to display filter counts
149struct FcDisp<'a>(&'a FilterCounts, &'a RelaySelector<'a>);
150impl<'a> fmt::Display for FcDisp<'a> {
151    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152        let counts = &self.0.counts;
153        let restrictions = self.1.all_restrictions();
154        write!(f, "rejected ")?;
155        let mut first = true;
156        let mut found_any_rejected = false;
157        for (c, r) in counts.iter().zip(restrictions) {
158            if let Some(desc) = r.restriction.rejection_description() {
159                if first {
160                    first = false;
161                } else {
162                    write!(f, "; ")?;
163                }
164                write!(f, "{} as {}", c.display_frac_rejected(), desc)?;
165                found_any_rejected = true;
166            } else {
167                debug_assert_eq!(c.n_rejected, 0);
168            }
169        }
170        if !found_any_rejected {
171            write!(f, "none")?;
172        }
173        Ok(())
174    }
175}
176
177impl<'a> RelaySelector<'a> {
178    /// Create a new RelaySelector to pick relays with a given
179    /// [`RelayUsage`] and [`RelayExclusion`].
180    ///
181    /// Both arguments are required, since every caller should consider them explicitly.
182    ///
183    /// The provided usage and exclusion are strict by default.
184    ///
185    // TODO: Possibly have this take a struct with named pieces instead, when we
186    // get a third thing that we want everybody to think about.
187    pub fn new(usage: RelayUsage, exclusion: RelayExclusion<'a>) -> Self {
188        Self {
189            usage: Restr {
190                restriction: RelayRestriction::for_usage(usage),
191                strict: true,
192            },
193            exclusion: Restr {
194                restriction: exclusion.into(),
195                strict: true,
196            },
197            other_restrictions: vec![],
198        }
199    }
200
201    /// Mark the originally provided `RelayUsage` as flexible.
202    pub fn mark_usage_flexible(&mut self) {
203        self.usage.strict = false;
204    }
205
206    /// Mark the originally provided `RelayExclusion` as flexible.
207    pub fn mark_exclusion_flexible(&mut self) {
208        self.exclusion.strict = false;
209    }
210
211    /// Add a new _strict_ [`RelayRestriction`] to this selector.
212    pub fn push_restriction(&mut self, restriction: RelayRestriction<'a>) {
213        self.push_inner(restriction, true);
214    }
215
216    /// Add a new _flexible_ [`RelayRestriction`] to this selector.
217    pub fn push_flexible_restriction(&mut self, restriction: RelayRestriction<'a>) {
218        self.push_inner(restriction, false);
219    }
220
221    /// Helper to implement adding a new restriction.
222    fn push_inner(&mut self, restriction: RelayRestriction<'a>, strict: bool) {
223        self.other_restrictions.push(Restr {
224            restriction,
225            strict,
226        });
227    }
228
229    /// Return the usage for this selector.
230    pub fn usage(&self) -> &RelayUsage {
231        // See invariants for explanation of why these `expects` are safe.
232        self.usage
233            .restriction
234            .as_usage()
235            .expect("Usage not a usage!?")
236    }
237
238    /// Return the [`WeightRole`] to use when randomly picking relays according
239    /// to this selector.
240    fn weight_role(&self) -> WeightRole {
241        self.usage().selection_weight_role()
242    }
243
244    /// Return true if `relay` is one that this selector would pick.
245    pub fn permits_relay(&self, relay: &tor_netdir::Relay<'_>) -> bool {
246        self.low_level_predicate_permits_relay(relay)
247    }
248
249    /// Return an iterator that yields each restriction from this selector,
250    /// including the usage and exclusion.
251    fn all_restrictions(&self) -> impl Iterator<Item = &Restr<'a>> {
252        use std::iter::once;
253        once(&self.usage)
254            .chain(once(&self.exclusion))
255            .chain(self.other_restrictions.iter())
256    }
257
258    /// Return the number of restrictions in this selector,
259    /// including the usage and exclusion.
260    fn n_restrictions(&self) -> usize {
261        self.other_restrictions.len() + 2
262    }
263
264    /// Try to pick a random relay from `netdir`,
265    /// according to the rules of this selector.
266    pub fn select_relay<'s, 'd, R: rand::Rng>(
267        &'s self,
268        rng: &mut R,
269        netdir: &'d NetDir,
270    ) -> (Option<Relay<'d>>, SelectionInfo<'s>) {
271        with_possible_relaxation(
272            self,
273            |selector| {
274                let role = selector.weight_role();
275                let mut fc = FilterCounts::new(selector);
276                let relay = netdir.pick_relay(rng, role, |r| selector.relay_usable(r, &mut fc));
277                (relay, fc)
278            },
279            Option::is_some,
280        )
281    }
282
283    /// Try to pick `n_relays` distinct random relay from `netdir`,
284    /// according to the rules of this selector.
285    pub fn select_n_relays<'s, 'd, R: rand::Rng>(
286        &'s self,
287        rng: &mut R,
288        n_relays: usize,
289        netdir: &'d NetDir,
290    ) -> (Vec<Relay<'d>>, SelectionInfo<'s>) {
291        with_possible_relaxation(
292            self,
293            |selector| {
294                let role = selector.weight_role();
295                let mut fc = FilterCounts::new(selector);
296                let relays = netdir
297                    .pick_n_relays(rng, n_relays, role, |r| selector.relay_usable(r, &mut fc));
298                (relays, fc)
299            },
300            |relays| !relays.is_empty(),
301        )
302    }
303
304    /// Check whether a given relay `r` obeys the restrictions of this selector,
305    /// updating `fc` according to which restrictions (if any) accepted or
306    /// rejected it.
307    ///
308    /// Requires that `fc` has the same length as self.restrictions.
309    ///
310    /// This differs from `<Self as RelayPredicate>::permits_relay` in taking
311    /// `fc` as an argument.
312    fn relay_usable(&self, r: &Relay<'_>, fc: &mut FilterCounts) -> bool {
313        debug_assert_eq!(self.n_restrictions(), fc.counts.len());
314
315        self.all_restrictions()
316            .zip(fc.counts.iter_mut())
317            .all(|(restr, restr_count)| {
318                restr_count.count(restr.restriction.low_level_predicate_permits_relay(r))
319            })
320    }
321
322    /// Return true if this selector has any flexible restrictions.
323    fn can_relax(&self) -> bool {
324        self.all_restrictions().any(|restr| !restr.strict)
325    }
326
327    /// Return a new selector created by relaxing every flexible restriction in
328    /// this selector.
329    fn relax(&self) -> Self {
330        let new_selector = RelaySelector {
331            usage: self.usage.maybe_relax(),
332            exclusion: self.exclusion.maybe_relax(),
333            other_restrictions: self
334                .other_restrictions
335                .iter()
336                .map(Restr::maybe_relax)
337                .collect(),
338        };
339        debug_assert!(!new_selector.can_relax());
340        new_selector
341    }
342}
343
344impl<'a> LowLevelRelayPredicate for RelaySelector<'a> {
345    fn low_level_predicate_permits_relay(&self, relay: &tor_netdir::Relay<'_>) -> bool {
346        self.all_restrictions()
347            .all(|r| r.restriction.low_level_predicate_permits_relay(relay))
348    }
349}
350
351/// Re-run relay selection, relaxing our selector as necessary.
352///
353/// This is a helper to implement our relay selection logic.
354/// We try to run `select` to find one or more random relays
355/// conforming to `selector`.
356/// If `ok` says that the result is good (by returning true),
357/// we return that result.
358/// Otherwise, we try to _relax_ the selector (if possible),
359/// and try again.
360/// If the selector can't be relaxed any further,
361/// we return the original (not-ok) result.
362//
363// TODO: Later, we might want to relax our restrictions one by one,
364// rather than all at once.
365fn with_possible_relaxation<'a, SEL, OK, T>(
366    selector: &'a RelaySelector,
367    mut select: SEL,
368    ok: OK,
369) -> (T, SelectionInfo<'a>)
370where
371    SEL: FnMut(&RelaySelector) -> (T, FilterCounts),
372    OK: Fn(&T) -> bool,
373{
374    let (outcome, count_strict) = select(selector);
375    let succeeded = ok(&outcome);
376    if succeeded || !selector.can_relax() {
377        let info = SelectionInfo {
378            first_try: count_strict,
379            relaxed_try: None,
380            succeeded,
381            in_selection: selector,
382        };
383        return (outcome, info);
384    }
385    let relaxed_selector = selector.relax();
386    let (relaxed_outcome, count_relaxed) = select(&relaxed_selector);
387    let info = SelectionInfo {
388        first_try: count_strict,
389        relaxed_try: Some(count_relaxed),
390        succeeded: ok(&relaxed_outcome),
391        in_selection: selector,
392    };
393    (relaxed_outcome, info)
394}
395
396#[cfg(test)]
397mod test {
398    // @@ begin test lint list maintained by maint/add_warning @@
399    #![allow(clippy::bool_assert_comparison)]
400    #![allow(clippy::clone_on_copy)]
401    #![allow(clippy::dbg_macro)]
402    #![allow(clippy::mixed_attributes_style)]
403    #![allow(clippy::print_stderr)]
404    #![allow(clippy::print_stdout)]
405    #![allow(clippy::single_char_pattern)]
406    #![allow(clippy::unwrap_used)]
407    #![allow(clippy::unchecked_duration_subtraction)]
408    #![allow(clippy::useless_vec)]
409    #![allow(clippy::needless_pass_by_value)]
410    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
411
412    use std::collections::HashSet;
413
414    use tor_basic_utils::test_rng::testing_rng;
415    use tor_linkspec::{HasRelayIds, RelayId};
416    use tor_netdir::{FamilyRules, Relay, SubnetConfig};
417
418    use super::*;
419    use crate::{
420        testing::{cfg, split_netdir, testnet},
421        RelaySelectionConfig, TargetPort,
422    };
423
424    #[test]
425    fn selector_as_predicate() {
426        let nd = testnet();
427        let id_4 = "$0404040404040404040404040404040404040404".parse().unwrap();
428        let usage = RelayUsage::middle_relay(None);
429        let exclusion = RelayExclusion::exclude_identities([id_4].into_iter().collect());
430        let sel = RelaySelector::new(usage.clone(), exclusion.clone());
431
432        let (yes, no) = split_netdir(&nd, &sel);
433        let p = |r: &Relay<'_>| {
434            usage.low_level_predicate_permits_relay(r)
435                && exclusion.low_level_predicate_permits_relay(r)
436        };
437        assert!(yes.iter().all(p));
438        assert!(no.iter().all(|r| !p(r)));
439    }
440
441    #[test]
442    fn selector_as_filter() {
443        let nd = testnet();
444        let id_4 = "$0404040404040404040404040404040404040404".parse().unwrap();
445        let usage = RelayUsage::middle_relay(None);
446        let exclusion = RelayExclusion::exclude_identities([id_4].into_iter().collect());
447        let sel = RelaySelector::new(usage.clone(), exclusion.clone());
448        let mut fc = FilterCounts::new(&sel);
449
450        let (yes, _no) = split_netdir(&nd, &sel);
451        let filtered: Vec<_> = nd
452            .relays()
453            .filter(|r| sel.relay_usable(r, &mut fc))
454            .collect();
455        assert_eq!(yes.len(), filtered.len());
456
457        let k1: HashSet<_> = yes.iter().map(|r| r.rsa_identity().unwrap()).collect();
458        let k2: HashSet<_> = filtered.iter().map(|r| r.rsa_identity().unwrap()).collect();
459        assert_eq!(k1, k2);
460
461        // 6 relays are rejected for not being suitable as a general-purpose middle relay
462        // (no Fast flag or no stable flag)
463        assert_eq!(fc.counts[0].n_rejected, 12);
464        // 1 additional relay is rejected for having id_4.
465        assert_eq!(fc.counts[1].n_rejected, 1);
466        // The remainder are accepted.
467        assert_eq!(fc.counts[1].n_accepted, yes.len());
468    }
469
470    #[test]
471    fn selector_pick_random() {
472        let nd = testnet();
473        let id_4 = "$0404040404040404040404040404040404040404".parse().unwrap();
474        let usage = RelayUsage::middle_relay(None);
475        let exclusion = RelayExclusion::exclude_identities([id_4].into_iter().collect());
476        let sel = RelaySelector::new(usage.clone(), exclusion.clone());
477
478        let (yes, _no) = split_netdir(&nd, &sel);
479        let k_yes: HashSet<_> = yes.iter().map(|r| r.rsa_identity().unwrap()).collect();
480        let p = |r: Relay<'_>| k_yes.contains(r.rsa_identity().unwrap());
481
482        let mut rng = testing_rng();
483        for _ in 0..50 {
484            // Select one relay; make sure it is ok.
485            let (r_rand, si) = sel.select_relay(&mut rng, &nd);
486            assert!(si.success());
487            assert!(!si.result_is_relaxed_success());
488            assert!(p(r_rand.unwrap()));
489
490            // Select 20 random relays; make sure they are distinct and ok.
491            let (rs_rand, si) = sel.select_n_relays(&mut rng, 20, &nd);
492            assert_eq!(rs_rand.len(), 20);
493            assert!(si.success());
494            assert!(!si.result_is_relaxed_success());
495            assert!(rs_rand.iter().cloned().all(p));
496            let k_got: HashSet<_> = rs_rand.iter().map(|r| r.rsa_identity().unwrap()).collect();
497            assert_eq!(k_got.len(), 20);
498        }
499    }
500
501    #[test]
502    fn selector_report() {
503        let nd = testnet();
504        let id_4 = "$0404040404040404040404040404040404040404".parse().unwrap();
505        let usage = RelayUsage::middle_relay(None);
506        let exclusion = RelayExclusion::exclude_identities([id_4].into_iter().collect());
507        let sel = RelaySelector::new(usage.clone(), exclusion.clone());
508
509        let mut rng = testing_rng();
510        let (_, si) = sel.select_relay(&mut rng, &nd);
511        assert_eq!(
512            si.to_string(),
513            "Success: rejected 12/40 as useless for middle relay; 1/28 as already selected"
514        );
515
516        // Now try failing.
517        // (The test network doesn't have ipv6 support.)
518        let unreachable_port = TargetPort::ipv6(80);
519        let sel = RelaySelector::new(
520            RelayUsage::exit_to_all_ports(&cfg(), vec![unreachable_port]),
521            exclusion.clone(),
522        );
523        let (r_none, si) = sel.select_relay(&mut rng, &nd);
524        assert!(r_none.is_none());
525        assert_eq!(
526            si.to_string(),
527            "Failed: rejected 40/40 as not exiting to desired ports; 0/0 as already selected"
528        );
529    }
530
531    #[test]
532    fn relax() {
533        let all_families = FamilyRules::all_family_info();
534
535        let nd = testnet();
536        let id_4: RelayId = "$0404040404040404040404040404040404040404".parse().unwrap();
537        let r4 = nd.by_id(&id_4).unwrap();
538        let usage = RelayUsage::middle_relay(None);
539        let very_silly_cfg = RelaySelectionConfig {
540            long_lived_ports: cfg().long_lived_ports,
541            // This should exclude everyone.
542            subnet_config: SubnetConfig::new(1, 1),
543        };
544        let exclude_relays = vec![r4];
545        let exclude_everyone = RelayExclusion::exclude_relays_in_same_family(
546            &very_silly_cfg,
547            exclude_relays,
548            all_families,
549        );
550
551        let mut sel = RelaySelector::new(usage.clone(), exclude_everyone.clone());
552        let mut rng = testing_rng();
553        let (r_none, _) = sel.select_relay(&mut rng, &nd);
554        assert!(r_none.is_none());
555
556        sel.mark_exclusion_flexible();
557        let (r_some, si) = sel.select_relay(&mut rng, &nd);
558        assert!(r_some.is_some());
559        assert_eq!(si.to_string(), "Failed at first, then succeeded. At first, rejected 12/40 as useless for middle relay; \
560                                    28/28 as in same family as already selected. \
561                                    After relaxing requirements, rejected 12/40 as useless for middle relay; \
562                                    0/28 as in same family as already selected");
563    }
564}