1
//! Vanguard sets
2

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

            
6
use derive_deftly::Deftly;
7
use rand::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 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
3762
    pub(super) fn next_expiry(&self) -> Option<SystemTime> {
86
3762
        let l2_expiry = self.l2_vanguards.next_expiry();
87
3762
        let l3_expiry = self.l3_vanguards.next_expiry();
88
3762
        match (l2_expiry, l3_expiry) {
89
560
            (Some(e), None) | (None, Some(e)) => Some(e),
90
718
            (Some(e1), Some(e2)) => Some(cmp::min(e1, e2)),
91
            (None, None) => {
92
                // Both vanguard sets are empty
93
2484
                None
94
            }
95
        }
96
3762
    }
97

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

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

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

            
115
3758
        l2_expired + l3_expired
116
3758
    }
117

            
118
    /// Remove the vanguards that are no longer listed in `netdir`.
119
    ///
120
    /// Returns whether either of the two sets have changed.
121
2526
    pub(super) fn remove_unlisted(&mut self, netdir: &NetDir) {
122
2526
        self.l2_vanguards.remove_unlisted(netdir);
123
2526
        self.l3_vanguards.remove_unlisted(netdir);
124
2526
    }
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
118
    pub(super) fn replenish_vanguards<R: Runtime>(
131
118
        &mut self,
132
118
        runtime: &R,
133
118
        netdir: &NetDir,
134
118
        params: &VanguardParams,
135
118
        mode: VanguardMode,
136
118
    ) -> Result<(), VanguardMgrError> {
137
118
        trace!("Replenishing vanguard sets");
138

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

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

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

            
164
118
        Ok(())
165
118
    }
166

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

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

            
197
286
            for v in new_vanguards {
198
286
                vanguard_set.add_vanguard(v);
199
286
            }
200
70
        }
201

            
202
160
        Ok(set_changed)
203
160
    }
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
90
    fn add_n_vanguards<R: Runtime, Rng: rand::Rng>(
210
90
        runtime: &R,
211
90
        rng: &mut Rng,
212
90
        netdir: &NetDir,
213
90
        n: usize,
214
90
        exclude: RelayExclusion,
215
90
        min_lifetime: Duration,
216
90
        max_lifetime: Duration,
217
90
    ) -> Result<Vec<TimeBoundVanguard>, VanguardMgrError> {
218
90
        trace!(relay_count = n, "selecting relays to use as vanguards");
219

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

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

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

            
231
286
                Ok(TimeBoundVanguard {
232
286
                    id: RelayIds::from_relay_ids(&relay),
233
286
                    when,
234
286
                })
235
286
            })
236
90
            .collect::<Result<Vec<_>, _>>()
237
90
    }
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
286
fn select_lifetime<Rng: rand::Rng>(
251
286
    rng: &mut Rng,
252
286
    min_lifetime: Duration,
253
286
    max_lifetime: Duration,
254
286
) -> Result<Duration, VanguardMgrError> {
255
286
    let err = || internal!("invalid consensus: vanguard min_lifetime > max_lifetime");
256

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

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

            
265
286
    Ok(std::cmp::max(l1, l2))
266
286
}
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
52
    pub(super) fn pick_relay<'a, R: rand::Rng>(
274
52
        &self,
275
52
        rng: &mut R,
276
52
        netdir: &'a NetDir,
277
52
        relay_selector: &RelaySelector<'a>,
278
52
    ) -> Option<Vanguard<'a>> {
279
52
        let good_relays = self
280
52
            .vanguards
281
52
            .iter()
282
224
            .filter_map(|vanguard| {
283
                // Skip over any unusable relays
284
224
                let relay = netdir.by_ids(&vanguard.id)?;
285
224
                relay_selector
286
224
                    .low_level_predicate_permits_relay(&relay)
287
224
                    .then_some(relay)
288
224
            })
289
52
            .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
52
        good_relays.choose(rng).map(|relay| Vanguard {
295
48
            relay: relay.clone(),
296
48
        })
297
52
    }
298

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

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

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

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

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

            
325
8070
            cond
326
8070
        })
327
5052
    }
328

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

            
336
8000
            if !cond {
337
16
                debug!(id=?v.id, "Removing expired vanguard");
338
7984
            }
339

            
340
8000
            cond
341
8000
        })
342
7516
    }
343

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

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

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

            
365
impl From<&VanguardSet> for RelayIdSet {
366
2842
    fn from(vanguard_set: &VanguardSet) -> Self {
367
2842
        vanguard_set
368
2842
            .vanguards
369
2842
            .iter()
370
2842
            .flat_map(|vanguard| {
371
2138
                vanguard
372
2138
                    .id
373
2138
                    .clone()
374
2138
                    .identities()
375
4276
                    .map(|id| id.to_owned())
376
2138
                    .collect::<Vec<_>>()
377
2138
            })
378
2842
            .collect()
379
2842
    }
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
74
            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
14
            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
20
            pub(super) fn $<$fname _deficit>(&self) -> usize {
401
                self.$fname.deficit()
402
            }
403

            
404
        )
405
    }
406
}