1
//! Code to construct paths to a directory for non-anonymous downloads
2
use super::TorPath;
3
use crate::{DirInfo, Error, Result};
4
use tor_guardmgr::{GuardMgr, GuardMonitor, GuardUsable};
5
use tor_netdir::{Relay, WeightRole};
6
use tor_rtcompat::Runtime;
7

            
8
use rand::{seq::SliceRandom, Rng};
9

            
10
/// A PathBuilder that can connect to a directory.
11
#[non_exhaustive]
12
pub struct DirPathBuilder {}
13

            
14
impl Default for DirPathBuilder {
15
1011
    fn default() -> Self {
16
1011
        Self::new()
17
1011
    }
18
}
19

            
20
impl DirPathBuilder {
21
    /// Create a new DirPathBuilder.
22
1172
    pub fn new() -> Self {
23
1172
        DirPathBuilder {}
24
1172
    }
25

            
26
    /// Try to create and return a path corresponding to the requirements of
27
    /// this builder.
28
    pub fn pick_path<'a, R: Rng, RT: Runtime>(
29
        &self,
30
        rng: &mut R,
31
        netdir: DirInfo<'a>,
32
        guards: Option<&GuardMgr<RT>>,
33
    ) -> Result<(TorPath<'a>, Option<GuardMonitor>, Option<GuardUsable>)> {
34
1172
        match (netdir, guards) {
35
11
            (DirInfo::Fallbacks(f), _) => {
36
11
                let relay = f.choose(rng);
37
11
                if let Some(r) = relay {
38
10
                    return Ok((TorPath::new_fallback_one_hop(r), None, None));
39
1
                }
40
            }
41
1001
            (DirInfo::Directory(netdir), None) => {
42
1001
                let relay = netdir.pick_relay(rng, WeightRole::BeginDir, Relay::is_dir_cache);
43
1001
                if let Some(r) = relay {
44
1001
                    return Ok((TorPath::new_one_hop(r), None, None));
45
                }
46
            }
47
160
            (DirInfo::Directory(netdir), Some(guardmgr)) => {
48
160
                // TODO: We might want to use the guardmgr even if
49
160
                // we don't have a netdir.  See arti#220.
50
160
                guardmgr.update_network(netdir); // possibly unnecessary.
51
160
                let guard_usage = tor_guardmgr::GuardUsageBuilder::default()
52
160
                    .kind(tor_guardmgr::GuardUsageKind::OneHopDirectory)
53
160
                    .build()
54
160
                    .expect("Unable to build directory guard usage");
55
160
                let (guard, mon, usable) = guardmgr.select_guard(guard_usage, Some(netdir))?;
56
160
                if let Some(r) = guard.get_relay(netdir) {
57
160
                    return Ok((TorPath::new_one_hop(r), Some(mon), Some(usable)));
58
                }
59
            }
60
        }
61
1
        Err(Error::NoPath(
62
1
            "No relays found for use as directory cache".into(),
63
1
        ))
64
1172
    }
