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}