1use rand::Rng;
4use std::fmt::{self, Display};
5use std::sync::Arc;
6use std::time::SystemTime;
7use tracing::trace;
8#[cfg(not(feature = "geoip"))]
9use void::Void;
10
11use crate::path::{dirpath::DirPathBuilder, exitpath::ExitPathBuilder, TorPath};
12use tor_chanmgr::ChannelUsage;
13#[cfg(feature = "geoip")]
14use tor_error::internal;
15use tor_guardmgr::{GuardMgr, GuardMonitor, GuardUsable};
16use tor_netdir::Relay;
17use tor_netdoc::types::policy::PortPolicy;
18use tor_rtcompat::Runtime;
19#[cfg(feature = "hs-common")]
20use {crate::path::hspath::HsPathBuilder, crate::HsCircKind, crate::HsCircStemKind};
21
22#[cfg(feature = "specific-relay")]
23use tor_linkspec::{HasChanMethod, HasRelayIds};
24
25#[cfg(feature = "geoip")]
26use tor_geoip::CountryCode;
27#[cfg(not(feature = "geoip"))]
36pub(crate) type CountryCode = Void;
37
38#[cfg(any(feature = "specific-relay", feature = "hs-common"))]
39use tor_linkspec::OwnedChanTarget;
40
41#[cfg(all(feature = "vanguards", feature = "hs-common"))]
42use tor_guardmgr::vanguards::VanguardMgr;
43
44use crate::isolation::{IsolationHelper, StreamIsolation};
45use crate::mgr::{AbstractCirc, OpenEntry, RestrictionFailed};
46use crate::Result;
47
48pub use tor_relay_selection::TargetPort;
49
50#[derive(Clone, Debug, PartialEq, Eq)]
52pub(crate) struct ExitPolicy {
53 v4: Arc<PortPolicy>,
55 v6: Arc<PortPolicy>,
57}
58
59#[derive(Debug, Clone, Default)]
63pub struct TargetPorts(Vec<TargetPort>);
64
65impl From<&'_ [TargetPort]> for TargetPorts {
66 fn from(ports: &'_ [TargetPort]) -> Self {
67 TargetPorts(ports.into())
68 }
69}
70
71impl Display for TargetPorts {
72 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
73 let brackets = self.0.len() != 1;
74 if brackets {
75 write!(f, "[")?;
76 }
77 for (i, port) in self.0.iter().enumerate() {
78 if i > 0 {
79 write!(f, ",")?;
80 }
81 write!(f, "{}", port)?;
82 }
83 if brackets {
84 write!(f, "]")?;
85 }
86 Ok(())
87 }
88}
89
90impl ExitPolicy {
91 pub(crate) fn from_relay(relay: &Relay<'_>) -> Self {
93 Self {
97 v4: relay.low_level_details().ipv4_policy(),
98 v6: relay.low_level_details().ipv6_policy(),
99 }
100 }
101
102 #[cfg(test)]
104 pub(crate) fn from_target_ports(target_ports: &TargetPorts) -> Self {
105 let (v6_ports, v4_ports) = target_ports
106 .0
107 .iter()
108 .partition::<Vec<TargetPort>, _>(|port| port.ipv6);
109
110 Self {
111 v4: PortPolicy::from_allowed_port_list(v4_ports.iter().map(|port| port.port).collect())
112 .intern(),
113 v6: PortPolicy::from_allowed_port_list(v6_ports.iter().map(|port| port.port).collect())
114 .intern(),
115 }
116 }
117
118 fn allows_port(&self, p: TargetPort) -> bool {
120 let policy = if p.ipv6 { &self.v6 } else { &self.v4 };
121 policy.allows_port(p.port)
122 }
123
124 fn allows_some_port(&self) -> bool {
126 self.v4.allows_some_port() || self.v6.allows_some_port()
127 }
128}
129
130#[derive(Clone, Debug)]
135pub(crate) enum TargetCircUsage {
136 Dir,
138 Exit {
140 ports: Vec<TargetPort>,
145 isolation: StreamIsolation,
147 country_code: Option<CountryCode>,
149 require_stability: bool,
153 },
154 TimeoutTesting,
156 Preemptive {
164 port: Option<TargetPort>,
168 circs: usize,
170 require_stability: bool,
173 },
174 #[cfg(feature = "specific-relay")]
177 DirSpecificTarget(OwnedChanTarget),
178
179 #[cfg(feature = "hs-common")]
182 HsCircBase {
183 compatible_with_target: Option<OwnedChanTarget>,
190 stem_kind: HsCircStemKind,
192 circ_kind: Option<HsCircKind>,
195 },
196}
197
198#[derive(Clone, Debug)]
203pub(crate) enum SupportedCircUsage {
204 Dir,
206 Exit {
208 policy: ExitPolicy,
210 isolation: Option<StreamIsolation>,
213 country_code: Option<CountryCode>,
215 all_relays_stable: bool,
219 },
220 NoUsage,
222 #[cfg(feature = "hs-common")]
226 HsOnly,
227 #[cfg(feature = "specific-relay")]
230 DirSpecificTarget(OwnedChanTarget),
231}
232
233impl TargetCircUsage {
234 pub(crate) fn build_path<'a, R: Rng, RT: Runtime>(
237 &self,
238 rng: &mut R,
239 netdir: crate::DirInfo<'a>,
240 guards: &GuardMgr<RT>,
241 #[cfg(all(feature = "vanguards", feature = "hs-common"))] vanguards: &VanguardMgr<RT>,
242 config: &crate::PathConfig,
243 now: SystemTime,
244 ) -> Result<(
245 TorPath<'a>,
246 SupportedCircUsage,
247 Option<GuardMonitor>,
248 Option<GuardUsable>,
249 )> {
250 match self {
251 TargetCircUsage::Dir => {
252 let (path, mon, usable) = DirPathBuilder::new().pick_path(guards)?;
253 Ok((path, SupportedCircUsage::Dir, Some(mon), Some(usable)))
254 }
255 TargetCircUsage::Preemptive {
256 port,
257 require_stability,
258 ..
259 } => {
260 let (path, mon, usable) = ExitPathBuilder::from_target_ports(port.iter().copied())
262 .require_stability(*require_stability)
263 .pick_path(rng, netdir, guards, config, now)?;
264 let policy = path
265 .exit_policy()
266 .expect("ExitPathBuilder gave us a one-hop circuit?");
267 #[cfg(feature = "geoip")]
268 let country_code = path.country_code();
269 #[cfg(not(feature = "geoip"))]
270 let country_code = None;
271 let all_relays_stable = path.appears_stable();
272 Ok((
273 path,
274 SupportedCircUsage::Exit {
275 policy,
276 isolation: None,
277 country_code,
278 all_relays_stable,
279 },
280 Some(mon),
281 Some(usable),
282 ))
283 }
284 TargetCircUsage::Exit {
285 ports: p,
286 isolation,
287 country_code,
288 require_stability,
289 } => {
290 #[cfg(feature = "geoip")]
291 let mut builder = if let Some(cc) = country_code {
292 ExitPathBuilder::in_given_country(*cc, p.clone())
293 } else {
294 ExitPathBuilder::from_target_ports(p.clone())
295 };
296 #[cfg(not(feature = "geoip"))]
297 let mut builder = ExitPathBuilder::from_target_ports(p.clone());
298
299 builder.require_stability(*require_stability);
300
301 let (path, mon, usable) = builder.pick_path(rng, netdir, guards, config, now)?;
302 let policy = path
303 .exit_policy()
304 .expect("ExitPathBuilder gave us a one-hop circuit?");
305
306 #[cfg(feature = "geoip")]
307 let resulting_cc = path.country_code();
308 #[cfg(feature = "geoip")]
309 if resulting_cc != *country_code {
310 internal!(
311 "asked for a country code of {:?}, got {:?}",
312 country_code,
313 resulting_cc
314 );
315 }
316 let all_relays_stable = path.appears_stable();
317
318 #[cfg(not(feature = "geoip"))]
319 let resulting_cc = *country_code; Ok((
321 path,
322 SupportedCircUsage::Exit {
323 policy,
324 isolation: Some(isolation.clone()),
325 country_code: resulting_cc,
326 all_relays_stable,
327 },
328 Some(mon),
329 Some(usable),
330 ))
331 }
332 TargetCircUsage::TimeoutTesting => {
333 let (path, mon, usable) = ExitPathBuilder::for_timeout_testing()
334 .require_stability(false)
335 .pick_path(rng, netdir, guards, config, now)?;
336 let policy = path.exit_policy();
337 #[cfg(feature = "geoip")]
338 let country_code = path.country_code();
339 #[cfg(not(feature = "geoip"))]
340 let country_code = None;
341 let usage = match policy {
342 Some(policy) if policy.allows_some_port() => SupportedCircUsage::Exit {
343 policy,
344 isolation: None,
345 country_code,
346 all_relays_stable: path.appears_stable(),
347 },
348 _ => SupportedCircUsage::NoUsage,
349 };
350
351 Ok((path, usage, Some(mon), Some(usable)))
352 }
353 #[cfg(feature = "specific-relay")]
354 TargetCircUsage::DirSpecificTarget(target) => {
355 let path = TorPath::new_one_hop_owned(target);
356 let usage = SupportedCircUsage::DirSpecificTarget(target.clone());
357 Ok((path, usage, None, None))
358 }
359 #[cfg(feature = "hs-common")]
360 TargetCircUsage::HsCircBase {
361 compatible_with_target,
362 stem_kind,
363 circ_kind,
364 } => {
365 let path_builder =
366 HsPathBuilder::new(compatible_with_target.clone(), *stem_kind, *circ_kind);
367 cfg_if::cfg_if! {
368 if #[cfg(all(feature = "vanguards", feature = "hs-common"))] {
369 let (path, mon, usable) = path_builder
370 .pick_path_with_vanguards::<_, RT>(rng, netdir, guards, vanguards, config, now)?;
371 } else {
372 let (path, mon, usable) = path_builder
373 .pick_path::<_, RT>(rng, netdir, guards, config, now)?;
374 }
375 };
376 let usage = SupportedCircUsage::HsOnly;
377 Ok((path, usage, Some(mon), Some(usable)))
378 }
379 }
380 }
381
382 #[cfg(test)]
385 pub(crate) fn new_from_ipv4_ports(ports: &[u16]) -> Self {
386 TargetCircUsage::Exit {
387 ports: ports.iter().map(|p| TargetPort::ipv4(*p)).collect(),
388 isolation: StreamIsolation::no_isolation(),
389 country_code: None,
390 require_stability: false,
391 }
392 }
393}
394
395#[cfg(feature = "specific-relay")]
398fn owned_targets_equivalent(a: &OwnedChanTarget, b: &OwnedChanTarget) -> bool {
399 a.same_relay_ids(b) && a.chan_method() == b.chan_method()
403}
404
405impl SupportedCircUsage {
406 pub(crate) fn supports(&self, target: &TargetCircUsage) -> bool {
411 use SupportedCircUsage::*;
412 match (self, target) {
413 (Dir, TargetCircUsage::Dir) => true,
414 (
415 Exit {
416 policy: p1,
417 isolation: i1,
418 country_code: cc1,
419 all_relays_stable,
420 },
421 TargetCircUsage::Exit {
422 ports: p2,
423 isolation: i2,
424 country_code: cc2,
425 require_stability,
426 },
427 ) => {
428 i1.as_ref()
431 .map(|i1| i1.compatible_same_type(i2))
432 .unwrap_or(true)
433 && (!require_stability || *all_relays_stable)
434 && p2.iter().all(|port| p1.allows_port(*port))
435 && (cc2.is_none() || cc1 == cc2)
436 }
437 (
438 Exit {
439 policy,
440 isolation,
441 all_relays_stable,
442 ..
443 },
444 TargetCircUsage::Preemptive {
445 port,
446 require_stability,
447 ..
448 },
449 ) => {
450 if *require_stability && !all_relays_stable {
453 return false;
454 }
455 if isolation.is_some() {
456 return false;
459 }
460 if let Some(p) = port {
463 policy.allows_port(*p)
464 } else {
465 true
466 }
467 }
468 (Exit { .. } | NoUsage, TargetCircUsage::TimeoutTesting) => true,
469 #[cfg(feature = "specific-relay")]
470 (DirSpecificTarget(a), TargetCircUsage::DirSpecificTarget(b)) => {
471 owned_targets_equivalent(a, b)
472 }
473 (_, _) => false,
474 }
475 }
476
477 pub(crate) fn restrict_mut(
484 &mut self,
485 usage: &TargetCircUsage,
486 ) -> std::result::Result<(), RestrictionFailed> {
487 use SupportedCircUsage::*;
488 match (self, usage) {
489 (Dir, TargetCircUsage::Dir) => Ok(()),
490 (Exit { .. }, TargetCircUsage::Preemptive { .. }) => Ok(()),
494 (
495 Exit {
496 isolation: ref mut isol1,
497 ..
498 },
499 TargetCircUsage::Exit { isolation: i2, .. },
500 ) => {
501 if let Some(i1) = isol1 {
502 if let Some(new_isolation) = i1.join_same_type(i2) {
503 *isol1 = Some(new_isolation);
506 Ok(())
507 } else {
508 Err(RestrictionFailed::NotSupported)
509 }
510 } else {
511 *isol1 = Some(i2.clone());
513 Ok(())
514 }
515 }
516 (Exit { .. } | NoUsage, TargetCircUsage::TimeoutTesting) => Ok(()),
517 #[cfg(feature = "specific-relay")]
518 (DirSpecificTarget(a), TargetCircUsage::DirSpecificTarget(b))
519 if owned_targets_equivalent(a, b) =>
520 {
521 Ok(())
522 }
523 (_, _) => Err(RestrictionFailed::NotSupported),
524 }
525 }
526
527 pub(crate) fn find_supported<'a, 'b, C: AbstractCirc>(
529 list: impl Iterator<Item = &'b mut OpenEntry<C>>,
530 usage: &TargetCircUsage,
531 ) -> Vec<&'b mut OpenEntry<C>> {
532 fn find_supported_internal<'a, 'b, C: AbstractCirc>(
534 list: impl Iterator<Item = &'b mut OpenEntry<C>>,
535 usage: &TargetCircUsage,
536 ) -> Vec<&'b mut OpenEntry<C>> {
537 list.filter(|circ| circ.supports(usage)).collect()
538 }
539
540 match usage {
541 TargetCircUsage::Preemptive { circs, .. } => {
542 let supported = find_supported_internal(list, usage);
543 trace!(
547 "preemptive usage {:?} matches {} active circuits",
548 usage,
549 supported.len()
550 );
551 if supported.len() >= *circs {
552 supported
553 } else {
554 vec![]
555 }
556 }
557 _ => find_supported_internal(list, usage),
558 }
559 }
560
561 pub(crate) fn channel_usage(&self) -> ChannelUsage {
563 use ChannelUsage as CU;
564 use SupportedCircUsage as SCU;
565 match self {
566 SCU::Dir => CU::Dir,
567 #[cfg(feature = "specific-relay")]
568 SCU::DirSpecificTarget(_) => CU::Dir,
569 SCU::Exit { .. } => CU::UserTraffic,
570 SCU::NoUsage => CU::UselessCircuit,
571 #[cfg(feature = "hs-common")]
572 SCU::HsOnly => CU::UserTraffic,
573 }
574 }
575}
576
577#[cfg(test)]
578pub(crate) mod test {
579 #![allow(clippy::unwrap_used)]
580 use super::*;
581 use crate::isolation::test::{assert_isoleq, IsolationTokenEq};
582 use crate::isolation::{IsolationToken, StreamIsolationBuilder};
583 use crate::path::OwnedPath;
584 use tor_basic_utils::test_rng::testing_rng;
585 use tor_guardmgr::TestConfig;
586 use tor_llcrypto::pk::ed25519::Ed25519Identity;
587 use tor_netdir::testnet;
588 use tor_persist::TestingStateMgr;
589
590 impl IsolationTokenEq for TargetCircUsage {
591 fn isol_eq(&self, other: &Self) -> bool {
592 use TargetCircUsage::*;
593 match (self, other) {
594 (Dir, Dir) => true,
595 (
596 Exit {
597 ports: p1,
598 isolation: is1,
599 country_code: cc1,
600 ..
601 },
602 Exit {
603 ports: p2,
604 isolation: is2,
605 country_code: cc2,
606 ..
607 },
608 ) => p1 == p2 && cc1 == cc2 && is1.isol_eq(is2),
609 (TimeoutTesting, TimeoutTesting) => true,
610 (
611 Preemptive {
612 port: p1,
613 circs: c1,
614 ..
615 },
616 Preemptive {
617 port: p2,
618 circs: c2,
619 ..
620 },
621 ) => p1 == p2 && c1 == c2,
622 _ => false,
623 }
624 }
625 }
626
627 impl IsolationTokenEq for SupportedCircUsage {
628 fn isol_eq(&self, other: &Self) -> bool {
629 use SupportedCircUsage::*;
630 match (self, other) {
631 (Dir, Dir) => true,
632 (
633 Exit {
634 policy: p1,
635 isolation: is1,
636 country_code: cc1,
637 ..
638 },
639 Exit {
640 policy: p2,
641 isolation: is2,
642 country_code: cc2,
643 ..
644 },
645 ) => p1 == p2 && is1.isol_eq(is2) && cc1 == cc2,
646 (NoUsage, NoUsage) => true,
647 _ => false,
648 }
649 }
650 }
651
652 #[test]
653 fn exit_policy() {
654 use tor_netdir::testnet::construct_custom_netdir;
655 use tor_netdoc::doc::netstatus::RelayFlags;
656
657 let network = construct_custom_netdir(|idx, nb, _| {
658 if (0x21..0x27).contains(&idx) {
659 nb.rs.add_flags(RelayFlags::BAD_EXIT);
660 }
661 })
662 .unwrap()
663 .unwrap_if_sufficient()
664 .unwrap();
665
666 let id_noexit: Ed25519Identity = [0x05; 32].into();
671 let id_webexit: Ed25519Identity = [0x11; 32].into();
672 let id_fullexit: Ed25519Identity = [0x20; 32].into();
673 let id_badexit: Ed25519Identity = [0x25; 32].into();
674
675 let not_exit = network.by_id(&id_noexit).unwrap();
676 let web_exit = network.by_id(&id_webexit).unwrap();
677 let full_exit = network.by_id(&id_fullexit).unwrap();
678 let bad_exit = network.by_id(&id_badexit).unwrap();
679
680 let ep_none = ExitPolicy::from_relay(¬_exit);
681 let ep_web = ExitPolicy::from_relay(&web_exit);
682 let ep_full = ExitPolicy::from_relay(&full_exit);
683 let ep_bad = ExitPolicy::from_relay(&bad_exit);
684
685 assert!(!ep_none.allows_port(TargetPort::ipv4(80)));
686 assert!(!ep_none.allows_port(TargetPort::ipv4(9999)));
687
688 assert!(ep_web.allows_port(TargetPort::ipv4(80)));
689 assert!(ep_web.allows_port(TargetPort::ipv4(443)));
690 assert!(!ep_web.allows_port(TargetPort::ipv4(9999)));
691
692 assert!(ep_full.allows_port(TargetPort::ipv4(80)));
693 assert!(ep_full.allows_port(TargetPort::ipv4(443)));
694 assert!(ep_full.allows_port(TargetPort::ipv4(9999)));
695
696 assert!(!ep_bad.allows_port(TargetPort::ipv4(80)));
697
698 assert!(!ep_none.allows_port(TargetPort::ipv6(80)));
700 assert!(!ep_web.allows_port(TargetPort::ipv6(80)));
701 assert!(!ep_full.allows_port(TargetPort::ipv6(80)));
702 assert!(!ep_bad.allows_port(TargetPort::ipv6(80)));
703
704 assert!(TargetPort::ipv4(80).is_supported_by(&web_exit.low_level_details()));
706 assert!(!TargetPort::ipv6(80).is_supported_by(&web_exit.low_level_details()));
707 assert!(!TargetPort::ipv6(80).is_supported_by(&bad_exit.low_level_details()));
708 }
709
710 #[test]
711 fn usage_ops() {
712 let policy = ExitPolicy {
715 v4: Arc::new("accept 80,443".parse().unwrap()),
716 v6: Arc::new("accept 23".parse().unwrap()),
717 };
718 let tok1 = IsolationToken::new();
719 let tok2 = IsolationToken::new();
720 let isolation = StreamIsolationBuilder::new()
721 .owner_token(tok1)
722 .build()
723 .unwrap();
724 let isolation2 = StreamIsolationBuilder::new()
725 .owner_token(tok2)
726 .build()
727 .unwrap();
728
729 let supp_dir = SupportedCircUsage::Dir;
730 let targ_dir = TargetCircUsage::Dir;
731 let supp_exit = SupportedCircUsage::Exit {
732 policy: policy.clone(),
733 isolation: Some(isolation.clone()),
734 country_code: None,
735 all_relays_stable: true,
736 };
737 let supp_exit_iso2 = SupportedCircUsage::Exit {
738 policy: policy.clone(),
739 isolation: Some(isolation2.clone()),
740 country_code: None,
741 all_relays_stable: true,
742 };
743 let supp_exit_no_iso = SupportedCircUsage::Exit {
744 policy,
745 isolation: None,
746 country_code: None,
747 all_relays_stable: true,
748 };
749 let supp_none = SupportedCircUsage::NoUsage;
750
751 let targ_80_v4 = TargetCircUsage::Exit {
752 ports: vec![TargetPort::ipv4(80)],
753 isolation: isolation.clone(),
754 country_code: None,
755 require_stability: false,
756 };
757 let targ_80_v4_iso2 = TargetCircUsage::Exit {
758 ports: vec![TargetPort::ipv4(80)],
759 isolation: isolation2,
760 country_code: None,
761 require_stability: false,
762 };
763 let targ_80_23_v4 = TargetCircUsage::Exit {
764 ports: vec![TargetPort::ipv4(80), TargetPort::ipv4(23)],
765 isolation: isolation.clone(),
766 country_code: None,
767 require_stability: false,
768 };
769
770 let targ_80_23_mixed = TargetCircUsage::Exit {
771 ports: vec![TargetPort::ipv4(80), TargetPort::ipv6(23)],
772 isolation: isolation.clone(),
773 country_code: None,
774 require_stability: false,
775 };
776 let targ_999_v6 = TargetCircUsage::Exit {
777 ports: vec![TargetPort::ipv6(999)],
778 isolation,
779 country_code: None,
780 require_stability: false,
781 };
782 let targ_testing = TargetCircUsage::TimeoutTesting;
783
784 assert!(supp_dir.supports(&targ_dir));
785 assert!(!supp_dir.supports(&targ_80_v4));
786 assert!(!supp_exit.supports(&targ_dir));
787 assert!(supp_exit.supports(&targ_80_v4));
788 assert!(!supp_exit.supports(&targ_80_v4_iso2));
789 assert!(supp_exit.supports(&targ_80_23_mixed));
790 assert!(!supp_exit.supports(&targ_80_23_v4));
791 assert!(!supp_exit.supports(&targ_999_v6));
792 assert!(!supp_exit_iso2.supports(&targ_80_v4));
793 assert!(supp_exit_iso2.supports(&targ_80_v4_iso2));
794 assert!(supp_exit_no_iso.supports(&targ_80_v4));
795 assert!(supp_exit_no_iso.supports(&targ_80_v4_iso2));
796 assert!(!supp_exit_no_iso.supports(&targ_80_23_v4));
797 assert!(!supp_none.supports(&targ_dir));
798 assert!(!supp_none.supports(&targ_80_23_v4));
799 assert!(!supp_none.supports(&targ_80_v4_iso2));
800 assert!(!supp_dir.supports(&targ_testing));
801 assert!(supp_exit.supports(&targ_testing));
802 assert!(supp_exit_no_iso.supports(&targ_testing));
803 assert!(supp_exit_iso2.supports(&targ_testing));
804 assert!(supp_none.supports(&targ_testing));
805 }
806
807 #[test]
808 fn restrict_mut() {
809 let policy = ExitPolicy {
810 v4: Arc::new("accept 80,443".parse().unwrap()),
811 v6: Arc::new("accept 23".parse().unwrap()),
812 };
813
814 let tok1 = IsolationToken::new();
815 let tok2 = IsolationToken::new();
816 let isolation = StreamIsolationBuilder::new()
817 .owner_token(tok1)
818 .build()
819 .unwrap();
820 let isolation2 = StreamIsolationBuilder::new()
821 .owner_token(tok2)
822 .build()
823 .unwrap();
824
825 let supp_dir = SupportedCircUsage::Dir;
826 let targ_dir = TargetCircUsage::Dir;
827 let supp_exit = SupportedCircUsage::Exit {
828 policy: policy.clone(),
829 isolation: Some(isolation.clone()),
830 country_code: None,
831 all_relays_stable: true,
832 };
833 let supp_exit_iso2 = SupportedCircUsage::Exit {
834 policy: policy.clone(),
835 isolation: Some(isolation2.clone()),
836 country_code: None,
837 all_relays_stable: true,
838 };
839 let supp_exit_no_iso = SupportedCircUsage::Exit {
840 policy,
841 isolation: None,
842 country_code: None,
843 all_relays_stable: true,
844 };
845 let supp_none = SupportedCircUsage::NoUsage;
846 let targ_exit = TargetCircUsage::Exit {
847 ports: vec![TargetPort::ipv4(80)],
848 isolation,
849 country_code: None,
850 require_stability: false,
851 };
852 let targ_exit_iso2 = TargetCircUsage::Exit {
853 ports: vec![TargetPort::ipv4(80)],
854 isolation: isolation2,
855 country_code: None,
856 require_stability: false,
857 };
858 let targ_testing = TargetCircUsage::TimeoutTesting;
859
860 let mut supp_dir_c = supp_dir.clone();
862 assert!(supp_dir_c.restrict_mut(&targ_exit).is_err());
863 assert!(supp_dir_c.restrict_mut(&targ_testing).is_err());
864 assert_isoleq!(supp_dir, supp_dir_c);
865
866 let mut supp_exit_c = supp_exit.clone();
867 assert!(supp_exit_c.restrict_mut(&targ_dir).is_err());
868 assert_isoleq!(supp_exit, supp_exit_c);
869
870 let mut supp_exit_c = supp_exit.clone();
871 assert!(supp_exit_c.restrict_mut(&targ_exit_iso2).is_err());
872 assert_isoleq!(supp_exit, supp_exit_c);
873
874 let mut supp_exit_iso2_c = supp_exit_iso2.clone();
875 assert!(supp_exit_iso2_c.restrict_mut(&targ_exit).is_err());
876 assert_isoleq!(supp_exit_iso2, supp_exit_iso2_c);
877
878 let mut supp_none_c = supp_none.clone();
879 assert!(supp_none_c.restrict_mut(&targ_exit).is_err());
880 assert!(supp_none_c.restrict_mut(&targ_dir).is_err());
881 assert_isoleq!(supp_none_c, supp_none);
882
883 let mut supp_dir_c = supp_dir.clone();
885 supp_dir_c.restrict_mut(&targ_dir).unwrap();
886 assert_isoleq!(supp_dir, supp_dir_c);
887
888 let mut supp_exit_c = supp_exit.clone();
889 supp_exit_c.restrict_mut(&targ_exit).unwrap();
890 assert_isoleq!(supp_exit, supp_exit_c);
891
892 let mut supp_exit_iso2_c = supp_exit_iso2.clone();
893 supp_exit_iso2_c.restrict_mut(&targ_exit_iso2).unwrap();
894 supp_none_c.restrict_mut(&targ_testing).unwrap();
895 assert_isoleq!(supp_exit_iso2, supp_exit_iso2_c);
896
897 let mut supp_none_c = supp_none.clone();
898 supp_none_c.restrict_mut(&targ_testing).unwrap();
899 assert_isoleq!(supp_none_c, supp_none);
900
901 let mut supp_exit_no_iso_c = supp_exit_no_iso.clone();
903 supp_exit_no_iso_c.restrict_mut(&targ_exit).unwrap();
904 assert!(supp_exit_no_iso_c.supports(&targ_exit));
905 assert!(!supp_exit_no_iso_c.supports(&targ_exit_iso2));
906
907 let mut supp_exit_no_iso_c = supp_exit_no_iso;
908 supp_exit_no_iso_c.restrict_mut(&targ_exit_iso2).unwrap();
909 assert!(!supp_exit_no_iso_c.supports(&targ_exit));
910 assert!(supp_exit_no_iso_c.supports(&targ_exit_iso2));
911 }
912
913 #[test]
914 fn buildpath() {
915 tor_rtcompat::test_with_all_runtimes!(|rt| async move {
916 let mut rng = testing_rng();
917 let netdir = testnet::construct_netdir().unwrap_if_sufficient().unwrap();
918 let di = (&netdir).into();
919 let config = crate::PathConfig::default();
920 let statemgr = TestingStateMgr::new();
921 let guards =
922 tor_guardmgr::GuardMgr::new(rt.clone(), statemgr.clone(), &TestConfig::default())
923 .unwrap();
924 guards.install_test_netdir(&netdir);
925 let now = SystemTime::now();
926
927 #[cfg(all(feature = "vanguards", feature = "hs-common"))]
932 let vanguards =
933 VanguardMgr::new(&Default::default(), rt.clone(), statemgr, false).unwrap();
934
935 let (p_dir, u_dir, _, _) = TargetCircUsage::Dir
937 .build_path(
938 &mut rng,
939 di,
940 &guards,
941 #[cfg(all(feature = "vanguards", feature = "hs-common"))]
942 &vanguards,
943 &config,
944 now,
945 )
946 .unwrap();
947 assert!(matches!(u_dir, SupportedCircUsage::Dir));
948 assert_eq!(p_dir.len(), 1);
949
950 let tok1 = IsolationToken::new();
952 let isolation = StreamIsolationBuilder::new()
953 .owner_token(tok1)
954 .build()
955 .unwrap();
956
957 let exit_usage = TargetCircUsage::Exit {
958 ports: vec![TargetPort::ipv4(995)],
959 isolation: isolation.clone(),
960 country_code: None,
961 require_stability: false,
962 };
963 let (p_exit, u_exit, _, _) = exit_usage
964 .build_path(
965 &mut rng,
966 di,
967 &guards,
968 #[cfg(all(feature = "vanguards", feature = "hs-common"))]
969 &vanguards,
970 &config,
971 now,
972 )
973 .unwrap();
974 assert!(matches!(
975 u_exit,
976 SupportedCircUsage::Exit {
977 isolation: ref iso,
978 ..
979 } if iso.isol_eq(&Some(isolation))
980 ));
981 assert!(u_exit.supports(&exit_usage));
982 assert_eq!(p_exit.len(), 3);
983
984 let (path, usage, _, _) = TargetCircUsage::TimeoutTesting
986 .build_path(
987 &mut rng,
988 di,
989 &guards,
990 #[cfg(all(feature = "vanguards", feature = "hs-common"))]
991 &vanguards,
992 &config,
993 now,
994 )
995 .unwrap();
996 let path = match OwnedPath::try_from(&path).unwrap() {
997 OwnedPath::ChannelOnly(_) => panic!("Impossible path type."),
998 OwnedPath::Normal(p) => p,
999 };
1000 assert_eq!(path.len(), 3);
1001
1002 let last_relay = netdir.by_ids(&path[2]).unwrap();
1004 let policy = ExitPolicy::from_relay(&last_relay);
1005 assert!(policy.allows_some_port());
1008 assert!(last_relay.low_level_details().policies_allow_some_port());
1009 assert_isoleq!(
1010 usage,
1011 SupportedCircUsage::Exit {
1012 policy,
1013 isolation: None,
1014 country_code: None,
1015 all_relays_stable: true
1016 }
1017 );
1018 });
1019 }
1020
1021 #[test]
1022 fn build_testing_noexit() {
1023 tor_rtcompat::test_with_all_runtimes!(|rt| async move {
1026 let mut rng = testing_rng();
1027 let netdir = testnet::construct_custom_netdir(|_idx, bld, _| {
1028 bld.md.parse_ipv4_policy("reject 1-65535").unwrap();
1029 })
1030 .unwrap()
1031 .unwrap_if_sufficient()
1032 .unwrap();
1033 let di = (&netdir).into();
1034 let config = crate::PathConfig::default();
1035 let statemgr = TestingStateMgr::new();
1036 let guards =
1037 tor_guardmgr::GuardMgr::new(rt.clone(), statemgr.clone(), &TestConfig::default())
1038 .unwrap();
1039 guards.install_test_netdir(&netdir);
1040 let now = SystemTime::now();
1041
1042 #[cfg(all(feature = "vanguards", feature = "hs-common"))]
1043 let vanguards =
1044 VanguardMgr::new(&Default::default(), rt.clone(), statemgr, false).unwrap();
1045
1046 let (path, usage, _, _) = TargetCircUsage::TimeoutTesting
1047 .build_path(
1048 &mut rng,
1049 di,
1050 &guards,
1051 #[cfg(all(feature = "vanguards", feature = "hs-common"))]
1052 &vanguards,
1053 &config,
1054 now,
1055 )
1056 .unwrap();
1057 assert_eq!(path.len(), 3);
1058 assert_isoleq!(usage, SupportedCircUsage::NoUsage);
1059 });
1060 }
1061
1062 #[test]
1063 fn display_target_ports() {
1064 let ports = [];
1065 assert_eq!(TargetPorts::from(&ports[..]).to_string(), "[]");
1066
1067 let ports = [TargetPort::ipv4(80)];
1068 assert_eq!(TargetPorts::from(&ports[..]).to_string(), "80v4");
1069 let ports = [TargetPort::ipv4(80), TargetPort::ipv6(443)];
1070 assert_eq!(TargetPorts::from(&ports[..]).to_string(), "[80v4,443v6]");
1071 }
1072}