1
//! Define a type describing how we're going to use a relay.
2

            
3
use crate::{LowLevelRelayPredicate, RelaySelectionConfig, TargetPort};
4
use tor_netdir::{Relay, WeightRole};
5

            
6
/// Description for how we plan to use a single relay.
7
#[derive(Clone, Debug)]
8
pub struct RelayUsage {
9
    /// Interior enumeration to describe the particular usage.
10
    inner: RelayUsageInner,
11
    /// Does this usage require the `Stable` flag?
12
    ///
13
    /// This is derived when we construct the RelayUsage, since it may require
14
    /// access to the config, and since it's cheaper to pre-compute.
15
    need_stable: bool,
16
}
17

            
18
/// Implementation type for RelayUsage.
19
///
20
/// This is a separate type so that we can hide its variants.
21
#[derive(Clone, Debug)]
22
enum RelayUsageInner {
23
    /// Allow any relay that exits to any port.
24
    AnyExit,
25
    /// Require that the relay can exit to every port in `TargetPort`.
26
    ExitToAllPorts(Vec<TargetPort>),
27
    /// Require that the relay can exit to at least one port in a given set.
28
    ///
29
    /// (We split the ports into those that require Stability and those that do
30
    /// not, for efficiency.)
31
    ExitToAnyPort {
32
        /// The desired ports that require the Stable flag.
33
        stable_ports: Vec<TargetPort>,
34
        /// The desired ports that do not require the Stable flag.
35
        unstable_ports: Vec<TargetPort>,
36
    },
37
    /// Allow any relay that's suitable as a middle-point.
38
    Middle,
39
    /// Allow any relay that's suitable as a newly selected introduction point.
40
    NewIntroPoint,
41
    /// Allow any relay that's suitable for continued use as a pre-existing
42
    /// introduction point.
43
    ContinuingIntroPoint,
44
    /// Allow any relay that's suitable as a newly selected guard.
45
    NewGuard,
46
    /// Allow any relay that's suitable for continued use as a pre-existing
47
    /// guard.
48
    ContinuingGuard,
49
    /// Allow any relay that's suitable as a vanguard.
50
    #[cfg(feature = "vanguards")]
51
    Vanguard,
52
    /// Allow any relay that's suitable as a one-hop directory cache.
53
    DirectoryCache,
54
}
55

            
56
impl RelayUsage {
57
    /// Require a relay that exits to at least one port.
58
    ///
59
    /// This usage is generally suitable as the final relay for a testing
60
    /// circuit of some kind, or for a circuit that needs to _look_ like an
61
    /// exit circuit without actually being useful for any exit in particular.
62
144580
    pub fn any_exit(_cfg: &RelaySelectionConfig) -> Self {
63
144580
        // TODO: properly, we ought to make sure that this does not select
64
144580
        // relays that only exit to long_lived ports, unless they have the
65
144580
        // Stable flag.
66
144580
        //
67
144580
        // C tor doesn't make this distinction, however, and so neither do we.
68
144580
        RelayUsage {
69
144580
            inner: RelayUsageInner::AnyExit,
70
144580
            need_stable: false,
71
144580
        }
72
144580
    }
73

            
74
    /// Require a relay that exits to every port in a given list.
75
147174
    pub fn exit_to_all_ports(cfg: &RelaySelectionConfig, ports: Vec<TargetPort>) -> Self {
76
295265
        let need_stable = ports.iter().any(|p| cfg.port_requires_stable_flag(p.port));
77
147174
        RelayUsage {
78
147174
            inner: RelayUsageInner::ExitToAllPorts(ports),
79
147174
            need_stable,
80
147174
        }
81
147174
    }
82

            
83
    /// Require a relay that exits to at least one port in a given list.
84
2
    pub fn exit_to_any_port(cfg: &RelaySelectionConfig, ports: Vec<TargetPort>) -> Self {
85
2
        let (stable_ports, unstable_ports): (Vec<_>, Vec<_>) = ports
86
2
            .into_iter()
87
5
            .partition(|p| cfg.port_requires_stable_flag(p.port));
88
2
        let need_stable = unstable_ports.is_empty() && !stable_ports.is_empty();
89
2
        RelayUsage {
90
2
            inner: RelayUsageInner::ExitToAnyPort {
91
2
                stable_ports,
92
2
                unstable_ports,
93
2
            },
94
2
            need_stable,
95
2
        }
96
2
    }
97

            
98
    /// Require a relay that is suitable for a middle relay.
99
    ///
100
    /// If `known_final_hop_usage` is provided, then the middle relay must support any
101
    /// additional properties needed in order to build a circuit for the usage
102
    /// of the final hop.
103
    ///
104
    /// If `known_final_hop_usage` is *not* provided, then the middle relay must
105
    /// support all possible such additional properties.
106
    ///
107
    /// (Note that providing a `known_final_hop_usage` can only _weaken_ the
108
    /// requirements of this usage.)
109
306916
    pub fn middle_relay(known_final_hop_usage: Option<&RelayUsage>) -> Self {
110
315425
        let need_stable = known_final_hop_usage.map(|u| u.need_stable).unwrap_or(true);
111
306916
        RelayUsage {
112
306916
            inner: RelayUsageInner::Middle,
113
306916
            need_stable,
114
306916
        }
115
306916
    }
116

            
117
    /// Require a relay that is suitable as a newly selected introduction point.
118
    ///
119
    /// This usage is suitable for selecting _new_ introduction points for an
120
    /// onion service.  When deciding whether to _keep_ an introduction point,
121
    /// use [`RelayUsage::continuing_intro_point`].
122
7778
    pub fn new_intro_point() -> Self {
123
7778
        RelayUsage {
124
7778
            inner: RelayUsageInner::NewIntroPoint,
125
7778
            need_stable: true,
126
7778
        }
127
7778
    }
128

            
129
    /// Require a relay that is suitable to keep using as a pre-existing introduction point.
130
    pub fn continuing_intro_point() -> Self {
131
        RelayUsage {
132
            inner: RelayUsageInner::ContinuingIntroPoint,
133
            need_stable: true,
134
        }
135
    }
136

            
137
    /// Require a relay that is suitable as a newly selected guard.
138
    ///
139
    /// This usage is suitable for selecting _new_ guards.
140
    /// When deciding whether to _keep_ a guard,
141
    /// use [`RelayUsage::continuing_guard`].
142
21316
    pub fn new_guard() -> Self {
143
21316
        RelayUsage {
144
21316
            inner: RelayUsageInner::NewGuard,
145
21316
            need_stable: true,
146
21316
        }
147
21316
    }
148

            
149
    /// Require a relay that is suitable to keep using as a pre-existing guard.
150
    pub fn continuing_guard() -> Self {
151
        RelayUsage {
152
            inner: RelayUsageInner::ContinuingGuard,
153
            need_stable: true,
154
        }
155
    }
156

            
157
    /// Require a relay that is suitable as a vanguard.
158
    #[cfg(feature = "vanguards")]
159
3240
    pub fn vanguard() -> Self {
160
3240
        RelayUsage {
161
3240
            inner: RelayUsageInner::Vanguard,
162
3240
            // Vanguards must have the Fast, Stable, and Valid flags.
163
3240
            need_stable: true,
164
3240
        }
165
3240
    }
166

            
167
    /// Require a relay that is suitable to use for a directory request.
168
    ///
169
    /// Note that this usage is suitable for fetching consensuses, authority certificates,
170
    /// descriptors and microdescriptors.  It is _not_ suitable for use with the
171
    /// HsDir system.
172
2
    pub fn directory_cache() -> Self {
173
2
        RelayUsage {
174
2
            inner: RelayUsageInner::DirectoryCache,
175
2
            need_stable: false,
176
2
        }
177
2
    }
178

            
179
    /// Return the [`WeightRole`] to use when picking a relay for this usage.
180
622074
    pub(crate) fn selection_weight_role(&self) -> WeightRole {
181
        use RelayUsageInner::*;
182

            
183
622074
        match &self.inner {
184
291746
            AnyExit | ExitToAllPorts(_) | ExitToAnyPort { .. } => WeightRole::Exit,
185
307108
            Middle => WeightRole::Middle,
186
504
            NewIntroPoint | ContinuingIntroPoint => WeightRole::HsIntro,
187
19476
            NewGuard | ContinuingGuard => WeightRole::Guard,
188
            #[cfg(feature = "vanguards")]
189
3240
            Vanguard => WeightRole::Middle,
190
            DirectoryCache => WeightRole::BeginDir,
191
        }
192
622074
    }
193

            
194
    /// Return a string describing why we rejected the relays that _don't_ match
195
    /// this usage.
196
512
    pub(crate) fn rejection_description(&self) -> &'static str {
197
        use RelayUsageInner::*;
198
512
        match &self.inner {
199
144
            AnyExit => "non-exit",
200
146
            ExitToAllPorts(_) => "not exiting to desired ports",
201
            ExitToAnyPort { .. } => "not exiting to any desired port",
202
222
            Middle => "useless for middle relay",
203
            NewIntroPoint | ContinuingIntroPoint => "not introduction point",
204
            NewGuard | ContinuingGuard => "not guard",
205
            #[cfg(feature = "vanguards")]
206
            Vanguard => "not usable as vanguard",
207
            DirectoryCache => "not directory cache",
208
        }
209
512
    }
