Skip to main content

tor_chanmgr/mgr/
select.rs

1//! Logic for filtering and selecting channels in order to find suitable channels for a target.
2
3use crate::mgr::AbstractChannel;
4use crate::mgr::state::{ChannelState, OpenEntry, PendingEntry};
5use tor_linkspec::{HasChanMethod, HasRelayIds, RelayIds};
6
7/// Returns `true` if the open channel is allowed to be used for a new channel request to the
8/// target.
9pub(crate) fn open_channel_is_allowed<C: AbstractChannel>(
10    chan: &OpenEntry<C>,
11    target: &(impl HasRelayIds + HasChanMethod),
12) -> bool {
13    Some(chan)
14        // only usable channels
15        .filter(|entry| entry.channel.is_usable())
16        // only channels which have *all* the relay ids of `target`
17        .filter(|entry| entry.channel.has_all_relay_ids_from(target))
18        // TODO: only channels that satisfy the torspec rules at
19        // https://spec.torproject.org/tor-spec/creating-circuits.html#canonical-connections
20        .filter(|_entry| {
21            // Any of:
22            // - the IP matches the requested IP
23            // - the relay knows that the IP of the connection it's using is canonical because it
24            //   was listed in the NETINFO cell
25            // - the IP matches the relay address in the consensus
26            true
27        })
28        .is_some()
29}
30
31/// Returns `true` if the pending channel could possibly be used for a new channel request to the
32/// target. You still need to verify the final built channel with [`open_channel_is_allowed`] before
33/// using it.
34pub(crate) fn pending_channel_maybe_allowed(
35    chan: &PendingEntry,
36    target: &(impl HasRelayIds + HasChanMethod),
37) -> bool {
38    /// An empty [`RelayIds`].
39    const EMPTY_IDS: RelayIds = RelayIds::empty();
40
41    // TODO RELAY: The comments and behaviour below assume that it's better to create a new channel
42    // than to wait around for a channel which may or may not end up being usable for `target`. This
43    // has the benefit that malicious circuit extension requests won't delay legitimate circuit
44    // extension requests, but also means that we could end up creating more channels than
45    // necessary. This is different from C-tor, which will wait for a channel even if that channel
46    // might not end up being usable for `target`. For example in tor's `channel_get_for_extend`,
47    // tor will wait for an "in progress" channel if all of the following are true:
48    //
49    // - The requested ids are a subset of the channel's ids. (Note that in the comments below we
50    //   require it to be a superset, not a subset.)
51    // - The requested IPv4 or IPv6 address matches either the channel's IPv4 or IPv6 address. (Note
52    //   that in the comments below, we require `target`s addresses to exactly match.)
53    //
54    // It might be good to re-evaluate what behaviour we want as we implement more channel code.
55    //
56    // NOTE (opara): It has been decided that C-tor's approach would be better. See the thread at:
57    // https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/2544#note_3094696
58
59    // We want to avoid returning pending channels that were initially created from malicious
60    // channel requests (for example from malicious relay-extend requests) that build channels which
61    // will never complete successfully. Two cases where this can happen are:
62    // 1. A malicious channel request asks us to build a channel to a target with a correct relay id
63    //    and address, but also an additional incorrect relay id. Later when the target sends its
64    //    CERTS cell, all of the relay ids won't match and the channel will fail to build. We don't
65    //    want to assign non-malicious channel requests to this pending channel that will eventually
66    //    fail to build.
67    // 2. A malicious channel request asks us to build a channel to a target with an incorrect
68    //    address. This pending channel may stall. We don't want to assign non-malicious channel
69    //    requests to this pending channel that will stall for potentially a long time.
70    Some(chan)
71        // Only channels where `target`s relay ids are a superset of `entry`s relay ids.
72        // - Hopefully the built channel will gain the additional ids that are requested by
73        //   `target`. This should happen in most cases where none of the channels are made
74        //   maliciously, since the `target` should return all of its relay ids in its CERTS cell.
75        // - (Addressing 1. above) By only returning pending channels that have a subset of
76        //   `target`s relay ids, we ensure that the returned pending channel does not have
77        //   additional incorrect relay ids that will intentionally cause the pending channel to
78        //   fail.
79        // - If the built channel does not gain the remaining ids required by `target, then we won't
80        //   be able to use this channel for the channel request to `target`. But we won't be able
81        //   to create a new channel either, since we know that that a new channel also won't have
82        //   all of the relay ids. So this channel request was doomed from the start.
83        // - If the built channel gains additional ids that `target` doesn't have, that's fine and
84        //   we can still use the channel for `target`.
85        .filter(|entry| target.has_all_relay_ids_from(&entry.ids))
86        // TODO: Only channels which have the exact same address list as `target` (the two sets of
87        // addresses must match exactly).
88        // - While an EXTEND2 message usually only contains one IPv4 and IPv6 address, `target`
89        //   (which is a `HasAddrs`) may have more addresses. According to tor-spec, an EXTEND2
90        //   message can contain multiple IPv4 and IPv6 addresses:
91        //   > Nodes MUST ignore unrecognized specifiers, and MUST accept multiple instances of
92        //   > specifiers other than 'legacy identity' and 'Ed25519 identity'. (Nodes SHOULD reject
93        //   > link specifier lists that include multiple instances of either one of those
94        //   > specifiers.)
95        // - (Addressing 2. above) By only returning pending channels that have exactly the same
96        //   addresses, we ensure that the returned pending channel does not have any incorrect
97        //   addresses that will cause the pending channel to stall.
98        // - If the pending channel had additional addresses compared to `target`, the channel could
99        //   get built using an address that is not valid for `target` and we wouldn't be able to
100        //   use the built channel.
101        // - If the pending channel had fewer addresses compared to `target`, the channel would have
102        //   a lower possibility of building successfully compared to a newly created channel to
103        //   `target`, so this would not be a good channel for us to return.
104        .filter(|_entry| true)
105        // Don't allow a pending channel that has no relay ids. I don't have a good reason for
106        // excluding this, other than "it seems weird".
107        .filter(|entry| entry.ids != EMPTY_IDS)
108        .is_some()
109}
110
111/// Returns the best channel for `target`.
112// TODO: remove me when the below TODOs are implemented
113#[allow(clippy::only_used_in_recursion)]
114pub(crate) fn choose_best_channel<'a, C: AbstractChannel>(
115    channels: impl IntoIterator<Item = &'a ChannelState<C>>,
116    target: &(impl HasRelayIds + HasChanMethod),
117) -> Option<&'a ChannelState<C>> {
118    use ChannelState::*;
119    use std::cmp::Ordering;
120
121    let channels = channels.into_iter();
122
123    /// Compare two channels to determine the better channel for `target`.
124    fn choose_channel<C: AbstractChannel>(
125        a: &&ChannelState<C>,
126        b: &&ChannelState<C>,
127        target: &(impl HasRelayIds + HasChanMethod),
128    ) -> Choice {
129        // TODO: follow `channel_is_better` in C tor
130        match (a, b) {
131            // if the open channel is not usable, prefer the pending channel
132            (Open(a), Building(_b)) if !a.channel.is_usable() => Choice::Second,
133            // otherwise prefer the open channel
134            (Open(_a), Building(_b)) => Choice::First,
135
136            // the logic above, but reversed
137            (Building(_), Open(_)) => choose_channel(b, a, target).reverse(),
138
139            // not much info to help choose when both channels are pending, but this should be rare
140            (Building(_a), Building(_b)) => Choice::Either,
141
142            // both channels are open
143            (Open(a), Open(b)) => {
144                let a_is_usable = a.channel.is_usable();
145                let b_is_usable = b.channel.is_usable();
146
147                // if neither open channel is usable, don't take preference
148                if !a_is_usable && !b_is_usable {
149                    return Choice::Either;
150                }
151
152                // prefer a channel that is usable
153                if !a_is_usable {
154                    return Choice::Second;
155                }
156                if !b_is_usable {
157                    return Choice::First;
158                }
159
160                // Prefer a channel that we see as canonical.
161                let a_is_canonical = a.channel.is_canonical();
162                let b_is_canonical = b.channel.is_canonical();
163
164                if a_is_canonical && !b_is_canonical {
165                    return Choice::First;
166                }
167                if !a_is_canonical && b_is_canonical {
168                    return Choice::Second;
169                }
170
171                // Prefer a channel that the peer sees as canonical.
172                let a_is_canonical_to_peer = a.channel.is_canonical_to_peer();
173                let b_is_canonical_to_peer = b.channel.is_canonical_to_peer();
174
175                if a_is_canonical_to_peer && !b_is_canonical_to_peer {
176                    return Choice::First;
177                }
178                if !a_is_canonical_to_peer && b_is_canonical_to_peer {
179                    return Choice::Second;
180                }
181
182                // TODO: prefer a channel where the address matches the target
183
184                // TODO: prefer older channels
185
186                // TODO: use number of circuits as tie-breaker?
187
188                Choice::Either
189            }
190        }
191    }
192
193    // preferred channels will be ordered higher, and we choose the max
194    channels.max_by(|a, b| match choose_channel(a, b, target) {
195        Choice::First => Ordering::Greater,
196        Choice::Second => Ordering::Less,
197        Choice::Either => Ordering::Equal,
198    })
199}
200
201/// Similar to [`Ordering`](std::cmp::Ordering), but is easier to reason about when comparing two
202/// objects that don't have a numeric sense of ordering (ex: returning `Greater` is confusing if the
203/// ordering isn't numeric).
204#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
205enum Choice {
206    /// Choose the first.
207    First,
208    /// Choose the second.
209    Second,
210    /// Choose either.
211    Either,
212}
213
214impl Choice {
215    /// Reverses the `Choice`.
216    ///
217    /// - `First` becomes `Second`.
218    /// - `Second` becomes `First`.
219    /// - `Either` becomes `Either`.
220    fn reverse(self) -> Self {
221        match self {
222            Self::First => Self::Second,
223            Self::Second => Self::First,
224            Self::Either => Self::Either,
225        }
226    }
227}
228
229#[cfg(test)]
230mod test {
231    // @@ begin test lint list maintained by maint/add_warning @@
232    #![allow(clippy::bool_assert_comparison)]
233    #![allow(clippy::clone_on_copy)]
234    #![allow(clippy::dbg_macro)]
235    #![allow(clippy::mixed_attributes_style)]
236    #![allow(clippy::print_stderr)]
237    #![allow(clippy::print_stdout)]
238    #![allow(clippy::single_char_pattern)]
239    #![allow(clippy::unwrap_used)]
240    #![allow(clippy::unchecked_time_subtraction)]
241    #![allow(clippy::useless_vec)]
242    #![allow(clippy::needless_pass_by_value)]
243    #![allow(clippy::string_slice)] // See arti#2571
244    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
245
246    use super::*;
247
248    use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
249    use std::sync::Arc;
250    use std::time::Duration;
251
252    use tor_linkspec::ChannelMethod;
253    use tor_llcrypto::pk::ed25519::Ed25519Identity;
254    use tor_llcrypto::pk::rsa::RsaIdentity;
255    use tor_proto::channel::ChannelPaddingInstructionsUpdates;
256    use tor_proto::channel::kist::KistParams;
257
258    // Address we can use in tests.
259    const ADDR_A: SocketAddr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(1, 1, 1, 1), 443));
260
261    #[derive(Debug)]
262    struct FakeChannel {
263        usable: bool,
264        ids: RelayIds,
265    }
266
267    impl AbstractChannel for FakeChannel {
268        fn is_canonical(&self) -> bool {
269            unimplemented!()
270        }
271        fn is_canonical_to_peer(&self) -> bool {
272            unimplemented!()
273        }
274        fn is_usable(&self) -> bool {
275            self.usable
276        }
277        fn duration_unused(&self) -> Option<Duration> {
278            None
279        }
280        fn reparameterize(
281            &self,
282            _updates: Arc<ChannelPaddingInstructionsUpdates>,
283        ) -> tor_proto::Result<()> {
284            Ok(())
285        }
286        fn reparameterize_kist(&self, _kist_params: KistParams) -> tor_proto::Result<()> {
287            Ok(())
288        }
289        fn engage_padding_activities(&self) {}
290    }
291
292    impl HasRelayIds for FakeChannel {
293        fn identity(
294            &self,
295            key_type: tor_linkspec::RelayIdType,
296        ) -> Option<tor_linkspec::RelayIdRef<'_>> {
297            self.ids.identity(key_type)
298        }
299    }
300
301    #[derive(Clone, Debug)]
302    struct FakeBuildSpec {
303        ids: RelayIds,
304        addrs: Vec<SocketAddr>,
305    }
306
307    impl FakeBuildSpec {
308        fn new(ids: RelayIds, addrs: Vec<SocketAddr>) -> Self {
309            Self { ids, addrs }
310        }
311    }
312
313    impl HasRelayIds for FakeBuildSpec {
314        fn identity(
315            &self,
316            key_type: tor_linkspec::RelayIdType,
317        ) -> Option<tor_linkspec::RelayIdRef<'_>> {
318            self.ids.identity(key_type)
319        }
320    }
321
322    impl HasChanMethod for FakeBuildSpec {
323        fn chan_method(&self) -> ChannelMethod {
324            ChannelMethod::Direct(self.addrs.clone())
325        }
326    }
327
328    /// Assert that two `Option<&T>` point to the same data.
329    macro_rules! assert_opt_ptr_eq {
330        ($a:expr, $b:expr) => {
331            assert_opt_ptr_eq!($a, $b,);
332        };
333        ($a:expr, $b:expr, $($x:tt)*) => {
334            assert_eq!($a.map(std::ptr::from_ref), $b.map(std::ptr::from_ref), $($x)*);
335        };
336    }
337
338    /// Calls `f` with every permutation of `list`. Don't use with large lists :)
339    fn with_permutations<T>(list: &[T], mut f: impl FnMut(Vec<&T>)) {
340        use itertools::Itertools;
341        for new_list in list.iter().permutations(list.len()) {
342            f(new_list);
343        }
344    }
345
346    /// Helper to make a fake Ed identity from some bytes.
347    fn ed(a: &[u8]) -> Ed25519Identity {
348        let mut bytes = [0; 32];
349        bytes[0..a.len()].copy_from_slice(a);
350        bytes.into()
351    }
352
353    /// Helper to make a fake rsa identity from some bytes.
354    fn rsa(a: &[u8]) -> RsaIdentity {
355        let mut bytes = [0; 20];
356        bytes[0..a.len()].copy_from_slice(a);
357        bytes.into()
358    }
359
360    /// Helper to build a `RelayIds` to make tests shorter.
361    fn ids(
362        rsa: impl Into<Option<RsaIdentity>>,
363        ed: impl Into<Option<Ed25519Identity>>,
364    ) -> RelayIds {
365        let mut ids = tor_linkspec::RelayIdsBuilder::default();
366        if let Some(rsa) = rsa.into() {
367            ids.rsa_identity(rsa);
368        }
369        if let Some(ed) = ed.into() {
370            ids.ed_identity(ed);
371        }
372        ids.build().unwrap()
373    }
374
375    /// Create an open channel entry.
376    fn open_channel<C>(chan: C) -> OpenEntry<C> {
377        OpenEntry {
378            channel: Arc::new(chan),
379            max_unused_duration: Duration::from_secs(0),
380        }
381    }
382
383    /// Create a pending channel entry with the given IDs.
384    fn pending_channel(ids: RelayIds) -> PendingEntry {
385        use crate::mgr::state::UniqPendingChanId;
386        use futures::FutureExt;
387        use oneshot_fused_workaround as oneshot;
388
389        PendingEntry {
390            ids,
391            pending: oneshot::channel().1.shared(),
392            unique_id: UniqPendingChanId::new(),
393        }
394    }
395
396    #[test]
397    fn best_channel_usable_unusable() {
398        // two channels where only the first is usable
399        let channels = [
400            ChannelState::Open(open_channel(FakeChannel {
401                usable: true,
402                ids: ids(None, ed(b"A")),
403            })),
404            ChannelState::Open(open_channel(FakeChannel {
405                usable: false,
406                ids: ids(None, ed(b"A")),
407            })),
408        ];
409
410        // should return the usable channel
411        let target = FakeBuildSpec::new(ids(None, ed(b"A")), vec![ADDR_A]);
412        with_permutations(&channels, |x| {
413            assert_opt_ptr_eq!(choose_best_channel(x, &target), Some(&channels[0]));
414        });
415    }
416
417    #[test]
418    fn best_channel_open_pending() {
419        // a usable open channel and a pending channel
420        let channels = [
421            ChannelState::Open(open_channel(FakeChannel {
422                usable: true,
423                ids: ids(None, ed(b"A")),
424            })),
425            ChannelState::Building(pending_channel(ids(None, ed(b"A")))),
426        ];
427
428        // should return the open channel
429        let target = FakeBuildSpec::new(ids(None, ed(b"A")), vec![ADDR_A]);
430        with_permutations(&channels, |x| {
431            assert_opt_ptr_eq!(choose_best_channel(x, &target), Some(&channels[0]));
432        });
433
434        // an unusable open channel and a pending channel
435        let channels = [
436            ChannelState::Open(open_channel(FakeChannel {
437                usable: false,
438                ids: ids(None, ed(b"A")),
439            })),
440            ChannelState::Building(pending_channel(ids(None, ed(b"A")))),
441        ];
442
443        // should return the pending channel
444        let target = FakeBuildSpec::new(ids(None, ed(b"A")), vec![ADDR_A]);
445        with_permutations(&channels, |x| {
446            assert_opt_ptr_eq!(choose_best_channel(x, &target), Some(&channels[1]));
447        });
448    }
449
450    #[test]
451    fn best_channel_many() {
452        // some misc channels (as we make `choose_best_channel` more complex, hopeful we can add
453        // more channels here)
454        let channels = [
455            ChannelState::Open(open_channel(FakeChannel {
456                usable: false,
457                ids: ids(None, ed(b"A")),
458            })),
459            ChannelState::Open(open_channel(FakeChannel {
460                usable: true,
461                ids: ids(None, ed(b"A")),
462            })),
463            ChannelState::Building(pending_channel(ids(None, ed(b"A")))),
464            ChannelState::Building(pending_channel(ids(None, None))),
465        ];
466
467        // should return the open+usable channel
468        let target = FakeBuildSpec::new(ids(None, ed(b"A")), vec![ADDR_A]);
469        with_permutations(&channels, |x| {
470            assert_opt_ptr_eq!(choose_best_channel(x, &target), Some(&channels[1]));
471        });
472    }
473
474    #[test]
475    fn test_open_channel_is_allowed() {
476        // target with an ed relay id
477        let target = FakeBuildSpec::new(ids(None, ed(b"A")), vec![ADDR_A]);
478
479        // not allowed: unusable channel
480        assert!(!open_channel_is_allowed(
481            &open_channel(FakeChannel {
482                usable: false,
483                ids: ids(None, ed(b"A")),
484            }),
485            &target,
486        ));
487
488        // allowed: usable channel with correct relay id
489        assert!(open_channel_is_allowed(
490            &open_channel(FakeChannel {
491                usable: true,
492                ids: ids(None, ed(b"A")),
493            }),
494            &target,
495        ));
496
497        // not allowed: usable channel with incorrect relay id
498        assert!(!open_channel_is_allowed(
499            &open_channel(FakeChannel {
500                usable: true,
501                ids: ids(None, ed(b"B")),
502            }),
503            &target,
504        ));
505
506        // not allowed: usable channel with no relay ids
507        assert!(!open_channel_is_allowed(
508            &open_channel(FakeChannel {
509                usable: true,
510                ids: ids(None, None),
511            }),
512            &target,
513        ));
514
515        // allowed: usable channel with additional relay id
516        assert!(open_channel_is_allowed(
517            &open_channel(FakeChannel {
518                usable: true,
519                ids: ids(rsa(b"X"), ed(b"A")),
520            }),
521            &target,
522        ));
523
524        // not allowed: usable channel with missing ed relay id
525        assert!(!open_channel_is_allowed(
526            &open_channel(FakeChannel {
527                usable: true,
528                ids: ids(rsa(b"X"), None),
529            }),
530            &target,
531        ));
532
533        // target with no relay id
534        let target = FakeBuildSpec::new(ids(None, None), vec![ADDR_A]);
535
536        // not allowed: unusable channel
537        assert!(!open_channel_is_allowed(
538            &open_channel(FakeChannel {
539                usable: false,
540                ids: ids(None, None),
541            }),
542            &target,
543        ));
544
545        // allowed: usable channel with no relay ids
546        assert!(open_channel_is_allowed(
547            &open_channel(FakeChannel {
548                usable: true,
549                ids: ids(None, None),
550            }),
551            &target,
552        ));
553
554        // target with multiple relay ids
555        let target = FakeBuildSpec::new(ids(rsa(b"X"), ed(b"A")), vec![ADDR_A]);
556
557        // not allowed: unusable channel
558        assert!(!open_channel_is_allowed(
559            &open_channel(FakeChannel {
560                usable: false,
561                ids: ids(rsa(b"X"), ed(b"A")),
562            }),
563            &target,
564        ));
565
566        // allowed: usable channel with correct relay ids
567        assert!(open_channel_is_allowed(
568            &open_channel(FakeChannel {
569                usable: true,
570                ids: ids(rsa(b"X"), ed(b"A")),
571            }),
572            &target,
573        ));
574
575        // not allowed: usable channel with partial relay ids
576        assert!(!open_channel_is_allowed(
577            &open_channel(FakeChannel {
578                usable: true,
579                ids: ids(None, ed(b"A")),
580            }),
581            &target,
582        ));
583        assert!(!open_channel_is_allowed(
584            &open_channel(FakeChannel {
585                usable: true,
586                ids: ids(rsa(b"X"), None),
587            }),
588            &target,
589        ));
590
591        // not allowed: usable channel with one incorrect relay id
592        assert!(!open_channel_is_allowed(
593            &open_channel(FakeChannel {
594                usable: true,
595                ids: ids(rsa(b"X"), ed(b"B")),
596            }),
597            &target,
598        ));
599        assert!(!open_channel_is_allowed(
600            &open_channel(FakeChannel {
601                usable: true,
602                ids: ids(rsa(b"Y"), ed(b"A")),
603            }),
604            &target,
605        ));
606    }
607
608    #[test]
609    fn test_pending_channel_maybe_allowed() {
610        // target with an ed relay id
611        let target = FakeBuildSpec::new(ids(None, ed(b"A")), vec![ADDR_A]);
612
613        // allowed: channel with same relay id
614        assert!(pending_channel_maybe_allowed(
615            &pending_channel(ids(None, ed(b"A"))),
616            &target,
617        ));
618
619        // not allowed: channel with additional relay id
620        assert!(!pending_channel_maybe_allowed(
621            &pending_channel(ids(rsa(b"X"), ed(b"A"))),
622            &target,
623        ));
624
625        // target with multiple relay ids
626        let target = FakeBuildSpec::new(ids(rsa(b"X"), ed(b"A")), vec![ADDR_A]);
627
628        // allowed: channel with same relay ids
629        assert!(pending_channel_maybe_allowed(
630            &pending_channel(ids(rsa(b"X"), ed(b"A"))),
631            &target,
632        ));
633
634        // allowed: channel with fewer relay ids
635        assert!(pending_channel_maybe_allowed(
636            &pending_channel(ids(None, ed(b"A"))),
637            &target,
638        ));
639        assert!(pending_channel_maybe_allowed(
640            &pending_channel(ids(rsa(b"X"), None)),
641            &target,
642        ));
643
644        // not allowed: channel with no relay ids
645        assert!(!pending_channel_maybe_allowed(
646            &pending_channel(ids(None, None)),
647            &target,
648        ));
649
650        // target with no relay ids
651        let target = FakeBuildSpec::new(ids(None, None), vec![ADDR_A]);
652
653        // not allowed: channel with a relay id
654        assert!(!pending_channel_maybe_allowed(
655            &pending_channel(ids(None, ed(b"A"))),
656            &target,
657        ));
658
659        // not allowed: channel with no relay ids
660        assert!(!pending_channel_maybe_allowed(
661            &pending_channel(ids(None, None)),
662            &target,
663        ));
664    }
665}