1
//! Code related to tracking what activities a circuit can be used for.
2

            
3
use rand::Rng;
4
use std::fmt::{self, Display};
5
use std::sync::Arc;
6
use std::time::SystemTime;
7
use tracing::trace;
8
#[cfg(not(feature = "geoip"))]
9
use void::Void;
10

            
11
use crate::path::{dirpath::DirPathBuilder, exitpath::ExitPathBuilder, TorPath};
12
use tor_chanmgr::ChannelUsage;
13
#[cfg(feature = "geoip")]
14
use tor_error::internal;
15
use tor_guardmgr::{GuardMgr, GuardMonitor, GuardUsable};
16
use tor_netdir::Relay;
17
use tor_netdoc::types::policy::PortPolicy;
18
use tor_rtcompat::Runtime;
19
#[cfg(feature = "hs-common")]
20
use {crate::path::hspath::HsPathBuilder, crate::HsCircStemKind};
21

            
22
#[cfg(feature = "specific-relay")]
23
use tor_linkspec::{HasChanMethod, HasRelayIds};
24

            
25
#[cfg(feature = "geoip")]
26
use 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"))]
36
pub(crate) type CountryCode = Void;
37

            
38
#[cfg(any(feature = "specific-relay", feature = "hs-common"))]
39
use tor_linkspec::OwnedChanTarget;
40

            
41
#[cfg(all(feature = "vanguards", feature = "hs-common"))]
42
use tor_guardmgr::vanguards::VanguardMgr;
43

            
44
use crate::isolation::{IsolationHelper, StreamIsolation};
45
use crate::mgr::{AbstractCirc, OpenEntry, RestrictionFailed};
46
use crate::Result;
47

            
48
pub 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)]
52
pub(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)]
63
pub struct TargetPorts(Vec<TargetPort>);
64

            
65
impl From<&'_ [TargetPort]> for TargetPorts {
66
186
    fn from(ports: &'_ [TargetPort]) -> Self {
67
186
        TargetPorts(ports.into())
68
186
    }
69
}
70

            
71
impl Display for TargetPorts {
72
6
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
73
6
        let brackets = self.0.len() != 1;
74
6
        if brackets {
75
4
            write!(f, "[")?;
76
2
        }
77
6
        for (i, port) in self.0.iter().enumerate() {
78
6
            if i > 0 {
79
2
                write!(f, ",")?;
80
4
            }
81
6
            write!(f, "{}", port)?;
82
        }
83
6
        if brackets {
84
4
            write!(f, "]")?;
85
2
        }
86
6
        Ok(())
87
6
    }
88
}
89

            
90
impl ExitPolicy {
91
    /// Make a new exit policy from a given Relay.
92
46
    pub(crate) fn from_relay(relay: &Relay<'_>) -> Self {
93
46
        // TODO #504: it might be a good idea to lower this whole type into
94
46
        // tor-netdir or tor-relay-selection.  That way we wouldn't need to
95
46
        // invoke these Relay-specific methods in tor-circmgr.
96
46
        Self {
97
46
            v4: relay.low_level_details().ipv4_policy(),
98
46
            v6: relay.low_level_details().ipv6_policy(),
99
46
        }
100
46
    }
101

            
102
    /// Make a exit policy based on the allowed ports in TargetPorts.
103
    #[cfg(test)]
104
180
    pub(crate) fn from_target_ports(target_ports: &TargetPorts) -> Self {
105
180
        let (v6_ports, v4_ports) = target_ports
106
180
            .0
107
180
            .iter()
108
366
            .partition::<Vec<TargetPort>, _>(|port| port.ipv6);
109
180

            
110
180
        Self {
111
366
            v4: PortPolicy::from_allowed_port_list(v4_ports.iter().map(|port| port.port).collect())
112
180
                .intern(),
113
180
            v6: PortPolicy::from_allowed_port_list(v6_ports.iter().map(|port| port.port).collect())
114
180
                .intern(),
115
180
        }
116
180
    }
117

            
118
    /// Return true if a given port is contained in this ExitPolicy.
119
710
    fn allows_port(&self, p: TargetPort) -> bool {
120
710
        let policy = if p.ipv6 { &self.v6 } else { &self.v4 };
121
710
        policy.allows_port(p.port)
122
710
    }
123

            
124
    /// Returns true if this policy allows any ports at all.
125
24
    fn allows_some_port(&self) -> bool {
126
24
        self.v4.allows_some_port() || self.v6.allows_some_port()
127
24
    }
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)]
135
pub(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
        kind: HsCircStemKind,
192
    },
