1
//! Vanguard sets
2

            
3
use std::cmp;
4
use std::time::{Duration, SystemTime};
5

            
6
use derive_deftly::{derive_deftly_adhoc, Deftly};
7
use rand::{seq::SliceRandom as _, RngCore};
8
use serde::{Deserialize, Serialize};
9

            
10
use tor_basic_utils::RngExt as _;
11
use tor_error::internal;
12
use tor_linkspec::{HasRelayIds as _, RelayIdSet, RelayIds};
13
use tor_netdir::{NetDir, Relay};
14
use tor_relay_selection::{LowLevelRelayPredicate as _, RelayExclusion, RelaySelector, RelayUsage};
15
use tor_rtcompat::Runtime;
16
use tracing::{debug, trace};
17

            
18
use crate::{VanguardMgrError, VanguardMode};
19

            
20
use super::VanguardParams;
21

            
22
/// A vanguard relay.
23
#[derive(Clone, amplify::Getters)]
24
pub struct Vanguard<'a> {
25
    /// The relay.
26
    relay: Relay<'a>,
27
}
28

            
29
/// An identifier for a time-bound vanguard.
30
///
31
/// Each vanguard [`Layer`](crate::vanguards::Layer) consists of a [`VanguardSet`],
32
/// which contains multiple `TimeBoundVanguard`s.
33
///
34
/// A [`VanguardSet`]'s `TimeBoundVanguard`s are rotated
35
/// by [`VanguardMgr`](crate::vanguards::VanguardMgr) as soon as they expire.
36
/// If [Full](crate::vanguards::VanguardMode) vanguards are in use,
37
/// the `TimeBoundVanguard`s from all layers are persisted to disk.
38
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] //
39
pub(crate) struct TimeBoundVanguard {
40
    /// The ID of this relay.
41
    pub(super) id: RelayIds,
42
    /// When to stop using this relay as a vanguard.
43
    pub(super) when: SystemTime,
44
}
45

            
46
/// A set of vanguards, for use in a particular [`Layer`](crate::vanguards::Layer).
47
///
48
/// `VanguardSet`s start out with a target size of `0`.
49
///
50
/// Upon obtaining a `NetDir`, users of this type should update the target
51
/// based on the the current [`NetParameters`](tor_netdir::params::NetParameters).
52
#[derive(Default, Debug, Clone, PartialEq)] //
53
48
#[derive(Serialize, Deserialize)] //
54
#[serde(transparent)]
55
pub(super) struct VanguardSet {
56
    /// The time-bound vanguards of a given [`Layer`](crate::vanguards::Layer).
57
    vanguards: Vec<TimeBoundVanguard>,
58
    /// The number of vanguards we would like to have in this set.
59
    ///
60
    /// We do not serialize this value, as it should be derived from, and kept up to date with,
61
    /// the current [`NetParameters`](tor_netdir::params::NetParameters).
62
    #[serde(skip)]
63
    target: usize,
64
}
65

            
66
/// The L2 and L3 vanguard sets,
67
/// stored in the same struct to simplify serialization.
68
#[derive(Default, Debug, Clone, PartialEq)] //
69
#[derive(Deftly, Serialize, Deserialize)] //
70
#[derive_deftly_adhoc]
71
pub(super) struct VanguardSets {
72
    /// The L2 vanguard sets.
73
    l2_vanguards: VanguardSet,
74
    /// The L3 vanguard sets.
75
    ///
76
    /// Only used if full vanguards are enabled.
77
    l3_vanguards: VanguardSet,
78
}
79

            
80
impl VanguardSets {
81
    /// Find the timestamp of the vanguard that is due to expire next.
82
3282
    pub(super) fn next_expiry(&self) -> Option<SystemTime> {
83
3282
        let l2_expiry = self.l2_vanguards.next_expiry();
84
3282
        let l3_expiry = self.l3_vanguards.next_expiry();
85
3282
        match (l2_expiry, l3_expiry) {
86
484
            (Some(e), None) | (None, Some(e)) => Some(e),
87
588
            (Some(e1), Some(e2)) => Some(cmp::min(e1, e2)),
88
            (None, None) => {
89
                // Both vanguard sets are empty
90
2210
                None
91
            }
92
        }
93
3282
    }
94

            
95
    /// Return a reference to the L2 [`VanguardSet`].
96
2560
    pub(super) fn l2(&self) -> &VanguardSet {
97
2560
        &self.l2_vanguards
98
2560
    }
99

            
100
    /// Return a reference to the L3 [`VanguardSet`].
101
576
    pub(super) fn l3(&self) -> &VanguardSet {
102
576
        &self.l3_vanguards
103
576
    }
104

            
105
    /// Remove the vanguards that are expired at the specified timestamp.
106
    ///
107
    /// Returns the number of vanguards that were removed.
108
3274
    pub(super) fn remove_expired(&mut self, now: SystemTime) -> usize {
109
3274
        let l2_expired = self.l2_vanguards.remove_expired(now);
110
3274
        let l3_expired = self.l3_vanguards.remove_expired(now);
111
3274

            
112
3274
        l2_expired + l3_expired
113
3274
    }
114

            
115
    /// Remove the vanguards that are no longer listed in `netdir`.
116
    ///
117
    /// Returns whether either of the two sets have changed.
118
2084
    pub(super) fn remove_unlisted(&mut self, netdir: &NetDir) {
119
2084
        self.l2_vanguards.remove_unlisted(netdir);
120
2084
        self.l3_vanguards.remove_unlisted(netdir);
121
2084
    }
122

            
123
    /// Replenish the vanguard sets if necessary, using the directory information
124
    /// from the specified [`NetDir`].
125
    ///
126
    /// Note: the L3 set is only replenished if [`Full`](VanguardMode::Full) vanguards are enabled.
127
236
    pub(super) fn replenish_vanguards<R: Runtime>(
128
236
        &mut self,
129
236
        runtime: &R,
130
236
        netdir: &NetDir,
131
236
        params: &VanguardParams,
132
236
        mode: VanguardMode,
133
236
    ) -> Result<(), VanguardMgrError> {
134
236
        trace!("Replenishing vanguard sets");
135

            
136
        // Resize the vanguard sets if necessary.
137
236
        self.l2_vanguards.update_target(params.l2_pool_size());
138
236

            
139
236
        let mut rng = rand::thread_rng();
140
236
        Self::replenish_set(
141
236
            runtime,
142
236
            &mut rng,
143
236
            netdir,
144
236
            &mut self.l2_vanguards,
145
236
            params.l2_lifetime_min(),
146
236
            params.l2_lifetime_max(),
147
236
        )?;
148

            
149
236
        if mode == VanguardMode::Full {
150
84
            self.l3_vanguards.update_target(params.l3_pool_size());
151
84
            Self::replenish_set(
152
84
                runtime,
153
84
                &mut rng,
154
84
                netdir,
155
84
                &mut self.l3_vanguards,
156
84
                params.l3_lifetime_min(),
157
84
                params.l3_lifetime_max(),
158
84
            )?;
159
152
        }
160

            
161
236
        Ok(())
162
236
    }
163

            
164
    /// Replenish a single `VanguardSet` with however many vanguards it is short of.
165
320
    fn replenish_set<R: Runtime, Rng: RngCore>(
166
320
        runtime: &R,
167
320
        rng: &mut Rng,
168
320
        netdir: &NetDir,
169
320
        vanguard_set: &mut VanguardSet,
170
320
        min_lifetime: Duration,
171
320
        max_lifetime: Duration,
172
320
    ) -> Result<bool, VanguardMgrError> {
173
320
        let mut set_changed = false;
174
320
        let deficit = vanguard_set.deficit();
175
320
        if deficit > 0 {
176
            // Exclude the relays that are already in this vanguard set.
177
180
            let exclude_ids = RelayIdSet::from(&*vanguard_set);
178
180
            let exclude = RelayExclusion::exclude_identities(exclude_ids);
179
            // Pick some vanguards to add to the vanguard_set.
180
180
            let new_vanguards = Self::add_n_vanguards(
181
180
                runtime,
182
180
                rng,
183
180
                netdir,
184
180
                deficit,
185
180
                exclude,
186
180
                min_lifetime,
187
180
                max_lifetime,
188
180
            )?;
189

            
190
180
            if !new_vanguards.is_empty() {
191
140
                set_changed = true;
192
140
            }
193

            
194
752
            for v in new_vanguards {
195
572
                vanguard_set.add_vanguard(v);
196
572
            }
197
140
        }
198

            
199
320
        Ok(set_changed)
200
320
    }
201

            
202
    /// Select `n` relays to use as vanguards.
203
    ///
204
    /// Each selected vanguard will have a random lifetime
205
    /// between `min_lifetime` and `max_lifetime`.
206
180
    fn add_n_vanguards<R: Runtime, Rng: RngCore>(
207
180
        runtime: &R,
208
180
        rng: &mut Rng,
209
180
        netdir: &NetDir,
210
180
        n: usize,
211
180
        exclude: RelayExclusion,
212
180
        min_lifetime: Duration,
213
180
        max_lifetime: Duration,
214
180
    ) -> Result<Vec<TimeBoundVanguard>, VanguardMgrError> {
215
180
        trace!(relay_count = n, "selecting relays to use as vanguards");
216

            
217
180
        let vanguard_sel = RelaySelector::new(RelayUsage::vanguard(), exclude);
218
180

            
219
180
        let (relays, _outcome) = vanguard_sel.select_n_relays(rng, n, netdir);
220
180

            
221
180
        relays
222
180
            .into_iter()
223
572
            .map(|relay| {
224
                // Pick an expiration for this vanguard.
225
572
                let duration = select_lifetime(rng, min_lifetime, max_lifetime)?;
226
572
                let when = runtime.wallclock() + duration;
227
572

            
228
572
                Ok(TimeBoundVanguard {
229
572
                    id: RelayIds::from_relay_ids(&relay),
230
572
                    when,
231
572
                })
232
572
            })
233
180
            .collect::<Result<Vec<_>, _>>()
234
180
    }
235
}
236

            
237
/// Randomly select the lifetime of a vanguard from the `max(X,X)` distribution,
238
/// where `X` is a uniform random value between `min_lifetime` and `max_lifetime`.
239
///
240
/// This ensures we are biased towards longer lifetimes.
241
///
242
/// See
243
/// <https://spec.torproject.org/vanguards-spec/vanguards-stats.html>
244
//
245
// Note: the lifetimes of the vanguards (both L2 and L3) are selected
246
// from the max(X,X) distribution.
247
572
fn select_lifetime<Rng: RngCore>(
248
572
    rng: &mut Rng,
249
572
    min_lifetime: Duration,
250
572
    max_lifetime: Duration,
251
572
) -> Result<Duration, VanguardMgrError> {
252
572
    let err = || internal!("invalid consensus: vanguard min_lifetime > max_lifetime");
253

            
254
572
    let l1 = rng
255
572
        .gen_range_checked(min_lifetime..=max_lifetime)
256
572
        .ok_or_else(err)?;
257

            
258
572
    let l2 = rng
259
572
        .gen_range_checked(min_lifetime..=max_lifetime)
260
572
        .ok_or_else(err)?;
261

            
262
572
    Ok(std::cmp::max(l1, l2))
263
572
}
264

            
265
impl VanguardSet {
266
    /// Pick a relay from this set.
267
    ///
268
    /// See [`VanguardMgr::select_vanguard`](crate::vanguards::VanguardMgr::select_vanguard)
269
    /// for more information.
270
104
    pub(super) fn pick_relay<'a, R: RngCore>(
271
104
        &self,
272
104
        rng: &mut R,
273
104
        netdir: &'a NetDir,
274
104
        neighbor_exclusion: &RelayExclusion<'a>,
275
104
    ) -> Option<Vanguard<'a>> {
