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