193
}
194

            
195
/// The purposes for which a circuit is usable.
196
///
197
/// This type should stay internal to the circmgr crate for now: we'll probably
198
/// want to refactor it a lot.
199
#[derive(Clone, Debug)]
200
pub(crate) enum SupportedCircUsage {
201
    /// Usable for BEGINDIR-based non-anonymous directory connections
202
    Dir,
203
    /// Usable to exit to a set of ports.
204
    Exit {
205
        /// Exit policy of the circuit
206
        policy: ExitPolicy,
207
        /// Isolation group the circuit is part of. None when the circuit is not yet assigned to an
208
        /// isolation group.
209
        isolation: Option<StreamIsolation>,
210
        /// Country code the exit is in, or `None` if no country could be determined.
211
        country_code: Option<CountryCode>,
212
        /// Whether every relay in this circuit has the "Stable" flag.
213
        //
214
        // TODO #504: It would be good to remove this field, if we can.
215
        all_relays_stable: bool,
216
    },
217
    /// This circuit is not suitable for any usage.
218
    NoUsage,
219
    /// This circuit is for some hs-related usage.
220
    /// (It should never be given to the circuit manager; the
221
    /// `HsPool` code will handle it instead.)
222
    #[cfg(feature = "hs-common")]
223
    HsOnly,
224
    /// Use only for BEGINDIR-based non-anonymous directory connections
225
    /// to a particular target (which may not be in the netdir).
226
    #[cfg(feature = "specific-relay")]
227
    DirSpecificTarget(OwnedChanTarget),
228
}
229

            
230
impl TargetCircUsage {
231
    /// Construct path for a given circuit purpose; return it and the
232
    /// usage that it _actually_ supports.
233
32
    pub(crate) fn build_path<'a, R: Rng, RT: Runtime>(
234
32
        &self,
235
32
        rng: &mut R,
236
32
        netdir: crate::DirInfo<'a>,
237
32
        guards: &GuardMgr<RT>,
238
32
        #[cfg(all(feature = "vanguards", feature = "hs-common"))] vanguards: &VanguardMgr<RT>,
239
32
        config: &crate::PathConfig,
240
32
        now: SystemTime,
241
32
    ) -> Result<(
242
32
        TorPath<'a>,
243
32
        SupportedCircUsage,
244
32
        Option<GuardMonitor>,
245
32
        Option<GuardUsable>,
246
32
    )> {
247
32
        match self {
248
            TargetCircUsage::Dir => {
249
8
                let (path, mon, usable) = DirPathBuilder::new().pick_path(guards)?;
250
8
                Ok((path, SupportedCircUsage::Dir, Some(mon), Some(usable)))
251
            }
252
            TargetCircUsage::Preemptive {
253
                port,
254
                require_stability,
255
                ..
256
            } => {
257
                // FIXME(eta): this is copypasta from `TargetCircUsage::Exit`.
258
                let (path, mon, usable) = ExitPathBuilder::from_target_ports(port.iter().copied())
259
                    .require_stability(*require_stability)
260
                    .pick_path(rng, netdir, guards, config, now)?;
261
                let policy = path
262
                    .exit_policy()
263
                    .expect("ExitPathBuilder gave us a one-hop circuit?");
264
                #[cfg(feature = "geoip")]
265
                let country_code = path.country_code();
266
                #[cfg(not(feature = "geoip"))]
267
                let country_code = None;
268
                let all_relays_stable = path.appears_stable();
269
                Ok((
270
                    path,
271
                    SupportedCircUsage::Exit {
272
                        policy,
273
                        isolation: None,
274
                        country_code,
275
                        all_relays_stable,
276
                    },
277
                    Some(mon),
278
                    Some(usable),
279
                ))
280
            }
281
            TargetCircUsage::Exit {
282
8
                ports: p,
283
8
                isolation,
284
8
                country_code,
285
8
                require_stability,
286
            } => {
287
                #[cfg(feature = "geoip")]
288
8
                let mut builder = if let Some(cc) = country_code {
289
                    ExitPathBuilder::in_given_country(*cc, p.clone())
290
                } else {
291
8
                    ExitPathBuilder::from_target_ports(p.clone())
292
                };
293
                #[cfg(not(feature = "geoip"))]
294
                let mut builder = ExitPathBuilder::from_target_ports(p.clone());
295

            
296
8
                builder.require_stability(*require_stability);
297

            
298
8
                let (path, mon, usable) = builder.pick_path(rng, netdir, guards, config, now)?;
299
8
                let policy = path
300
8
                    .exit_policy()
301
8
                    .expect("ExitPathBuilder gave us a one-hop circuit?");
302
8

            
303
8
                #[cfg(feature = "geoip")]
304
8
                let resulting_cc = path.country_code();
305
8
                #[cfg(feature = "geoip")]
306
8
                if resulting_cc != *country_code {
307
                    internal!(
308
                        "asked for a country code of {:?}, got {:?}",
309
                        country_code,
310
                        resulting_cc
311
                    );
312
8
                }
313
8
                let all_relays_stable = path.appears_stable();
314
8

            
315
8
                #[cfg(not(feature = "geoip"))]
316
8
                let resulting_cc = *country_code; // avoid unused var warning
317
8
                Ok((
318
8
                    path,
319
8
                    SupportedCircUsage::Exit {
320
8
                        policy,
321
8
                        isolation: Some(isolation.clone()),
322
8
                        country_code: resulting_cc,
323
8
                        all_relays_stable,
324
8
                    },
325
8
                    Some(mon),
326
8
                    Some(usable),
327
8
                ))
328
            }
329
            TargetCircUsage::TimeoutTesting => {
330
16
                let (path, mon, usable) = ExitPathBuilder::for_timeout_testing()
331
16
                    .require_stability(false)
332
16
                    .pick_path(rng, netdir, guards, config, now)?;
333
16
                let policy = path.exit_policy();
334
16
                #[cfg(feature = "geoip")]
335
16
                let country_code = path.country_code();
336
                #[cfg(not(feature = "geoip"))]
337
                let country_code = None;
338
16
                let usage = match policy {
339
16
                    Some(policy) if policy.allows_some_port() => SupportedCircUsage::Exit {
340
8
                        policy,
341
8
                        isolation: None,
342
8
                        country_code,
343
8
                        all_relays_stable: path.appears_stable(),
344
8
                    },
345
8
                    _ => SupportedCircUsage::NoUsage,
346
                };
347

            
348
16
                Ok((path, usage, Some(mon), Some(usable)))
349
            }
350
            #[cfg(feature = "specific-relay")]
351
            TargetCircUsage::DirSpecificTarget(target) => {
352
                let path = TorPath::new_one_hop_owned(target);
353
                let usage = SupportedCircUsage::DirSpecificTarget(target.clone());
354
                Ok((path, usage, None, None))
355
            }
356
            #[cfg(feature = "hs-common")]
357
            TargetCircUsage::HsCircBase {
358
                compatible_with_target,
359
                kind,
360
            } => {
361
                let path_builder = HsPathBuilder::new(compatible_with_target.clone(), *kind);
362
                cfg_if::cfg_if! {
363
                    if #[cfg(all(feature = "vanguards", feature = "hs-common"))] {
364
                        let (path, mon, usable) = path_builder
365
                            .pick_path_with_vanguards::<_, RT>(rng, netdir, guards, vanguards, config, now)?;
366
                    } else {
367
                        let (path, mon, usable) = path_builder
368
                            .pick_path::<_, RT>(rng, netdir, guards, config, now)?;
369
                    }
370
                };
371
                let usage = SupportedCircUsage::HsOnly;
372
                Ok((path, usage, Some(mon), Some(usable)))
373
            }
374
        }