276
104
        let good_relays = self
277
104
            .vanguards
278
104
            .iter()
279
448
            .filter_map(|vanguard| {
280
                // Skip over any unusable relays
281
448
                let relay = netdir.by_ids(&vanguard.id)?;
282
448
                neighbor_exclusion
283
448
                    .low_level_predicate_permits_relay(&relay)
284
448
                    .then_some(relay)
285
448
            })
286
104
            .collect::<Vec<_>>();
287
104

            
288
104
        // Note: We make a uniform choice instead of a weighted one,
289
104
        // because we already made a bandwidth-weighted choice when we added
290
104
        // the vanguards to this set in the first place.
291
104
        good_relays.choose(rng).map(|relay| Vanguard {
292
96
            relay: relay.clone(),
293
104
        })
294
104
    }
295

            
296
    /// Whether this vanguard set is empty.
297
1580
    pub(super) fn is_empty(&self) -> bool {
298
1580
        self.vanguards.is_empty()
299
1580
    }
300

            
301
    /// The number of vanguards we're missing.
302
3264
    fn deficit(&self) -> usize {
303
3264
        self.target.saturating_sub(self.vanguards.len())
304
3264
    }
305

            
306
    /// Add a vanguard to this set.
307
6380
    fn add_vanguard(&mut self, v: TimeBoundVanguard) {
308
6380
        self.vanguards.push(v);
309
6380
    }
