1
//! Vanguard sets
2

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

            
6
use derive_deftly::Deftly;
7
use rand::{RngCore, seq::IndexedRandom as _};
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
#[cfg(test)]
19
use derive_deftly::derive_deftly_adhoc;
20

            
21
use crate::{VanguardMgrError, VanguardMode};
22

            
23
use super::VanguardParams;
24

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

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

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

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

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

            
98
    /// Return a reference to the L2 [`VanguardSet`].
99
3208
    pub(super) fn l2(&self) -> &VanguardSet {
100
3208
        &self.l2_vanguards
101
3208
    }
102

            
103
    /// Return a reference to the L3 [`VanguardSet`].
104
720
    pub(super) fn l3(&self) -> &VanguardSet {
105
720
        &self.l3_vanguards
106
720
    }
107

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

            
115
4788
        l2_expired + l3_expired
116
4788
    }
117

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

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

            
139
        // Resize the vanguard sets if necessary.
140
236
        self.l2_vanguards.update_target(params.l2_pool_size());
141

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

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

            
164
236
        Ok(())
165
236
    }
166

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

            
193
180
            if !new_vanguards.is_empty() {
194
140
                set_changed = true;
195
140
            }
196

            
197
752
            for v in new_vanguards {
198
572
                vanguard_set.add_vanguard(v);
199
572
            }
200
140
        }
201

            
202
320
        Ok(set_changed)
203
320
    }
204

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

            
220
180
        let vanguard_sel = RelaySelector::new(RelayUsage::vanguard(), exclude);
221

            
222
180
        let (relays, _outcome) = vanguard_sel.select_n_relays(rng, n, netdir);
223

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

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

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

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

            
261
572
    let l2 = rng
262
572
        .gen_range_checked(min_lifetime..=max_lifetime)
263
572
        .ok_or_else(err)?;
264

            
265
572
    Ok(std::cmp::max(l1, l2))
266
572
}
267

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

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

            
299
    /// Whether this vanguard set is empty.
300
1976
    pub(super) fn is_empty(&self) -> bool {
301
1976
        self.vanguards.is_empty()
302
1976
    }
303

            
304
    /// The number of vanguards we're missing.
305
4056
    fn deficit(&self) -> usize {
306
4056
        self.target.saturating_sub(self.vanguards.len())
307
4056
    }
308

            
309
    /// Add a vanguard to this set.
310
7964
    fn add_vanguard(&mut self, v: TimeBoundVanguard) {
311
7964
        self.vanguards.push(v);
312
7964
    }
313

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

            
321
8386
            if !cond {
322
6
                debug!(id=?v.id, "Removing newly-unlisted vanguard");
323
8380
            }
324

            
325
8386
            cond
326
8386
        })
327
5176
    }
328

            
329
    /// Remove the vanguards that are expired at the specified timestamp.
330
    ///
331
    /// Returns the number of vanguards that expired.
332
9576
    fn remove_expired(&mut self, now: SystemTime) -> usize {
333
10134
        self.retain(|v| {
334
8250
            let cond = v.when > now;
335

            
336
8250
            if !cond {
337
32
                debug!(id=?v.id, "Removing expired vanguard");
338
8218
            }
339

            
340
8250
            cond
341
8250
        })
342
9576
    }
343

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

            
354
    /// Find the timestamp of the vanguard that is due to expire next.
355
9592
    fn next_expiry(&self) -> Option<SystemTime> {
356
9592
        self.vanguards.iter().map(|v| v.when).min()
357
9592
    }
358

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

            
365
impl From<&VanguardSet> for RelayIdSet {
366
2868
    fn from(vanguard_set: &VanguardSet) -> Self {
367
2868
        vanguard_set
368
2868
            .vanguards
369
2868
            .iter()
370
2942
            .flat_map(|vanguard| {
371
2164
                vanguard
372
2164
                    .id
373
2164
                    .clone()
374
2164
                    .identities()
375
4328
                    .map(|id| id.to_owned())
376
2164
                    .collect::<Vec<_>>()
377
2164
            })
378
2868
            .collect()
379
2868
    }
380
}
381

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

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

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

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

            
404
        )
405
    }
406
}