375
32
    }
376

            
377
    /// Create a TargetCircUsage::Exit for a given set of IPv4 ports, with no stream isolation, for
378
    /// use in tests.
379
    #[cfg(test)]
380
114
    pub(crate) fn new_from_ipv4_ports(ports: &[u16]) -> Self {
381
114
        TargetCircUsage::Exit {
382
207
            ports: ports.iter().map(|p| TargetPort::ipv4(*p)).collect(),
383
114
            isolation: StreamIsolation::no_isolation(),
384
114
            country_code: None,
385
114
            require_stability: false,
386
114
        }
387
114
    }
388
}
389

            
390
/// Return true if `a` and `b` count as the same target for the purpose of
391
/// comparing `DirSpecificTarget` values.
392
#[cfg(feature = "specific-relay")]
393
fn owned_targets_equivalent(a: &OwnedChanTarget, b: &OwnedChanTarget) -> bool {
394
    // We ignore `addresses` here, since they can be different if one of our
395
    // arguments comes from only a bridge line, and the other comes from a
396
    // bridge line and a descriptor.
397
    a.same_relay_ids(b) && a.chan_method() == b.chan_method()
398
}
399

            
400
impl SupportedCircUsage {
401
    /// Return true if this spec permits the usage described by `other`.
402
    ///
403
    /// If this function returns `true`, then it is okay to use a circuit
404
    /// with this spec for the target usage described by `other`.
405
1470
    pub(crate) fn supports(&self, target: &TargetCircUsage) -> bool {
406
        use SupportedCircUsage::*;
407
1470
        match (self, target) {
408
2
            (Dir, TargetCircUsage::Dir) => true,
409
            (
410
                Exit {
411
1274
                    policy: p1,
412
1274
                    isolation: i1,
413
1274
                    country_code: cc1,
414
1274
                    all_relays_stable,
415
1274
                },
416
1274
                TargetCircUsage::Exit {
417
1274
                    ports: p2,
418
1274
                    isolation: i2,
419
1274
                    country_code: cc2,
420
1274
                    require_stability,
421
1274
                },
422
1274
            ) => {
423
1274
                // TODO #504: These calculations don't touch Relays, but they
424
1274
                // seem like they should be done using the types of tor-relay-selection.
425
1274
                i1.as_ref()
426
1875
                    .map(|i1| i1.compatible_same_type(i2))
427
1274
                    .unwrap_or(true)
428
494
                    && (!require_stability || *all_relays_stable)
429
763
                    && p2.iter().all(|port| p1.allows_port(*port))
430
444
                    && (cc2.is_none() || cc1 == cc2)
431
            }
432
            (
433
                Exit {
434
174
                    policy,
435
174
                    isolation,
436
174
                    all_relays_stable,
437
174
                    ..
438
174
                },
439
174
                TargetCircUsage::Preemptive {
440
174
                    port,
441
174
                    require_stability,
442
174
                    ..
443
174
                },
444
174
            ) => {
445
174
                // TODO #504: It would be good to simply remove stability
446
174
                // calculation from tor-circmgr.
447
174
                if *require_stability && !all_relays_stable {
448
                    return false;
449
174
                }
450
174
                if isolation.is_some() {
451
                    // If the circuit has a stream isolation, we might not be able to use it
452
                    // for new streams that don't share it.
453
                    return false;
454
174
                }
455
                // TODO #504: Similarly, it would be good to have exit port
456
                // calculation done elsewhere.
457
174
                if let Some(p) = port {
458
168
                    policy.allows_port(*p)
459
                } else {
460
6
                    true
461
                }
462
            }
463
8
            (Exit { .. } | NoUsage, TargetCircUsage::TimeoutTesting) => true,
464
            #[cfg(feature = "specific-relay")]
465
            (DirSpecificTarget(a), TargetCircUsage::DirSpecificTarget(b)) => {
466
                owned_targets_equivalent(a, b)
467
            }
468
12
            (_, _) => false,
469
        }
470
1470
    }
471

            
472
    /// Change the value of this spec based on the circuit having been used for `usage`.
473
    ///
474
    /// Returns an error and makes no changes to `self` if `usage` was not supported by this spec.
475
    ///
476
    /// If this function returns Ok, the resulting spec will be contained by the original spec, and
477
    /// will support `usage`.
478
502
    pub(crate) fn restrict_mut(
479
502
        &mut self,
480
502
        usage: &TargetCircUsage,
481
502
    ) -> std::result::Result<(), RestrictionFailed> {
482
        use SupportedCircUsage::*;
483
502
        match (self, usage) {
484
2
            (Dir, TargetCircUsage::Dir) => Ok(()),
485
            // This usage is only used to create circuits preemptively, and doesn't actually
486
            // correspond to any streams; accordingly, we don't need to modify the circuit's
487
            // acceptable usage at all.
488
            (Exit { .. }, TargetCircUsage::Preemptive { .. }) => Ok(()),
489
            (
490
                Exit {
491
486
                    isolation: ref mut isol1,
492
486
                    ..
493
486
                },
494
486
                TargetCircUsage::Exit { isolation: i2, .. },
495
            ) => {
496
486
                if let Some(i1) = isol1 {
497
416
                    if let Some(new_isolation) = i1.join_same_type(i2) {
498
                        // there was some isolation, and the requested usage is compatible, saving
499
                        // the new isolation into self
500
412
                        *isol1 = Some(new_isolation);
501
412
                        Ok(())
502
                    } else {
503
4
                        Err(RestrictionFailed::NotSupported)
504
                    }
505
                } else {
506
                    // there was no isolation yet on self, applying the restriction from usage
507
70
                    *isol1 = Some(i2.clone());
508
70
                    Ok(())
509
                }
510
            }
511
4
            (Exit { .. } | NoUsage, TargetCircUsage::TimeoutTesting) => Ok(()),
512
            #[cfg(feature = "specific-relay")]
513
            (DirSpecificTarget(a), TargetCircUsage::DirSpecificTarget(b))
514
                if owned_targets_equivalent(a, b) =>
515
            {
516
                Ok(())
517
            }
518
10
            (_, _) => Err(RestrictionFailed::NotSupported),
519
        }
520
502
    }
521

            
522
    /// Find all open circuits in `list` whose specifications permit `usage`.
523
612
    pub(crate) fn find_supported<'a, 'b, C: AbstractCirc>(
524
612
        list: impl Iterator<Item = &'b mut OpenEntry<C>>,
525
612
        usage: &TargetCircUsage,
526
612
    ) -> Vec<&'b mut OpenEntry<C>> {
527
        /// Returns all circuits in `list` for which `circuit.spec.supports(usage)` returns `true`.
528
612
        fn find_supported_internal<'a, 'b, C: AbstractCirc>(
529
612
            list: impl Iterator<Item = &'b mut OpenEntry<C>>,
530
612
            usage: &TargetCircUsage,
531
612
        ) -> Vec<&'b mut OpenEntry<C>> {
532
1324
            list.filter(|circ| circ.supports(usage)).collect()
533
612
        }