310

            
311
    /// Remove the vanguards that are no longer listed in `netdir`
312
    ///
313
    /// Returns the number of vanguards that were unlisted.
314
4168
    fn remove_unlisted(&mut self, netdir: &NetDir) -> usize {
315
7048
        self.retain(|v| {
316
6812
            let cond = netdir.ids_listed(&v.id) != Some(false);
317
6812

            
318
6812
            if !cond {
319
4
                debug!(id=?v.id, "Removing newly-unlisted vanguard");
320
6808
            }
321

            
322
6812
            cond
323
7048
        })
324
4168
    }
325

            
326
    /// Remove the vanguards that are expired at the specified timestamp.
327
    ///
328
    /// Returns the number of vanguards that expired.
329
6548
    fn remove_expired(&mut self, now: SystemTime) -> usize {
330
7112
        self.retain(|v| {
331
6672
            let cond = v.when > now;
332
6672

            
333
6672
            if !cond {
334
32
                debug!(id=?v.id, "Removing expired vanguard");
335
6640
            }
336

            
337
6672
            cond
338
7112
        })
339
6548
    }
340

            
341
    /// A wrapper around [`Vec::retain`] that returns the number of discarded elements.
342
10716
    fn retain<F>(&mut self, f: F) -> usize
343
10716
    where
