Skip to main content

tor_circmgr/
usage.rs

1//! Code related to tracking what activities a circuit can be used for.
2
3use rand::Rng;
4use std::fmt::{self, Display};
5use std::sync::Arc;
6use std::time::SystemTime;
7use tracing::{instrument, trace};
8#[cfg(not(feature = "geoip"))]
9use void::Void;
10
11use crate::path::{TorPath, dirpath::DirPathBuilder, exitpath::ExitPathBuilder};
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::HsCircKind, crate::HsCircStemKind, crate::path::hspath::HsPathBuilder};
21
22#[cfg(feature = "specific-relay")]
23use tor_linkspec::{HasChanMethod, HasRelayIds};
24
25#[cfg(feature = "geoip")]
26use tor_geoip::CountryCode;
27/// A non-existent country code type, used as a placeholder for the real `tor_geoip::CountryCode`
28/// when the `geoip` crate feature is not present.
29///
30/// This type exists to simplify conditional compilation: without it, we'd have to duplicate a lot
31/// of match patterns and things would suck a lot.
32// TODO GEOIP: propagate this refactor down through the stack (i.e. all the way down to the
33//            `tor-geoip` crate)
34//             We can also get rid of a lot of #[cfg] then.
35#[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::Result;
45use crate::isolation::{IsolationHelper, StreamIsolation};
46use crate::mgr::{AbstractTunnel, OpenEntry, RestrictionFailed};
47
48pub use tor_relay_selection::TargetPort;
49
50/// An exit policy, as supported by the last hop of a circuit.
51#[derive(Clone, Debug, PartialEq, Eq)]
52pub(crate) struct ExitPolicy {
53    /// Permitted IPv4 ports.
54    v4: Arc<PortPolicy>,
55    /// Permitted IPv6 ports.
56    v6: Arc<PortPolicy>,
57}
58
59/// Set of requested target ports, mostly for use in error reporting
60///
61/// Displays nicely.
62#[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    /// Make a new exit policy from a given Relay.
92    pub(crate) fn from_relay(relay: &Relay<'_>) -> Self {
93        // TODO #504: it might be a good idea to lower this whole type into
94        // tor-netdir or tor-relay-selection.  That way we wouldn't need to
95        // invoke these Relay-specific methods in tor-circmgr.
96        Self {
97            v4: relay.low_level_details().ipv4_policy(),
98            v6: relay.low_level_details().ipv6_policy(),
99        }
100    }
101
102    /// Make a exit policy based on the allowed ports in TargetPorts.
103    #[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                .into(),
114            v6: PortPolicy::from_allowed_port_list(v6_ports.iter().map(|port| port.port).collect())
115                .intern()
116                .into(),
117        }
118    }
119
120    /// Return true if a given port is contained in this ExitPolicy.
121    fn allows_port(&self, p: TargetPort) -> bool {
122        let policy = if p.ipv6 { &self.v6 } else { &self.v4 };
123        policy.allows_port(p.port)
124    }
125
126    /// Returns true if this policy allows any ports at all.
127    fn allows_some_port(&self) -> bool {
128        self.v4.allows_some_port() || self.v6.allows_some_port()
129    }
130}
131
132/// The purpose for which a circuit is being created.
133///
134/// This type should stay internal to the circmgr crate for now: we'll probably
135/// want to refactor it a lot.
136#[derive(Clone, Debug)]
137pub(crate) enum TargetTunnelUsage {
138    /// Use for BEGINDIR-based non-anonymous directory connections
139    Dir,
140    /// Use to exit to one or more ports.
141    Exit {
142        /// List of ports the circuit has to allow.
143        ///
144        /// If this list of ports is empty, then the circuit doesn't need
145        /// to support any particular port, but it still needs to be an exit.
146        ports: Vec<TargetPort>,
147        /// Isolation group the circuit shall be part of
148        isolation: StreamIsolation,
149        /// Restrict the circuit to only exits in the provided country code.
150        country_code: Option<CountryCode>,
151        /// If true, all relays on this circuit need to have the Stable flag.
152        //
153        // TODO #504: It would be good to remove this field, if we can.
154        require_stability: bool,
155    },
156    /// For a circuit is only used for the purpose of building it.
157    TimeoutTesting,
158    /// For internal usage only: build a circuit preemptively, to reduce wait times.
159    ///
160    /// # Warning
161    ///
162    /// This **MUST NOT** be used by code outside of the preemptive circuit predictor. In
163    /// particular, this usage doesn't support stream isolation, so using it to ask for
164    /// circuits (for example, by passing it to `get_or_launch`) could be unsafe!
165    Preemptive {
166        /// A port the circuit has to allow, if specified.
167        ///
168        /// If this is `None`, we just want a circuit capable of doing DNS resolution.
169        port: Option<TargetPort>,
170        /// The number of exit circuits needed for a port
171        circs: usize,
172        /// If true, all relays on this circuit need to have the Stable flag.
173        // TODO #504: It would be good to remove this field, if we can.
174        require_stability: bool,
175    },
176    /// Use for BEGINDIR-based non-anonymous directory connections to a particular target,
177    /// and therefore to a specific relay (which need not be in any netdir).
178    #[cfg(feature = "specific-relay")]
179    DirSpecificTarget(OwnedChanTarget),
180
181    /// Used to build a circuit (currently always 3 hops) to serve as the basis of some
182    /// onion-serivice-related operation.
183    #[cfg(feature = "hs-common")]
184    HsCircBase {
185        /// A target to avoid when constructing this circuit.
186        ///
187        /// This target is not appended to the end of the circuit; rather, the
188        /// circuit is built so that its relays are all allowed to share a
189        /// circuit with this target (without, for example, violating any
190        /// family restrictions).
191        compatible_with_target: Option<OwnedChanTarget>,
192        /// The kind of circuit stem to build.
193        stem_kind: HsCircStemKind,
194        /// If present, add additional rules to the stem so it can _definitely_
195        /// be used as a circuit of this kind.
196        circ_kind: Option<HsCircKind>,
197    },
198}
199
200/// The purposes for which a circuit is usable.
201///
202/// This type should stay internal to the circmgr crate for now: we'll probably
203/// want to refactor it a lot.
204#[derive(Clone, Debug)]
205pub(crate) enum SupportedTunnelUsage {
206    /// Usable for BEGINDIR-based non-anonymous directory connections
207    Dir,
208    /// Usable to exit to a set of ports.
209    Exit {
210        /// Exit policy of the circuit
211        policy: ExitPolicy,
212        /// Isolation group the circuit is part of. None when the circuit is not yet assigned to an
213        /// isolation group.
214        isolation: Option<StreamIsolation>,
215        /// Country code the exit is in, or `None` if no country could be determined.
216        country_code: Option<CountryCode>,
217        /// Whether every relay in this circuit has the "Stable" flag.
218        //
219        // TODO #504: It would be good to remove this field, if we can.
220        all_relays_stable: bool,
221    },
222    /// This circuit is not suitable for any usage.
223    NoUsage,
224    /// This circuit is for some hs-related usage.
225    /// (It should never be given to the circuit manager; the
226    /// `HsPool` code will handle it instead.)
227    #[cfg(feature = "hs-common")]
228    HsOnly,
229    /// Use only for BEGINDIR-based non-anonymous directory connections
230    /// to a particular target (which may not be in the netdir).
231    #[cfg(feature = "specific-relay")]
232    DirSpecificTarget(OwnedChanTarget),
233}
234
235impl TargetTunnelUsage {
236    /// Construct path for a given circuit purpose; return it and the
237    /// usage that it _actually_ supports.
238    #[instrument(level = "trace", skip_all)]
239    pub(crate) fn build_path<'a, R: Rng, RT: Runtime>(
240        &self,
241        rng: &mut R,
242        netdir: crate::DirInfo<'a>,
243        guards: &GuardMgr<RT>,
244        #[cfg(all(feature = "vanguards", feature = "hs-common"))] vanguards: &VanguardMgr<RT>,
245        config: &crate::PathConfig,
246        now: SystemTime,
247    ) -> Result<(
248        TorPath<'a>,
249        SupportedTunnelUsage,
250        Option<GuardMonitor>,
251        Option<GuardUsable>,
252    )> {
253        match self {
254            TargetTunnelUsage::Dir => {
255                let (path, mon, usable) = DirPathBuilder::new().pick_path(guards)?;
256                Ok((path, SupportedTunnelUsage::Dir, Some(mon), Some(usable)))
257            }
258            TargetTunnelUsage::Preemptive {
259                port,
260                require_stability,
261                ..
262            } => {
263                // FIXME(eta): this is copypasta from `TargetCircUsage::Exit`.
264                let (path, mon, usable) = ExitPathBuilder::from_target_ports(port.iter().copied())
265                    .require_stability(*require_stability)
266                    .pick_path(rng, netdir, guards, config, now)?;
267                let policy = path
268                    .exit_policy()
269                    .expect("ExitPathBuilder gave us a one-hop circuit?");
270                #[cfg(feature = "geoip")]
271                let country_code = path.country_code();
272                #[cfg(not(feature = "geoip"))]
273                let country_code = None;
274                let all_relays_stable = path.appears_stable();
275                Ok((
276                    path,
277                    SupportedTunnelUsage::Exit {
278                        policy,
279                        isolation: None,
280                        country_code,
281                        all_relays_stable,
282                    },
283                    Some(mon),
284                    Some(usable),
285                ))
286            }
287            TargetTunnelUsage::Exit {
288                ports: p,
289                isolation,
290                country_code,
291                require_stability,
292            } => {
293                #[cfg(feature = "geoip")]
294                let mut builder = if let Some(cc) = country_code {
295                    ExitPathBuilder::in_given_country(*cc, p.clone())
296                } else {
297                    ExitPathBuilder::from_target_ports(p.clone())
298                };
299                #[cfg(not(feature = "geoip"))]
300                let mut builder = ExitPathBuilder::from_target_ports(p.clone());
301
302                builder.require_stability(*require_stability);
303
304                let (path, mon, usable) = builder.pick_path(rng, netdir, guards, config, now)?;
305                let policy = path
306                    .exit_policy()
307                    .expect("ExitPathBuilder gave us a one-hop circuit?");
308
309                #[cfg(feature = "geoip")]
310                let resulting_cc = path.country_code();
311                #[cfg(feature = "geoip")]
312                if resulting_cc != *country_code {
313                    internal!(
314                        "asked for a country code of {:?}, got {:?}",
315                        country_code,
316                        resulting_cc
317                    );
318                }
319                let all_relays_stable = path.appears_stable();
320
321                #[cfg(not(feature = "geoip"))]
322                let resulting_cc = *country_code; // avoid unused var warning
323                Ok((
324                    path,
325                    SupportedTunnelUsage::Exit {
326                        policy,
327                        isolation: Some(isolation.clone()),
328                        country_code: resulting_cc,
329                        all_relays_stable,
330                    },
331                    Some(mon),
332                    Some(usable),
333                ))
334            }
335            TargetTunnelUsage::TimeoutTesting => {
336                let (path, mon, usable) = ExitPathBuilder::for_timeout_testing()
337                    .require_stability(false)
338                    .pick_path(rng, netdir, guards, config, now)?;
339                let policy = path.exit_policy();
340                #[cfg(feature = "geoip")]
341                let country_code = path.country_code();
342                #[cfg(not(feature = "geoip"))]
343                let country_code = None;
344                let usage = match policy {
345                    Some(policy) if policy.allows_some_port() => SupportedTunnelUsage::Exit {
346                        policy,
347                        isolation: None,
348                        country_code,
349                        all_relays_stable: path.appears_stable(),
350                    },
351                    _ => SupportedTunnelUsage::NoUsage,
352                };
353
354                Ok((path, usage, Some(mon), Some(usable)))
355            }
356            #[cfg(feature = "specific-relay")]
357            TargetTunnelUsage::DirSpecificTarget(target) => {
358                let path = TorPath::new_one_hop_owned(target);
359                let usage = SupportedTunnelUsage::DirSpecificTarget(target.clone());
360                Ok((path, usage, None, None))
361            }
362            #[cfg(feature = "hs-common")]
363            TargetTunnelUsage::HsCircBase {
364                compatible_with_target,
365                stem_kind,
366                circ_kind,
367            } => {
368                let path_builder =
369                    HsPathBuilder::new(compatible_with_target.clone(), *stem_kind, *circ_kind);
370                cfg_if::cfg_if! {
371                    if #[cfg(all(feature = "vanguards", feature = "hs-common"))] {
372                        let (path, mon, usable) = path_builder
373                            .pick_path_with_vanguards::<_, RT>(rng, netdir, guards, vanguards, config, now)?;
374                    } else {
375                        let (path, mon, usable) = path_builder
376                            .pick_path::<_, RT>(rng, netdir, guards, config, now)?;
377                    }
378                };
379                let usage = SupportedTunnelUsage::HsOnly;
380                Ok((path, usage, Some(mon), Some(usable)))
381            }
382        }
383    }
384
385    /// Create a TargetCircUsage::Exit for a given set of IPv4 ports, with no stream isolation, for
386    /// use in tests.
387    #[cfg(test)]
388    pub(crate) fn new_from_ipv4_ports(ports: &[u16]) -> Self {
389        TargetTunnelUsage::Exit {
390            ports: ports.iter().map(|p| TargetPort::ipv4(*p)).collect(),
391            isolation: StreamIsolation::no_isolation(),
392            country_code: None,
393            require_stability: false,
394        }
395    }
396}
397
398/// Return true if `a` and `b` count as the same target for the purpose of
399/// comparing `DirSpecificTarget` values.
400#[cfg(feature = "specific-relay")]
401fn owned_targets_equivalent(a: &OwnedChanTarget, b: &OwnedChanTarget) -> bool {
402    // We ignore `addresses` here, since they can be different if one of our
403    // arguments comes from only a bridge line, and the other comes from a
404    // bridge line and a descriptor.
405    a.same_relay_ids(b) && a.chan_method() == b.chan_method()
406}
407
408impl SupportedTunnelUsage {
409    /// Return true if this spec permits the usage described by `other`.
410    ///
411    /// If this function returns `true`, then it is okay to use a circuit
412    /// with this spec for the target usage described by `other`.
413    pub(crate) fn supports(&self, target: &TargetTunnelUsage) -> bool {
414        use SupportedTunnelUsage::*;
415        match (self, target) {
416            (Dir, TargetTunnelUsage::Dir) => true,
417            (
418                Exit {
419                    policy: p1,
420                    isolation: i1,
421                    country_code: cc1,
422                    all_relays_stable,
423                },
424                TargetTunnelUsage::Exit {
425                    ports: p2,
426                    isolation: i2,
427                    country_code: cc2,
428                    require_stability,
429                },
430            ) => {
431                // TODO #504: These calculations don't touch Relays, but they
432                // seem like they should be done using the types of tor-relay-selection.
433                i1.as_ref()
434                    .map(|i1| i1.compatible_same_type(i2))
435                    .unwrap_or(true)
436                    && (!require_stability || *all_relays_stable)
437                    && p2.iter().all(|port| p1.allows_port(*port))
438                    && (cc2.is_none() || cc1 == cc2)
439            }
440            (
441                Exit {
442                    policy,
443                    isolation,
444                    all_relays_stable,
445                    ..
446                },
447                TargetTunnelUsage::Preemptive {
448                    port,
449                    require_stability,
450                    ..
451                },
452            ) => {
453                // TODO #504: It would be good to simply remove stability
454                // calculation from tor-circmgr.
455                if *require_stability && !all_relays_stable {
456                    return false;
457                }
458                if isolation.is_some() {
459                    // If the circuit has a stream isolation, we might not be able to use it
460                    // for new streams that don't share it.
461                    return false;
462                }
463                // TODO #504: Similarly, it would be good to have exit port
464                // calculation done elsewhere.
465                if let Some(p) = port {
466                    policy.allows_port(*p)
467                } else {
468                    true
469                }
470            }
471            (Exit { .. } | NoUsage, TargetTunnelUsage::TimeoutTesting) => true,
472            #[cfg(feature = "specific-relay")]
473            (DirSpecificTarget(a), TargetTunnelUsage::DirSpecificTarget(b)) => {
474                owned_targets_equivalent(a, b)
475            }
476            (_, _) => false,
477        }
478    }
479
480    /// Change the value of this spec based on the circuit having been used for `usage`.
481    ///
482    /// Returns an error and makes no changes to `self` if `usage` was not supported by this spec.
483    ///
484    /// If this function returns Ok, the resulting spec will be contained by the original spec, and
485    /// will support `usage`.
486    pub(crate) fn restrict_mut(
487        &mut self,
488        usage: &TargetTunnelUsage,
489    ) -> std::result::Result<(), RestrictionFailed> {
490        use SupportedTunnelUsage::*;
491        match (self, usage) {
492            (Dir, TargetTunnelUsage::Dir) => Ok(()),
493            // This usage is only used to create circuits preemptively, and doesn't actually
494            // correspond to any streams; accordingly, we don't need to modify the circuit's
495            // acceptable usage at all.
496            (Exit { .. }, TargetTunnelUsage::Preemptive { .. }) => Ok(()),
497            (
498                Exit {
499                    isolation: isol1, ..
500                },
501                TargetTunnelUsage::Exit { isolation: i2, .. },
502            ) => {
503                if let Some(i1) = isol1 {
504                    if let Some(new_isolation) = i1.join_same_type(i2) {
505                        // there was some isolation, and the requested usage is compatible, saving
506                        // the new isolation into self
507                        *isol1 = Some(new_isolation);
508                        Ok(())
509                    } else {
510                        Err(RestrictionFailed::NotSupported)
511                    }
512                } else {
513                    // there was no isolation yet on self, applying the restriction from usage
514                    *isol1 = Some(i2.clone());
515                    Ok(())
516                }
517            }
518            (Exit { .. } | NoUsage, TargetTunnelUsage::TimeoutTesting) => Ok(()),
519            #[cfg(feature = "specific-relay")]
520            (DirSpecificTarget(a), TargetTunnelUsage::DirSpecificTarget(b))
521                if owned_targets_equivalent(a, b) =>
522            {
523                Ok(())
524            }
525            (_, _) => Err(RestrictionFailed::NotSupported),
526        }
527    }
528
529    /// Find all open circuits in `list` whose specifications permit `usage`.
530    pub(crate) fn find_supported<'a, 'b, C: AbstractTunnel>(
531        list: impl Iterator<Item = &'b mut OpenEntry<C>>,
532        usage: &TargetTunnelUsage,
533    ) -> Vec<&'b mut OpenEntry<C>> {
534        /// Returns all circuits in `list` for which `circuit.spec.supports(usage)` returns `true`.
535        fn find_supported_internal<'a, 'b, C: AbstractTunnel>(
536            list: impl Iterator<Item = &'b mut OpenEntry<C>>,
537            usage: &TargetTunnelUsage,
538        ) -> Vec<&'b mut OpenEntry<C>> {
539            list.filter(|circ| circ.supports(usage)).collect()
540        }
541
542        match usage {
543            TargetTunnelUsage::Preemptive { circs, .. } => {
544                let supported = find_supported_internal(list, usage);
545                // We need to have at least two circuits that support `port` in order
546                // to reuse them; otherwise, we must create a new circuit, so
547                // that we get closer to having two circuits.
548                trace!(
549                    "preemptive usage {:?} matches {} active circuits",
550                    usage,
551                    supported.len()
552                );
553                if supported.len() >= *circs {
554                    supported
555                } else {
556                    vec![]
557                }
558            }
559            _ => find_supported_internal(list, usage),
560        }
561    }
562
563    /// How the circuit will be used, for use by the channel
564    pub(crate) fn channel_usage(&self) -> ChannelUsage {
565        use ChannelUsage as CU;
566        use SupportedTunnelUsage as SCU;
567        match self {
568            SCU::Dir => CU::Dir,
569            #[cfg(feature = "specific-relay")]
570            SCU::DirSpecificTarget(_) => CU::Dir,
571            SCU::Exit { .. } => CU::UserTraffic,
572            SCU::NoUsage => CU::UselessCircuit,
573            #[cfg(feature = "hs-common")]
574            SCU::HsOnly => CU::UserTraffic,
575        }
576    }
577
578    /// True if this usage is compatible with a long-lived circuit.
579    ///
580    /// (We leave long-lived circuits alive until they have been disused for a long time,
581    /// and never expire them for being dirty.)
582    pub(crate) fn is_long_lived(&self) -> bool {
583        use SupportedTunnelUsage::*;
584        match self {
585            // We _could_ say "true" for these, but in practice we are done with them pretty
586            // quickly, and we can make another cheaply.
587            //
588            // If we did make these "true", we'd want to give them a different lifetime.
589            Dir => false,
590            #[cfg(feature = "specific-relay")]
591            DirSpecificTarget(_) => false,
592
593            Exit { isolation, .. } => {
594                // For Exit usage, we just care about whether the isolation enables long-lived circuits.
595                isolation
596                    .as_ref()
597                    .is_some_and(StreamIsolation::enables_long_lived_circuits)
598            }
599            NoUsage => {
600                // If the circuit is suitable for nothing, it is not long-lived.
601                false
602            }
603            #[cfg(feature = "hs-common")]
604            HsOnly => {
605                // This circuit's lifetime is managed by the hspool code, and later by the hsclient
606                // code.  We will not manage it within the mgr.rs code.
607                false
608            }
609        }
610    }
611}
612
613#[cfg(test)]
614pub(crate) mod test {
615    #![allow(clippy::unwrap_used)]
616    use super::*;
617    use crate::isolation::test::{IsolationTokenEq, assert_isoleq};
618    use crate::isolation::{IsolationToken, StreamIsolationBuilder};
619    use crate::path::OwnedPath;
620    use tor_basic_utils::test_rng::testing_rng;
621    use tor_guardmgr::TestConfig;
622    use tor_llcrypto::pk::ed25519::Ed25519Identity;
623    use tor_netdir::testnet;
624    use tor_persist::TestingStateMgr;
625    use web_time_compat::SystemTimeExt;
626
627    impl IsolationTokenEq for TargetTunnelUsage {
628        fn isol_eq(&self, other: &Self) -> bool {
629            use TargetTunnelUsage::*;
630            match (self, other) {
631                (Dir, Dir) => true,
632                (
633                    Exit {
634                        ports: p1,
635                        isolation: is1,
636                        country_code: cc1,
637                        ..
638                    },
639                    Exit {
640                        ports: p2,
641                        isolation: is2,
642                        country_code: cc2,
643                        ..
644                    },
645                ) => p1 == p2 && cc1 == cc2 && is1.isol_eq(is2),
646                (TimeoutTesting, TimeoutTesting) => true,
647                (
648                    Preemptive {
649                        port: p1,
650                        circs: c1,
651                        ..
652                    },
653                    Preemptive {
654                        port: p2,
655                        circs: c2,
656                        ..
657                    },
658                ) => p1 == p2 && c1 == c2,
659                _ => false,
660            }
661        }
662    }
663
664    impl IsolationTokenEq for SupportedTunnelUsage {
665        fn isol_eq(&self, other: &Self) -> bool {
666            use SupportedTunnelUsage::*;
667            match (self, other) {
668                (Dir, Dir) => true,
669                (
670                    Exit {
671                        policy: p1,
672                        isolation: is1,
673                        country_code: cc1,
674                        ..
675                    },
676                    Exit {
677                        policy: p2,
678                        isolation: is2,
679                        country_code: cc2,
680                        ..
681                    },
682                ) => p1 == p2 && is1.isol_eq(is2) && cc1 == cc2,
683                (NoUsage, NoUsage) => true,
684                _ => false,
685            }
686        }
687    }
688
689    #[test]
690    fn exit_policy() {
691        use tor_netdir::testnet::construct_custom_netdir;
692        use tor_netdoc::types::relay_flags::RelayFlag;
693
694        let network = construct_custom_netdir(|idx, nb, _| {
695            if (0x21..0x27).contains(&idx) {
696                nb.rs.add_flags(RelayFlag::BadExit);
697            }
698        })
699        .unwrap()
700        .unwrap_if_sufficient()
701        .unwrap();
702
703        // Nodes with ID 0x0a through 0x13 and 0x1e through 0x27 are
704        // exits.  Odd-numbered ones allow only ports 80 and 443;
705        // even-numbered ones allow all ports.  Nodes with ID 0x21
706        // through 0x27 are bad exits.
707        let id_noexit: Ed25519Identity = [0x05; 32].into();
708        let id_webexit: Ed25519Identity = [0x11; 32].into();
709        let id_fullexit: Ed25519Identity = [0x20; 32].into();
710        let id_badexit: Ed25519Identity = [0x25; 32].into();
711
712        let not_exit = network.by_id(&id_noexit).unwrap();
713        let web_exit = network.by_id(&id_webexit).unwrap();
714        let full_exit = network.by_id(&id_fullexit).unwrap();
715        let bad_exit = network.by_id(&id_badexit).unwrap();
716
717        let ep_none = ExitPolicy::from_relay(&not_exit);
718        let ep_web = ExitPolicy::from_relay(&web_exit);
719        let ep_full = ExitPolicy::from_relay(&full_exit);
720        let ep_bad = ExitPolicy::from_relay(&bad_exit);
721
722        assert!(!ep_none.allows_port(TargetPort::ipv4(80)));
723        assert!(!ep_none.allows_port(TargetPort::ipv4(9999)));
724
725        assert!(ep_web.allows_port(TargetPort::ipv4(80)));
726        assert!(ep_web.allows_port(TargetPort::ipv4(443)));
727        assert!(!ep_web.allows_port(TargetPort::ipv4(9999)));
728
729        assert!(ep_full.allows_port(TargetPort::ipv4(80)));
730        assert!(ep_full.allows_port(TargetPort::ipv4(443)));
731        assert!(ep_full.allows_port(TargetPort::ipv4(9999)));
732
733        assert!(!ep_bad.allows_port(TargetPort::ipv4(80)));
734
735        // Note that nobody in the testdir::network allows ipv6.
736        assert!(!ep_none.allows_port(TargetPort::ipv6(80)));
737        assert!(!ep_web.allows_port(TargetPort::ipv6(80)));
738        assert!(!ep_full.allows_port(TargetPort::ipv6(80)));
739        assert!(!ep_bad.allows_port(TargetPort::ipv6(80)));
740
741        // Check is_supported_by while we're here.
742        assert!(TargetPort::ipv4(80).is_supported_by(&web_exit.low_level_details()));
743        assert!(!TargetPort::ipv6(80).is_supported_by(&web_exit.low_level_details()));
744        assert!(!TargetPort::ipv6(80).is_supported_by(&bad_exit.low_level_details()));
745    }
746
747    #[test]
748    fn usage_ops() {
749        // Make an exit-policy object that allows web on IPv4 and
750        // smtp on IPv6.
751        let policy = ExitPolicy {
752            v4: Arc::new("accept 80,443".parse().unwrap()),
753            v6: Arc::new("accept 23".parse().unwrap()),
754        };
755        let tok1 = IsolationToken::new();
756        let tok2 = IsolationToken::new();
757        let isolation = StreamIsolationBuilder::new()
758            .owner_token(tok1)
759            .build()
760            .unwrap();
761        let isolation2 = StreamIsolationBuilder::new()
762            .owner_token(tok2)
763            .build()
764            .unwrap();
765
766        let supp_dir = SupportedTunnelUsage::Dir;
767        let targ_dir = TargetTunnelUsage::Dir;
768        let supp_exit = SupportedTunnelUsage::Exit {
769            policy: policy.clone(),
770            isolation: Some(isolation.clone()),
771            country_code: None,
772            all_relays_stable: true,
773        };
774        let supp_exit_iso2 = SupportedTunnelUsage::Exit {
775            policy: policy.clone(),
776            isolation: Some(isolation2.clone()),
777            country_code: None,
778            all_relays_stable: true,
779        };
780        let supp_exit_no_iso = SupportedTunnelUsage::Exit {
781            policy,
782            isolation: None,
783            country_code: None,
784            all_relays_stable: true,
785        };
786        let supp_none = SupportedTunnelUsage::NoUsage;
787
788        let targ_80_v4 = TargetTunnelUsage::Exit {
789            ports: vec![TargetPort::ipv4(80)],
790            isolation: isolation.clone(),
791            country_code: None,
792            require_stability: false,
793        };
794        let targ_80_v4_iso2 = TargetTunnelUsage::Exit {
795            ports: vec![TargetPort::ipv4(80)],
796            isolation: isolation2,
797            country_code: None,
798            require_stability: false,
799        };
800        let targ_80_23_v4 = TargetTunnelUsage::Exit {
801            ports: vec![TargetPort::ipv4(80), TargetPort::ipv4(23)],
802            isolation: isolation.clone(),
803            country_code: None,
804            require_stability: false,
805        };
806
807        let targ_80_23_mixed = TargetTunnelUsage::Exit {
808            ports: vec![TargetPort::ipv4(80), TargetPort::ipv6(23)],
809            isolation: isolation.clone(),
810            country_code: None,
811            require_stability: false,
812        };
813        let targ_999_v6 = TargetTunnelUsage::Exit {
814            ports: vec![TargetPort::ipv6(999)],
815            isolation,
816            country_code: None,
817            require_stability: false,
818        };
819        let targ_testing = TargetTunnelUsage::TimeoutTesting;
820
821        assert!(supp_dir.supports(&targ_dir));
822        assert!(!supp_dir.supports(&targ_80_v4));
823        assert!(!supp_exit.supports(&targ_dir));
824        assert!(supp_exit.supports(&targ_80_v4));
825        assert!(!supp_exit.supports(&targ_80_v4_iso2));
826        assert!(supp_exit.supports(&targ_80_23_mixed));
827        assert!(!supp_exit.supports(&targ_80_23_v4));
828        assert!(!supp_exit.supports(&targ_999_v6));
829        assert!(!supp_exit_iso2.supports(&targ_80_v4));
830        assert!(supp_exit_iso2.supports(&targ_80_v4_iso2));
831        assert!(supp_exit_no_iso.supports(&targ_80_v4));
832        assert!(supp_exit_no_iso.supports(&targ_80_v4_iso2));
833        assert!(!supp_exit_no_iso.supports(&targ_80_23_v4));
834        assert!(!supp_none.supports(&targ_dir));
835        assert!(!supp_none.supports(&targ_80_23_v4));
836        assert!(!supp_none.supports(&targ_80_v4_iso2));
837        assert!(!supp_dir.supports(&targ_testing));
838        assert!(supp_exit.supports(&targ_testing));
839        assert!(supp_exit_no_iso.supports(&targ_testing));
840        assert!(supp_exit_iso2.supports(&targ_testing));
841        assert!(supp_none.supports(&targ_testing));
842    }
843
844    #[test]
845    fn restrict_mut() {
846        let policy = ExitPolicy {
847            v4: Arc::new("accept 80,443".parse().unwrap()),
848            v6: Arc::new("accept 23".parse().unwrap()),
849        };
850
851        let tok1 = IsolationToken::new();
852        let tok2 = IsolationToken::new();
853        let isolation = StreamIsolationBuilder::new()
854            .owner_token(tok1)
855            .build()
856            .unwrap();
857        let isolation2 = StreamIsolationBuilder::new()
858            .owner_token(tok2)
859            .build()
860            .unwrap();
861
862        let supp_dir = SupportedTunnelUsage::Dir;
863        let targ_dir = TargetTunnelUsage::Dir;
864        let supp_exit = SupportedTunnelUsage::Exit {
865            policy: policy.clone(),
866            isolation: Some(isolation.clone()),
867            country_code: None,
868            all_relays_stable: true,
869        };
870        let supp_exit_iso2 = SupportedTunnelUsage::Exit {
871            policy: policy.clone(),
872            isolation: Some(isolation2.clone()),
873            country_code: None,
874            all_relays_stable: true,
875        };
876        let supp_exit_no_iso = SupportedTunnelUsage::Exit {
877            policy,
878            isolation: None,
879            country_code: None,
880            all_relays_stable: true,
881        };
882        let supp_none = SupportedTunnelUsage::NoUsage;
883        let targ_exit = TargetTunnelUsage::Exit {
884            ports: vec![TargetPort::ipv4(80)],
885            isolation,
886            country_code: None,
887            require_stability: false,
888        };
889        let targ_exit_iso2 = TargetTunnelUsage::Exit {
890            ports: vec![TargetPort::ipv4(80)],
891            isolation: isolation2,
892            country_code: None,
893            require_stability: false,
894        };
895        let targ_testing = TargetTunnelUsage::TimeoutTesting;
896
897        // not allowed, do nothing
898        let mut supp_dir_c = supp_dir.clone();
899        assert!(supp_dir_c.restrict_mut(&targ_exit).is_err());
900        assert!(supp_dir_c.restrict_mut(&targ_testing).is_err());
901        assert_isoleq!(supp_dir, supp_dir_c);
902
903        let mut supp_exit_c = supp_exit.clone();
904        assert!(supp_exit_c.restrict_mut(&targ_dir).is_err());
905        assert_isoleq!(supp_exit, supp_exit_c);
906
907        let mut supp_exit_c = supp_exit.clone();
908        assert!(supp_exit_c.restrict_mut(&targ_exit_iso2).is_err());
909        assert_isoleq!(supp_exit, supp_exit_c);
910
911        let mut supp_exit_iso2_c = supp_exit_iso2.clone();
912        assert!(supp_exit_iso2_c.restrict_mut(&targ_exit).is_err());
913        assert_isoleq!(supp_exit_iso2, supp_exit_iso2_c);
914
915        let mut supp_none_c = supp_none.clone();
916        assert!(supp_none_c.restrict_mut(&targ_exit).is_err());
917        assert!(supp_none_c.restrict_mut(&targ_dir).is_err());
918        assert_isoleq!(supp_none_c, supp_none);
919
920        // allowed but nothing to do
921        let mut supp_dir_c = supp_dir.clone();
922        supp_dir_c.restrict_mut(&targ_dir).unwrap();
923        assert_isoleq!(supp_dir, supp_dir_c);
924
925        let mut supp_exit_c = supp_exit.clone();
926        supp_exit_c.restrict_mut(&targ_exit).unwrap();
927        assert_isoleq!(supp_exit, supp_exit_c);
928
929        let mut supp_exit_iso2_c = supp_exit_iso2.clone();
930        supp_exit_iso2_c.restrict_mut(&targ_exit_iso2).unwrap();
931        supp_none_c.restrict_mut(&targ_testing).unwrap();
932        assert_isoleq!(supp_exit_iso2, supp_exit_iso2_c);
933
934        let mut supp_none_c = supp_none.clone();
935        supp_none_c.restrict_mut(&targ_testing).unwrap();
936        assert_isoleq!(supp_none_c, supp_none);
937
938        // allowed, do something
939        let mut supp_exit_no_iso_c = supp_exit_no_iso.clone();
940        supp_exit_no_iso_c.restrict_mut(&targ_exit).unwrap();
941        assert!(supp_exit_no_iso_c.supports(&targ_exit));
942        assert!(!supp_exit_no_iso_c.supports(&targ_exit_iso2));
943
944        let mut supp_exit_no_iso_c = supp_exit_no_iso;
945        supp_exit_no_iso_c.restrict_mut(&targ_exit_iso2).unwrap();
946        assert!(!supp_exit_no_iso_c.supports(&targ_exit));
947        assert!(supp_exit_no_iso_c.supports(&targ_exit_iso2));
948    }
949
950    #[test]
951    fn buildpath() {
952        tor_rtcompat::test_with_all_runtimes!(|rt| async move {
953            let mut rng = testing_rng();
954            let netdir = testnet::construct_netdir().unwrap_if_sufficient().unwrap();
955            let di = (&netdir).into();
956            let config = crate::PathConfig::default();
957            let statemgr = TestingStateMgr::new();
958            let guards =
959                tor_guardmgr::GuardMgr::new(rt.clone(), statemgr.clone(), &TestConfig::default())
960                    .unwrap();
961            guards.install_test_netdir(&netdir);
962            let now = SystemTime::get();
963
964            // Only doing basic tests for now.  We'll test the path
965            // building code a lot more closely in the tests for TorPath
966            // and friends.
967
968            #[cfg(all(feature = "vanguards", feature = "hs-common"))]
969            let vanguards =
970                VanguardMgr::new(&Default::default(), rt.clone(), statemgr, false).unwrap();
971
972            // First, a one-hop directory circuit
973            let (p_dir, u_dir, _, _) = TargetTunnelUsage::Dir
974                .build_path(
975                    &mut rng,
976                    di,
977                    &guards,
978                    #[cfg(all(feature = "vanguards", feature = "hs-common"))]
979                    &vanguards,
980                    &config,
981                    now,
982                )
983                .unwrap();
984            assert!(matches!(u_dir, SupportedTunnelUsage::Dir));
985            assert_eq!(p_dir.len(), 1);
986
987            // Now an exit circuit, to port 995.
988            let tok1 = IsolationToken::new();
989            let isolation = StreamIsolationBuilder::new()
990                .owner_token(tok1)
991                .build()
992                .unwrap();
993
994            let exit_usage = TargetTunnelUsage::Exit {
995                ports: vec![TargetPort::ipv4(995)],
996                isolation: isolation.clone(),
997                country_code: None,
998                require_stability: false,
999            };
1000            let (p_exit, u_exit, _, _) = exit_usage
1001                .build_path(
1002                    &mut rng,
1003                    di,
1004                    &guards,
1005                    #[cfg(all(feature = "vanguards", feature = "hs-common"))]
1006                    &vanguards,
1007                    &config,
1008                    now,
1009                )
1010                .unwrap();
1011            assert!(matches!(
1012                u_exit,
1013                SupportedTunnelUsage::Exit {
1014                    isolation: ref iso,
1015                    ..
1016                } if iso.isol_eq(&Some(isolation))
1017            ));
1018            assert!(u_exit.supports(&exit_usage));
1019            assert_eq!(p_exit.len(), 3);
1020
1021            // Now try testing circuits.
1022            let (path, usage, _, _) = TargetTunnelUsage::TimeoutTesting
1023                .build_path(
1024                    &mut rng,
1025                    di,
1026                    &guards,
1027                    #[cfg(all(feature = "vanguards", feature = "hs-common"))]
1028                    &vanguards,
1029                    &config,
1030                    now,
1031                )
1032                .unwrap();
1033            let path = match OwnedPath::try_from(&path).unwrap() {
1034                OwnedPath::ChannelOnly(_) => panic!("Impossible path type."),
1035                OwnedPath::Normal(p) => p,
1036            };
1037            assert_eq!(path.len(), 3);
1038
1039            // Make sure that the usage is correct.
1040            let last_relay = netdir.by_ids(&path[2]).unwrap();
1041            let policy = ExitPolicy::from_relay(&last_relay);
1042            // We'll always get exits for these, since we try to build
1043            // paths with an exit if there are any exits.
1044            assert!(policy.allows_some_port());
1045            assert!(last_relay.low_level_details().policies_allow_some_port());
1046            assert_isoleq!(
1047                usage,
1048                SupportedTunnelUsage::Exit {
1049                    policy,
1050                    isolation: None,
1051                    country_code: None,
1052                    all_relays_stable: true
1053                }
1054            );
1055        });
1056    }
1057
1058    #[test]
1059    fn build_testing_noexit() {
1060        // Here we'll try to build paths for testing circuits on a network
1061        // with no exits.
1062        tor_rtcompat::test_with_all_runtimes!(|rt| async move {
1063            let mut rng = testing_rng();
1064            let netdir = testnet::construct_custom_netdir(|_idx, bld, _| {
1065                bld.md.parse_ipv4_policy("reject 1-65535").unwrap();
1066            })
1067            .unwrap()
1068            .unwrap_if_sufficient()
1069            .unwrap();
1070            let di = (&netdir).into();
1071            let config = crate::PathConfig::default();
1072            let statemgr = TestingStateMgr::new();
1073            let guards =
1074                tor_guardmgr::GuardMgr::new(rt.clone(), statemgr.clone(), &TestConfig::default())
1075                    .unwrap();
1076            guards.install_test_netdir(&netdir);
1077            let now = SystemTime::get();
1078
1079            #[cfg(all(feature = "vanguards", feature = "hs-common"))]
1080            let vanguards =
1081                VanguardMgr::new(&Default::default(), rt.clone(), statemgr, false).unwrap();
1082
1083            let (path, usage, _, _) = TargetTunnelUsage::TimeoutTesting
1084                .build_path(
1085                    &mut rng,
1086                    di,
1087                    &guards,
1088                    #[cfg(all(feature = "vanguards", feature = "hs-common"))]
1089                    &vanguards,
1090                    &config,
1091                    now,
1092                )
1093                .unwrap();
1094            assert_eq!(path.len(), 3);
1095            assert_isoleq!(usage, SupportedTunnelUsage::NoUsage);
1096        });
1097    }
1098
1099    #[test]
1100    fn display_target_ports() {
1101        let ports = [];
1102        assert_eq!(TargetPorts::from(&ports[..]).to_string(), "[]");
1103
1104        let ports = [TargetPort::ipv4(80)];
1105        assert_eq!(TargetPorts::from(&ports[..]).to_string(), "80v4");
1106        let ports = [TargetPort::ipv4(80), TargetPort::ipv6(443)];
1107        assert_eq!(TargetPorts::from(&ports[..]).to_string(), "[80v4,443v6]");
1108    }
1109}