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}