344
10716
        F: FnMut(&TimeBoundVanguard) -> bool,
345
10716
    {
346
10716
        let old_len = self.vanguards.len();
347
10716
        self.vanguards.retain(f);
348
10716
        old_len - self.vanguards.len()
349
10716
    }
350

            
351
    /// Find the timestamp of the vanguard that is due to expire next.
352
6564
    fn next_expiry(&self) -> Option<SystemTime> {
353
7112
        self.vanguards.iter().map(|v| v.when).min()
354
6564
    }
355

            
356
    /// Update the target size of this set, discarding or requesting additional vanguards if needed.
357
3224
    fn update_target(&mut self, target: usize) {
358
3224
        self.target = target;
359
3224
    }
360
}
361

            
362
impl From<&VanguardSet> for RelayIdSet {
363
2292
    fn from(vanguard_set: &VanguardSet) -> Self {
364
2292
        vanguard_set
365
2292
            .vanguards
366
2292
            .iter()
367
2366
            .flat_map(|vanguard| {
368
1732
                vanguard
369
1732
                    .id
370
1732
                    .clone()
371
1732
                    .identities()
372
3464
                    .map(|id| id.to_owned())
373
1732
                    .collect::<Vec<_>>()
374
2366
            })
375
2292
            .collect()
376
2292
    }
377
}
378

            
379
// Some accessors we need in the VanguardMgr tests.
380
#[cfg(test)]
381
derive_deftly_adhoc! {
382
    VanguardSets expect items:
383

            
384
    impl VanguardSets {
385
        $(
386
            #[doc = concat!("Return the ", stringify!($fname))]
387
148
            pub(super) fn $fname(&self) -> &Vec<TimeBoundVanguard> {
388
                &self.$fname.vanguards
389
            }
390

            
391
            #[doc = concat!("Return the target size of the ", stringify!($fname), " set")]
392
28
            pub(super) fn $<$fname _target>(&self) -> usize {
393
                self.$fname.target
394
            }
395

            
396
            #[doc = concat!("Return the deficit of the ", stringify!($fname), " set")]
397
40
            pub(super) fn $<$fname _deficit>(&self) -> usize {
398
                self.$fname.deficit()
399
            }
400

            
401
        )
402
    }
403
}