1
//! Code to represent its single guard node and track its status.
2

            
3
use tor_basic_utils::retry::RetryDelay;
4

            
5
use serde::{Deserialize, Serialize};
6
use std::collections::HashMap;
7
use std::net::SocketAddr;
8
use std::time::{Duration, Instant, SystemTime};
9
use tracing::{info, trace, warn};
10

            
11
use crate::dirstatus::DirStatus;
12
use crate::sample::Candidate;
13
use crate::skew::SkewObservation;
14
use crate::util::randomize_time;
15
use crate::{ids::GuardId, GuardParams, GuardRestriction, GuardUsage};
16
use crate::{sample, ExternalActivity, GuardSetSelector, GuardUsageKind};
17

            
18
#[cfg(feature = "bridge-client")]
19
use safelog::Redactable as _;
20

            
21
use tor_linkspec::{
22
    ChanTarget, ChannelMethod, HasAddrs, HasChanMethod, HasRelayIds, PtTarget, RelayIds,
23
};
24
use tor_persist::{Futureproof, JsonValue};
25

            
26
/// Tri-state to represent whether a guard is believed to be reachable or not.
27
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
28
#[allow(clippy::enum_variant_names)]
29
pub(crate) enum Reachable {
30
    /// A guard is believed to be reachable, since we have successfully
31
    /// used it more recently than we've failed.
32
    Reachable,
33
    /// A guard is believed to be unreachable, since recent attempts
34
    /// to use it have failed, and not enough time has elapsed since then.
35
    Unreachable,
36
    /// We have never (during the lifetime of the current guard manager)
37
    /// tried to connect to this guard.
38
    #[default]
39
    Untried,
40
    /// The last time that we tried to connect to this guard, it failed,
41
    /// but enough time has elapsed that we think it is worth trying again.
42
    Retriable,
43
}
44

            
45
/// The name and version of the crate that first picked a potential
46
/// guard.
47
///
48
/// The C Tor implementation has found it useful to keep this information
49
/// about guards, to better work around any bugs discovered in the guard
50
/// implementation.
51
#[derive(Clone, Debug, Serialize, Deserialize)]
52
struct CrateId {
53
    /// The name of the crate that added this guard.
54
    #[serde(rename = "crate")]
55
    crate_name: String,
56
    /// The version of the crate that added this guard.
57
    version: String,
58
}
59

            
60
impl CrateId {
61
    /// Return a new CrateId representing this crate.
62
103552
    fn this_crate() -> Option<Self> {
63
103552
        let crate_name = option_env!("CARGO_PKG_NAME")?.to_string();
64
103552
        let version = option_env!("CARGO_PKG_VERSION")?.to_string();
65
103552
        Some(CrateId {
66
103552
            crate_name,
67
103552
            version,
68
103552
        })
69
103552
    }
70
}
71

            
72
/// What rule do we use when we're displaying information about a guard?
73
#[derive(Clone, Default, Debug)]
74
pub(crate) enum DisplayRule {
75
    /// The guard is Sensitive; we should display it as "\[scrubbed\]".
76
    ///
77
    /// We use this for public relays on the network, since displaying even the
78
    /// redacted info about them can enough to identify them uniquely within the
79
    /// NetDir.
80
    ///
81
    /// This should not be too much of a hit for UX (we hope), since the user is
82
    /// not typically expected to work around issues with these guards themself.
83
    #[default]
84
    Sensitive,
85
    /// The guard should be Redacted; we display it as something like "192.x.x.x
86
    /// $ab...".
87
    ///
88
    /// We use this for bridges.
89
    #[cfg(feature = "bridge-client")]
90
    Redacted,
91
}
92

            
93
/// A single guard node, as held by the guard manager.
94
///
95
/// A Guard is a Tor relay that clients use for the first hop of their circuits.
96
/// It doesn't need to be a relay that's currently on the network (that is, one
97
/// that we could represent as a [`Relay`](tor_netdir::Relay)): guards might be
98
/// temporarily unlisted.
99
///
100
/// Some fields in guards are persistent; others are reset with every process.
101
///
102
/// # Identity
103
///
104
/// Every guard has at least one `RelayId`.  A guard may _gain_ identities over
105
/// time, as we learn more about it, but it should never _lose_ or _change_ its
106
/// identities of a given type.
107
///
108
/// # TODO
109
///
110
/// This structure uses [`Instant`] to represent non-persistent points in time,
111
/// and [`SystemTime`] to represent points in time that need to be persistent.
112
/// That's possibly undesirable; maybe we should come up with a better solution.
113
#[derive(Clone, Debug, Serialize, Deserialize)]
114
pub(crate) struct Guard {
115
    /// The identity keys for this guard.
116
    id: GuardId,
117

            
118
    /// The most recently seen addresses for this guard.  If `pt_targets` is
119
    /// empty, these are the addresses we use for making OR connections to this
120
    /// guard directly.  If `pt_targets` is nonempty, these are addresses at
121
    /// which the server is "located" (q.v. [`HasAddrs`]), but not ways to
122
    /// connect to it.
123
    orports: Vec<SocketAddr>,
124

            
125
    /// Any `PtTarget` instances that we know about for connecting to this guard
126
    /// over a pluggable transport.
127
    ///
128
    /// If this is empty, then this guard only supports direct connections, at
129
    /// the locations in `orports`.
130
    ///
131
    /// (Currently, this is always empty, or a singleton.  If we find more than
132
    /// one, we only look at the first. It is a vector only for forward
133
    /// compatibility.)
134
    //
135
    // TODO: We may want to replace pt_targets and orports with a new structure;
136
    // maybe a PtAddress and a list of SocketAddr.  But we'll keep them like
137
    // this for now to keep backward compatibility.
138
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
139
    pt_targets: Vec<PtTarget>,
140

            
141
    /// When, approximately, did we first add this guard to our sample?
142
    #[serde(with = "humantime_serde")]
143
    added_at: SystemTime,
144

            
145
    /// What version of this crate added this guard to our sample?
146
    added_by: Option<CrateId>,
147

            
148
    /// If present, this guard is permanently disabled, and this
149
    /// object tells us why.
150
    #[serde(default)]
151
    disabled: Option<Futureproof<GuardDisabled>>,
152

            
153
    /// When, approximately, did we first successfully use this guard?
154
    ///
155
    /// (We call a guard "confirmed" if we have successfully used it at
156
    /// least once.)
157
    #[serde(with = "humantime_serde")]
158
    confirmed_at: Option<SystemTime>,
159

            
160
    /// If this guard is not listed in the current-consensus, this is the
161
    /// `valid_after` date of the oldest consensus in which it was not listed.
162
    ///
163
    /// A guard counts as "unlisted" if it is absent, unusable, or
164
    /// doesn't have the Guard flag.
165
    #[serde(with = "humantime_serde")]
166
    unlisted_since: Option<SystemTime>,
167

            
168
    /// True if this guard is listed in the latest consensus, but we don't
169
    /// have a microdescriptor for it.
170
    #[serde(skip)]
171
    dir_info_missing: bool,
172

            
173
    /// When did we last give out this guard in response to a request?
174
    #[serde(skip)]
175
    last_tried_to_connect_at: Option<Instant>,
176

            
177
    /// If this guard is currently Unreachable, when should we next
178
    /// retry it?
179
    ///
180
    /// (Retrying a guard involves clearing this field, and setting
181
    /// `reachable`)
182
    #[serde(skip)]
183
    retry_at: Option<Instant>, // derived from retry_schedule.
184

            
185
    /// Schedule use to determine when we can next attempt to connect to this
186
    /// guard.
187
    #[serde(skip)]
188
    retry_schedule: Option<RetryDelay>,
189

            
190
    /// Current reachability status for this guard.
191
    #[serde(skip)]
192
    reachable: Reachable,
193

            
194
    /// If true, then the last time we saw a relay entry for this
195
    /// guard, it seemed like a valid directory cache.
196
    #[serde(skip)]
197
    is_dir_cache: bool,
198

            
199
    /// Status for this guard, when used as a directory cache.
200
    ///
201
    /// (This is separate from `Reachable` and `retry_schedule`, since being
202
    /// usable for circuit construction does not necessarily mean that the guard
203
    /// will have good, timely cache information.  If it were not separate, then
204
    /// circuit success would clear directory failures.)
205
    #[serde(skip, default = "guard_dirstatus")]
206
    dir_status: DirStatus,
207

            
208
    /// If true, we have given this guard out for an exploratory circuit,
209
    /// and that exploratory circuit is still pending.
210
    ///
211
    /// A circuit is "exploratory" if we launched it on a non-primary guard.
212
    // TODO: Maybe this should be an integer that counts a number of such
213
    // circuits?
214
    #[serde(skip)]
215
    exploratory_circ_pending: bool,
216

            
217
    /// A count of all the circuit statuses we've seen on this guard.
218
    ///
219
    /// Used to implement a lightweight version of path-bias detection.
220
    #[serde(skip)]
221
    circ_history: CircHistory,
222

            
223
    /// True if we have warned about this guard behaving suspiciously.
224
    #[serde(skip)]
225
    suspicious_behavior_warned: bool,
226

            
227
    /// Latest clock skew (if any) we have observed from this guard.
228
    #[serde(skip)]
229
    clock_skew: Option<SkewObservation>,
230

            
231
    /// How should we display information about this guard?
232
    #[serde(skip)]
233
    sensitivity: DisplayRule,
234

            
235
    /// Fields from the state file that was used to make this `Guard` that
236
    /// this version of Arti doesn't understand.
237
    #[serde(flatten)]
238
    unknown_fields: HashMap<String, JsonValue>,
239
}
240

            
241
/// Lower bound for delay after get a failure using a guard as a directory
242
/// cache.
243
const GUARD_DIR_RETRY_FLOOR: Duration = Duration::from_secs(60);
244

            
245
/// Return a DirStatus entry for a guard.
246
103590
fn guard_dirstatus() -> DirStatus {
247
103590
    DirStatus::new(GUARD_DIR_RETRY_FLOOR)
248
103590
}
249

            
250
/// Wrapper to declare whether a given successful use of a guard is the
251
/// _first_ successful use of the guard.
252
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
253
pub(crate) enum NewlyConfirmed {
254
    /// This was the first successful use of a guard.
255
    Yes,
256
    /// This guard has been used successfully before.
257
    No,
258
}
259

            
260
impl Guard {
261
    /// Create a new unused [`Guard`] from a [`Candidate`].
262
103514
    pub(crate) fn from_candidate(
263
103514
        candidate: Candidate,
264
103514
        now: SystemTime,
265
103514
        params: &GuardParams,
266
103514
    ) -> Self {
267
103514
        let Candidate {
268
103514
            is_dir_cache,
269
103514
            full_dir_info,
270
103514
            owned_target,
271
103514
            ..
272
103514
        } = candidate;
273
103514

            
274
103514
        Guard {
275
103514
            is_dir_cache,
276
103514
            dir_info_missing: !full_dir_info,
277
103514
            ..Self::from_chan_target(&owned_target, now, params)
278
103514
        }
279
103514
    }
280

            
281
    /// Create a new unused [`Guard`] from a [`ChanTarget`].
282
    ///
283
    /// This function doesn't check whether the provided relay is a
284
    /// suitable guard node or not: that's up to the caller to decide.
285
103516
    fn from_chan_target<T>(relay: &T, now: SystemTime, params: &GuardParams) -> Self
286
103516
    where
287
103516
        T: ChanTarget,
288
103516
    {
289
103516
        let added_at = randomize_time(&mut rand::rng(), now, params.lifetime_unconfirmed / 10);
290

            
291
103516
        let pt_target = match relay.chan_method() {
292
            #[cfg(feature = "pt-client")]
293
            ChannelMethod::Pluggable(pt) => Some(pt),
294
103516
            _ => None,
295
        };
296

            
297
103516
        Self::new(
298
103516
            GuardId::from_relay_ids(relay),
299
103516
            relay.addrs().into(),
300
103516
            pt_target,
301
103516
            added_at,
302
103516
        )
303
103516
    }
304

            
305
    /// Return a new, manually constructed [`Guard`].
306
103550
    fn new(
307
103550
        id: GuardId,
308
103550
        orports: Vec<SocketAddr>,
309
103550
        pt_target: Option<PtTarget>,
310
103550
        added_at: SystemTime,
311
103550
    ) -> Self {
312
103550
        Guard {
313
103550
            id,
314
103550
            orports,
315
103550
            pt_targets: pt_target.into_iter().collect(),
316
103550
            added_at,
317
103550
            added_by: CrateId::this_crate(),
318
103550
            disabled: None,
319
103550
            confirmed_at: None,
320
103550
            unlisted_since: None,
321
103550
            dir_info_missing: false,
322
103550
            last_tried_to_connect_at: None,
323
103550
            reachable: Reachable::Untried,
324
103550
            retry_at: None,
325
103550
            dir_status: guard_dirstatus(),
326
103550
            retry_schedule: None,
327
103550
            is_dir_cache: true,
328
103550
            exploratory_circ_pending: false,
329
103550
            circ_history: CircHistory::default(),
330
103550
            suspicious_behavior_warned: false,
331
103550
            clock_skew: None,
332
103550
            unknown_fields: Default::default(),
333
103550
            sensitivity: DisplayRule::Sensitive,
334
103550
        }
335
103550
    }
336

            
337
    /// Return the identity of this Guard.
338
9558274
    pub(crate) fn guard_id(&self) -> &GuardId {
339
9558274
        &self.id
340
9558274
    }
341

            
342
    /// Return the reachability status for this guard.
343
497889
    pub(crate) fn reachable(&self) -> Reachable {
344
497889
        self.reachable
345
497889
    }
346

            
347
    /// Return the next time at which this guard will be retriable for a given
348
    /// usage.
349
    ///
350
    /// (Return None if we think this guard might be reachable right now.)
351
26
    pub(crate) fn next_retry(&self, usage: &GuardUsage) -> Option<Instant> {
352
26
        match &usage.kind {
353
18
            GuardUsageKind::Data => self.retry_at,
354
8
            GuardUsageKind::OneHopDirectory => [self.retry_at, self.dir_status.next_retriable()]
355
8
                .iter()
356
8
                .flatten()
357
8
                .max()
358
8
                .copied(),
359
        }
360
26
    }
361

            
362
    /// Return true if this guard is usable and working according to our latest
363
    /// configuration and directory information, and hasn't been turned off for
364
    /// some other reason.
365
1516211
    pub(crate) fn usable(&self) -> bool {
366
1516211
        self.unlisted_since.is_none() && self.disabled.is_none()
367
1516211
    }
368

            
369
    /// Return true if this guard is ready (with respect to any timeouts) for
370
    /// the given `usage` at `now`.
371
362900
    pub(crate) fn ready_for_usage(&self, usage: &GuardUsage, now: Instant) -> bool {
372
362900
        if let Some(retry_at) = self.retry_at {
373
8
            if retry_at > now {
374
8
                return false;
375
            }
376
362892
        }
377

            
378
362892
        match usage.kind {
379
342304
            GuardUsageKind::Data => true,
380
20588
            GuardUsageKind::OneHopDirectory => self.dir_status.usable_at(now),
381
        }
382
362900
    }
383

            
384
    /// Copy all _non-persistent_ status from `other` to self.
385
    ///
386
    /// We do this when we were not the owner of our persistent state, and we
387
    /// have just reloaded it (as `self`), but we have some ephemeral knowledge
388
    /// about this guard (as `other`).
389
    ///
390
    /// You should not invent new uses for this function; instead we should come
391
    /// up with alternatives.
392
    ///
393
    /// # Panics
394
    ///
395
    /// Panics if the identities in `self` are not exactly the same as the
396
    /// identities in `other`.
397
18
    pub(crate) fn copy_ephemeral_status_into_newly_loaded_state(self, other: Guard) -> Guard {
398
18
        // It is not safe to copy failure information unless these identities
399
18
        // are a superset of those in `other`; but it is not safe to copy success
400
18
        // information unless these identities are a subset of those in `other`.
401
18
        //
402
18
        // To simplify matters, we just insist that the identities have to be the same.
403
18
        assert!(self.same_relay_ids(&other));
404

            
405
18
        Guard {
406
18
            // All other persistent fields are taken from `self`.
407
18
            id: self.id,
408
18
            pt_targets: self.pt_targets,
409
18
            orports: self.orports,
410
18
            added_at: self.added_at,
411
18
            added_by: self.added_by,
412
18
            disabled: self.disabled,
413
18
            confirmed_at: self.confirmed_at,
414
18
            unlisted_since: self.unlisted_since,
415
18
            unknown_fields: self.unknown_fields,
416
18

            
417
18
            // All non-persistent fields get taken from `other`.
418
18
            last_tried_to_connect_at: other.last_tried_to_connect_at,
419
18
            retry_at: other.retry_at,
420
18
            retry_schedule: other.retry_schedule,
421
18
            reachable: other.reachable,
422
18
            is_dir_cache: other.is_dir_cache,
423
18
            exploratory_circ_pending: other.exploratory_circ_pending,
424
18
            dir_info_missing: other.dir_info_missing,
425
18
            circ_history: other.circ_history,
426
18
            suspicious_behavior_warned: other.suspicious_behavior_warned,
427
18
            dir_status: other.dir_status,
428
18
            clock_skew: other.clock_skew,
429
18
            sensitivity: other.sensitivity,
430
18
            // Note that we _could_ remove either of the above blocks and add
431
18
            // `..self` or `..other`, but that would be risky: it would increase
432
18
            // the odds that we would forget to add some persistent or
433
18
            // non-persistent field to the right group in the future.
434
18
        }
435
18
    }
436

            
437
    /// Change the reachability status for this guard.
438
9650
    fn set_reachable(&mut self, r: Reachable) {
439
        use Reachable as R;
440

            
441
9650
        if self.reachable != r {
442
            // High-level logs, if change is interesting to user.
443
780
            match (self.reachable, r) {
444
718
                (_, R::Reachable) => info!("We have found that guard {} is usable.", self),
445
54
                (R::Untried | R::Reachable, R::Unreachable) => warn!(
446
                    "Could not connect to guard {}. We'll retry later, and let you know if it succeeds.",
447
                    self
448
                ),
449
8
                (_, _) => {} // not interesting.
450
            }
451
            //
452
780
            trace!(guard_id = ?self.id, old=?self.reachable, new=?r, "Guard status changed.");
453
780
            self.reachable = r;
454
8870
        }
455
9650
    }
456

            
457
    /// Return true if at least one exploratory circuit is pending to this
458
    /// guard.
459
    ///
460
    /// A circuit is "exploratory" if launched on a non-primary guard.
461
    ///
462
    /// # TODO
463
    ///
464
    /// The "exploratory" definition doesn't quite match up with the behavior
465
    /// in the spec, but it is what Tor does.
466
362876
    pub(crate) fn exploratory_circ_pending(&self) -> bool {
467
362876
        self.exploratory_circ_pending
468
362876
    }
469

            
470
    /// Note that an exploratory circuit is pending (if `pending` is true),
471
    /// or not pending (if `pending` is false.
472
1378220
    pub(crate) fn note_exploratory_circ(&mut self, pending: bool) {
473
1378220
        self.exploratory_circ_pending = pending;
474
1378220
    }
475

            
476
    /// Possibly mark this guard as retriable, if it has been down for
477
    /// long enough.
478
    ///
479
    /// Specifically, if the guard is to be Unreachable, and our last attempt
480
    /// to connect to it is far enough in the past from `now`, we change its
481
    /// status to Unknown.
482
3390302
    pub(crate) fn consider_retry(&mut self, now: Instant) {
483
3390302
        if let Some(retry_at) = self.retry_at {
484
46
            debug_assert!(self.reachable == Reachable::Unreachable);
485
46
            if retry_at <= now {
486
2
                self.mark_retriable();
487
44
            }
488
3390256
        }
489
3390302
    }
490

            
491
    /// If this guard is marked Unreachable, clear its unreachability status
492
    /// and mark it as Retriable.
493
12
    pub(crate) fn mark_retriable(&mut self) {
494
12
        if self.reachable == Reachable::Unreachable {
495
8
            self.set_reachable(Reachable::Retriable);
496
8
            self.retry_at = None;
497
8
            self.retry_schedule = None;
498
8
        }
499
12
    }
500

            
501
    /// Return true if this guard obeys all of the given restrictions.
502
362910
    fn obeys_restrictions(&self, restrictions: &[GuardRestriction]) -> bool {
503
363029
        restrictions.iter().all(|r| self.obeys_restriction(r))
504
362910
    }
505

            
506
    /// Return true if this guard obeys a single restriction.
507
4645
    fn obeys_restriction(&self, r: &GuardRestriction) -> bool {
508
4645
        match r {
509
8
            GuardRestriction::AvoidId(avoid_id) => !self.id.0.has_identity(avoid_id.as_ref()),
510
4637
            GuardRestriction::AvoidAllIds(avoid_ids) => {
511
9387
                self.id.0.identities().all(|id| !avoid_ids.contains(id))
512
            }
513
        }
514
4645
    }
515

            
516
    /// Return true if this guard is suitable to use for the provided `usage`.
517
362914
    pub(crate) fn conforms_to_usage(&self, usage: &GuardUsage) -> bool {
518
362914
        match usage.kind {
519
            GuardUsageKind::OneHopDirectory => {
520
20582
                if !self.is_dir_cache {
521
2
                    return false;
522
20580
                }
523
            }
524
            GuardUsageKind::Data => {
525
                // We need a "definitely listed" guard to build a multihop
526
                // circuit.
527
342332
                if self.dir_info_missing {
528
2
                    return false;
529
342330
                }
530
            }
531
        }
532
362910
        self.obeys_restrictions(&usage.restrictions[..])
533
362914
    }
534

            
535
    /// Check whether this guard is listed in the provided [`sample::Universe`].
536
    ///
537
    /// Returns `Some(true)` if it is definitely listed, and `Some(false)` if it
538
    /// is definitely not listed.  A `None` return indicates that we need to
539
    /// download more directory information about this guard before we can be
540
    /// certain whether this guard is listed or not.
541
42
    pub(crate) fn listed_in<U: sample::Universe>(&self, universe: &U) -> Option<bool> {
542
42
        universe.contains(self)
543
42
    }
544

            
545
    /// Change this guard's status based on a newly received or newly updated
546
    /// [`sample::Universe`].
547
    ///
548
    /// A guard may become "listed" or "unlisted": a listed guard is one that
549
    /// appears in the consensus with the Guard flag.
550
    ///
551
    /// A guard may acquire additional identities if we learned them from the
552
    /// guard, either directly or via an authenticated directory document.
553
    ///
554
    /// Additionally, a guard's `orports` or `pt_targets` may change, if the
555
    /// `universe` lists a new address for the relay.
556
48
    pub(crate) fn update_from_universe<U: sample::Universe>(&mut self, universe: &U) {
557
        // This is a tricky check, since if we're missing directory information
558
        // for the guard, we won't know its full set of identities.
559
        use sample::CandidateStatus::*;
560
48
        let listed_as_guard = match universe.status(self) {
561
            Present(Candidate {
562
42
                listed_as_guard,
563
42
                is_dir_cache,
564
42
                full_dir_info,
565
42
                owned_target,
566
42
                sensitivity,
567
42
            }) => {
568
42
                // Update address information.
569
42
                self.orports = owned_target.addrs().into();
570
                // Update Pt information.
571
42
                self.pt_targets = match owned_target.chan_method() {
572
                    #[cfg(feature = "pt-client")]
573
                    ChannelMethod::Pluggable(pt) => vec![pt],
574
42
                    _ => Vec::new(),
575
                };
576
                // Check whether we can currently use it as a directory cache.
577
42
                self.is_dir_cache = is_dir_cache;
578
42
                // Update our IDs: the Relay will have strictly more.
579
42
                assert!(owned_target.has_all_relay_ids_from(self));
580
42
                self.id = GuardId(RelayIds::from_relay_ids(&owned_target));
581
42
                self.dir_info_missing = !full_dir_info;
582
42
                self.sensitivity = sensitivity;
583
42

            
584
42
                listed_as_guard
585
            }
586
4
            Absent => false, // Definitely not listed.
587
            Uncertain => {
588
                // We can't tell if this is listed without more directory information.
589
2
                self.dir_info_missing = true;
590
2
                return;
591
            }
592
        };
593

            
594
46
        if listed_as_guard {
595
42
            // Definitely listed, so clear unlisted_since.
596
42
            self.mark_listed();
597
42
        } else {
598
4
            // Unlisted or not a guard; mark it unlisted.
599
4
            self.mark_unlisted(universe.timestamp());
600
4
        }
601
48
    }
602

            
603
    /// Mark this guard as currently listed in the directory.
604
42
    fn mark_listed(&mut self) {
605
42
        if self.unlisted_since.is_some() {
606
            trace!(guard_id = ?self.id, "Guard is now listed again.");
607
            self.unlisted_since = None;
608
42
        }
609
42
    }
610

            
611
    /// Mark this guard as having been unlisted since `now`, if it is not
612
    /// already so marked.
613
6
    fn mark_unlisted(&mut self, now: SystemTime) {
614
6
        if self.unlisted_since.is_none() {
615
6
            trace!(guard_id = ?self.id, "Guard is now unlisted.");
616
6
            self.unlisted_since = Some(now);
617
        }
618
6
    }
619

            
620
    /// Return true if we should remove this guard from the current guard
621
    /// sample.
622
    ///
623
    /// Guards may be ready for removal because they have been
624
    /// confirmed too long ago, if they have been sampled too long ago
625
    /// (if they are not confirmed), or if they have been unlisted for
626
    /// too long.
627
12082
    pub(crate) fn is_expired(&self, params: &GuardParams, now: SystemTime) -> bool {
628
        /// Helper: Return true if `t2` is after `t1` by at least `d`.
629
12088
        fn expired_by(t1: SystemTime, d: Duration, t2: SystemTime) -> bool {
630
12088
            if let Ok(elapsed) = t2.duration_since(t1) {
631
12082
                elapsed > d
632
            } else {
633
6
                false
634
            }
635
12088
        }
636
12082
        if self.disabled.is_some() {
637
            // We never forget a guard that we've disabled: we've disabled
638
            // it for a reason.
639
            return false;
640
12082
        }
641
12082
        if let Some(confirmed_at) = self.confirmed_at {
642
28
            if expired_by(confirmed_at, params.lifetime_confirmed, now) {
643
4
                return true;
644
24
            }
645
12054
        } else if expired_by(self.added_at, params.lifetime_unconfirmed, now) {
646
20
            return true;
647
12034
        }
648

            
649
12058
        if let Some(unlisted_since) = self.unlisted_since {
650
6
            if expired_by(unlisted_since, params.lifetime_unlisted, now) {
651
2
                return true;
652
4
            }
653
12052
        }
654

            
655
12056
        false
656
12082
    }
657

            
658
    /// Record that a failure has happened for this guard.
659
    ///
660
    /// If `is_primary` is true, this is a primary guard (q.v.).
661
58
    pub(crate) fn record_failure(&mut self, now: Instant, is_primary: bool) {
662
58
        self.set_reachable(Reachable::Unreachable);
663
58
        self.exploratory_circ_pending = false;
664
58

            
665
58
        let mut rng = rand::rng();
666
58
        let retry_interval = self
667
58
            .retry_schedule
668
85
            .get_or_insert_with(|| retry_schedule(is_primary))
669
58
            .next_delay(&mut rng);
670
58

            
671
58
        // TODO-SPEC: Document this behavior in guard-spec.
672
58
        self.retry_at = Some(now + retry_interval);
673
58

            
674
58
        self.circ_history.n_failures += 1;
675
58
    }
676

            
677
    /// Note that we have launch an attempted use of this guard.
678
    ///
679
    /// We use this time to decide when to retry failing guards, and
680
    /// to see if the guard has been "pending" for a long time.
681
348534
    pub(crate) fn record_attempt(&mut self, connect_attempt: Instant) {
682
348534
        self.last_tried_to_connect_at = self
683
348534
            .last_tried_to_connect_at
684
356774
            .map(|last| last.max(connect_attempt))
685
348534
            .or(Some(connect_attempt));
686
348534
    }
687

            
688
    /// Return true if this guard has an exploratory circuit pending and
689
    /// if the most recent attempt to connect to it is after `when`.
690
    ///
691
    /// See [`Self::exploratory_circ_pending`].
692
12
    pub(crate) fn exploratory_attempt_after(&self, when: Instant) -> bool {
693
12
        self.exploratory_circ_pending
694
9
            && self.last_tried_to_connect_at.map(|t| t > when) == Some(true)
695
12
    }
696

            
697
    /// Note that a guard has been used successfully.
698
    ///
699
    /// Updates that guard's status to reachable, clears any failing status
700
    /// information for it, and decides whether the guard is newly confirmed.
701
    ///
702
    /// If the guard is newly confirmed, the caller must add it to the
703
    /// list of confirmed guards.
704
    #[must_use = "You need to check whether a succeeding guard is confirmed."]
705
9584
    pub(crate) fn record_success(
706
9584
        &mut self,
707
9584
        now: SystemTime,
708
9584
        params: &GuardParams,
709
9584
    ) -> NewlyConfirmed {
710
9584
        self.retry_at = None;
711
9584
        self.retry_schedule = None;
712
9584
        self.set_reachable(Reachable::Reachable);
713
9584
        self.exploratory_circ_pending = false;
714
9584
        self.circ_history.n_successes += 1;
715
9584

            
716
9584
        if self.confirmed_at.is_none() {
717
716
            self.confirmed_at = Some(
718
716
                randomize_time(&mut rand::rng(), now, params.lifetime_unconfirmed / 10)
719
716
                    .max(self.added_at),
720
716
            );
721
716
            // TODO-SPEC: The "max" above isn't specified by guard-spec,
722
716
            // but I think it's wise.
723
716
            trace!(guard_id = ?self.id, "Newly confirmed");
724
716
            NewlyConfirmed::Yes
725
        } else {
726
8868
            NewlyConfirmed::No
727
        }
728
9584
    }
729

            
730
    /// Record that an external operation has succeeded on this guard.
731
10
    pub(crate) fn record_external_success(&mut self, how: ExternalActivity) {
732
10
        match how {
733
10
            ExternalActivity::DirCache => {
734
10
                self.dir_status.note_success();
735
10
            }
736
10
        }
737
10
    }
738

            
739
    /// Record that an external operation has failed on this guard.
740
10
    pub(crate) fn record_external_failure(&mut self, how: ExternalActivity, now: Instant) {
741
10
        match how {
742
10
            ExternalActivity::DirCache => {
743
10
                self.dir_status.note_failure(now);
744
10
            }
745
10
        }
746
10
    }
747

            
748
    /// Note that a circuit through this guard died in a way that we couldn't
749
    /// necessarily attribute to the guard.
750
28
    pub(crate) fn record_indeterminate_result(&mut self) {
751
28
        self.circ_history.n_indeterminate += 1;
752

            
753
28
        if let Some(ratio) = self.circ_history.indeterminate_ratio() {
754
            // TODO: These should not be hardwired, and they may be set
755
            // too high.
756
            /// If this fraction of circs are suspicious, we should disable
757
            /// the guard.
758
            const DISABLE_THRESHOLD: f64 = 0.7;
759
            /// If this fraction of circuits are suspicious, we should
760
            /// warn.
761
            const WARN_THRESHOLD: f64 = 0.5;
762

            
763
2
            if ratio > DISABLE_THRESHOLD {
764
2
                let reason = GuardDisabled::TooManyIndeterminateFailures {
765
2
                    history: self.circ_history.clone(),
766
2
                    failure_ratio: ratio,
767
2
                    threshold_ratio: DISABLE_THRESHOLD,
768
2
                };
769
2
                warn!(guard=?self.id, "Disabling guard: {:.1}% of circuits died under mysterious circumstances, exceeding threshold of {:.1}%", ratio*100.0, (DISABLE_THRESHOLD*100.0));
770
2
                self.disabled = Some(reason.into());
771
            } else if ratio > WARN_THRESHOLD && !self.suspicious_behavior_warned {
772
                warn!(guard=?self.id, "Questionable guard: {:.1}% of circuits died under mysterious circumstances.", ratio*100.0);
773
                self.suspicious_behavior_warned = true;
774
            }
775
26
        }
776
28
    }
777

            
778
    /// Return a [`FirstHop`](crate::FirstHop) object to represent this guard.
779
348506
    pub(crate) fn get_external_rep(&self, selection: GuardSetSelector) -> crate::FirstHop {
780
348506
        crate::FirstHop {
781
348506
            sample: Some(selection),
782
348506
            inner: crate::FirstHopInner::Chan(tor_linkspec::OwnedChanTarget::from_chan_target(
783
348506
                self,
784
348506
            )),
785
348506
        }
786
348506
    }
787

            
788
    /// Record that a given fallback has told us about clock skew.
789
    pub(crate) fn note_skew(&mut self, observation: SkewObservation) {
790
        self.clock_skew = Some(observation);
791
    }
792

            
793
    /// Return the most recent clock skew observation for this guard, if we have
794
    /// made one.
795
    pub(crate) fn skew(&self) -> Option<&SkewObservation> {
796
        self.clock_skew.as_ref()
797
    }
798

            
799
    /// Testing only: Return true if this guard was ever contacted successfully.
800
    #[cfg(test)]
801
4
    pub(crate) fn confirmed(&self) -> bool {
802
4
        self.confirmed_at.is_some()
803
4
    }
804
}
805

            
806
impl tor_linkspec::HasAddrs for Guard {
807
348508
    fn addrs(&self) -> &[SocketAddr] {
808
348508
        &self.orports[..]
809
348508
    }
810
}
811

            
812
impl tor_linkspec::HasRelayIds for Guard {
813
39381528
    fn identity(
814
39381528
        &self,
815
39381528
        key_type: tor_linkspec::RelayIdType,
816
39381528
    ) -> Option<tor_linkspec::RelayIdRef<'_>> {
817
39381528
        self.id.0.identity(key_type)
818
39381528
    }
