1
//! Code for building paths to an exit relay.
2

            
3
use super::TorPath;
4
use crate::{DirInfo, Error, PathConfig, Result, TargetPort};
5
use rand::Rng;
6
use std::time::{Duration, SystemTime};
7
use tor_error::{bad_api_usage, internal};
8
use tor_guardmgr::{GuardMgr, GuardMonitor, GuardUsable};
9
use tor_netdir::{NetDir, Relay, SubnetConfig, WeightRole};
10
use tor_rtcompat::Runtime;
11

            
12
/// Internal representation of PathBuilder.
13
enum ExitPathBuilderInner<'a> {
14
    /// Request a path that allows exit to the given `TargetPort]`s.
15
    WantsPorts(Vec<TargetPort>),
16

            
17
    /// Request a path that allows exit to _any_ port.
18
    AnyExit {
19
        /// If false, then we fall back to non-exit nodes if we can't find an
20
        /// exit.
21
        strict: bool,
22
    },
23

            
24
    /// Request a path that uses a given relay as exit node.
25
    ChosenExit(Relay<'a>),
26
}
27

            
28
/// A PathBuilder that builds a path to an exit relay supporting a given
29
/// set of ports.
30
pub struct ExitPathBuilder<'a> {
31
    /// The inner ExitPathBuilder state.
32
    inner: ExitPathBuilderInner<'a>,
33
}
34

            
35
impl<'a> ExitPathBuilder<'a> {
36
    /// Create a new builder that will try to get an exit relay
37
    /// containing all the ports in `ports`.
38
    ///
39
    /// If the list of ports is empty, tries to get any exit relay at all.
40
1082
    pub fn from_target_ports(wantports: impl IntoIterator<Item = TargetPort>) -> Self {
41
1082
        let ports: Vec<TargetPort> = wantports.into_iter().collect();
42
1082
        if ports.is_empty() {
43
            return Self::for_any_exit();
44
1082
        }
45
1082
        Self {
46
1082
            inner: ExitPathBuilderInner::WantsPorts(ports),
47
1082
        }
48
1082
    }
49

            
50
    /// Create a new builder that will try to build a path with the given exit
51
    /// relay as the last hop.
52
1008
    pub fn from_chosen_exit(exit_relay: Relay<'a>) -> Self {
53
1008
        Self {
54
1008
            inner: ExitPathBuilderInner::ChosenExit(exit_relay),
55
1008
        }
56
1008
    }
57

            
58
    /// Create a new builder that will try to get any exit relay at all.
59
1001
    pub fn for_any_exit() -> Self {
60
1001
        Self {
61
1001
            inner: ExitPathBuilderInner::AnyExit { strict: true },
62
1001
        }
63
1001
    }
64

            
65
    /// Create a new builder that will try to get an exit relay, but which
66
    /// will be satisfied with a non-exit relay.
67
3
    pub(crate) fn for_timeout_testing() -> Self {
68
3
        Self {
69
3
            inner: ExitPathBuilderInner::AnyExit { strict: false },
70
3
        }
71
3
    }
72

            
73
    /// Find a suitable exit node from either the chosen exit or from the network directory.
74
3086
    fn pick_exit<R: Rng>(
75
3086
        &self,
76
3086
        rng: &mut R,
77
3086
        netdir: &'a NetDir,
78
3086
        guard: Option<&Relay<'a>>,
79
3086
        config: SubnetConfig,
80
3086
    ) -> Result<Relay<'a>> {
81
3086
        match &self.inner {
82
1004
            ExitPathBuilderInner::AnyExit { strict } => {
83
40160
                let exit = netdir.pick_relay(rng, WeightRole::Exit, |r| {
84
40160
                    r.policies_allow_some_port() && relays_can_share_circuit_opt(r, guard, config)
85
40160
                });
86
1004
                match (exit, strict) {
87
1001
                    (Some(exit), _) => return Ok(exit),
88
1
                    (None, true) => return Err(Error::NoExit("No exit relay found".into())),
89
2
                    (None, false) => {}
90
2
                }
91
2

            
92
2
                // Non-strict case.  Arguably this doesn't belong in
93
2
                // ExitPathBuilder.
94
2
                netdir
95
80
                    .pick_relay(rng, WeightRole::Exit, |r| {
96
80
                        relays_can_share_circuit_opt(r, guard, config)
97
80
                    })
98
2
                    .ok_or_else(|| Error::NoExit("No relay found".into()))
99
            }
100

            
101
1074
            ExitPathBuilderInner::WantsPorts(wantports) => Ok(netdir
102
43279
                .pick_relay(rng, WeightRole::Exit, |r| {
103
43279
                    relays_can_share_circuit_opt(r, guard, config)
104
49053
                        && wantports.iter().all(|p| p.is_supported_by(r))
105
43279
                })
106
1074
                .ok_or_else(|| Error::NoExit("No exit relay found".into()))?),
107

            
108
1008
            ExitPathBuilderInner::ChosenExit(exit_relay) => {
109
1008
                // NOTE that this doesn't check
110
1008
                // relays_can_share_circuit_opt(exit_relay,guard).  we
111
1008
                // already did that, sort of, in pick_path.
112
1008
                Ok(exit_relay.clone())
113
            }
114
        }
115
3094
    }