210
}
211

            
212
impl LowLevelRelayPredicate for RelayUsage {
213
24246736
    fn low_level_predicate_permits_relay(&self, relay_in: &Relay<'_>) -> bool {
214
        use RelayUsageInner::*;
215
24246736
        let relay = relay_in.low_level_details();
216
24246736
        if !relay.is_flagged_fast() {
217
1428
            return false;
218
24245308
        }
219
24245308
        if self.need_stable && !relay.is_flagged_stable() {
220
1356
            return false;
221
24243952
        }
222
24243952
        match &self.inner {
223
5783108
            AnyExit => relay.policies_allow_some_port(),
224
8930542
            ExitToAllPorts(ports) => ports.iter().all(|p| p.is_supported_by(&relay)),
225
            ExitToAnyPort {
226
68
                stable_ports,
227
68
                unstable_ports,
228
68
            } => {
229
68
                if relay.is_flagged_stable()
230
84
                    && stable_ports.iter().any(|p| p.is_supported_by(&relay))
231
                {
232
14
                    return true;
233
54
                }
234
81
                unstable_ports.iter().any(|p| p.is_supported_by(&relay))
235
            }
236
11974788
            Middle => true,
237
            // TODO: Is there a distinction we should implement?
238
            // TODO: Move is_hs_intro_point logic here.
239
20216
            NewIntroPoint | ContinuingIntroPoint => relay.is_hs_intro_point(),
240
            // TODO: Is there a distinction we should implement?
241
            // TODO: Move is_suitable_as_guard logic here.
242
512048
            NewGuard | ContinuingGuard => relay.is_suitable_as_guard() && relay.is_dir_cache(),
243
            #[cfg(feature = "vanguards")]
244
            Vanguard => {
245
                // TODO: we might want to impose additional restrictions here
246
66744
                true
247
            }
248
68
            DirectoryCache => relay.is_dir_cache(),
249
        }
250
24246736
    }
251
}
252

            
253
#[cfg(test)]
254
mod test {
255
    // @@ begin test lint list maintained by maint/add_warning @@
256
    #![allow(clippy::bool_assert_comparison)]
257
    #![allow(clippy::clone_on_copy)]
258
    #![allow(clippy::dbg_macro)]
259
    #![allow(clippy::mixed_attributes_style)]
260
    #![allow(clippy::print_stderr)]
261
    #![allow(clippy::print_stdout)]
262
    #![allow(clippy::single_char_pattern)]
263
    #![allow(clippy::unwrap_used)]
264
    #![allow(clippy::unchecked_duration_subtraction)]
265
    #![allow(clippy::useless_vec)]
266
    #![allow(clippy::needless_pass_by_value)]
267
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
268

            
269
    use super::*;
270
    use crate::testing::{cfg, split_netdir, testnet};
271

            
272
    #[test]
273
    fn any_exits() {
274
        let nd = testnet();
275

            
276
        let (yes, no) = split_netdir(&nd, &RelayUsage::any_exit(&cfg()));
277

            
278
        let p = |r: &Relay<'_>| {
279
            r.low_level_details().is_flagged_fast()
280
                && r.low_level_details().policies_allow_some_port()
281
        };
282
        assert!(yes.iter().all(p));
283
        assert!(no.iter().all(|r| !p(r)));
284
    }