534

            
535
612
        match usage {
536
58
            TargetCircUsage::Preemptive { circs, .. } => {
537
58
                let supported = find_supported_internal(list, usage);
538
58
                // We need to have at least two circuits that support `port` in order
539
58
                // to reuse them; otherwise, we must create a new circuit, so
540
58
                // that we get closer to having two circuits.
541
58
                trace!(
542
                    "preemptive usage {:?} matches {} active circuits",
543
                    usage,
544
                    supported.len()
545
                );
546
58
                if supported.len() >= *circs {
547
12
                    supported
548
                } else {
549
46
                    vec![]
550
                }
551
            }
552
554
            _ => find_supported_internal(list, usage),
553
        }
554
612
    }
555

            
556
    /// How the circuit will be used, for use by the channel
557
    pub(crate) fn channel_usage(&self) -> ChannelUsage {
558
        use ChannelUsage as CU;
559
        use SupportedCircUsage as SCU;
560
        match self {
561
            SCU::Dir => CU::Dir,
562
            #[cfg(feature = "specific-relay")]
563
            SCU::DirSpecificTarget(_) => CU::Dir,
564
            SCU::Exit { .. } => CU::UserTraffic,
565
            SCU::NoUsage => CU::UselessCircuit,
566
            #[cfg(feature = "hs-common")]
567
            SCU::HsOnly => CU::UserTraffic,
568
        }
569
    }
