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