285

            
286
    #[test]
287
    fn all_ports() {
288
        let nd = testnet();
289
        let ports_stable = vec![TargetPort::ipv4(22), TargetPort::ipv4(80)];
290
        let usage_stable = RelayUsage::exit_to_all_ports(&cfg(), ports_stable);
291
        assert!(usage_stable.need_stable);
292

            
293
        let p1 = |relay: &Relay<'_>| {
294
            let r = relay.low_level_details();
295
            r.is_flagged_fast()
296
                && r.is_flagged_stable()
297
                && r.ipv4_policy().allows_port(22)
298
                && r.ipv4_policy().allows_port(80)
299
        };
300

            
301
        let (yes, no) = split_netdir(&nd, &usage_stable);
302
        assert!(yes.iter().all(p1));
303
        assert!(no.iter().all(|r| !p1(r)));
304

            
305
        let ports_not_stable = vec![TargetPort::ipv4(80)];
306
        let usage_not_stable = RelayUsage::exit_to_all_ports(&cfg(), ports_not_stable);
307

            
308
        let p2 = |relay: &Relay<'_>| {
309
            let r = relay.low_level_details();
310
            r.is_flagged_fast() && r.ipv4_policy().allows_port(80)
311
        };
312
        let (yes, no) = split_netdir(&nd, &usage_not_stable);