570
}
571

            
572
#[cfg(test)]
573
pub(crate) mod test {
574
    #![allow(clippy::unwrap_used)]
575
    use super::*;
576
    use crate::isolation::test::{assert_isoleq, IsolationTokenEq};
577
    use crate::isolation::{IsolationToken, StreamIsolationBuilder};
578
    use crate::path::OwnedPath;
579
    use tor_basic_utils::test_rng::testing_rng;
580
    use tor_guardmgr::TestConfig;
581
    use tor_llcrypto::pk::ed25519::Ed25519Identity;
582
    use tor_netdir::testnet;
583
    use tor_persist::TestingStateMgr;
584

            
585
    impl IsolationTokenEq for TargetCircUsage {
586
        fn isol_eq(&self, other: &Self) -> bool {
587
            use TargetCircUsage::*;
588
            match (self, other) {
589
                (Dir, Dir) => true,
590
                (
591
                    Exit {
592
                        ports: p1,
593
                        isolation: is1,
594
                        country_code: cc1,
595
                        ..
596
                    },
597
                    Exit {
598
                        ports: p2,
599
                        isolation: is2,
600
                        country_code: cc2,
601
                        ..
602
                    },
603
                ) => p1 == p2 && cc1 == cc2 && is1.isol_eq(is2),
604
                (TimeoutTesting, TimeoutTesting) => true,
605
                (
606
                    Preemptive {
607
                        port: p1,
608
                        circs: c1,
609
                        ..
610
                    },
611
                    Preemptive {
612
                        port: p2,
613
                        circs: c2,
614
                        ..
615
                    },
616
                ) => p1 == p2 && c1 == c2,
617
                _ => false,
618
            }
619
        }
620
    }
621

            
622
    impl IsolationTokenEq for SupportedCircUsage {
623
        fn isol_eq(&self, other: &Self) -> bool {
624
            use SupportedCircUsage::*;
625
            match (self, other) {
626
                (Dir, Dir) => true,
627
                (
628
                    Exit {
629
                        policy: p1,
630
                        isolation: is1,
631
                        country_code: cc1,
632
                        ..
633
                    },
634
                    Exit {
635
                        policy: p2,
636
                        isolation: is2,
637
                        country_code: cc2,
638
                        ..
639
                    },
640
                ) => p1 == p2 && is1.isol_eq(is2) && cc1 == cc2,
641
                (NoUsage, NoUsage) => true,
642
                _ => false,
643
            }
644
        }
645
    }
646

            
647
    #[test]
648
    fn exit_policy() {
649
        use tor_netdir::testnet::construct_custom_netdir;
650
        use tor_netdoc::doc::netstatus::RelayFlags;
651

            
652
        let network = construct_custom_netdir(|idx, nb, _| {
653
            if (0x21..0x27).contains(&idx) {
654
                nb.rs.add_flags(RelayFlags::BAD_EXIT);
655
            }
656
        })
657
        .unwrap()
658
        .unwrap_if_sufficient()
659
        .unwrap();
660

            
661
        // Nodes with ID 0x0a through 0x13 and 0x1e through 0x27 are
662
        // exits.  Odd-numbered ones allow only ports 80 and 443;
663
        // even-numbered ones allow all ports.  Nodes with ID 0x21
664
        // through 0x27 are bad exits.
665
        let id_noexit: Ed25519Identity = [0x05; 32].into();
666
        let id_webexit: Ed25519Identity = [0x11; 32].into();
667
        let id_fullexit: Ed25519Identity = [0x20; 32].into();
668
        let id_badexit: Ed25519Identity = [0x25; 32].into();
669

            
670
        let not_exit = network.by_id(&id_noexit).unwrap();
671
        let web_exit = network.by_id(&id_webexit).unwrap();
672
        let full_exit = network.by_id(&id_fullexit).unwrap();
673
        let bad_exit = network.by_id(&id_badexit).unwrap();
674

            
675
        let ep_none = ExitPolicy::from_relay(&not_exit);
676
        let ep_web = ExitPolicy::from_relay(&web_exit);
677
        let ep_full = ExitPolicy::from_relay(&full_exit);
678
        let ep_bad = ExitPolicy::from_relay(&bad_exit);
679

            
680
        assert!(!ep_none.allows_port(TargetPort::ipv4(80)));
681
        assert!(!ep_none.allows_port(TargetPort::ipv4(9999)));
682

            
683
        assert!(ep_web.allows_port(TargetPort::ipv4(80)));
684
        assert!(ep_web.allows_port(TargetPort::ipv4(443)));
685
        assert!(!ep_web.allows_port(TargetPort::ipv4(9999)));
686

            
687
        assert!(ep_full.allows_port(TargetPort::ipv4(80)));
688
        assert!(ep_full.allows_port(TargetPort::ipv4(443)));
689
        assert!(ep_full.allows_port(TargetPort::ipv4(9999)));
690

            
691
        assert!(!ep_bad.allows_port(TargetPort::ipv4(80)));
692

            
693
        // Note that nobody in the testdir::network allows ipv6.
694
        assert!(!ep_none.allows_port(TargetPort::ipv6(80)));
695
        assert!(!ep_web.allows_port(TargetPort::ipv6(80)));
696
        assert!(!ep_full.allows_port(TargetPort::ipv6(80)));
697
        assert!(!ep_bad.allows_port(TargetPort::ipv6(80)));
698

            
699
        // Check is_supported_by while we're here.
700
        assert!(TargetPort::ipv4(80).is_supported_by(&web_exit.low_level_details()));
701
        assert!(!TargetPort::ipv6(80).is_supported_by(&web_exit.low_level_details()));
702
        assert!(!TargetPort::ipv6(80).is_supported_by(&bad_exit.low_level_details()));
703
    }
704

            
705
    #[test]
706
    fn usage_ops() {
707
        // Make an exit-policy object that allows web on IPv4 and
708
        // smtp on IPv6.
709
        let policy = ExitPolicy {
710
            v4: Arc::new("accept 80,443".parse().unwrap()),
711
            v6: Arc::new("accept 23".parse().unwrap()),
712
        };
713
        let tok1 = IsolationToken::new();
714
        let tok2 = IsolationToken::new();
715
        let isolation = StreamIsolationBuilder::new()
716
            .owner_token(tok1)
717
            .build()
718
            .unwrap();
719
        let isolation2 = StreamIsolationBuilder::new()
720
            .owner_token(tok2)
721
            .build()
722
            .unwrap();
723

            
724
        let supp_dir = SupportedCircUsage::Dir;
725
        let targ_dir = TargetCircUsage::Dir;
726
        let supp_exit = SupportedCircUsage::Exit {
727
            policy: policy.clone(),
728
            isolation: Some(isolation.clone()),
729
            country_code: None,
730
            all_relays_stable: true,
731
        };
732
        let supp_exit_iso2 = SupportedCircUsage::Exit {
733
            policy: policy.clone(),
734
            isolation: Some(isolation2.clone()),
735
            country_code: None,
736
            all_relays_stable: true,
737
        };
738
        let supp_exit_no_iso = SupportedCircUsage::Exit {
739
            policy,
740
            isolation: None,
741
            country_code: None,
742
            all_relays_stable: true,
743
        };
744
        let supp_none = SupportedCircUsage::NoUsage;
745

            
746
        let targ_80_v4 = TargetCircUsage::Exit {
747
            ports: vec![TargetPort::ipv4(80)],
748
            isolation: isolation.clone(),
749
            country_code: None,
750
            require_stability: false,
751
        };
752
        let targ_80_v4_iso2 = TargetCircUsage::Exit {
753
            ports: vec![TargetPort::ipv4(80)],
754
            isolation: isolation2,
755
            country_code: None,
756
            require_stability: false,
757
        };
758
        let targ_80_23_v4 = TargetCircUsage::Exit {
759
            ports: vec![TargetPort::ipv4(80), TargetPort::ipv4(23)],
760
            isolation: isolation.clone(),
761
            country_code: None,
762
            require_stability: false,
763
        };
764

            
765
        let targ_80_23_mixed = TargetCircUsage::Exit {
766
            ports: vec![TargetPort::ipv4(80), TargetPort::ipv6(23)],
767
            isolation: isolation.clone(),
768
            country_code: None,
769
            require_stability: false,
770
        };
771
        let targ_999_v6 = TargetCircUsage::Exit {
772
            ports: vec![TargetPort::ipv6(999)],
773
            isolation,
774
            country_code: None,
775
            require_stability: false,
776
        };
777
        let targ_testing = TargetCircUsage::TimeoutTesting;
778

            
779
        assert!(supp_dir.supports(&targ_dir));
780
        assert!(!supp_dir.supports(&targ_80_v4));
781
        assert!(!supp_exit.supports(&targ_dir));
782
        assert!(supp_exit.supports(&targ_80_v4));
783
        assert!(!supp_exit.supports(&targ_80_v4_iso2));
784
        assert!(supp_exit.supports(&targ_80_23_mixed));
785
        assert!(!supp_exit.supports(&targ_80_23_v4));
786
        assert!(!supp_exit.supports(&targ_999_v6));
787
        assert!(!supp_exit_iso2.supports(&targ_80_v4));
788
        assert!(supp_exit_iso2.supports(&targ_80_v4_iso2));
789
        assert!(supp_exit_no_iso.supports(&targ_80_v4));
790
        assert!(supp_exit_no_iso.supports(&targ_80_v4_iso2));
791
        assert!(!supp_exit_no_iso.supports(&targ_80_23_v4));
792
        assert!(!supp_none.supports(&targ_dir));
793
        assert!(!supp_none.supports(&targ_80_23_v4));
794
        assert!(!supp_none.supports(&targ_80_v4_iso2));
795
        assert!(!supp_dir.supports(&targ_testing));
796
        assert!(supp_exit.supports(&targ_testing));
797
        assert!(supp_exit_no_iso.supports(&targ_testing));
798
        assert!(supp_exit_iso2.supports(&targ_testing));
799
        assert!(supp_none.supports(&targ_testing));
800
    }
801

            
802
    #[test]
803
    fn restrict_mut() {
804
        let policy = ExitPolicy {
805
            v4: Arc::new("accept 80,443".parse().unwrap()),
806
            v6: Arc::new("accept 23".parse().unwrap()),
807
        };
808

            
809
        let tok1 = IsolationToken::new();
810
        let tok2 = IsolationToken::new();
811
        let isolation = StreamIsolationBuilder::new()
812
            .owner_token(tok1)
813
            .build()
814
            .unwrap();
815
        let isolation2 = StreamIsolationBuilder::new()
816
            .owner_token(tok2)
817
            .build()
818
            .unwrap();
819

            
820
        let supp_dir = SupportedCircUsage::Dir;
821
        let targ_dir = TargetCircUsage::Dir;
822
        let supp_exit = SupportedCircUsage::Exit {
823
            policy: policy.clone(),
824
            isolation: Some(isolation.clone()),
825
            country_code: None,
826
            all_relays_stable: true,
827
        };
828
        let supp_exit_iso2 = SupportedCircUsage::Exit {
829
            policy: policy.clone(),
830
            isolation: Some(isolation2.clone()),
831
            country_code: None,
832
            all_relays_stable: true,
833
        };
834
        let supp_exit_no_iso = SupportedCircUsage::Exit {
835
            policy,
836
            isolation: None,
837
            country_code: None,
838
            all_relays_stable: true,
839
        };
840
        let supp_none = SupportedCircUsage::NoUsage;
841
        let targ_exit = TargetCircUsage::Exit {
842
            ports: vec![TargetPort::ipv4(80)],
843
            isolation,
844
            country_code: None,
845
            require_stability: false,
846
        };
847
        let targ_exit_iso2 = TargetCircUsage::Exit {
848
            ports: vec![TargetPort::ipv4(80)],
849
            isolation: isolation2,
850
            country_code: None,
851
            require_stability: false,
852
        };
853
        let targ_testing = TargetCircUsage::TimeoutTesting;
854

            
855
        // not allowed, do nothing
856
        let mut supp_dir_c = supp_dir.clone();
857
        assert!(supp_dir_c.restrict_mut(&targ_exit).is_err());
858
        assert!(supp_dir_c.restrict_mut(&targ_testing).is_err());
859
        assert_isoleq!(supp_dir, supp_dir_c);
860

            
861
        let mut supp_exit_c = supp_exit.clone();
862
        assert!(supp_exit_c.restrict_mut(&targ_dir).is_err());
863
        assert_isoleq!(supp_exit, supp_exit_c);
864

            
865
        let mut supp_exit_c = supp_exit.clone();
866
        assert!(supp_exit_c.restrict_mut(&targ_exit_iso2).is_err());
867
        assert_isoleq!(supp_exit, supp_exit_c);
868

            
869
        let mut supp_exit_iso2_c = supp_exit_iso2.clone();
870
        assert!(supp_exit_iso2_c.restrict_mut(&targ_exit).is_err());
871
        assert_isoleq!(supp_exit_iso2, supp_exit_iso2_c);
872

            
873
        let mut supp_none_c = supp_none.clone();
874
        assert!(supp_none_c.restrict_mut(&targ_exit).is_err());
875
        assert!(supp_none_c.restrict_mut(&targ_dir).is_err());
876
        assert_isoleq!(supp_none_c, supp_none);
877

            
878
        // allowed but nothing to do
879
        let mut supp_dir_c = supp_dir.clone();
880
        supp_dir_c.restrict_mut(&targ_dir).unwrap();
881
        assert_isoleq!(supp_dir, supp_dir_c);
882

            
883
        let mut supp_exit_c = supp_exit.clone();
884
        supp_exit_c.restrict_mut(&targ_exit).unwrap();
885
        assert_isoleq!(supp_exit, supp_exit_c);
886

            
887
        let mut supp_exit_iso2_c = supp_exit_iso2.clone();
888
        supp_exit_iso2_c.restrict_mut(&targ_exit_iso2).unwrap();
889
        supp_none_c.restrict_mut(&targ_testing).unwrap();
890
        assert_isoleq!(supp_exit_iso2, supp_exit_iso2_c);
891

            
892
        let mut supp_none_c = supp_none.clone();
893
        supp_none_c.restrict_mut(&targ_testing).unwrap();
894
        assert_isoleq!(supp_none_c, supp_none);
895

            
896
        // allowed, do something
897
        let mut supp_exit_no_iso_c = supp_exit_no_iso.clone();
898
        supp_exit_no_iso_c.restrict_mut(&targ_exit).unwrap();
899
        assert!(supp_exit_no_iso_c.supports(&targ_exit));
900
        assert!(!supp_exit_no_iso_c.supports(&targ_exit_iso2));
901

            
902
        let mut supp_exit_no_iso_c = supp_exit_no_iso;
903
        supp_exit_no_iso_c.restrict_mut(&targ_exit_iso2).unwrap();
904
        assert!(!supp_exit_no_iso_c.supports(&targ_exit));
905
        assert!(supp_exit_no_iso_c.supports(&targ_exit_iso2));
906
    }
907

            
908
    #[test]
909
    fn buildpath() {
910
        tor_rtcompat::test_with_all_runtimes!(|rt| async move {
911
            let mut rng = testing_rng();
912
            let netdir = testnet::construct_netdir().unwrap_if_sufficient().unwrap();
913
            let di = (&netdir).into();
914
            let config = crate::PathConfig::default();
915
            let statemgr = TestingStateMgr::new();
916
            let guards =
917
                tor_guardmgr::GuardMgr::new(rt.clone(), statemgr.clone(), &TestConfig::default())
918
                    .unwrap();
919
            guards.install_test_netdir(&netdir);
920
            let now = SystemTime::now();
921

            
922
            // Only doing basic tests for now.  We'll test the path
923
            // building code a lot more closely in the tests for TorPath
924
            // and friends.
925

            
926
            #[cfg(all(feature = "vanguards", feature = "hs-common"))]
927
            let vanguards =
928
                VanguardMgr::new(&Default::default(), rt.clone(), statemgr, false).unwrap();
929

            
930
            // First, a one-hop directory circuit
931
            let (p_dir, u_dir, _, _) = TargetCircUsage::Dir
932
                .build_path(
933
                    &mut rng,
934
                    di,
935
                    &guards,
936
                    #[cfg(all(feature = "vanguards", feature = "hs-common"))]
937
                    &vanguards,
938
                    &config,
939
                    now,
940
                )
941
                .unwrap();
942
            assert!(matches!(u_dir, SupportedCircUsage::Dir));
943
            assert_eq!(p_dir.len(), 1);
944

            
945
            // Now an exit circuit, to port 995.
946
            let tok1 = IsolationToken::new();
947
            let isolation = StreamIsolationBuilder::new()
948
                .owner_token(tok1)
949
                .build()
950
                .unwrap();
951

            
952
            let exit_usage = TargetCircUsage::Exit {
953
                ports: vec![TargetPort::ipv4(995)],
954
                isolation: isolation.clone(),
955
                country_code: None,
956
                require_stability: false,
957
            };
958
            let (p_exit, u_exit, _, _) = exit_usage
959
                .build_path(
960
                    &mut rng,
961
                    di,
962
                    &guards,
963
                    #[cfg(all(feature = "vanguards", feature = "hs-common"))]
964
                    &vanguards,
965
                    &config,
966
                    now,
967
                )
968
                .unwrap();
969
            assert!(matches!(
970
                u_exit,
971
                SupportedCircUsage::Exit {
972
                    isolation: ref iso,
973
                    ..
974
                } if iso.isol_eq(&Some(isolation))
975
            ));
976
            assert!(u_exit.supports(&exit_usage));
977
            assert_eq!(p_exit.len(), 3);
978

            
979
            // Now try testing circuits.
980
            let (path, usage, _, _) = TargetCircUsage::TimeoutTesting
981
                .build_path(
982
                    &mut rng,
983
                    di,
984
                    &guards,
985
                    #[cfg(all(feature = "vanguards", feature = "hs-common"))]
986
                    &vanguards,
987
                    &config,
988
                    now,
989
                )
990
                .unwrap();
991
            let path = match OwnedPath::try_from(&path).unwrap() {
992
                OwnedPath::ChannelOnly(_) => panic!("Impossible path type."),
993
                OwnedPath::Normal(p) => p,
994
            };
995
            assert_eq!(path.len(), 3);
996

            
997
            // Make sure that the usage is correct.
998
            let last_relay = netdir.by_ids(&path[2]).unwrap();
999
            let policy = ExitPolicy::from_relay(&last_relay);
            // We'll always get exits for these, since we try to build
            // paths with an exit if there are any exits.
            assert!(policy.allows_some_port());
            assert!(last_relay.low_level_details().policies_allow_some_port());
            assert_isoleq!(
                usage,
                SupportedCircUsage::Exit {
                    policy,
                    isolation: None,
                    country_code: None,
                    all_relays_stable: true
                }
            );
        });
    }
    #[test]
    fn build_testing_noexit() {
        // Here we'll try to build paths for testing circuits on a network
        // with no exits.
        tor_rtcompat::test_with_all_runtimes!(|rt| async move {
            let mut rng = testing_rng();
            let netdir = testnet::construct_custom_netdir(|_idx, bld, _| {
                bld.md.parse_ipv4_policy("reject 1-65535").unwrap();
            })
            .unwrap()
            .unwrap_if_sufficient()
            .unwrap();
            let di = (&netdir).into();
            let config = crate::PathConfig::default();
            let statemgr = TestingStateMgr::new();
            let guards =
                tor_guardmgr::GuardMgr::new(rt.clone(), statemgr.clone(), &TestConfig::default())
                    .unwrap();
            guards.install_test_netdir(&netdir);
            let now = SystemTime::now();
            #[cfg(all(feature = "vanguards", feature = "hs-common"))]
            let vanguards =
                VanguardMgr::new(&Default::default(), rt.clone(), statemgr, false).unwrap();
            let (path, usage, _, _) = TargetCircUsage::TimeoutTesting
                .build_path(
                    &mut rng,
                    di,
                    &guards,
                    #[cfg(all(feature = "vanguards", feature = "hs-common"))]
                    &vanguards,
                    &config,
                    now,
                )
                .unwrap();
            assert_eq!(path.len(), 3);
            assert_isoleq!(usage, SupportedCircUsage::NoUsage);
        });
    }
    #[test]
    fn display_target_ports() {
        let ports = [];
        assert_eq!(TargetPorts::from(&ports[..]).to_string(), "[]");
        let ports = [TargetPort::ipv4(80)];
        assert_eq!(TargetPorts::from(&ports[..]).to_string(), "80v4");
        let ports = [TargetPort::ipv4(80), TargetPort::ipv6(443)];
        assert_eq!(TargetPorts::from(&ports[..]).to_string(), "[80v4,443v6]");
    }
}