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    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
244
245    use super::*;
246
247    use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
248    use std::sync::Arc;
249    use std::time::Duration;
250
251    use tor_linkspec::ChannelMethod;
252    use tor_llcrypto::pk::ed25519::Ed25519Identity;
253    use tor_llcrypto::pk::rsa::RsaIdentity;
254    use tor_proto::channel::ChannelPaddingInstructionsUpdates;
255    use tor_proto::channel::kist::KistParams;
256
257    // Address we can use in tests.
258    const ADDR_A: SocketAddr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(1, 1, 1, 1), 443));
259
260    #[derive(Debug)]
261    struct FakeChannel {
262        usable: bool,
263        ids: RelayIds,
264    }
265
266    impl AbstractChannel for FakeChannel {
267        fn is_canonical(&self) -> bool {
268            unimplemented!()
269        }
270        fn is_canonical_to_peer(&self) -> bool {
271            unimplemented!()
272        }
273        fn is_usable(&self) -> bool {
274            self.usable
275        }
276        fn duration_unused(&self) -> Option<Duration> {
277            None
278        }
279        fn reparameterize(
280            &self,
281            _updates: Arc<ChannelPaddingInstructionsUpdates>,
282        ) -> tor_proto::Result<()> {
283            Ok(())
284        }
285        fn reparameterize_kist(&self, _kist_params: KistParams) -> tor_proto::Result<()> {
286            Ok(())
287        }
288        fn engage_padding_activities(&self) {}
289    }
290
291    impl HasRelayIds for FakeChannel {
292        fn identity(
293            &self,
294            key_type: tor_linkspec::RelayIdType,
295        ) -> Option<tor_linkspec::RelayIdRef<'_>> {
296            self.ids.identity(key_type)
297        }
298    }
299
300    #[derive(Clone, Debug)]
301    struct FakeBuildSpec {
302        ids: RelayIds,
303        addrs: Vec<SocketAddr>,
304    }
305
306    impl FakeBuildSpec {
307        fn new(ids: RelayIds, addrs: Vec<SocketAddr>) -> Self {
308            Self { ids, addrs }
309        }
310    }
311
312    impl HasRelayIds for FakeBuildSpec {
313        fn identity(
314            &self,
315            key_type: tor_linkspec::RelayIdType,
316        ) -> Option<tor_linkspec::RelayIdRef<'_>> {
317            self.ids.identity(key_type)
318        }
319    }
320
321    impl HasChanMethod for FakeBuildSpec {
322        fn chan_method(&self) -> ChannelMethod {
323            ChannelMethod::Direct(self.addrs.clone())
324        }
325    }
326
327    /// Assert that two `Option<&T>` point to the same data.
328    macro_rules! assert_opt_ptr_eq {
329        ($a:expr, $b:expr) => {
330            assert_opt_ptr_eq!($a, $b,);
331        };
332        ($a:expr, $b:expr, $($x:tt)*) => {
333            assert_eq!($a.map(std::ptr::from_ref), $b.map(std::ptr::from_ref), $($x)*);
334        };
335    }
336
337    /// Calls `f` with every permutation of `list`. Don't use with large lists :)
338    fn with_permutations<T>(list: &[T], mut f: impl FnMut(Vec<&T>)) {
339        use itertools::Itertools;
340        for new_list in list.iter().permutations(list.len()) {
341            f(new_list);
342        }
343    }
344
345    /// Helper to make a fake Ed identity from some bytes.
346    fn ed(a: &[u8]) -> Ed25519Identity {
347        let mut bytes = [0; 32];
348        bytes[0..a.len()].copy_from_slice(a);
349        bytes.into()
350    }
351
352    /// Helper to make a fake rsa identity from some bytes.
353    fn rsa(a: &[u8]) -> RsaIdentity {
354        let mut bytes = [0; 20];
355        bytes[0..a.len()].copy_from_slice(a);
356        bytes.into()
357    }
358
359    /// Helper to build a `RelayIds` to make tests shorter.
360    fn ids(
361        rsa: impl Into<Option<RsaIdentity>>,
362        ed: impl Into<Option<Ed25519Identity>>,
363    ) -> RelayIds {
364        let mut ids = tor_linkspec::RelayIdsBuilder::default();
365        if let Some(rsa) = rsa.into() {
366            ids.rsa_identity(rsa);
367        }
368        if let Some(ed) = ed.into() {
369            ids.ed_identity(ed);
370        }
371        ids.build().unwrap()
372    }
373
374    /// Create an open channel entry.
375    fn open_channel<C>(chan: C) -> OpenEntry<C> {
376        OpenEntry {
377            channel: Arc::new(chan),
378            max_unused_duration: Duration::from_secs(0),
379        }
380    }
381
382    /// Create a pending channel entry with the given IDs.
383    fn pending_channel(ids: RelayIds) -> PendingEntry {
384        use crate::mgr::state::UniqPendingChanId;
385        use futures::FutureExt;
386        use oneshot_fused_workaround as oneshot;
387
388        PendingEntry {
389            ids,
390            pending: oneshot::channel().1.shared(),
391            unique_id: UniqPendingChanId::new(),
392        }
393    }
394
395    #[test]
396    fn best_channel_usable_unusable() {
397        // two channels where only the first is usable
398        let channels = [
399            ChannelState::Open(open_channel(FakeChannel {
400                usable: true,
401                ids: ids(None, ed(b"A")),
402            })),
403            ChannelState::Open(open_channel(FakeChannel {
404                usable: false,
405                ids: ids(None, ed(b"A")),
406            })),
407        ];
408
409        // should return the usable channel
410        let target = FakeBuildSpec::new(ids(None, ed(b"A")), vec![ADDR_A]);
411        with_permutations(&channels, |x| {
412            assert_opt_ptr_eq!(choose_best_channel(x, &target), Some(&channels[0]));
413        });
414    }
415
416    #[test]
417    fn best_channel_open_pending() {
418        // a usable open channel and a pending channel
419        let channels = [
420            ChannelState::Open(open_channel(FakeChannel {
421                usable: true,
422                ids: ids(None, ed(b"A")),
423            })),
424            ChannelState::Building(pending_channel(ids(None, ed(b"A")))),
425        ];
426
427        // should return the open channel
428        let target = FakeBuildSpec::new(ids(None, ed(b"A")), vec![ADDR_A]);
429        with_permutations(&channels, |x| {
430            assert_opt_ptr_eq!(choose_best_channel(x, &target), Some(&channels[0]));
431        });
432
433        // an unusable open channel and a pending channel
434        let channels = [
435            ChannelState::Open(open_channel(FakeChannel {
436                usable: false,
437                ids: ids(None, ed(b"A")),
438            })),
439            ChannelState::Building(pending_channel(ids(None, ed(b"A")))),
440        ];
441
442        // should return the pending channel
443        let target = FakeBuildSpec::new(ids(None, ed(b"A")), vec![ADDR_A]);
444        with_permutations(&channels, |x| {
445            assert_opt_ptr_eq!(choose_best_channel(x, &target), Some(&channels[1]));
446        });
447    }
448
449    #[test]
450    fn best_channel_many() {
451        // some misc channels (as we make `choose_best_channel` more complex, hopeful we can add
452        // more channels here)
453        let channels = [
454            ChannelState::Open(open_channel(FakeChannel {
455                usable: false,
456                ids: ids(None, ed(b"A")),
457            })),
458            ChannelState::Open(open_channel(FakeChannel {
459                usable: true,
460                ids: ids(None, ed(b"A")),
461            })),
462            ChannelState::Building(pending_channel(ids(None, ed(b"A")))),
463            ChannelState::Building(pending_channel(ids(None, None))),
464        ];
465
466        // should return the open+usable channel
467        let target = FakeBuildSpec::new(ids(None, ed(b"A")), vec![ADDR_A]);
468        with_permutations(&channels, |x| {
469            assert_opt_ptr_eq!(choose_best_channel(x, &target), Some(&channels[1]));
470        });
471    }
472
473    #[test]
474    fn test_open_channel_is_allowed() {
475        // target with an ed relay id
476        let target = FakeBuildSpec::new(ids(None, ed(b"A")), vec![ADDR_A]);
477
478        // not allowed: unusable channel
479        assert!(!open_channel_is_allowed(
480            &open_channel(FakeChannel {
481                usable: false,
482                ids: ids(None, ed(b"A")),
483            }),
484            &target,
485        ));
486
487        // allowed: usable channel with correct relay id
488        assert!(open_channel_is_allowed(
489            &open_channel(FakeChannel {
490                usable: true,
491                ids: ids(None, ed(b"A")),
492            }),
493            &target,
494        ));
495
496        // not allowed: usable channel with incorrect relay id
497        assert!(!open_channel_is_allowed(
498            &open_channel(FakeChannel {
499                usable: true,
500                ids: ids(None, ed(b"B")),
501            }),
502            &target,
503        ));
504
505        // not allowed: usable channel with no relay ids
506        assert!(!open_channel_is_allowed(
507            &open_channel(FakeChannel {
508                usable: true,
509                ids: ids(None, None),
510            }),
511            &target,
512        ));
513
514        // allowed: usable channel with additional relay id
515        assert!(open_channel_is_allowed(
516            &open_channel(FakeChannel {
517                usable: true,
518                ids: ids(rsa(b"X"), ed(b"A")),
519            }),
520            &target,
521        ));
522
523        // not allowed: usable channel with missing ed relay id
524        assert!(!open_channel_is_allowed(
525            &open_channel(FakeChannel {
526                usable: true,
527                ids: ids(rsa(b"X"), None),
528            }),
529            &target,
530        ));
531
532        // target with no relay id
533        let target = FakeBuildSpec::new(ids(None, None), vec![ADDR_A]);
534
535        // not allowed: unusable channel
536        assert!(!open_channel_is_allowed(
537            &open_channel(FakeChannel {
538                usable: false,
539                ids: ids(None, None),
540            }),
541            &target,
542        ));
543
544        // allowed: usable channel with no relay ids
545        assert!(open_channel_is_allowed(
546            &open_channel(FakeChannel {
547                usable: true,
548                ids: ids(None, None),
549            }),
550            &target,
551        ));
552
553        // target with multiple relay ids
554        let target = FakeBuildSpec::new(ids(rsa(b"X"), ed(b"A")), vec![ADDR_A]);
555
556        // not allowed: unusable channel
557        assert!(!open_channel_is_allowed(
558            &open_channel(FakeChannel {
559                usable: false,
560                ids: ids(rsa(b"X"), ed(b"A")),
561            }),
562            &target,
563        ));
564
565        // allowed: usable channel with correct relay ids
566        assert!(open_channel_is_allowed(
567            &open_channel(FakeChannel {
568                usable: true,
569                ids: ids(rsa(b"X"), ed(b"A")),
570            }),
571            &target,
572        ));
573
574        // not allowed: usable channel with partial relay ids
575        assert!(!open_channel_is_allowed(
576            &open_channel(FakeChannel {
577                usable: true,
578                ids: ids(None, ed(b"A")),
579            }),
580            &target,
581        ));
582        assert!(!open_channel_is_allowed(
583            &open_channel(FakeChannel {
584                usable: true,
585                ids: ids(rsa(b"X"), None),
586            }),
587            &target,
588        ));
589
590        // not allowed: usable channel with one incorrect relay id
591        assert!(!open_channel_is_allowed(
592            &open_channel(FakeChannel {
593                usable: true,
594                ids: ids(rsa(b"X"), ed(b"B")),
595            }),
596            &target,
597        ));
598        assert!(!open_channel_is_allowed(
599            &open_channel(FakeChannel {
600                usable: true,
601                ids: ids(rsa(b"Y"), ed(b"A")),
602            }),
603            &target,
604        ));
605    }
606
607    #[test]
608    fn test_pending_channel_maybe_allowed() {
609        // target with an ed relay id
610        let target = FakeBuildSpec::new(ids(None, ed(b"A")), vec![ADDR_A]);
611
612        // allowed: channel with same relay id
613        assert!(pending_channel_maybe_allowed(
614            &pending_channel(ids(None, ed(b"A"))),
615            &target,
616        ));
617
618        // not allowed: channel with additional relay id
619        assert!(!pending_channel_maybe_allowed(
620            &pending_channel(ids(rsa(b"X"), ed(b"A"))),
621            &target,
622        ));
623
624        // target with multiple relay ids
625        let target = FakeBuildSpec::new(ids(rsa(b"X"), ed(b"A")), vec![ADDR_A]);
626
627        // allowed: channel with same relay ids
628        assert!(pending_channel_maybe_allowed(
629            &pending_channel(ids(rsa(b"X"), ed(b"A"))),
630            &target,
631        ));
632
633        // allowed: channel with fewer relay ids
634        assert!(pending_channel_maybe_allowed(
635            &pending_channel(ids(None, ed(b"A"))),
636            &target,
637        ));
638        assert!(pending_channel_maybe_allowed(
639            &pending_channel(ids(rsa(b"X"), None)),
640            &target,
641        ));
642
643        // not allowed: channel with no relay ids
644        assert!(!pending_channel_maybe_allowed(
645            &pending_channel(ids(None, None)),
646            &target,
647        ));
648
649        // target with no relay ids
650        let target = FakeBuildSpec::new(ids(None, None), vec![ADDR_A]);
651
652        // not allowed: channel with a relay id
653        assert!(!pending_channel_maybe_allowed(
654            &pending_channel(ids(None, ed(b"A"))),
655            &target,
656        ));
657
658        // not allowed: channel with no relay ids
659        assert!(!pending_channel_maybe_allowed(
660            &pending_channel(ids(None, None)),
661            &target,
662        ));
663    }
664}