313
        assert!(yes.iter().all(p2));
314
        assert!(no.iter().all(|r| !p2(r)));
315
    }
316

            
317
    #[test]
318
    fn any_port() {
319
        let nd = testnet();
320
        let ports = vec![TargetPort::ipv4(22), TargetPort::ipv4(80)];
321
        let usage = RelayUsage::exit_to_any_port(&cfg(), ports);
322
        assert!(!usage.need_stable);
323
        match &usage.inner {
324
            RelayUsageInner::ExitToAnyPort {
325
                stable_ports,
326
                unstable_ports,
327
            } => {
328
                assert_eq!(&stable_ports[..], &[TargetPort::ipv4(22)]);
329
                assert_eq!(&unstable_ports[..], &[TargetPort::ipv4(80)]);
330
            }
331
            _ => {
332
                panic!("Wrong kind of usage.");
333
            }
334
        }
335

            
336
        let p = |relay: &Relay<'_>| {
337
            let r = relay.low_level_details();
338
            let port_22 = r.is_flagged_stable() && r.ipv4_policy().allows_port(22);
339
            let port_80 = r.ipv4_policy().allows_port(80);
340
            r.is_flagged_fast() && (port_22 || port_80)
341
        };
342

            
343
        let (yes, no) = split_netdir(&nd, &usage);
344
        assert!(yes.iter().all(p));
345
        assert!(no.iter().all(|r| !p(r)));
346
    }
347

            
348
    #[test]
349
    fn middle() {
350
        let nd = testnet();
351

            
352
        let u_unstable = RelayUsage::any_exit(&cfg());
353
        let u_stable = RelayUsage::new_guard();
354
        let mid_stable = RelayUsage::middle_relay(Some(&u_stable));
355
        let mid_unstable = RelayUsage::middle_relay(Some(&u_unstable));
356
        let mid_default = RelayUsage::middle_relay(None);
357
        assert!(mid_stable.need_stable);
358
        assert!(!mid_unstable.need_stable);
359
        assert!(mid_default.need_stable);
360

            
361
        let (yes, no) = split_netdir(&nd, &mid_unstable);
362
        let p1 = |relay: &Relay<'_>| {
363
            let r = relay.low_level_details();
364
            r.is_flagged_fast()
365
        };
366
        assert!(yes.iter().all(p1));
367
        assert!(no.iter().all(|r| !p1(r)));
368

            
369
        let (yes, no) = split_netdir(&nd, &mid_stable);
370
        let p2 = |relay: &Relay<'_>| {
371
            let r = relay.low_level_details();
372
            r.is_flagged_fast() && r.is_flagged_stable()
373
        };
374
        assert!(yes.iter().all(p2));
375
        assert!(no.iter().all(|r| !p2(r)));
376
    }
377

            
378
    #[test]
379
    fn intro() {
380
        let nd = testnet();
381
        let usage = RelayUsage::new_intro_point();
382

            
383
        let (yes, no) = split_netdir(&nd, &usage);
384
        let p1 = |relay: &Relay<'_>| {
385
            let r = relay.low_level_details();
386
            r.is_flagged_fast() && r.is_flagged_stable()
387
        };
388
        assert!(yes.iter().all(p1));
389
        assert!(no.iter().all(|r| !p1(r)));
390
    }
391

            
392
    #[test]
393
    fn guard() {
394
        let nd = testnet();
395
        let usage = RelayUsage::new_guard();
396

            
397
        let (yes, no) = split_netdir(&nd, &usage);
398
        let p1 = |relay: &Relay<'_>| {
399
            let r = relay.low_level_details();
400
            r.is_flagged_fast()
401
                && r.is_flagged_stable()
402
                && r.is_suitable_as_guard()
403
                && r.is_dir_cache()
404
        };
405
        assert!(yes.iter().all(p1));
406
        assert!(no.iter().all(|r| !p1(r)));
407
    }
408

            
409
    #[test]
410
    fn cache() {
411
        let nd = testnet();
412
        let usage = RelayUsage::directory_cache();
413

            
414
        let (yes, no) = split_netdir(&nd, &usage);
415
        let p1 = |relay: &Relay<'_>| {
416
            let r = relay.low_level_details();
417
            r.is_flagged_fast() && r.is_dir_cache()
418
        };
419
        assert!(yes.iter().all(p1));
420
        assert!(no.iter().all(|r| !p1(r)));
421
    }
422
}