116

            
117
    /// Try to create and return a path corresponding to the requirements of
118
    /// this builder.
119
3094
    pub fn pick_path<R: Rng, RT: Runtime>(
120
3094
        &self,
121
3094
        rng: &mut R,
122
3094
        netdir: DirInfo<'a>,
123
3094
        guards: Option<&GuardMgr<RT>>,
124
3094
        config: &PathConfig,
125
3094
    ) -> Result<(TorPath<'a>, Option<GuardMonitor>, Option<GuardUsable>)> {
126
3094
        let netdir = match netdir {
127
            DirInfo::Fallbacks(_) => {
128
                return Err(bad_api_usage!(
129
                    "Tried to build a multihop path using only a list of fallback caches"
130
                )
131
                .into())
132
            }
133
3094
            DirInfo::Directory(d) => d,
134
3094
        };
135
3094
        let subnet_config = config.subnet_config();
136
3094
        let lifetime = netdir.lifetime();
137
3094

            
138
3094
        // Check if the consensus isn't expired by > 72 hours
139
3094
        if SystemTime::now() > lifetime.valid_until() + Duration::new(72 * 60 * 60, 0) {
140
            return Err(Error::ExpiredConsensus);
141
3094
        }
142

            
143
3094
        let chosen_exit = if let ExitPathBuilderInner::ChosenExit(e) = &self.inner {
144
1009
            Some(e)
145
        } else {
146
2085
            None
147
        };
148
3094
        let path_is_fully_random = chosen_exit.is_none();
149

            
150
        // TODO-SPEC: Because of limitations in guard selection, we have to
151
        // pick the guard before the exit, which is not what our spec says.
152
3094
        let (guard, mon, usable) = match guards {
153
88
            Some(guardmgr) => {
154
88
                let mut b = tor_guardmgr::GuardUsageBuilder::default();
155
88
                b.kind(tor_guardmgr::GuardUsageKind::Data);
156
88
                guardmgr.update_network(netdir); // possibly unnecessary.
157
88
                if let Some(exit_relay) = chosen_exit {
158
8
                    let mut family = std::collections::HashSet::new();
159
8
                    family.insert(*exit_relay.id());
160
8
                    // TODO(nickm): See "limitations" note on `known_family_members`.
161
8
                    family.extend(netdir.known_family_members(exit_relay).map(|r| *r.id()));
162
8
                    b.push_restriction(tor_guardmgr::GuardRestriction::AvoidAllIds(family));
163
80
                }
164
88
                let guard_usage = b.build().expect("Failed while building guard usage!");
165
88
                let (guard, mut mon, usable) = guardmgr.select_guard(guard_usage, Some(netdir))?;
166
88
                let guard = guard.get_relay(netdir).ok_or_else(|| {
167
                    internal!(
168
                        "Somehow the guardmgr gave us an unlisted guard {:?}!",
169
                        guard
170
                    )
171
88
                })?;
172
88
                if !path_is_fully_random {
173
8
                    // We were given a specific exit relay to use, and
174
8
                    // the choice of exit relay might be forced by
175
8
                    // something outside of our control.
176
8
                    //
177
8
                    // Therefore, we must not blame the guard for any failure
178
8
                    // to complete the circuit.
179
8
                    mon.ignore_indeterminate_status();
180
80
                }
181
88
                (guard, Some(mon), Some(usable))
182
            }
183
            None => {
184
3006
                let entry = netdir
185
120152
                    .pick_relay(rng, WeightRole::Guard, |r| {
186
120152
                        r.is_flagged_guard()
187
60095
                            && relays_can_share_circuit_opt(r, chosen_exit, subnet_config)
188
120152
                    })
189
3006
                    .ok_or_else(|| Error::NoPath("No suitable  entry relay found".into()))?;
190
3006
                (entry, None, None)
191
            }
192
        };
193

            
194
3094
        let exit = self.pick_exit(rng, netdir, Some(&guard), subnet_config)?;
195

            
196
3092
        let middle = netdir
197
123436
            .pick_relay(rng, WeightRole::Middle, |r| {
198
123436
                relays_can_share_circuit(r, &exit, subnet_config)
199
95788
                    && relays_can_share_circuit(r, &guard, subnet_config)
200
123436
            })
201
3092
            .ok_or_else(|| Error::NoPath("No suitable middle relay found".into()))?;
202

            
203
3091
        Ok((
204
3091
            TorPath::new_multihop(vec![guard, middle, exit]),
205
3091
            mon,
206
3091
            usable,
207
3091
        ))
208
3093
    }
