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::trace;
8#[cfg(not(feature = "geoip"))]
9use void::Void;
10
11use crate::path::{dirpath::DirPathBuilder, exitpath::ExitPathBuilder, TorPath};
12use tor_chanmgr::ChannelUsage;
13#[cfg(feature = "geoip")]
14use tor_error::internal;
15use tor_guardmgr::{GuardMgr, GuardMonitor, GuardUsable};
16use tor_netdir::Relay;
17use tor_netdoc::types::policy::PortPolicy;
18use tor_rtcompat::Runtime;
19#[cfg(feature = "hs-common")]
20use {crate::path::hspath::HsPathBuilder, crate::HsCircKind, crate::HsCircStemKind};
21
22#[cfg(feature = "specific-relay")]
23use tor_linkspec::{HasChanMethod, HasRelayIds};
24
25#[cfg(feature = "geoip")]
26use tor_geoip::CountryCode;
27/// 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::isolation::{IsolationHelper, StreamIsolation};
45use crate::mgr::{AbstractCirc, OpenEntry, RestrictionFailed};
46use crate::Result;
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 TargetCircUsage {
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 SupportedCircUsage {
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 TargetCircUsage {
234    /// Construct path for a given circuit purpose; return it and the
235    /// usage that it _actually_ supports.
236    pub(crate) fn build_path<'a, R: Rng, RT: Runtime>(
237        &self,
238        rng: &mut R,
239        netdir: crate::DirInfo<'a>,
240        guards: &GuardMgr<RT>,
241        #[cfg(all(feature = "vanguards", feature = "hs-common"))] vanguards: &VanguardMgr<RT>,
242        config: &crate::PathConfig,
243        now: SystemTime,
244    ) -> Result<(
245        TorPath<'a>,
246        SupportedCircUsage,
247        Option<GuardMonitor>,
248        Option<GuardUsable>,
249    )> {
250        match self {
251            TargetCircUsage::Dir => {
252                let (path, mon, usable) = DirPathBuilder::new().pick_path(guards)?;
253                Ok((path, SupportedCircUsage::Dir, Some(mon), Some(usable)))
254            }
255            TargetCircUsage::Preemptive {
256                port,
257                require_stability,
258                ..
259            } => {
260                // FIXME(eta): this is copypasta from `TargetCircUsage::Exit`.
261                let (path, mon, usable) = ExitPathBuilder::from_target_ports(port.iter().copied())
262                    .require_stability(*require_stability)
263                    .pick_path(rng, netdir, guards, config, now)?;
264                let policy = path
265                    .exit_policy()
266                    .expect("ExitPathBuilder gave us a one-hop circuit?");
267                #[cfg(feature = "geoip")]
268                let country_code = path.country_code();
269                #[cfg(not(feature = "geoip"))]
270                let country_code = None;
271                let all_relays_stable = path.appears_stable();
272                Ok((
273                    path,
274                    SupportedCircUsage::Exit {
275                        policy,
276                        isolation: None,
277                        country_code,
278                        all_relays_stable,
279                    },
280                    Some(mon),
281                    Some(usable),
282                ))
283            }
284            TargetCircUsage::Exit {
285                ports: p,
286                isolation,
287                country_code,
288                require_stability,
289            } => {
290                #[cfg(feature = "geoip")]
291                let mut builder = if let Some(cc) = country_code {
292                    ExitPathBuilder::in_given_country(*cc, p.clone())
293                } else {
294                    ExitPathBuilder::from_target_ports(p.clone())
295                };
296                #[cfg(not(feature = "geoip"))]
297                let mut builder = ExitPathBuilder::from_target_ports(p.clone());
298
299                builder.require_stability(*require_stability);
300
301                let (path, mon, usable) = builder.pick_path(rng, netdir, guards, config, now)?;
302                let policy = path
303                    .exit_policy()
304                    .expect("ExitPathBuilder gave us a one-hop circuit?");
305
306                #[cfg(feature = "geoip")]
307                let resulting_cc = path.country_code();
308                #[cfg(feature = "geoip")]
309                if resulting_cc != *country_code {
310                    internal!(
311                        "asked for a country code of {:?}, got {:?}",
312                        country_code,
313                        resulting_cc
314                    );
315                }
316                let all_relays_stable = path.appears_stable();
317
318                #[cfg(not(feature = "geoip"))]
319                let resulting_cc = *country_code; // avoid unused var warning
320                Ok((
321                    path,
322                    SupportedCircUsage::Exit {
323                        policy,
324                        isolation: Some(isolation.clone()),
325                        country_code: resulting_cc,
326                        all_relays_stable,
327                    },
328                    Some(mon),
329                    Some(usable),
330                ))
331            }
332            TargetCircUsage::TimeoutTesting => {
333                let (path, mon, usable) = ExitPathBuilder::for_timeout_testing()
334                    .require_stability(false)
335                    .pick_path(rng, netdir, guards, config, now)?;
336                let policy = path.exit_policy();
337                #[cfg(feature = "geoip")]
338                let country_code = path.country_code();
339                #[cfg(not(feature = "geoip"))]
340                let country_code = None;
341                let usage = match policy {
342                    Some(policy) if policy.allows_some_port() => SupportedCircUsage::Exit {
343                        policy,
344                        isolation: None,
345                        country_code,
346                        all_relays_stable: path.appears_stable(),
347                    },
348                    _ => SupportedCircUsage::NoUsage,
349                };
350
351                Ok((path, usage, Some(mon), Some(usable)))
352            }
353            #[cfg(feature = "specific-relay")]
354            TargetCircUsage::DirSpecificTarget(target) => {
355                let path = TorPath::new_one_hop_owned(target);
356                let usage = SupportedCircUsage::DirSpecificTarget(target.clone());
357                Ok((path, usage, None, None))
358            }
359            #[cfg(feature = "hs-common")]
360            TargetCircUsage::HsCircBase {
361                compatible_with_target,
362                stem_kind,
363                circ_kind,
364            } => {
365                let path_builder =
366                    HsPathBuilder::new(compatible_with_target.clone(), *stem_kind, *circ_kind);
367                cfg_if::cfg_if! {
368                    if #[cfg(all(feature = "vanguards", feature = "hs-common"))] {
369                        let (path, mon, usable) = path_builder
370                            .pick_path_with_vanguards::<_, RT>(rng, netdir, guards, vanguards, config, now)?;
371                    } else {
372                        let (path, mon, usable) = path_builder
373                            .pick_path::<_, RT>(rng, netdir, guards, config, now)?;
374                    }
375                };
376                let usage = SupportedCircUsage::HsOnly;
377                Ok((path, usage, Some(mon), Some(usable)))
378            }
379        }
380    }
381
382    /// Create a TargetCircUsage::Exit for a given set of IPv4 ports, with no stream isolation, for
383    /// use in tests.
384    #[cfg(test)]
385    pub(crate) fn new_from_ipv4_ports(ports: &[u16]) -> Self {
386        TargetCircUsage::Exit {
387            ports: ports.iter().map(|p| TargetPort::ipv4(*p)).collect(),
388            isolation: StreamIsolation::no_isolation(),
389            country_code: None,
390            require_stability: false,
391        }
392    }
393}
394
395/// Return true if `a` and `b` count as the same target for the purpose of
396/// comparing `DirSpecificTarget` values.
397#[cfg(feature = "specific-relay")]
398fn owned_targets_equivalent(a: &OwnedChanTarget, b: &OwnedChanTarget) -> bool {
399    // We ignore `addresses` here, since they can be different if one of our
400    // arguments comes from only a bridge line, and the other comes from a
401    // bridge line and a descriptor.
402    a.same_relay_ids(b) && a.chan_method() == b.chan_method()
403}
404
405impl SupportedCircUsage {
406    /// Return true if this spec permits the usage described by `other`.
407    ///
408    /// If this function returns `true`, then it is okay to use a circuit
409    /// with this spec for the target usage described by `other`.
410    pub(crate) fn supports(&self, target: &TargetCircUsage) -> bool {
411        use SupportedCircUsage::*;
412        match (self, target) {
413            (Dir, TargetCircUsage::Dir) => true,
414            (
415                Exit {
416                    policy: p1,
417                    isolation: i1,
418                    country_code: cc1,
419                    all_relays_stable,
420                },
421                TargetCircUsage::Exit {
422                    ports: p2,
423                    isolation: i2,
424                    country_code: cc2,
425                    require_stability,
426                },
427            ) => {
428                // TODO #504: These calculations don't touch Relays, but they
429                // seem like they should be done using the types of tor-relay-selection.
430                i1.as_ref()
431                    .map(|i1| i1.compatible_same_type(i2))
432                    .unwrap_or(true)
433                    && (!require_stability || *all_relays_stable)
434                    && p2.iter().all(|port| p1.allows_port(*port))
435                    && (cc2.is_none() || cc1 == cc2)
436            }
437            (
438                Exit {
439                    policy,
440                    isolation,
441                    all_relays_stable,
442                    ..
443                },
444                TargetCircUsage::Preemptive {
445                    port,
446                    require_stability,
447                    ..
448                },
449            ) => {
450                // TODO #504: It would be good to simply remove stability
451                // calculation from tor-circmgr.
452                if *require_stability && !all_relays_stable {
453                    return false;
454                }
455                if isolation.is_some() {
456                    // If the circuit has a stream isolation, we might not be able to use it
457                    // for new streams that don't share it.
458                    return false;
459                }
460                // TODO #504: Similarly, it would be good to have exit port
461                // calculation done elsewhere.
462                if let Some(p) = port {
463                    policy.allows_port(*p)
464                } else {
465                    true
466                }
467            }
468            (Exit { .. } | NoUsage, TargetCircUsage::TimeoutTesting) => true,
469            #[cfg(feature = "specific-relay")]
470            (DirSpecificTarget(a), TargetCircUsage::DirSpecificTarget(b)) => {
471                owned_targets_equivalent(a, b)
472            }
473            (_, _) => false,
474        }
475    }
476
477    /// Change the value of this spec based on the circuit having been used for `usage`.
478    ///
479    /// Returns an error and makes no changes to `self` if `usage` was not supported by this spec.
480    ///
481    /// If this function returns Ok, the resulting spec will be contained by the original spec, and
482    /// will support `usage`.
483    pub(crate) fn restrict_mut(
484        &mut self,
485        usage: &TargetCircUsage,
486    ) -> std::result::Result<(), RestrictionFailed> {
487        use SupportedCircUsage::*;
488        match (self, usage) {
489            (Dir, TargetCircUsage::Dir) => Ok(()),
490            // This usage is only used to create circuits preemptively, and doesn't actually
491            // correspond to any streams; accordingly, we don't need to modify the circuit's
492            // acceptable usage at all.
493            (Exit { .. }, TargetCircUsage::Preemptive { .. }) => Ok(()),
494            (
495                Exit {
496                    isolation: ref mut isol1,
497                    ..
498                },
499                TargetCircUsage::Exit { isolation: i2, .. },
500            ) => {
501                if let Some(i1) = isol1 {
502                    if let Some(new_isolation) = i1.join_same_type(i2) {
503                        // 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, TargetCircUsage::TimeoutTesting) => Ok(()),
517            #[cfg(feature = "specific-relay")]
518            (DirSpecificTarget(a), TargetCircUsage::DirSpecificTarget(b))
519                if owned_targets_equivalent(a, b) =>
520            {
521                Ok(())
522            }
523            (_, _) => Err(RestrictionFailed::NotSupported),
524        }
525    }
526
527    /// Find all open circuits in `list` whose specifications permit `usage`.
528    pub(crate) fn find_supported<'a, 'b, C: AbstractCirc>(
529        list: impl Iterator<Item = &'b mut OpenEntry<C>>,
530        usage: &TargetCircUsage,
531    ) -> Vec<&'b mut OpenEntry<C>> {
532        /// Returns all circuits in `list` for which `circuit.spec.supports(usage)` returns `true`.
533        fn find_supported_internal<'a, 'b, C: AbstractCirc>(
534            list: impl Iterator<Item = &'b mut OpenEntry<C>>,
535            usage: &TargetCircUsage,
536        ) -> Vec<&'b mut OpenEntry<C>> {
537            list.filter(|circ| circ.supports(usage)).collect()
538        }
539
540        match usage {
541            TargetCircUsage::Preemptive { circs, .. } => {
542                let supported = find_supported_internal(list, usage);
543                // 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 SupportedCircUsage as SCU;
565        match self {
566            SCU::Dir => CU::Dir,
567            #[cfg(feature = "specific-relay")]
568            SCU::DirSpecificTarget(_) => CU::Dir,
569            SCU::Exit { .. } => CU::UserTraffic,
570            SCU::NoUsage => CU::UselessCircuit,
571            #[cfg(feature = "hs-common")]
572            SCU::HsOnly => CU::UserTraffic,
573        }
574    }
575}
576
577#[cfg(test)]
578pub(crate) mod test {
579    #![allow(clippy::unwrap_used)]
580    use super::*;
581    use crate::isolation::test::{assert_isoleq, IsolationTokenEq};
582    use crate::isolation::{IsolationToken, StreamIsolationBuilder};
583    use crate::path::OwnedPath;
584    use tor_basic_utils::test_rng::testing_rng;
585    use tor_guardmgr::TestConfig;
586    use tor_llcrypto::pk::ed25519::Ed25519Identity;
587    use tor_netdir::testnet;
588    use tor_persist::TestingStateMgr;
589
590    impl IsolationTokenEq for TargetCircUsage {
591        fn isol_eq(&self, other: &Self) -> bool {
592            use TargetCircUsage::*;
593            match (self, other) {
594                (Dir, Dir) => true,
595                (
596                    Exit {
597                        ports: p1,
598                        isolation: is1,
599                        country_code: cc1,
600                        ..
601                    },
602                    Exit {
603                        ports: p2,
604                        isolation: is2,
605                        country_code: cc2,
606                        ..
607                    },
608                ) => p1 == p2 && cc1 == cc2 && is1.isol_eq(is2),
609                (TimeoutTesting, TimeoutTesting) => true,
610                (
611                    Preemptive {
612                        port: p1,
613                        circs: c1,
614                        ..
615                    },
616                    Preemptive {
617                        port: p2,
618                        circs: c2,
619                        ..
620                    },
621                ) => p1 == p2 && c1 == c2,
622                _ => false,
623            }
624        }
625    }
626
627    impl IsolationTokenEq for SupportedCircUsage {
628        fn isol_eq(&self, other: &Self) -> bool {
629            use SupportedCircUsage::*;
630            match (self, other) {
631                (Dir, Dir) => true,
632                (
633                    Exit {
634                        policy: p1,
635                        isolation: is1,
636                        country_code: cc1,
637                        ..
638                    },
639                    Exit {
640                        policy: p2,
641                        isolation: is2,
642                        country_code: cc2,
643                        ..
644                    },
645                ) => p1 == p2 && is1.isol_eq(is2) && cc1 == cc2,
646                (NoUsage, NoUsage) => true,
647                _ => false,
648            }
649        }
650    }
651
652    #[test]
653    fn exit_policy() {
654        use tor_netdir::testnet::construct_custom_netdir;
655        use tor_netdoc::doc::netstatus::RelayFlags;
656
657        let network = construct_custom_netdir(|idx, nb, _| {
658            if (0x21..0x27).contains(&idx) {
659                nb.rs.add_flags(RelayFlags::BAD_EXIT);
660            }
661        })
662        .unwrap()
663        .unwrap_if_sufficient()
664        .unwrap();
665
666        // Nodes with ID 0x0a through 0x13 and 0x1e through 0x27 are
667        // exits.  Odd-numbered ones allow only ports 80 and 443;
668        // even-numbered ones allow all ports.  Nodes with ID 0x21
669        // through 0x27 are bad exits.
670        let id_noexit: Ed25519Identity = [0x05; 32].into();
671        let id_webexit: Ed25519Identity = [0x11; 32].into();
672        let id_fullexit: Ed25519Identity = [0x20; 32].into();
673        let id_badexit: Ed25519Identity = [0x25; 32].into();
674
675        let not_exit = network.by_id(&id_noexit).unwrap();
676        let web_exit = network.by_id(&id_webexit).unwrap();
677        let full_exit = network.by_id(&id_fullexit).unwrap();
678        let bad_exit = network.by_id(&id_badexit).unwrap();
679
680        let ep_none = ExitPolicy::from_relay(&not_exit);
681        let ep_web = ExitPolicy::from_relay(&web_exit);
682        let ep_full = ExitPolicy::from_relay(&full_exit);
683        let ep_bad = ExitPolicy::from_relay(&bad_exit);
684
685        assert!(!ep_none.allows_port(TargetPort::ipv4(80)));
686        assert!(!ep_none.allows_port(TargetPort::ipv4(9999)));
687
688        assert!(ep_web.allows_port(TargetPort::ipv4(80)));
689        assert!(ep_web.allows_port(TargetPort::ipv4(443)));
690        assert!(!ep_web.allows_port(TargetPort::ipv4(9999)));
691
692        assert!(ep_full.allows_port(TargetPort::ipv4(80)));
693        assert!(ep_full.allows_port(TargetPort::ipv4(443)));
694        assert!(ep_full.allows_port(TargetPort::ipv4(9999)));
695
696        assert!(!ep_bad.allows_port(TargetPort::ipv4(80)));
697
698        // Note that nobody in the testdir::network allows ipv6.
699        assert!(!ep_none.allows_port(TargetPort::ipv6(80)));
700        assert!(!ep_web.allows_port(TargetPort::ipv6(80)));
701        assert!(!ep_full.allows_port(TargetPort::ipv6(80)));
702        assert!(!ep_bad.allows_port(TargetPort::ipv6(80)));
703
704        // Check is_supported_by while we're here.
705        assert!(TargetPort::ipv4(80).is_supported_by(&web_exit.low_level_details()));
706        assert!(!TargetPort::ipv6(80).is_supported_by(&web_exit.low_level_details()));
707        assert!(!TargetPort::ipv6(80).is_supported_by(&bad_exit.low_level_details()));
708    }
709
710    #[test]
711    fn usage_ops() {
712        // Make an exit-policy object that allows web on IPv4 and
713        // smtp on IPv6.
714        let policy = ExitPolicy {
715            v4: Arc::new("accept 80,443".parse().unwrap()),
716            v6: Arc::new("accept 23".parse().unwrap()),
717        };
718        let tok1 = IsolationToken::new();
719        let tok2 = IsolationToken::new();
720        let isolation = StreamIsolationBuilder::new()
721            .owner_token(tok1)
722            .build()
723            .unwrap();
724        let isolation2 = StreamIsolationBuilder::new()
725            .owner_token(tok2)
726            .build()
727            .unwrap();
728
729        let supp_dir = SupportedCircUsage::Dir;
730        let targ_dir = TargetCircUsage::Dir;
731        let supp_exit = SupportedCircUsage::Exit {
732            policy: policy.clone(),
733            isolation: Some(isolation.clone()),
734            country_code: None,
735            all_relays_stable: true,
736        };
737        let supp_exit_iso2 = SupportedCircUsage::Exit {
738            policy: policy.clone(),
739            isolation: Some(isolation2.clone()),
740            country_code: None,
741            all_relays_stable: true,
742        };
743        let supp_exit_no_iso = SupportedCircUsage::Exit {
744            policy,
745            isolation: None,
746            country_code: None,
747            all_relays_stable: true,
748        };
749        let supp_none = SupportedCircUsage::NoUsage;
750
751        let targ_80_v4 = TargetCircUsage::Exit {
752            ports: vec![TargetPort::ipv4(80)],
753            isolation: isolation.clone(),
754            country_code: None,
755            require_stability: false,
756        };
757        let targ_80_v4_iso2 = TargetCircUsage::Exit {
758            ports: vec![TargetPort::ipv4(80)],
759            isolation: isolation2,
760            country_code: None,
761            require_stability: false,
762        };
763        let targ_80_23_v4 = TargetCircUsage::Exit {
764            ports: vec![TargetPort::ipv4(80), TargetPort::ipv4(23)],
765            isolation: isolation.clone(),
766            country_code: None,
767            require_stability: false,
768        };
769
770        let targ_80_23_mixed = TargetCircUsage::Exit {
771            ports: vec![TargetPort::ipv4(80), TargetPort::ipv6(23)],
772            isolation: isolation.clone(),
773            country_code: None,
774            require_stability: false,
775        };
776        let targ_999_v6 = TargetCircUsage::Exit {
777            ports: vec![TargetPort::ipv6(999)],
778            isolation,
779            country_code: None,
780            require_stability: false,
781        };
782        let targ_testing = TargetCircUsage::TimeoutTesting;
783
784        assert!(supp_dir.supports(&targ_dir));
785        assert!(!supp_dir.supports(&targ_80_v4));
786        assert!(!supp_exit.supports(&targ_dir));
787        assert!(supp_exit.supports(&targ_80_v4));
788        assert!(!supp_exit.supports(&targ_80_v4_iso2));
789        assert!(supp_exit.supports(&targ_80_23_mixed));
790        assert!(!supp_exit.supports(&targ_80_23_v4));
791        assert!(!supp_exit.supports(&targ_999_v6));
792        assert!(!supp_exit_iso2.supports(&targ_80_v4));
793        assert!(supp_exit_iso2.supports(&targ_80_v4_iso2));
794        assert!(supp_exit_no_iso.supports(&targ_80_v4));
795        assert!(supp_exit_no_iso.supports(&targ_80_v4_iso2));
796        assert!(!supp_exit_no_iso.supports(&targ_80_23_v4));
797        assert!(!supp_none.supports(&targ_dir));
798        assert!(!supp_none.supports(&targ_80_23_v4));
799        assert!(!supp_none.supports(&targ_80_v4_iso2));
800        assert!(!supp_dir.supports(&targ_testing));
801        assert!(supp_exit.supports(&targ_testing));
802        assert!(supp_exit_no_iso.supports(&targ_testing));
803        assert!(supp_exit_iso2.supports(&targ_testing));
804        assert!(supp_none.supports(&targ_testing));
805    }
806
807    #[test]
808    fn restrict_mut() {
809        let policy = ExitPolicy {
810            v4: Arc::new("accept 80,443".parse().unwrap()),
811            v6: Arc::new("accept 23".parse().unwrap()),
812        };
813
814        let tok1 = IsolationToken::new();
815        let tok2 = IsolationToken::new();
816        let isolation = StreamIsolationBuilder::new()
817            .owner_token(tok1)
818            .build()
819            .unwrap();
820        let isolation2 = StreamIsolationBuilder::new()
821            .owner_token(tok2)
822            .build()
823            .unwrap();
824
825        let supp_dir = SupportedCircUsage::Dir;
826        let targ_dir = TargetCircUsage::Dir;
827        let supp_exit = SupportedCircUsage::Exit {
828            policy: policy.clone(),
829            isolation: Some(isolation.clone()),
830            country_code: None,
831            all_relays_stable: true,
832        };
833        let supp_exit_iso2 = SupportedCircUsage::Exit {
834            policy: policy.clone(),
835            isolation: Some(isolation2.clone()),
836            country_code: None,
837            all_relays_stable: true,
838        };
839        let supp_exit_no_iso = SupportedCircUsage::Exit {
840            policy,
841            isolation: None,
842            country_code: None,
843            all_relays_stable: true,
844        };
845        let supp_none = SupportedCircUsage::NoUsage;
846        let targ_exit = TargetCircUsage::Exit {
847            ports: vec![TargetPort::ipv4(80)],
848            isolation,
849            country_code: None,
850            require_stability: false,
851        };
852        let targ_exit_iso2 = TargetCircUsage::Exit {
853            ports: vec![TargetPort::ipv4(80)],
854            isolation: isolation2,
855            country_code: None,
856            require_stability: false,
857        };
858        let targ_testing = TargetCircUsage::TimeoutTesting;
859
860        // not allowed, do nothing
861        let mut supp_dir_c = supp_dir.clone();
862        assert!(supp_dir_c.restrict_mut(&targ_exit).is_err());
863        assert!(supp_dir_c.restrict_mut(&targ_testing).is_err());
864        assert_isoleq!(supp_dir, supp_dir_c);
865
866        let mut supp_exit_c = supp_exit.clone();
867        assert!(supp_exit_c.restrict_mut(&targ_dir).is_err());
868        assert_isoleq!(supp_exit, supp_exit_c);
869
870        let mut supp_exit_c = supp_exit.clone();
871        assert!(supp_exit_c.restrict_mut(&targ_exit_iso2).is_err());
872        assert_isoleq!(supp_exit, supp_exit_c);
873
874        let mut supp_exit_iso2_c = supp_exit_iso2.clone();
875        assert!(supp_exit_iso2_c.restrict_mut(&targ_exit).is_err());
876        assert_isoleq!(supp_exit_iso2, supp_exit_iso2_c);
877
878        let mut supp_none_c = supp_none.clone();
879        assert!(supp_none_c.restrict_mut(&targ_exit).is_err());
880        assert!(supp_none_c.restrict_mut(&targ_dir).is_err());
881        assert_isoleq!(supp_none_c, supp_none);
882
883        // allowed but nothing to do
884        let mut supp_dir_c = supp_dir.clone();
885        supp_dir_c.restrict_mut(&targ_dir).unwrap();
886        assert_isoleq!(supp_dir, supp_dir_c);
887
888        let mut supp_exit_c = supp_exit.clone();
889        supp_exit_c.restrict_mut(&targ_exit).unwrap();
890        assert_isoleq!(supp_exit, supp_exit_c);
891
892        let mut supp_exit_iso2_c = supp_exit_iso2.clone();
893        supp_exit_iso2_c.restrict_mut(&targ_exit_iso2).unwrap();
894        supp_none_c.restrict_mut(&targ_testing).unwrap();
895        assert_isoleq!(supp_exit_iso2, supp_exit_iso2_c);
896
897        let mut supp_none_c = supp_none.clone();
898        supp_none_c.restrict_mut(&targ_testing).unwrap();
899        assert_isoleq!(supp_none_c, supp_none);
900
901        // allowed, do something
902        let mut supp_exit_no_iso_c = supp_exit_no_iso.clone();
903        supp_exit_no_iso_c.restrict_mut(&targ_exit).unwrap();
904        assert!(supp_exit_no_iso_c.supports(&targ_exit));
905        assert!(!supp_exit_no_iso_c.supports(&targ_exit_iso2));
906
907        let mut supp_exit_no_iso_c = supp_exit_no_iso;
908        supp_exit_no_iso_c.restrict_mut(&targ_exit_iso2).unwrap();
909        assert!(!supp_exit_no_iso_c.supports(&targ_exit));
910        assert!(supp_exit_no_iso_c.supports(&targ_exit_iso2));
911    }
912
913    #[test]
914    fn buildpath() {
915        tor_rtcompat::test_with_all_runtimes!(|rt| async move {
916            let mut rng = testing_rng();
917            let netdir = testnet::construct_netdir().unwrap_if_sufficient().unwrap();
918            let di = (&netdir).into();
919            let config = crate::PathConfig::default();
920            let statemgr = TestingStateMgr::new();
921            let guards =
922                tor_guardmgr::GuardMgr::new(rt.clone(), statemgr.clone(), &TestConfig::default())
923                    .unwrap();
924            guards.install_test_netdir(&netdir);
925            let now = SystemTime::now();
926
927            // Only doing basic tests for now.  We'll test the path
928            // building code a lot more closely in the tests for TorPath
929            // and friends.
930
931            #[cfg(all(feature = "vanguards", feature = "hs-common"))]
932            let vanguards =
933                VanguardMgr::new(&Default::default(), rt.clone(), statemgr, false).unwrap();
934
935            // First, a one-hop directory circuit
936            let (p_dir, u_dir, _, _) = TargetCircUsage::Dir
937                .build_path(
938                    &mut rng,
939                    di,
940                    &guards,
941                    #[cfg(all(feature = "vanguards", feature = "hs-common"))]
942                    &vanguards,
943                    &config,
944                    now,
945                )
946                .unwrap();
947            assert!(matches!(u_dir, SupportedCircUsage::Dir));
948            assert_eq!(p_dir.len(), 1);
949
950            // Now an exit circuit, to port 995.
951            let tok1 = IsolationToken::new();
952            let isolation = StreamIsolationBuilder::new()
953                .owner_token(tok1)
954                .build()
955                .unwrap();
956
957            let exit_usage = TargetCircUsage::Exit {
958                ports: vec![TargetPort::ipv4(995)],
959                isolation: isolation.clone(),
960                country_code: None,
961                require_stability: false,
962            };
963            let (p_exit, u_exit, _, _) = exit_usage
964                .build_path(
965                    &mut rng,
966                    di,
967                    &guards,
968                    #[cfg(all(feature = "vanguards", feature = "hs-common"))]
969                    &vanguards,
970                    &config,
971                    now,
972                )
973                .unwrap();
974            assert!(matches!(
975                u_exit,
976                SupportedCircUsage::Exit {
977                    isolation: ref iso,
978                    ..
979                } if iso.isol_eq(&Some(isolation))
980            ));
981            assert!(u_exit.supports(&exit_usage));
982            assert_eq!(p_exit.len(), 3);
983
984            // Now try testing circuits.
985            let (path, usage, _, _) = TargetCircUsage::TimeoutTesting
986                .build_path(
987                    &mut rng,
988                    di,
989                    &guards,
990                    #[cfg(all(feature = "vanguards", feature = "hs-common"))]
991                    &vanguards,
992                    &config,
993                    now,
994                )
995                .unwrap();
996            let path = match OwnedPath::try_from(&path).unwrap() {
997                OwnedPath::ChannelOnly(_) => panic!("Impossible path type."),
998                OwnedPath::Normal(p) => p,
999            };
1000            assert_eq!(path.len(), 3);
1001
1002            // Make sure that the usage is correct.
1003            let last_relay = netdir.by_ids(&path[2]).unwrap();
1004            let policy = ExitPolicy::from_relay(&last_relay);
1005            // We'll always get exits for these, since we try to build
1006            // paths with an exit if there are any exits.
1007            assert!(policy.allows_some_port());
1008            assert!(last_relay.low_level_details().policies_allow_some_port());
1009            assert_isoleq!(
1010                usage,
1011                SupportedCircUsage::Exit {
1012                    policy,
1013                    isolation: None,
1014                    country_code: None,
1015                    all_relays_stable: true
1016                }
1017            );
1018        });
1019    }
1020
1021    #[test]
1022    fn build_testing_noexit() {
1023        // Here we'll try to build paths for testing circuits on a network
1024        // with no exits.
1025        tor_rtcompat::test_with_all_runtimes!(|rt| async move {
1026            let mut rng = testing_rng();
1027            let netdir = testnet::construct_custom_netdir(|_idx, bld, _| {
1028                bld.md.parse_ipv4_policy("reject 1-65535").unwrap();
1029            })
1030            .unwrap()
1031            .unwrap_if_sufficient()
1032            .unwrap();
1033            let di = (&netdir).into();
1034            let config = crate::PathConfig::default();
1035            let statemgr = TestingStateMgr::new();
1036            let guards =
1037                tor_guardmgr::GuardMgr::new(rt.clone(), statemgr.clone(), &TestConfig::default())
1038                    .unwrap();
1039            guards.install_test_netdir(&netdir);
1040            let now = SystemTime::now();
1041
1042            #[cfg(all(feature = "vanguards", feature = "hs-common"))]
1043            let vanguards =
1044                VanguardMgr::new(&Default::default(), rt.clone(), statemgr, false).unwrap();
1045
1046            let (path, usage, _, _) = TargetCircUsage::TimeoutTesting
1047                .build_path(
1048                    &mut rng,
1049                    di,
1050                    &guards,
1051                    #[cfg(all(feature = "vanguards", feature = "hs-common"))]
1052                    &vanguards,
1053                    &config,
1054                    now,
1055                )
1056                .unwrap();
1057            assert_eq!(path.len(), 3);
1058            assert_isoleq!(usage, SupportedCircUsage::NoUsage);
1059        });
1060    }
1061
1062    #[test]
1063    fn display_target_ports() {
1064        let ports = [];
1065        assert_eq!(TargetPorts::from(&ports[..]).to_string(), "[]");
1066
1067        let ports = [TargetPort::ipv4(80)];
1068        assert_eq!(TargetPorts::from(&ports[..]).to_string(), "80v4");
1069        let ports = [TargetPort::ipv4(80), TargetPort::ipv6(443)];
1070        assert_eq!(TargetPorts::from(&ports[..]).to_string(), "[80v4,443v6]");
1071    }
1072}