65
}
66

            
67
#[cfg(test)]
68
mod test {
69
    #![allow(clippy::unwrap_used)]
70
    #![allow(clippy::clone_on_copy)]
71
    use super::*;
72
    use crate::path::assert_same_path_when_owned;
73
    use crate::test::OptDummyGuardMgr;
74
    use std::collections::HashSet;
75
    use tor_linkspec::ChanTarget;
76
    use tor_netdir::fallback::FallbackDir;
77
    use tor_netdir::testnet;
78

            
79
    #[test]
80
    fn dirpath_relay() {
81
        let netdir = testnet::construct_netdir()
82
            .unwrap()
83
            .unwrap_if_sufficient()
84
            .unwrap();
85
        let mut rng = rand::thread_rng();
86
        let dirinfo = (&netdir).into();
87
        let guards: OptDummyGuardMgr<'_> = None;
88

            
89
        for _ in 0..1000 {
90
            let p = DirPathBuilder::default().pick_path(&mut rng, dirinfo, guards);
91
            let (p, _, _) = p.unwrap();
92
            assert!(p.exit_relay().is_none());
93
            assert_eq!(p.len(), 1);
94
            assert_same_path_when_owned(&p);
95
            if let crate::path::TorPathInner::OneHop(r) = p.inner {
96
                assert!(r.is_dir_cache());
97
            } else {
98
                panic!("Generated the wrong kind of path.");
99
            }
100
        }
101
    }
102

            
103
    #[test]
104
    fn dirpath_fallback() {
105
        let fb = vec![
106
            FallbackDir::builder()
107
                .rsa_identity([0x01; 20].into())
108
                .ed_identity([0x01; 32].into())
109
                .orport("127.0.0.1:9000".parse().unwrap())
110
                .build()
111
                .unwrap(),
112
            FallbackDir::builder()
113
                .rsa_identity([0x03; 20].into())
114
                .ed_identity([0x03; 32].into())
115
                .orport("127.0.0.1:9003".parse().unwrap())
116
                .build()
117
                .unwrap(),
118
        ];
119
        let dirinfo = (&fb[..]).into();
120
        let mut rng = rand::thread_rng();
121
        let guards: OptDummyGuardMgr<'_> = None;
122

            
123
        for _ in 0..10 {
124
            let p = DirPathBuilder::default().pick_path(&mut rng, dirinfo, guards);
125
            let (p, _, _) = p.unwrap();
126
            assert!(p.exit_relay().is_none());
127
            assert_eq!(p.len(), 1);
128
            assert_same_path_when_owned(&p);
129

            
130
            if let crate::path::TorPathInner::FallbackOneHop(f) = p.inner {
131
                assert!(std::ptr::eq(f, &fb[0]) || std::ptr::eq(f, &fb[1]));
132
            } else {
133
                panic!("Generated the wrong kind of path.");
134
            }
135
        }
136
    }
137

            
138
    #[test]
139
    fn dirpath_no_fallbacks() {
140
        let fb = vec![];
141
        let dirinfo = DirInfo::Fallbacks(&fb[..]);
142
        let mut rng = rand::thread_rng();
143
        let guards: OptDummyGuardMgr<'_> = None;
144

            
145
        let err = DirPathBuilder::default().pick_path(&mut rng, dirinfo, guards);
146
        assert!(matches!(err, Err(Error::NoPath(_))));
147
    }
148

            
149
    #[test]
150
    fn dirpath_with_guards() {
151
        tor_rtcompat::test_with_all_runtimes!(|rt| async move {
152
            let netdir = testnet::construct_netdir()
153
                .unwrap()
154
                .unwrap_if_sufficient()
155
                .unwrap();
156
            let mut rng = rand::thread_rng();
157
            let dirinfo = (&netdir).into();
158
            let statemgr = tor_persist::TestingStateMgr::new();
159
            let guards = tor_guardmgr::GuardMgr::new(rt.clone(), statemgr).unwrap();
160
            guards.update_network(&netdir);
161

            
162
            let mut distinct_guards = HashSet::new();
163

            
164
            // This is a nice easy case, since we tested the harder cases
165
            // in guard-spec.  We'll just have every path succeed.
166
            for _ in 0..40 {
167
                let (path, mon, usable) = DirPathBuilder::new()
168
                    .pick_path(&mut rng, dirinfo, Some(&guards))
169
                    .unwrap();
170
                if let crate::path::TorPathInner::OneHop(relay) = path.inner {
171
                    distinct_guards.insert(relay.ed_identity().clone());
172
                    mon.unwrap().succeeded();
173
                    assert!(usable.unwrap().await.unwrap());
174
                } else {
175
                    panic!("Generated the wrong kind of path.");
176
                }
177
            }
178
            assert_eq!(
179
                distinct_guards.len(),
180
                netdir.params().guard_dir_use_parallelism.get() as usize
181
            );
182
        });
183
    }
184
}