819
}
820

            
821
impl tor_linkspec::HasChanMethod for Guard {
822
348558
    fn chan_method(&self) -> ChannelMethod {
823
348558
        match &self.pt_targets[..] {
824
            #[cfg(feature = "pt-client")]
825
            [first, ..] => ChannelMethod::Pluggable(first.clone()),
826
            #[cfg(not(feature = "pt-client"))]
827
            [_first, ..] => ChannelMethod::Direct(vec![]), // can't connect to this; no pt support.
828
348558
            [] => ChannelMethod::Direct(self.orports.clone()),
829
        }
830
348558
    }
831
}
832

            
833
impl tor_linkspec::ChanTarget for Guard {}
834

            
835
impl std::fmt::Display for Guard {
836
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
837
        match self.sensitivity {
838
            DisplayRule::Sensitive => safelog::sensitive(self.display_chan_target()).fmt(f),
839
            #[cfg(feature = "bridge-client")]
840
            DisplayRule::Redacted => self.display_chan_target().redacted().fmt(f),
841
        }
842
    }
843
}
844

            
845
/// A reason for permanently disabling a guard.
846
#[derive(Clone, Debug, Serialize, Deserialize)]
847
#[serde(tag = "type")]
848
enum GuardDisabled {
849
    /// Too many attempts to use this guard failed for indeterminate reasons.
850
    TooManyIndeterminateFailures {
851
        /// Observed count of status reports about this guard.
852
        history: CircHistory,
853
        /// Observed fraction of indeterminate status reports.
854
        failure_ratio: f64,
855
        /// Threshold that was exceeded.
856
        threshold_ratio: f64,
857
    },
858
}
859

            
860
/// Return a new RetryDelay tracker for a guard.
861
///
862
/// `is_primary should be true if the guard is primary.
863
54
fn retry_schedule(is_primary: bool) -> RetryDelay {
864
54
    let minimum = if is_primary {
865
40
        Duration::from_secs(30)
866
    } else {
867
14
        Duration::from_secs(150)
868
    };
869

            
870
54
    RetryDelay::from_duration(minimum)
871
54
}
872

            
873
/// The recent history of circuit activity on this guard.
874
///
875
/// We keep this information so that we can tell if too many circuits are
876
/// winding up in "indeterminate" status.
877
///
878
/// # What's this for?
879
///
880
/// Recall that an "indeterminate" circuit failure is one that might
881
/// or might not be the guard's fault.  For example, if the second hop
882
/// of the circuit fails, we can't tell whether to blame the guard,
883
/// the second hop, or the internet between them.
884
///
885
/// But we don't want to allow an unbounded number of indeterminate
886
/// failures: if we did, it would allow a malicious guard to simply
887
/// reject any circuit whose second hop it didn't like, and thereby
888
/// filter the client's paths down to a hostile subset.
889
///
890
/// So as a workaround, and to discourage this kind of behavior, we
891
/// track the fraction of indeterminate circuits, and disable any guard
892
/// where the fraction is too high.
893
//
894
// TODO: We may eventually want to make this structure persistent.  If we
895
// do, however, we'll need a way to make ancient history expire.  We might
896
// want that anyway, to make attacks harder.
897
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
898
pub(crate) struct CircHistory {
899
    /// How many times have we seen this guard succeed?
900
    n_successes: u32,
901
    /// How many times have we seen this guard fail?
902
    #[allow(dead_code)] // not actually used yet.
903
    n_failures: u32,
904
    /// How many times has this guard given us indeterminate results?
905
    n_indeterminate: u32,
906
}
907

            
908
impl CircHistory {
909
    /// If we hae seen enough, return the fraction of circuits that have
910
    /// "died under mysterious circumstances".
911
32
    fn indeterminate_ratio(&self) -> Option<f64> {
912
        // TODO: This should probably not be hardwired
913

            
914
        /// Don't try to give a ratio unless we've seen this many observations.
915
        const MIN_OBSERVATIONS: u32 = 15;
916

            
917
32
        let total = self.n_successes + self.n_indeterminate;
918
32
        if total < MIN_OBSERVATIONS {
919
28
            return None;
920
4
        }
921
4

            
922
4
        Some(f64::from(self.n_indeterminate) / f64::from(total))
923
32
    }
924
}
925

            
926
#[cfg(test)]
927
mod test {
928
    // @@ begin test lint list maintained by maint/add_warning @@
929
    #![allow(clippy::bool_assert_comparison)]
930
    #![allow(clippy::clone_on_copy)]
931
    #![allow(clippy::dbg_macro)]
932
    #![allow(clippy::mixed_attributes_style)]
933
    #![allow(clippy::print_stderr)]
934
    #![allow(clippy::print_stdout)]
935
    #![allow(clippy::single_char_pattern)]
936
    #![allow(clippy::unwrap_used)]
937
    #![allow(clippy::unchecked_duration_subtraction)]
938
    #![allow(clippy::useless_vec)]
939
    #![allow(clippy::needless_pass_by_value)]
940
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
941
    use super::*;
942
    use crate::ids::FirstHopId;
943
    use tor_linkspec::{HasRelayIds, RelayId};
944
    use tor_llcrypto::pk::ed25519::Ed25519Identity;
945

            
946
    #[test]
947
    fn crate_id() {
948
        let id = CrateId::this_crate().unwrap();
949
        assert_eq!(&id.crate_name, "tor-guardmgr");
950
        assert_eq!(Some(id.version.as_ref()), option_env!("CARGO_PKG_VERSION"));
951
    }
952

            
953
    fn basic_id() -> GuardId {
954
        GuardId::new([13; 32].into(), [37; 20].into())
955
    }
956
    fn basic_guard() -> Guard {
957
        let id = basic_id();
958
        let ports = vec!["127.0.0.7:7777".parse().unwrap()];
959
        let added = SystemTime::now();
960
        Guard::new(id, ports, None, added)
961
    }
962

            
963
    #[test]
964
    fn simple_accessors() {
965
        fn ed(id: [u8; 32]) -> RelayId {
966
            RelayId::Ed25519(id.into())
967
        }
968
        let id = basic_id();
969
        let g = basic_guard();
970

            
971
        assert_eq!(g.guard_id(), &id);
972
        assert!(g.same_relay_ids(&FirstHopId::in_sample(GuardSetSelector::Default, id)));
973
        assert_eq!(g.addrs(), &["127.0.0.7:7777".parse().unwrap()]);
974
        assert_eq!(g.reachable(), Reachable::Untried);
975
        assert_eq!(g.reachable(), Reachable::default());
976

            
977
        use crate::GuardUsageBuilder;
978
        let mut usage1 = GuardUsageBuilder::new();
979

            
980
        usage1
981
            .restrictions()
982
            .push(GuardRestriction::AvoidId(ed([22; 32])));
983
        let usage1 = usage1.build().unwrap();
984
        let mut usage2 = GuardUsageBuilder::new();
985
        usage2
986
            .restrictions()
987
            .push(GuardRestriction::AvoidId(ed([13; 32])));
988
        let usage2 = usage2.build().unwrap();
989
        let usage3 = GuardUsage::default();
990
        let mut usage4 = GuardUsageBuilder::new();
991
        usage4
992
            .restrictions()
993
            .push(GuardRestriction::AvoidId(ed([22; 32])));
994
        usage4
995
            .restrictions()
996
            .push(GuardRestriction::AvoidId(ed([13; 32])));
997
        let usage4 = usage4.build().unwrap();
998
        let mut usage5 = GuardUsageBuilder::new();
999
        usage5.restrictions().push(GuardRestriction::AvoidAllIds(
            vec![ed([22; 32]), ed([13; 32])].into_iter().collect(),
        ));
        let usage5 = usage5.build().unwrap();
        let mut usage6 = GuardUsageBuilder::new();
        usage6.restrictions().push(GuardRestriction::AvoidAllIds(
            vec![ed([99; 32]), ed([100; 32])].into_iter().collect(),
        ));
        let usage6 = usage6.build().unwrap();
        assert!(g.conforms_to_usage(&usage1));
        assert!(!g.conforms_to_usage(&usage2));
        assert!(g.conforms_to_usage(&usage3));
        assert!(!g.conforms_to_usage(&usage4));
        assert!(!g.conforms_to_usage(&usage5));
        assert!(g.conforms_to_usage(&usage6));
    }
    #[allow(clippy::redundant_clone)]
    #[test]
    fn trickier_usages() {
        let g = basic_guard();
        use crate::{GuardUsageBuilder, GuardUsageKind};
        let data_usage = GuardUsageBuilder::new()
            .kind(GuardUsageKind::Data)
            .build()
            .unwrap();
        let dir_usage = GuardUsageBuilder::new()
            .kind(GuardUsageKind::OneHopDirectory)
            .build()
            .unwrap();
        assert!(g.conforms_to_usage(&data_usage));
        assert!(g.conforms_to_usage(&dir_usage));
        let mut g2 = g.clone();
        g2.dir_info_missing = true;
        assert!(!g2.conforms_to_usage(&data_usage));
        assert!(g2.conforms_to_usage(&dir_usage));
        let mut g3 = g.clone();
        g3.is_dir_cache = false;
        assert!(g3.conforms_to_usage(&data_usage));
        assert!(!g3.conforms_to_usage(&dir_usage));
    }
    #[test]
    fn record_attempt() {
        let t1 = Instant::now() - Duration::from_secs(10);
        let t2 = Instant::now() - Duration::from_secs(5);
        let t3 = Instant::now();
        let mut g = basic_guard();
        assert!(g.last_tried_to_connect_at.is_none());
        g.record_attempt(t1);
        assert_eq!(g.last_tried_to_connect_at, Some(t1));
        g.record_attempt(t3);
        assert_eq!(g.last_tried_to_connect_at, Some(t3));
        g.record_attempt(t2);
        assert_eq!(g.last_tried_to_connect_at, Some(t3));
    }
    #[test]
    fn record_failure() {
        let t1 = Instant::now() - Duration::from_secs(10);
        let t2 = Instant::now();
        let mut g = basic_guard();
        g.record_failure(t1, true);
        assert!(g.retry_schedule.is_some());
        assert_eq!(g.reachable(), Reachable::Unreachable);
        let retry1 = g.retry_at.unwrap();
        assert_eq!(retry1, t1 + Duration::from_secs(30));
        g.record_failure(t2, true);
        let retry2 = g.retry_at.unwrap();
        assert!(retry2 >= t2 + Duration::from_secs(30));
        assert!(retry2 <= t2 + Duration::from_secs(200));
    }
    #[test]
    fn record_success() {
        let t1 = Instant::now() - Duration::from_secs(10);
        // has to be in the future, since the guard's "added_at" time is based on now.
        let now = SystemTime::now();
        let t2 = now + Duration::from_secs(300 * 86400);
        let t3 = Instant::now() + Duration::from_secs(310 * 86400);
        let t4 = now + Duration::from_secs(320 * 86400);
        let mut g = basic_guard();
        g.record_failure(t1, true);
        assert_eq!(g.reachable(), Reachable::Unreachable);
        let conf = g.record_success(t2, &GuardParams::default());
        assert_eq!(g.reachable(), Reachable::Reachable);
        assert_eq!(conf, NewlyConfirmed::Yes);
        assert!(g.retry_at.is_none());
        assert!(g.confirmed_at.unwrap() <= t2);
        assert!(g.confirmed_at.unwrap() >= t2 - Duration::from_secs(12 * 86400));
        let confirmed_at_orig = g.confirmed_at;
        g.record_failure(t3, true);
        assert_eq!(g.reachable(), Reachable::Unreachable);
        let conf = g.record_success(t4, &GuardParams::default());
        assert_eq!(conf, NewlyConfirmed::No);
        assert_eq!(g.reachable(), Reachable::Reachable);
        assert!(g.retry_at.is_none());
        assert_eq!(g.confirmed_at, confirmed_at_orig);
    }
    #[test]
    fn retry() {
        let t1 = Instant::now();
        let mut g = basic_guard();
        g.record_failure(t1, true);
        assert!(g.retry_at.is_some());
        assert_eq!(g.reachable(), Reachable::Unreachable);
        // Not yet retriable.
        g.consider_retry(t1);
        assert!(g.retry_at.is_some());
        assert_eq!(g.reachable(), Reachable::Unreachable);
        // Not retriable right before the retry time.
        g.consider_retry(g.retry_at.unwrap() - Duration::from_secs(1));
        assert!(g.retry_at.is_some());
        assert_eq!(g.reachable(), Reachable::Unreachable);
        // Retriable right after the retry time.
        g.consider_retry(g.retry_at.unwrap() + Duration::from_secs(1));
        assert!(g.retry_at.is_none());
        assert_eq!(g.reachable(), Reachable::Retriable);
    }
    #[test]
    fn expiration() {
        const DAY: Duration = Duration::from_secs(24 * 60 * 60);
        let params = GuardParams::default();
        let now = SystemTime::now();
        let g = basic_guard();
        assert!(!g.is_expired(&params, now));
        assert!(!g.is_expired(&params, now + 10 * DAY));
        assert!(!g.is_expired(&params, now + 25 * DAY));
        assert!(!g.is_expired(&params, now + 70 * DAY));
        assert!(g.is_expired(&params, now + 200 * DAY)); // lifetime_unconfirmed.
        let mut g = basic_guard();
        let _ = g.record_success(now, &params);
        assert!(!g.is_expired(&params, now));
        assert!(!g.is_expired(&params, now + 10 * DAY));
        assert!(!g.is_expired(&params, now + 25 * DAY));
        assert!(g.is_expired(&params, now + 70 * DAY)); // lifetime_confirmed.
        let mut g = basic_guard();
        g.mark_unlisted(now);
        assert!(!g.is_expired(&params, now));
        assert!(!g.is_expired(&params, now + 10 * DAY));
        assert!(g.is_expired(&params, now + 25 * DAY)); // lifetime_unlisted
    }
    #[test]
    fn netdir_integration() {
        use tor_netdir::testnet;
        let netdir = testnet::construct_netdir().unwrap_if_sufficient().unwrap();
        let params = GuardParams::default();
        let now = SystemTime::now();
        // Construct a guard from a relay from the netdir.
        let relay22 = netdir.by_id(&Ed25519Identity::from([22; 32])).unwrap();
        let guard22 = Guard::from_chan_target(&relay22, now, &params);
        assert!(guard22.same_relay_ids(&relay22));
        assert!(Some(guard22.added_at) <= Some(now));
        // Can we still get the relay back?
        let id = FirstHopId::in_sample(GuardSetSelector::Default, guard22.id);
        let r = id.get_relay(&netdir).unwrap();
        assert!(r.same_relay_ids(&relay22));
        // Now try a guard that isn't in the netdir.
        let guard255 = Guard::new(
            GuardId::new([255; 32].into(), [255; 20].into()),
            vec![],
            None,
            now,
        );
        let id = FirstHopId::in_sample(GuardSetSelector::Default, guard255.id);
        assert!(id.get_relay(&netdir).is_none());
    }
    #[test]
    fn update_from_netdir() {
        use tor_netdir::testnet;
        let netdir = testnet::construct_netdir().unwrap_if_sufficient().unwrap();
        // Same as above but omit [22]
        let netdir2 = testnet::construct_custom_netdir(|idx, node, _| {
            if idx == 22 {
                node.omit_rs = true;
            }
        })
        .unwrap()
        .unwrap_if_sufficient()
        .unwrap();
        // Same as above but omit [22] as well as MD for [23].
        let netdir3 = testnet::construct_custom_netdir(|idx, node, _| {
            if idx == 22 {
                node.omit_rs = true;
            } else if idx == 23 {
                node.omit_md = true;
            }
        })
        .unwrap()
        .unwrap_if_sufficient()
        .unwrap();
        //let params = GuardParams::default();
        let now = SystemTime::now();
        // Try a guard that isn't in the netdir at all.
        let mut guard255 = Guard::new(
            GuardId::new([255; 32].into(), [255; 20].into()),
            vec!["8.8.8.8:53".parse().unwrap()],
            None,
            now,
        );
        assert_eq!(guard255.unlisted_since, None);
        assert_eq!(guard255.listed_in(&netdir), Some(false));
        guard255.update_from_universe(&netdir);
        assert_eq!(
            guard255.unlisted_since,
            Some(netdir.lifetime().valid_after())
        );
        assert!(!guard255.orports.is_empty());
        // Try a guard that is in netdir, but not netdir2.
        let mut guard22 = Guard::new(
            GuardId::new([22; 32].into(), [22; 20].into()),
            vec![],
            None,
            now,
        );
        let id22: FirstHopId = FirstHopId::in_sample(GuardSetSelector::Default, guard22.id.clone());
        let relay22 = id22.get_relay(&netdir).unwrap();
        assert_eq!(guard22.listed_in(&netdir), Some(true));
        guard22.update_from_universe(&netdir);
        assert_eq!(guard22.unlisted_since, None); // It's listed.
        assert_eq!(&guard22.orports, relay22.addrs()); // Addrs are set.
        assert_eq!(guard22.listed_in(&netdir2), Some(false));
        guard22.update_from_universe(&netdir2);
        assert_eq!(
            guard22.unlisted_since,
            Some(netdir2.lifetime().valid_after())
        );
        assert_eq!(&guard22.orports, relay22.addrs()); // Addrs still set.
        assert!(!guard22.dir_info_missing);
        // Now see what happens for a guard that's in the consensus, but missing an MD.
        let mut guard23 = Guard::new(
            GuardId::new([23; 32].into(), [23; 20].into()),
            vec![],
            None,
            now,
        );
        assert_eq!(guard23.listed_in(&netdir2), Some(true));
        assert_eq!(guard23.listed_in(&netdir3), None);
        guard23.update_from_universe(&netdir3);
        assert!(guard23.dir_info_missing);
        assert!(guard23.is_dir_cache);
    }
    #[test]
    fn pending() {
        let mut g = basic_guard();
        let t1 = Instant::now();
        let t2 = t1 + Duration::from_secs(100);
        let t3 = t1 + Duration::from_secs(200);
        assert!(!g.exploratory_attempt_after(t1));
        assert!(!g.exploratory_circ_pending());
        g.note_exploratory_circ(true);
        g.record_attempt(t2);
        assert!(g.exploratory_circ_pending());
        assert!(g.exploratory_attempt_after(t1));
        assert!(!g.exploratory_attempt_after(t3));
        g.note_exploratory_circ(false);
        assert!(!g.exploratory_circ_pending());
        assert!(!g.exploratory_attempt_after(t1));
        assert!(!g.exploratory_attempt_after(t3));
    }
    #[test]
    fn circ_history() {
        let mut h = CircHistory {
            n_successes: 3,
            n_failures: 4,
            n_indeterminate: 3,
        };
        assert!(h.indeterminate_ratio().is_none());
        h.n_successes = 20;
        assert!((h.indeterminate_ratio().unwrap() - 3.0 / 23.0).abs() < 0.0001);
    }
    #[test]
    fn disable_on_failure() {
        let mut g = basic_guard();
        let params = GuardParams::default();
        let now = SystemTime::now();
        let _ignore = g.record_success(now, &params);
        for _ in 0..13 {
            g.record_indeterminate_result();
        }
        // We're still under the observation threshold.
        assert!(g.disabled.is_none());
        // This crosses the threshold.
        g.record_indeterminate_result();
        assert!(g.disabled.is_some());
        #[allow(unreachable_patterns)]
        match g.disabled.unwrap().into_option().unwrap() {
            GuardDisabled::TooManyIndeterminateFailures {
                history: _,
                failure_ratio,
                threshold_ratio,
            } => {
                assert!((failure_ratio - 0.933).abs() < 0.01);
                assert!((threshold_ratio - 0.7).abs() < 0.01);
            }
            other => {
                panic!("Wrong variant: {:?}", other);
            }
        }
    }
    #[test]
    fn mark_retriable() {
        let mut g = basic_guard();
        use super::Reachable::*;
        assert_eq!(g.reachable(), Untried);
        for (pre, post) in &[
            (Untried, Untried),
            (Unreachable, Retriable),
            (Reachable, Reachable),
        ] {
            g.reachable = *pre;
            g.mark_retriable();
            assert_eq!(g.reachable(), *post);
        }
    }
    #[test]
    fn dir_status() {
        // We're going to see how directory failures interact with circuit
        // failures.
        use crate::GuardUsageBuilder;
        let mut g = basic_guard();
        let inst = Instant::now();
        let st = SystemTime::now();
        let sec = Duration::from_secs(1);
        let params = GuardParams::default();
        let dir_usage = GuardUsageBuilder::new()
            .kind(GuardUsageKind::OneHopDirectory)
            .build()
            .unwrap();
        let data_usage = GuardUsage::default();
        // Record a circuit success.
        let _ = g.record_success(st, &params);
        assert_eq!(g.next_retry(&dir_usage), None);
        assert!(g.ready_for_usage(&dir_usage, inst));
        assert_eq!(g.next_retry(&data_usage), None);
        assert!(g.ready_for_usage(&data_usage, inst));
        // Record a dircache failure.  This does not influence data usage.
        g.record_external_failure(ExternalActivity::DirCache, inst);
        assert_eq!(g.next_retry(&data_usage), None);
        assert!(g.ready_for_usage(&data_usage, inst));
        let next_dir_retry = g.next_retry(&dir_usage).unwrap();
        assert!(next_dir_retry >= inst + GUARD_DIR_RETRY_FLOOR);
        assert!(!g.ready_for_usage(&dir_usage, inst));
        assert!(g.ready_for_usage(&dir_usage, next_dir_retry));
        // Record a circuit success again.  This does not make the guard usable
        // as a directory cache.
        let _ = g.record_success(st, &params);
        assert!(g.ready_for_usage(&data_usage, inst));
        assert!(!g.ready_for_usage(&dir_usage, inst));
        // Record a circuit failure.
        g.record_failure(inst + sec * 10, true);
        let next_circ_retry = g.next_retry(&data_usage).unwrap();
        assert!(!g.ready_for_usage(&data_usage, inst + sec * 10));
        assert!(!g.ready_for_usage(&dir_usage, inst + sec * 10));
        assert_eq!(
            g.next_retry(&dir_usage).unwrap(),
            std::cmp::max(next_circ_retry, next_dir_retry)
        );
        // Record a directory success.  This won't supersede the circuit
        // failure.
        g.record_external_success(ExternalActivity::DirCache);
        assert_eq!(g.next_retry(&data_usage).unwrap(), next_circ_retry);
        assert_eq!(g.next_retry(&dir_usage).unwrap(), next_circ_retry);
        assert!(!g.ready_for_usage(&dir_usage, inst + sec * 10));
        assert!(!g.ready_for_usage(&data_usage, inst + sec * 10));
    }
}