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
    /// Allow any relay that's suitable as a new rendezvous point
55
    /// (chosen by a client connecting to an onion service).
56
    NewRendPoint,
57
}
58

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

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

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

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

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

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

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

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

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

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

            
182
    /// Require a relay that is suitable as a newly selected rendezvous point
183
    /// (as chosen by a client to connect to an onion service).
184
    pub fn new_rend_point() -> Self {
185
        RelayUsage {
186
            inner: RelayUsageInner::NewRendPoint,
187
            need_stable: true,
188
        }
189
    }
190

            
191
    /// Return the [`WeightRole`] to use when picking a relay for this usage.
192
725760
    pub(crate) fn selection_weight_role(&self) -> WeightRole {
193
        use RelayUsageInner::*;
194

            
195
725760
        match &self.inner {
196
340370
            AnyExit | ExitToAllPorts(_) | ExitToAnyPort { .. } => WeightRole::Exit,
197
358258
            Middle => WeightRole::Middle,
198
588
            NewIntroPoint | ContinuingIntroPoint => WeightRole::HsIntro,
199
22764
            NewGuard | ContinuingGuard => WeightRole::Guard,
200
            #[cfg(feature = "vanguards")]
201
3780
            Vanguard => WeightRole::Middle,
202
            DirectoryCache => WeightRole::BeginDir,
203
            NewRendPoint => WeightRole::HsRend,
204
        }
205
725760
    }
206

            
207
    /// Return a string describing why we rejected the relays that _don't_ match
208
    /// this usage.
209
596
    pub(crate) fn rejection_description(&self) -> &'static str {
210
        use RelayUsageInner::*;
211
596
        match &self.inner {
212
168
            AnyExit => "non-exit",
213
170
            ExitToAllPorts(_) => "not exiting to desired ports",
214
            ExitToAnyPort { .. } => "not exiting to any desired port",
215
258
            Middle => "not usable as middle relay",
216
            NewIntroPoint | ContinuingIntroPoint => "not introduction point",
217
            NewGuard | ContinuingGuard => "not guard",
218
            #[cfg(feature = "vanguards")]
219
            Vanguard => "not usable as vanguard",
220
            DirectoryCache => "not directory cache",
221
            NewRendPoint => "not usable as rendezvous point",
222
        }
223
596
    }
224
}
225

            
226
impl LowLevelRelayPredicate for RelayUsage {
227
28299040
    fn low_level_predicate_permits_relay(&self, relay_in: &Relay<'_>) -> bool {
228
        use RelayUsageInner::*;
229
28299040
        let relay = relay_in.low_level_details();
230
28299040
        if !relay.is_flagged_fast() {
231
1428
            return false;
232
28297612
        }
233
28297612
        if self.need_stable && !relay.is_flagged_stable() {
234
1356
            return false;
235
28296256
        }
236
28296256
        match &self.inner {
237
6746948
            AnyExit => relay.policies_allow_some_port(),
238
10391662
            ExitToAllPorts(ports) => ports.iter().all(|p| p.is_supported_by(&relay)),
239
            ExitToAnyPort {
240
68
                stable_ports,
241
68
                unstable_ports,
242
68
            } => {
243
68
                if relay.is_flagged_stable()
244
84
                    && stable_ports.iter().any(|p| p.is_supported_by(&relay))
245
                {
246
14
                    return true;
247
54
                }
248
81
                unstable_ports.iter().any(|p| p.is_supported_by(&relay))
249
            }
250
13976940
            Middle => true,
251
            // TODO: Is there a distinction we should implement?
252
            // TODO: Move is_hs_intro_point logic here.
253
23576
            NewIntroPoint | ContinuingIntroPoint => relay.is_hs_intro_point(),
254
            // TODO: Is there a distinction we should implement?
255
            // TODO: Move is_suitable_as_guard logic here.
256
600740
            NewGuard | ContinuingGuard => relay.is_suitable_as_guard() && relay.is_dir_cache(),
257
            #[cfg(feature = "vanguards")]
258
            Vanguard => {
259
                // TODO: we might want to impose additional restrictions here
260
79884
                true
261
            }
262
68
            DirectoryCache => relay.is_dir_cache(),
263
            NewRendPoint => relay.is_hs_rend_point(),
264
        }
265
28299040
    }
266
}
267

            
268
#[cfg(test)]
269
mod test {
270
    // @@ begin test lint list maintained by maint/add_warning @@
271
    #![allow(clippy::bool_assert_comparison)]
272
    #![allow(clippy::clone_on_copy)]
273
    #![allow(clippy::dbg_macro)]
274
    #![allow(clippy::mixed_attributes_style)]
275
    #![allow(clippy::print_stderr)]
276
    #![allow(clippy::print_stdout)]
277
    #![allow(clippy::single_char_pattern)]
278
    #![allow(clippy::unwrap_used)]
279
    #![allow(clippy::unchecked_duration_subtraction)]
280
    #![allow(clippy::useless_vec)]
281
    #![allow(clippy::needless_pass_by_value)]
282
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
283

            
284
    use super::*;
285
    use crate::testing::{cfg, split_netdir, testnet};
286

            
287
    #[test]
288
    fn any_exits() {
289
        let nd = testnet();
290

            
291
        let (yes, no) = split_netdir(&nd, &RelayUsage::any_exit(&cfg()));
292

            
293
        let p = |r: &Relay<'_>| {
294
            r.low_level_details().is_flagged_fast()
295
                && r.low_level_details().policies_allow_some_port()
296
        };
297
        assert!(yes.iter().all(p));
298
        assert!(no.iter().all(|r| !p(r)));
299
    }