209
}
210

            
211
/// Returns true if both relays can appear together in the same circuit.
212
311299
fn relays_can_share_circuit(a: &Relay<'_>, b: &Relay<'_>, subnet_config: SubnetConfig) -> bool {
213
311299
    !a.in_same_family(b) && !a.in_same_subnet(b, &subnet_config)
214
311299
}
215

            
216
/// Helper: wraps relays_can_share_circuit but takes an option.
217
123252
fn relays_can_share_circuit_opt(r1: &Relay<'_>, r2: Option<&Relay<'_>>, c: SubnetConfig) -> bool {
218
123252
    match r2 {
219
83266
        Some(r2) => relays_can_share_circuit(r1, r2, c),
220
39986
        None => true,
221
    }
222
123252
}
223

            
224
#[cfg(test)]
225
mod test {
226
    #![allow(clippy::unwrap_used)]
227
    #![allow(clippy::clone_on_copy)]
228
    use super::*;
229
    use crate::path::{assert_same_path_when_owned, OwnedPath, TorPathInner};
230
    use crate::test::OptDummyGuardMgr;
231
    use std::collections::HashSet;
232
    use std::convert::TryInto;
233
    use tor_linkspec::ChanTarget;
234
    use tor_netdir::testnet;
235

            
236
    fn assert_exit_path_ok(relays: &[Relay<'_>]) {
237
        assert_eq!(relays.len(), 3);
238

            
239
        // TODO: Eventually assert that r1 has Guard, once we enforce that.
240

            
241
        let r1 = &relays[0];
242
        let r2 = &relays[1];
243
        let r3 = &relays[2];
244

            
245
        assert!(r1.ed_identity() != r2.ed_identity());
246
        assert!(r1.ed_identity() != r3.ed_identity());
247
        assert!(r2.ed_identity() != r3.ed_identity());
248

            
249
        let subnet_config = SubnetConfig::default();
250
        assert!(relays_can_share_circuit(r1, r2, subnet_config));
251
        assert!(relays_can_share_circuit(r1, r3, subnet_config));
252
        assert!(relays_can_share_circuit(r2, r3, subnet_config));
253
    }
254

            
255
    #[test]
256
    fn by_ports() {
257
        let mut rng = rand::thread_rng();
258
        let netdir = testnet::construct_netdir()
259
            .unwrap()
260
            .unwrap_if_sufficient()
261
            .unwrap();
262
        let ports = vec![TargetPort::ipv4(443), TargetPort::ipv4(1119)];
263
        let dirinfo = (&netdir).into();
264
        let config = PathConfig::default();
265
        let guards: OptDummyGuardMgr<'_> = None;
266

            
267
        for _ in 0..1000 {
268
            let (path, _, _) = ExitPathBuilder::from_target_ports(ports.clone())
269
                .pick_path(&mut rng, dirinfo, guards, &config)
270
                .unwrap();
271

            
272
            assert_same_path_when_owned(&path);
273

            
274
            if let TorPathInner::Path(p) = path.inner {
275
                assert_exit_path_ok(&p[..]);
276
                let exit = &p[2];
277
                assert!(exit.ipv4_policy().allows_port(1119));
278
            } else {
279
                panic!("Generated the wrong kind of path");
280
            }
281
        }
282

            
283
        let chosen = netdir.by_id(&[0x20; 32].into()).unwrap();
284

            
285
        let config = PathConfig::default();
286
        for _ in 0..1000 {
287
            let (path, _, _) = ExitPathBuilder::from_chosen_exit(chosen.clone())
288
                .pick_path(&mut rng, dirinfo, guards, &config)
289
                .unwrap();
290
            assert_same_path_when_owned(&path);
291
            if let TorPathInner::Path(p) = path.inner {
292
                assert_exit_path_ok(&p[..]);
293
                let exit = &p[2];
294
                assert_eq!(exit.ed_identity(), chosen.ed_identity());
295
            } else {
296
                panic!("Generated the wrong kind of path");
297
            }
298
        }
299
    }
300

            
301
    #[test]
302
    fn any_exit() {
303
        let mut rng = rand::thread_rng();
304
        let netdir = testnet::construct_netdir()
305
            .unwrap()
306
            .unwrap_if_sufficient()
307
            .unwrap();
308
        let dirinfo = (&netdir).into();
309
        let guards: OptDummyGuardMgr<'_> = None;
310

            
311
        let config = PathConfig::default();
312
        for _ in 0..1000 {
313
            let (path, _, _) = ExitPathBuilder::for_any_exit()
314
                .pick_path(&mut rng, dirinfo, guards, &config)
315
                .unwrap();
316
            assert_same_path_when_owned(&path);
317
            if let TorPathInner::Path(p) = path.inner {
318
                assert_exit_path_ok(&p[..]);
319
                let exit = &p[2];
320
                assert!(exit.policies_allow_some_port());
321
            } else {
322
                panic!("Generated the wrong kind of path");
323
            }
324
        }
325
    }
326

            
327
    #[test]
328
    fn empty_path() {
329
        // This shouldn't actually be constructable IRL, but let's test to
330
        // make sure our code can handle it.
331
        let bogus_path = TorPath {
332
            inner: TorPathInner::Path(vec![]),
333
        };
334

            
335
        assert!(bogus_path.exit_relay().is_none());
336
        assert!(bogus_path.exit_policy().is_none());
337
        assert_eq!(bogus_path.len(), 0);
338

            
339
        let owned: Result<OwnedPath> = (&bogus_path).try_into();
340
        assert!(owned.is_err());
341
    }
342

            
343
    #[test]
344
    fn no_exits() {
345
        // Construct a netdir with no exits.
346
        let netdir = testnet::construct_custom_netdir(|_idx, bld| {
347
            bld.md.parse_ipv4_policy("reject 1-65535").unwrap();
348
        })
349
        .unwrap()
350
        .unwrap_if_sufficient()
351
        .unwrap();
352
        let mut rng = rand::thread_rng();
353
        let dirinfo = (&netdir).into();
354
        let guards: OptDummyGuardMgr<'_> = None;
355
        let config = PathConfig::default();
356

            
357
        // With target ports
358
        let outcome = ExitPathBuilder::from_target_ports(vec![TargetPort::ipv4(80)])
359
            .pick_path(&mut rng, dirinfo, guards, &config);
360
        assert!(outcome.is_err());
361
        assert!(matches!(outcome, Err(Error::NoExit(_))));
362

            
363
        // For any exit
364
        let outcome = ExitPathBuilder::for_any_exit().pick_path(&mut rng, dirinfo, guards, &config);
365
        assert!(outcome.is_err());
366
        assert!(matches!(outcome, Err(Error::NoExit(_))));
367

            
368
        // For any exit (non-strict, so this will work).
369
        let outcome =
370
            ExitPathBuilder::for_timeout_testing().pick_path(&mut rng, dirinfo, guards, &config);
371
        assert!(outcome.is_ok());
372
    }
373

            
374
    #[test]
375
    fn exitpath_with_guards() {
376
        use tor_guardmgr::GuardStatus;
377

            
378
        tor_rtcompat::test_with_all_runtimes!(|rt| async move {
379
            let netdir = testnet::construct_netdir()
380
                .unwrap()
381
                .unwrap_if_sufficient()
382
                .unwrap();
383
            let mut rng = rand::thread_rng();
384
            let dirinfo = (&netdir).into();
385
            let statemgr = tor_persist::TestingStateMgr::new();
386
            let guards = tor_guardmgr::GuardMgr::new(rt.clone(), statemgr).unwrap();
387
            let config = PathConfig::default();
388
            guards.update_network(&netdir);
389
            let port443 = TargetPort::ipv4(443);
390

            
391
            // We're going to just have these all succeed and make sure
392
            // that they pick the same guard.  We won't test failing
393
            // cases here, since those are tested in guardmgr.
394
            let mut distinct_guards = HashSet::new();
395
            let mut distinct_mid = HashSet::new();
396
            let mut distinct_exit = HashSet::new();
397
            for _ in 0..20 {
398
                let (path, mon, usable) = ExitPathBuilder::from_target_ports(vec![port443])
399
                    .pick_path(&mut rng, dirinfo, Some(&guards), &config)
400
                    .unwrap();
401
                assert_eq!(path.len(), 3);
402
                assert_same_path_when_owned(&path);
403
                if let TorPathInner::Path(p) = path.inner {
404
                    assert_exit_path_ok(&p[..]);
405
                    distinct_guards.insert(p[0].ed_identity().clone());
406
                    distinct_mid.insert(p[1].ed_identity().clone());
407
                    distinct_exit.insert(p[2].ed_identity().clone());
408
                } else {
409
                    panic!("Wrong kind of path");
410
                }
411
                let mon = mon.unwrap();
412
                assert!(matches!(
413
                    mon.inspect_pending_status(),
414
                    (GuardStatus::AttemptAbandoned, false)
415
                ));
416
                mon.succeeded();
417
                assert!(usable.unwrap().await.unwrap());
418
            }
419
            assert_eq!(distinct_guards.len(), 1);
420
            assert_ne!(distinct_mid.len(), 1);
421
            assert_ne!(distinct_exit.len(), 1);
422

            
423
            let guard_relay = netdir
424
                .by_id(distinct_guards.iter().next().unwrap())
425
                .unwrap();
426
            let exit_relay = netdir.by_id(distinct_exit.iter().next().unwrap()).unwrap();
427

            
428
            // Now we'll try a forced exit that is not the same same as our
429
            // actual guard.
430
            let (path, mon, usable) = ExitPathBuilder::from_chosen_exit(exit_relay.clone())
431
                .pick_path(&mut rng, dirinfo, Some(&guards), &config)
432
                .unwrap();
433
            assert_eq!(path.len(), 3);
434
            if let TorPathInner::Path(p) = path.inner {
435
                assert_exit_path_ok(&p[..]);
436
                // We get our regular guard and our chosen exit.
437
                assert_eq!(p[0].ed_identity(), guard_relay.ed_identity());
438
                assert_eq!(p[2].ed_identity(), exit_relay.ed_identity());
439
            } else {
440
                panic!("Wrong kind of path");
441
            }
442
            let mon = mon.unwrap();
443
            // This time, "ignore indeterminate status" was set to true.
444
            assert!(matches!(
445
                mon.inspect_pending_status(),
446
                (GuardStatus::AttemptAbandoned, true)
447
            ));
448
            mon.succeeded();
449
            assert!(usable.unwrap().await.unwrap());
450

            
451
            // Finally, try with our exit forced to be our regular guard,
452
            // and make sure we get a different guard.
453
            let (path, mon, usable) = ExitPathBuilder::from_chosen_exit(guard_relay.clone())
454
                .pick_path(&mut rng, dirinfo, Some(&guards), &config)
455
                .unwrap();
456
            assert_eq!(path.len(), 3);
457
            if let TorPathInner::Path(p) = path.inner {
458
                // This is no longer guaranteed; see arti#183 :(
459
                // assert_exit_path_ok(&p[..]);
460
                // We get our chosen exit, and a different guard.
461
                assert_ne!(p[0].ed_identity(), guard_relay.ed_identity());
462
                assert_eq!(p[2].ed_identity(), guard_relay.ed_identity());
463
            } else {
464
                panic!("Wrong kind of path");
465
            }
466
            let mon = mon.unwrap();
467
            // This time, "ignore indeterminate status" was set to true.
468
            assert!(matches!(
469
                mon.inspect_pending_status(),
470
                (GuardStatus::AttemptAbandoned, true)
471
            ));
472
            mon.succeeded();
473
            assert!(usable.unwrap().await.unwrap());
474
        });
475
    }
476
}