300

            
301
    #[test]
302
    fn all_ports() {
303
        let nd = testnet();
304
        let ports_stable = vec![TargetPort::ipv4(22), TargetPort::ipv4(80)];
305
        let usage_stable = RelayUsage::exit_to_all_ports(&cfg(), ports_stable);
306
        assert!(usage_stable.need_stable);
307

            
308
        let p1 = |relay: &Relay<'_>| {
309
            let r = relay.low_level_details();
310
            r.is_flagged_fast()
311
                && r.is_flagged_stable()
312
                && r.ipv4_policy().allows_port(22)
313
                && r.ipv4_policy().allows_port(80)
314
        };
315

            
316
        let (yes, no) = split_netdir(&nd, &usage_stable);
317
        assert!(yes.iter().all(p1));
318
        assert!(no.iter().all(|r| !p1(r)));
319

            
320
        let ports_not_stable = vec![TargetPort::ipv4(80)];
321
        let usage_not_stable = RelayUsage::exit_to_all_ports(&cfg(), ports_not_stable);
322

            
323
        let p2 = |relay: &Relay<'_>| {
324
            let r = relay.low_level_details();
325
            r.is_flagged_fast() && r.ipv4_policy().allows_port(80)
326
        };
327
        let (yes, no) = split_netdir(&nd, &usage_not_stable);
328
        assert!(yes.iter().all(p2));
329
        assert!(no.iter().all(|r| !p2(r)));
330
    }
331

            
332
    #[test]
333
    fn any_port() {
334
        let nd = testnet();
335
        let ports = vec![TargetPort::ipv4(22), TargetPort::ipv4(80)];
336
        let usage = RelayUsage::exit_to_any_port(&cfg(), ports);
337
        assert!(!usage.need_stable);
338
        match &usage.inner {
339
            RelayUsageInner::ExitToAnyPort {
340
                stable_ports,
341
                unstable_ports,
342
            } => {
343
                assert_eq!(&stable_ports[..], &[TargetPort::ipv4(22)]);
344
                assert_eq!(&unstable_ports[..], &[TargetPort::ipv4(80)]);
345
            }
346
            _ => {
347
                panic!("Wrong kind of usage.");
348
            }
349
        }
350

            
351
        let p = |relay: &Relay<'_>| {
352
            let r = relay.low_level_details();
353
            let port_22 = r.is_flagged_stable() && r.ipv4_policy().allows_port(22);
354
            let port_80 = r.ipv4_policy().allows_port(80);
355
            r.is_flagged_fast() && (port_22 || port_80)
356
        };
357

            
358
        let (yes, no) = split_netdir(&nd, &usage);
359
        assert!(yes.iter().all(p));
360
        assert!(no.iter().all(|r| !p(r)));
361
    }
362

            
363
    #[test]
364
    fn middle() {
365
        let nd = testnet();
366

            
367
        let u_unstable = RelayUsage::any_exit(&cfg());
368
        let u_stable = RelayUsage::new_guard();
369
        let mid_stable = RelayUsage::middle_relay(Some(&u_stable));
370
        let mid_unstable = RelayUsage::middle_relay(Some(&u_unstable));
371
        let mid_default = RelayUsage::middle_relay(None);
372
        assert!(mid_stable.need_stable);
373
        assert!(!mid_unstable.need_stable);
374
        assert!(mid_default.need_stable);
375

            
376
        let (yes, no) = split_netdir(&nd, &mid_unstable);
377
        let p1 = |relay: &Relay<'_>| {
378
            let r = relay.low_level_details();
379
            r.is_flagged_fast()
380
        };
381
        assert!(yes.iter().all(p1));
382
        assert!(no.iter().all(|r| !p1(r)));
383

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

            
393
    #[test]
394
    fn intro() {
395
        let nd = testnet();
396
        let usage = RelayUsage::new_intro_point();
397

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

            
407
    #[test]
408
    fn guard() {
409
        let nd = testnet();
410
        let usage = RelayUsage::new_guard();
411

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

            
424
    #[test]
425
    fn cache() {
426
        let nd = testnet();
427
        let usage = RelayUsage::directory_cache();
428

            
429
        let (yes, no) = split_netdir(&nd, &usage);
430
        let p1 = |relay: &Relay<'_>| {
431
            let r = relay.low_level_details();
432
            r.is_flagged_fast() && r.is_dir_cache()
433
        };
434
        assert!(yes.iter().all(p1));
435
        assert!(no.iter().all(|r| !p1(r)));
436
    }
437
}