1mod candidate;
5
6use crate::filter::GuardFilter;
7use crate::guard::{Guard, NewlyConfirmed, Reachable};
8use crate::skew::SkewObservation;
9use crate::{
10 ExternalActivity, GuardParams, GuardUsage, GuardUsageKind, PickGuardError, ids::GuardId,
11};
12use crate::{FirstHop, GuardSetSelector};
13use tor_basic_utils::iter::{FilterCount, IteratorExt as _};
14use tor_basic_utils::onionperf_types::{OnionperfEvent, OnionperfGuardStatus};
15use tor_linkspec::{ByRelayIds, HasRelayIds};
16
17use itertools::Itertools;
18use rand::seq::IndexedRandom;
19use serde::{Deserialize, Serialize};
20use std::borrow::Cow;
21use std::collections::{HashMap, HashSet};
22use tracing::{debug, info, trace};
23use web_time_compat::{Instant, SystemTime};
24
25#[allow(unused_imports)]
26pub(crate) use candidate::{Candidate, CandidateStatus, Universe, UniverseRef, WeightThreshold};
27
28#[derive(Debug, Default, Clone, Deserialize)]
49#[serde(from = "GuardSample")]
50pub(crate) struct GuardSet {
51 guards: ByRelayIds<Guard>,
111 sample: Vec<GuardId>,
115 confirmed: Vec<GuardId>,
120 primary: Vec<GuardId>,
125 active_filter: GuardFilter,
132
133 filter_is_restrictive: bool,
135
136 primary_guards_invalidated: bool,
139
140 unknown_fields: HashMap<String, JsonValue>,
143}
144
145#[derive(Debug, Copy, Clone, Eq, PartialEq)]
147pub(crate) enum ListKind {
148 Primary,
150 Confirmed,
152 Sample,
154 Fallback,
156}
157
158impl ListKind {
159 pub(crate) fn is_primary(&self) -> bool {
161 self == &ListKind::Primary
162 }
163
164 pub(crate) fn usable_immediately(&self) -> bool {
168 match self {
169 ListKind::Primary | ListKind::Fallback => true,
170 ListKind::Confirmed | ListKind::Sample => false,
171 }
172 }
173}
174
175impl GuardSet {
176 fn inner_lengths(&self) -> (usize, usize, usize, usize) {
180 (
181 self.guards.len(),
182 self.sample.len(),
183 self.confirmed.len(),
184 self.primary.len(),
185 )
186 }
187
188 fn fix_consistency(&mut self) {
195 fn fix_id_list(guards: &ByRelayIds<Guard>, id_list: &mut Vec<GuardId>) {
199 id_list.retain_mut(|id| match guards.by_all_ids(id) {
200 Some(guard) => {
201 *id = guard.guard_id().clone();
202 true
203 }
204 None => false,
205 });
206 }
207
208 let sample_set: HashSet<_> = self.sample.iter().collect();
209 self.guards.retain(|g| sample_set.contains(g.guard_id()));
210 fix_id_list(&self.guards, &mut self.sample);
211 fix_id_list(&self.guards, &mut self.confirmed);
212 fix_id_list(&self.guards, &mut self.primary);
213 }
214
215 fn assert_consistency(&mut self) {
219 let len_pre = self.inner_lengths();
220 self.fix_consistency();
221 let len_post = self.inner_lengths();
222 assert_eq!(len_pre, len_post);
223 }
224
225 pub(crate) fn get(&self, id: &GuardId) -> Option<&Guard> {
227 self.guards.by_all_ids(id)
228 }
229
230 pub(crate) fn set_filter(&mut self, filter: GuardFilter, restrictive: bool) {
236 self.active_filter = filter;
237 self.filter_is_restrictive = restrictive;
238
239 self.assert_consistency();
240
241 let guards = &self.guards; let filt = &self.active_filter;
243 self.primary.retain(|id| {
244 guards
245 .by_all_ids(id)
246 .map(|g| g.usable() && filt.permits(g))
247 .unwrap_or(false)
248 });
249
250 self.primary_guards_invalidated = true;
251 }
252
253 pub(crate) fn filter(&self) -> &GuardFilter {
255 &self.active_filter
256 }
257
258 pub(crate) fn copy_ephemeral_status_into_newly_loaded_state(&mut self, mut other: GuardSet) {
266 let old_guards = std::mem::take(&mut self.guards);
267 self.guards = old_guards
268 .into_values()
269 .map(|guard| {
270 let id = guard.guard_id();
271
272 if let Some(other_guard) = other.guards.remove_exact(id) {
273 guard.copy_ephemeral_status_into_newly_loaded_state(other_guard)
274 } else {
275 guard
276 }
277 })
278 .collect();
279 }
280
281 fn get_state(&self) -> GuardSample<'_> {
284 let guards = self
285 .sample
286 .iter()
287 .map(|id| Cow::Borrowed(self.guards.by_all_ids(id).expect("Inconsistent state")))
288 .collect();
289
290 GuardSample {
291 guards,
292 confirmed: Cow::Borrowed(&self.confirmed),
293 remaining: self.unknown_fields.clone(),
294 }
295 }
296
297 fn from_state(state: GuardSample<'_>) -> Self {
299 let mut guards = ByRelayIds::new();
300 let mut sample = Vec::new();
301 for guard in state.guards {
302 sample.push(guard.guard_id().clone());
303 guards.insert(guard.into_owned());
304 }
305 let confirmed = state.confirmed.into_owned();
306 let primary = Vec::new();
307 let mut guard_set = GuardSet {
308 guards,
309 sample,
310 confirmed,
311 primary,
312 active_filter: GuardFilter::default(),
313 filter_is_restrictive: false,
314 primary_guards_invalidated: true,
315 unknown_fields: state.remaining,
316 };
317
318 let len_pre = guard_set.inner_lengths();
320 guard_set.fix_consistency();
321 let len_post = guard_set.inner_lengths();
322 if len_pre != len_post {
323 info!(
324 "Resolved a consistency issue in stored guard state. Diagnostic codes: {:?}, {:?}",
325 len_pre, len_post
326 );
327 }
328 debug!(
329 n_guards = len_post.0,
330 n_confirmed = len_post.2,
331 "Guard set loaded."
332 );
333
334 guard_set
335 }
336
337 pub(crate) fn contains(&self, id: &GuardId) -> Result<bool, &GuardId> {
344 let overlapping = self.guards.all_overlapping(id);
345 match &overlapping[..] {
346 [singleton] => {
347 if singleton.has_all_relay_ids_from(id) {
348 Ok(true)
349 } else {
350 Err(singleton.guard_id())
351 }
352 }
353 _ => Ok(false),
354 }
355 }
356
357 pub(crate) fn extend_sample_as_needed<U: Universe>(
367 &mut self,
368 now: SystemTime,
369 params: &GuardParams,
370 dir: &U,
371 ) -> crate::ExtendedStatus {
372 let mut any_added = crate::ExtendedStatus::No;
373 while self.extend_sample_inner(now, params, dir) {
374 any_added = crate::ExtendedStatus::Yes;
375 }
376 any_added
377 }
378
379 fn extend_sample_inner<U: Universe>(
389 &mut self,
390 now: SystemTime,
391 params: &GuardParams,
392 dir: &U,
393 ) -> bool {
394 self.assert_consistency();
395 let n_filtered_usable = self
396 .guards
397 .values()
398 .filter(|g| {
399 g.usable()
400 && self.active_filter.permits(*g)
401 && g.reachable() != Reachable::Unreachable
402 })
403 .count();
404 if n_filtered_usable >= params.min_filtered_sample_size {
405 return false; }
407 if self.guards.len() >= params.max_sample_size {
408 return false; }
410
411 let max_to_add = params.max_sample_size - self.sample.len();
413 let want_to_add = params.min_filtered_sample_size - n_filtered_usable;
414 let n_to_add = std::cmp::min(max_to_add, want_to_add);
415
416 let WeightThreshold {
417 mut current_weight,
418 maximum_weight,
419 } = dir.weight_threshold(&self.guards, params);
420
421 let no_filter = GuardFilter::unfiltered();
423 let (n_candidates, pre_filter) =
424 if self.filter_is_restrictive || self.active_filter.is_unfiltered() {
425 (n_to_add, &self.active_filter)
426 } else {
427 (n_to_add * 3, &no_filter)
430 };
431
432 let candidates = dir.sample(&self.guards, pre_filter, n_candidates);
433
434 let mut any_added = false;
436 let mut n_filtered_usable = n_filtered_usable;
437 for (candidate, weight) in candidates {
438 if current_weight >= maximum_weight
441 && self.guards.len() >= params.min_filtered_sample_size
442 {
443 break;
444 }
445 if self.guards.len() >= params.max_sample_size {
446 break;
448 }
449 if n_filtered_usable >= params.min_filtered_sample_size {
450 break;
452 }
453 if self.active_filter.permits(&candidate.owned_target) {
454 n_filtered_usable += 1;
455 }
456 current_weight += weight;
457 self.add_guard(candidate, now, params);
458 any_added = true;
459 }
460 self.assert_consistency();
461 any_added
462 }
463
464 fn add_guard(&mut self, relay: Candidate, now: SystemTime, params: &GuardParams) {
468 let id = GuardId::from_relay_ids(&relay.owned_target);
469 if self.guards.by_all_ids(&id).is_some() {
470 return;
471 }
472 debug!(guard_id=?id, "Adding guard to sample.");
473 trace!(
476 onionperf = true,
477 event = ?OnionperfEvent::Guard(OnionperfGuardStatus::New),
478 guard_id = ?id,
479 );
480 let guard = Guard::from_candidate(relay, now, params);
481 self.guards.insert(guard);
482 self.sample.push(id);
483 self.primary_guards_invalidated = true;
484 }
485
486 pub(crate) fn n_primary_without_id_info_in<U: Universe>(&mut self, universe: &U) -> usize {
494 self.primary
495 .iter()
496 .filter(|id| {
497 let g = self
498 .guards
499 .by_all_ids(*id)
500 .expect("Inconsistent guard state");
501 g.listed_in(universe).is_none()
502 })
503 .count()
504 }
505
506 pub(crate) fn update_status_from_dir<U: Universe>(&mut self, dir: &U) {
508 let old_guards = std::mem::take(&mut self.guards);
509 self.guards = old_guards
510 .into_values()
511 .map(|mut guard| {
512 guard.update_from_universe(dir);
513 guard
514 })
515 .collect();
516 self.fix_consistency();
518 }
519
520 pub(crate) fn select_primary_guards(&mut self, params: &GuardParams) {
529 let old_primary = self.primary.clone();
536
537 self.primary = self
538 .confirmed
540 .iter()
541 .chain(self.primary.iter())
543 .chain(self.reachable_sample_ids())
546 .unique()
548 .filter_map(|id| {
550 let g = self
551 .guards
552 .by_all_ids(id)
553 .expect("Inconsistent guard state");
554 if g.usable() && self.active_filter.permits(g) {
555 Some(id.clone())
556 } else {
557 None
558 }
559 })
560 .take(params.n_primary)
562 .collect();
563
564 if self.primary != old_primary {
565 debug!(old=?old_primary, new=?self.primary, "Updated primary guards.");
566 }
567
568 for id in &self.primary {
570 self.guards.modify_by_all_ids(id, |guard| {
571 guard.note_exploratory_circ(false);
572 });
573 }
574
575 self.assert_consistency();
579 self.primary_guards_invalidated = false;
580 }
581
582 pub(crate) fn expire_old_guards(&mut self, params: &GuardParams, now: SystemTime) {
585 self.assert_consistency();
586 let n_pre = self.guards.len();
587 for guard in self.guards.values() {
589 if guard.is_expired(params, now) {
590 tracing::trace!(
591 onionperf = true,
592 event = ?OnionperfEvent::Guard(OnionperfGuardStatus::Dropped),
593 guard_id = ?guard.guard_id(),
594 );
595 }
596 }
597 self.guards.retain(|g| !g.is_expired(params, now));
598 let guards = &self.guards;
599 self.sample.retain(|id| guards.by_all_ids(id).is_some());
600 self.confirmed.retain(|id| guards.by_all_ids(id).is_some());
601 self.primary.retain(|id| guards.by_all_ids(id).is_some());
602 self.assert_consistency();
603
604 if self.guards.len() < n_pre {
605 let n_expired = n_pre - self.guards.len();
606 debug!(n_expired, "Expired guards as too old.");
607 self.primary_guards_invalidated = true;
608 }
609 }
610
611 fn reachable_sample_ids(&self) -> impl Iterator<Item = &GuardId> {
614 self.sample.iter().filter(move |id| {
615 let g = self
616 .guards
617 .by_all_ids(*id)
618 .expect("Inconsistent guard state");
619 g.reachable() != Reachable::Unreachable
620 })
621 }
622
623 fn preference_order_ids(&self) -> impl Iterator<Item = (ListKind, &GuardId)> {
633 self.primary
634 .iter()
635 .map(|id| (ListKind::Primary, id))
636 .chain(self.confirmed.iter().map(|id| (ListKind::Confirmed, id)))
637 .chain(self.sample.iter().map(|id| (ListKind::Sample, id)))
638 .unique_by(|(_, id)| *id)
639 }
640
641 fn preference_order(&self) -> impl Iterator<Item = (ListKind, &Guard)> + '_ {
643 self.preference_order_ids()
644 .filter_map(move |(p, id)| self.guards.by_all_ids(id).map(|g| (p, g)))
645 }
646
647 fn guard_is_primary(&self, guard_id: &GuardId) -> bool {
649 self.primary
653 .iter()
654 .any(|p| p.has_all_relay_ids_from(guard_id))
655 }
656
657 pub(crate) fn consider_all_retries(&mut self, now: Instant) {
660 let old_guards = std::mem::take(&mut self.guards);
661 self.guards = old_guards
662 .into_values()
663 .map(|mut guard| {
664 guard.consider_retry(now);
665 guard
666 })
667 .collect();
668 }
669
670 pub(crate) fn next_retry(&self, usage: &GuardUsage) -> Option<Instant> {
672 self.guards
673 .values()
674 .filter_map(|g| g.next_retry(usage))
675 .min()
676 }
677
678 pub(crate) fn mark_primary_guards_retriable(&mut self) {
680 for id in &self.primary {
681 self.guards
682 .modify_by_all_ids(id, |guard| guard.mark_retriable());
683 }
684 }
685
686 pub(crate) fn all_primary_guards_are_unreachable(&mut self) -> bool {
689 self.primary
690 .iter()
691 .flat_map(|id| self.guards.by_all_ids(id))
692 .all(|g| g.reachable() == Reachable::Unreachable)
693 }
694
695 pub(crate) fn mark_all_guards_retriable(&mut self) {
697 let old_guards = std::mem::take(&mut self.guards);
698 self.guards = old_guards
699 .into_values()
700 .map(|mut guard| {
701 guard.mark_retriable();
702 guard
703 })
704 .collect();
705 }
706
707 pub(crate) fn record_attempt(&mut self, guard_id: &GuardId, now: Instant) {
710 let is_primary = self.guard_is_primary(guard_id);
711 self.guards.modify_by_all_ids(guard_id, |guard| {
712 guard.record_attempt(now);
713
714 if !is_primary {
715 guard.note_exploratory_circ(true);
716 }
717 });
718 }
719
720 pub(crate) fn record_success(
726 &mut self,
727 guard_id: &GuardId,
728 params: &GuardParams,
729 how: Option<ExternalActivity>,
730 now: SystemTime,
731 ) {
732 self.assert_consistency();
733 self.guards.modify_by_all_ids(guard_id, |guard| match how {
734 Some(external) => guard.record_external_success(external),
735 None => {
736 let newly_confirmed = guard.record_success(now, params);
737
738 if newly_confirmed == NewlyConfirmed::Yes {
739 self.confirmed.push(guard_id.clone());
740 self.primary_guards_invalidated = true;
741 }
742 }
743 });
744 self.assert_consistency();
745 }
746
747 pub(crate) fn record_failure(
750 &mut self,
751 guard_id: &GuardId,
752 how: Option<ExternalActivity>,
753 now: Instant,
754 ) {
755 trace!(
756 onionperf = true,
757 event = ?OnionperfEvent::Guard(OnionperfGuardStatus::Down),
758 ?guard_id,
759 );
760 let is_primary = self.guard_is_primary(guard_id);
762 self.guards.modify_by_all_ids(guard_id, |guard| match how {
763 Some(external) => guard.record_external_failure(external, now),
764 None => guard.record_failure(now, is_primary),
765 });
766 }
767
768 pub(crate) fn record_attempt_abandoned(&mut self, guard_id: &GuardId) {
771 self.guards
772 .modify_by_all_ids(guard_id, |guard| guard.note_exploratory_circ(false));
773 }
774
775 pub(crate) fn record_indeterminate_result(&mut self, guard_id: &GuardId) {
779 self.guards.modify_by_all_ids(guard_id, |guard| {
780 guard.note_exploratory_circ(false);
781 guard.record_indeterminate_result();
782 });
783 }
784
785 pub(crate) fn record_skew(&mut self, guard_id: &GuardId, observation: SkewObservation) {
787 self.guards
788 .modify_by_all_ids(guard_id, |guard| guard.note_skew(observation));
789 }
790
791 pub(crate) fn skew_observations(&self) -> impl Iterator<Item = &SkewObservation> {
793 self.guards.values().filter_map(|g| g.skew())
794 }
795
796 pub(crate) fn circ_usability_status(
802 &self,
803 guard_id: &GuardId,
804 usage: &GuardUsage,
805 params: &GuardParams,
806 now: Instant,
807 ) -> Option<bool> {
808 if self.guard_is_primary(guard_id) {
825 return Some(true);
830 }
831
832 let cutoff = now
838 .checked_sub(params.np_connect_timeout)
839 .expect("Can't subtract connect timeout from now.");
840
841 for (src, guard) in self.preference_order() {
842 if guard.guard_id() == guard_id {
843 return Some(true);
844 }
845 if guard.usable() && self.active_filter.permits(guard) && guard.conforms_to_usage(usage)
846 {
847 match (src, guard.reachable()) {
848 (_, Reachable::Reachable) => return Some(false),
849 (_, Reachable::Unreachable) => (),
850 (ListKind::Primary, Reachable::Untried | Reachable::Retriable) => {
851 return Some(false);
852 }
853 (_, Reachable::Untried | Reachable::Retriable) => {
854 if guard.exploratory_attempt_after(cutoff) {
855 return None;
856 }
857 }
858 }
859 }
860 }
861
862 Some(false)
864 }
865
866 pub(crate) fn pick_guard(
877 &self,
878 sample_id: &GuardSetSelector,
879 usage: &GuardUsage,
880 params: &GuardParams,
881 now: Instant,
882 ) -> Result<(ListKind, FirstHop), PickGuardError> {
883 let (list_kind, id) = self.pick_guard_id(usage, params, now)?;
884 let first_hop = self
885 .get(&id)
886 .expect("Somehow selected a guard we don't know!")
887 .get_external_rep(sample_id.clone());
888 let first_hop = self.active_filter.modify_hop(first_hop)?;
889
890 Ok((list_kind, first_hop))
891 }
892
893 fn pick_guard_id(
897 &self,
898 usage: &GuardUsage,
899 params: &GuardParams,
900 now: Instant,
901 ) -> Result<(ListKind, GuardId), PickGuardError> {
902 debug_assert!(!self.primary_guards_invalidated);
903 let n_options = match usage.kind {
904 GuardUsageKind::OneHopDirectory => params.dir_parallelism,
905 GuardUsageKind::Data => params.data_parallelism,
906 };
907
908 let mut running = FilterCount::default();
915 let mut pending = FilterCount::default();
916 let mut suitable = FilterCount::default();
917 let mut filtered = FilterCount::default();
918
919 let mut options: Vec<_> = self
920 .preference_order()
921 .filter_cnt(&mut running, |(_, g)| {
924 g.usable()
925 && g.reachable() != Reachable::Unreachable
926 && g.ready_for_usage(usage, now)
927 })
928 .filter_cnt(&mut pending, |(_, g)| !g.exploratory_circ_pending())
931 .filter_cnt(&mut suitable, |(_, g)| g.conforms_to_usage(usage))
934 .filter_cnt(&mut filtered, |(_, g)| self.active_filter.permits(*g))
936 .take(n_options)
938 .collect();
939
940 if options.iter().any(|(src, _)| src.is_primary()) {
941 options.retain(|(src, _)| src.is_primary());
943 } else {
944 options.truncate(1);
946 }
947
948 match options.choose(&mut rand::rng()) {
949 Some((src, g)) => Ok((*src, g.guard_id().clone())),
950 None => {
951 let retry_at = if running.n_accepted == 0 {
952 self.next_retry(usage)
953 } else {
954 None
955 };
956 Err(PickGuardError::AllGuardsDown {
957 retry_at,
958 running,
959 pending,
960 suitable,
961 filtered,
962 })
963 }
964 }
965 }
966
967 #[cfg(feature = "bridge-client")]
973 pub(crate) fn descriptors_to_request(&self, now: Instant, params: &GuardParams) -> Vec<&Guard> {
974 const MINIMUM: usize = 2;
978
979 let maximum = std::cmp::max(params.data_parallelism, MINIMUM);
980 let data_usage = GuardUsage::default();
981
982 self.preference_order()
992 .filter(|(_, g)| {
993 g.usable()
994 && g.reachable() != Reachable::Unreachable
995 && g.ready_for_usage(&data_usage, now)
996 && self.active_filter.permits(*g)
997 })
998 .take(maximum)
999 .map(|(_, g)| g)
1000 .collect()
1001 }
1002}
1003
1004use serde::Serializer;
1005use tor_persist::JsonValue;
1006
1007#[derive(Default, Debug, Clone, Serialize, Deserialize)]
1009pub(crate) struct GuardSample<'a> {
1010 guards: Vec<Cow<'a, Guard>>,
1012 confirmed: Cow<'a, [GuardId]>,
1014 #[serde(flatten)]
1016 remaining: HashMap<String, JsonValue>,
1017}
1018
1019impl Serialize for GuardSet {
1020 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1021 where
1022 S: Serializer,
1023 {
1024 GuardSample::from(self).serialize(serializer)
1025 }
1026}
1027
1028impl<'a> From<&'a GuardSet> for GuardSample<'a> {
1029 fn from(guards: &'a GuardSet) -> Self {
1030 guards.get_state()
1031 }
1032}
1033
1034impl<'a> From<GuardSample<'a>> for GuardSet {
1035 fn from(sample: GuardSample) -> Self {
1036 GuardSet::from_state(sample)
1037 }
1038}
1039
1040#[cfg(test)]
1041mod test {
1042 #![allow(clippy::bool_assert_comparison)]
1044 #![allow(clippy::clone_on_copy)]
1045 #![allow(clippy::dbg_macro)]
1046 #![allow(clippy::mixed_attributes_style)]
1047 #![allow(clippy::print_stderr)]
1048 #![allow(clippy::print_stdout)]
1049 #![allow(clippy::single_char_pattern)]
1050 #![allow(clippy::unwrap_used)]
1051 #![allow(clippy::unchecked_time_subtraction)]
1052 #![allow(clippy::useless_vec)]
1053 #![allow(clippy::needless_pass_by_value)]
1054 #![allow(clippy::string_slice)] use tor_linkspec::{HasRelayIds, RelayIdType};
1057 use tor_netdir::NetDir;
1058 use tor_netdoc::doc::netstatus::RelayWeight;
1059 use tor_netdoc::types::relay_flags::RelayFlag;
1060 use web_time_compat::{InstantExt, SystemTimeExt};
1061
1062 use super::*;
1063 use crate::FirstHopId;
1064 use std::time::Duration;
1065
1066 fn netdir() -> NetDir {
1067 use tor_netdir::testnet;
1068 testnet::construct_netdir().unwrap_if_sufficient().unwrap()
1069 }
1070
1071 #[test]
1072 fn sample_test() {
1073 let netdir = tor_netdir::testnet::construct_custom_netdir(|idx, builder, _| {
1078 builder.rs.weight(RelayWeight::Measured(1000));
1080 if idx >= 10 {
1083 builder.rs.add_flags(RelayFlag::Guard);
1084 if idx >= 20 {
1085 builder.rs.protos("DirCache=2".parse().unwrap());
1086 } else {
1087 builder.rs.protos("".parse().unwrap());
1088 }
1089 }
1090 })
1091 .unwrap()
1092 .unwrap_if_sufficient()
1093 .unwrap();
1094 assert_eq!(40, netdir.relays().count());
1096 assert_eq!(
1097 30,
1098 netdir
1099 .relays()
1100 .filter(|r| r.low_level_details().is_suitable_as_guard())
1101 .count()
1102 );
1103 assert_eq!(
1104 20,
1105 netdir
1106 .relays()
1107 .filter(|r| r.low_level_details().is_suitable_as_guard()
1108 && r.low_level_details().is_dir_cache())
1109 .count()
1110 );
1111
1112 let params = GuardParams {
1113 min_filtered_sample_size: 5,
1114 max_sample_bw_fraction: 1.0,
1115 ..GuardParams::default()
1116 };
1117
1118 let mut samples: Vec<HashSet<GuardId>> = Vec::new();
1119 for _ in 0..3 {
1120 let mut guards = GuardSet::default();
1121 guards.extend_sample_as_needed(SystemTime::get(), ¶ms, &netdir);
1122 assert_eq!(guards.guards.len(), params.min_filtered_sample_size);
1123 assert_eq!(guards.confirmed.len(), 0);
1124 assert_eq!(guards.primary.len(), 0);
1125 guards.assert_consistency();
1126
1127 for guard in guards.guards.values() {
1129 let id = FirstHopId::in_sample(GuardSetSelector::Default, guard.guard_id().clone());
1130 let relay = id.get_relay(&netdir).unwrap();
1131 assert!(relay.low_level_details().is_suitable_as_guard());
1132 assert!(relay.low_level_details().is_dir_cache());
1133 assert!(guards.guards.by_all_ids(&relay).is_some());
1134 {
1135 assert!(!guard.is_expired(¶ms, SystemTime::get()));
1136 }
1137 }
1138
1139 guards.extend_sample_as_needed(SystemTime::get(), ¶ms, &netdir);
1141 assert_eq!(guards.guards.len(), params.min_filtered_sample_size);
1142 guards.assert_consistency();
1143
1144 samples.push(guards.sample.into_iter().collect());
1145 }
1146
1147 assert!(samples[0] != samples[1] || samples[1] != samples[2]);
1150 }
1151
1152 #[test]
1153 fn persistence() {
1154 let netdir = netdir();
1155 let params = GuardParams {
1156 min_filtered_sample_size: 5,
1157 ..GuardParams::default()
1158 };
1159
1160 let t1 = SystemTime::get();
1161 let t2 = t1 + Duration::from_secs(20);
1162
1163 let mut guards = GuardSet::default();
1164 guards.extend_sample_as_needed(t1, ¶ms, &netdir);
1165
1166 let id1 = guards.sample[0].clone();
1168 guards.record_success(&id1, ¶ms, None, t2);
1169 assert_eq!(&guards.confirmed, std::slice::from_ref(&id1));
1170
1171 let state: GuardSample = (&guards).into();
1173 let guards2: GuardSet = state.into();
1174
1175 assert_eq!(&guards2.sample, &guards.sample);
1176 assert_eq!(&guards2.confirmed, &guards.confirmed);
1177 assert_eq!(&guards2.confirmed, &[id1]);
1178 assert_eq!(
1179 guards
1180 .guards
1181 .values()
1182 .map(Guard::guard_id)
1183 .collect::<HashSet<_>>(),
1184 guards2
1185 .guards
1186 .values()
1187 .map(Guard::guard_id)
1188 .collect::<HashSet<_>>()
1189 );
1190 for g in guards.guards.values() {
1191 let g2 = guards2.guards.by_all_ids(g.guard_id()).unwrap();
1192 assert_eq!(format!("{:?}", g), format!("{:?}", g2));
1193 }
1194 }
1195
1196 #[test]
1197 fn select_primary() {
1198 let netdir = netdir();
1199 let params = GuardParams {
1200 min_filtered_sample_size: 5,
1201 n_primary: 4,
1202 ..GuardParams::default()
1203 };
1204 let t1 = SystemTime::get();
1205 let t2 = t1 + Duration::from_secs(20);
1206 let t3 = t2 + Duration::from_secs(30);
1207
1208 let mut guards = GuardSet::default();
1209 guards.extend_sample_as_needed(t1, ¶ms, &netdir);
1210
1211 let id3 = guards.sample[3].clone();
1213 guards.record_success(&id3, ¶ms, None, t2);
1214 assert_eq!(&guards.confirmed, std::slice::from_ref(&id3));
1215 let id1 = guards.sample[1].clone();
1216 guards.record_success(&id1, ¶ms, None, t3);
1217 assert_eq!(&guards.confirmed, &[id3.clone(), id1.clone()]);
1218
1219 guards.select_primary_guards(¶ms);
1221 assert_eq!(guards.primary.len(), 4);
1222 assert_eq!(&guards.primary[0], &id3);
1223 assert_eq!(&guards.primary[1], &id1);
1224 let p3 = guards.primary[2].clone();
1225 let p4 = guards.primary[3].clone();
1226 assert_eq!(
1227 [id1.clone(), id3.clone(), p3.clone(), p4.clone()]
1228 .iter()
1229 .unique()
1230 .count(),
1231 4
1232 );
1233
1234 guards.record_success(&p4, ¶ms, None, t3);
1238 assert_eq!(&guards.confirmed, &[id3.clone(), id1.clone(), p4.clone()]);
1239 guards.select_primary_guards(¶ms);
1240 assert_eq!(guards.primary.len(), 4);
1241 assert_eq!(&guards.primary[0], &id3);
1242 assert_eq!(&guards.primary[1], &id1);
1243 assert_eq!(&guards.primary, &[id3, id1, p4, p3]);
1244 }
1245
1246 #[test]
1247 fn expiration() {
1248 let netdir = netdir();
1249 let params = GuardParams::default();
1250 let t1 = SystemTime::get();
1251
1252 let mut guards = GuardSet::default();
1253 guards.extend_sample_as_needed(t1, ¶ms, &netdir);
1254 assert_eq!(guards.sample.len(), 10);
1256
1257 let id1 = guards.sample[0].clone();
1260 guards.record_success(&id1, ¶ms, None, t1);
1261 assert_eq!(&guards.confirmed, &[id1]);
1262
1263 let one_day = Duration::from_secs(86400);
1264 guards.expire_old_guards(¶ms, t1 + one_day * 30);
1265 assert_eq!(guards.sample.len(), 10); guards.expire_old_guards(¶ms, t1 + one_day * 70);
1269 assert_eq!(guards.sample.len(), 9);
1270
1271 guards.expire_old_guards(¶ms, t1 + one_day * 200);
1272 assert_eq!(guards.sample.len(), 0);
1273 }
1274
1275 #[test]
1276 fn sampling_and_usage() {
1277 let netdir = netdir();
1278 let params = GuardParams {
1279 min_filtered_sample_size: 5,
1280 n_primary: 2,
1281 ..GuardParams::default()
1282 };
1283 let st1 = SystemTime::get();
1284 let i1 = Instant::get();
1285 let sec = Duration::from_secs(1);
1286
1287 let mut guards = GuardSet::default();
1288 guards.extend_sample_as_needed(st1, ¶ms, &netdir);
1289 guards.select_primary_guards(¶ms);
1290
1291 let usage = crate::GuardUsageBuilder::default().build().unwrap();
1293 let id1 = guards.primary[0].clone();
1294 let id2 = guards.primary[1].clone();
1295 let (src, id) = guards.pick_guard_id(&usage, ¶ms, i1).unwrap();
1296 assert_eq!(src, ListKind::Primary);
1297 assert_eq!(&id, &id1);
1298
1299 guards.record_attempt(&id, i1);
1300 guards.record_failure(&id, None, i1 + sec);
1301
1302 let (src, id) = guards.pick_guard_id(&usage, ¶ms, i1 + sec).unwrap();
1304 assert_eq!(src, ListKind::Primary);
1305 assert_eq!(&id, &id2);
1306 guards.record_attempt(&id, i1 + sec);
1307
1308 let (src, id_x) = guards.pick_guard_id(&usage, ¶ms, i1 + sec).unwrap();
1309 assert_eq!(id_x, id);
1312 assert_eq!(src, ListKind::Primary);
1313 guards.record_attempt(&id_x, i1 + sec * 2);
1314 guards.record_failure(&id_x, None, i1 + sec * 3);
1315 guards.record_failure(&id, None, i1 + sec * 4);
1316
1317 let (src, id3) = guards.pick_guard_id(&usage, ¶ms, i1 + sec * 4).unwrap();
1319 assert_eq!(src, ListKind::Sample);
1320 assert!(!guards.primary.contains(&id3));
1321 guards.record_attempt(&id3, i1 + sec * 5);
1322
1323 let (src, id4) = guards.pick_guard_id(&usage, ¶ms, i1 + sec * 5).unwrap();
1326 assert_eq!(src, ListKind::Sample);
1327 assert!(id3 != id4);
1328 assert!(!guards.primary.contains(&id4));
1329 guards.record_attempt(&id4, i1 + sec * 6);
1330
1331 assert_eq!(
1336 guards.circ_usability_status(&id1, &usage, ¶ms, i1 + sec * 6),
1337 Some(true)
1338 );
1339 assert_eq!(
1340 guards.circ_usability_status(&id2, &usage, ¶ms, i1 + sec * 6),
1341 Some(true)
1342 );
1343 assert_eq!(
1344 guards.circ_usability_status(&id3, &usage, ¶ms, i1 + sec * 6),
1345 Some(true)
1346 );
1347 assert_eq!(
1348 guards.circ_usability_status(&id4, &usage, ¶ms, i1 + sec * 6),
1349 None
1350 );
1351
1352 guards.record_success(&id3, ¶ms, None, st1 + sec * 7);
1354 guards.record_success(&id4, ¶ms, None, st1 + sec * 8);
1355
1356 assert!(guards.primary_guards_invalidated);
1358 guards.select_primary_guards(¶ms);
1359 assert_eq!(&guards.primary, &[id3.clone(), id4.clone()]);
1360
1361 let (src, id) = guards
1363 .pick_guard_id(&usage, ¶ms, i1 + sec * 10)
1364 .unwrap();
1365 assert_eq!(src, ListKind::Primary);
1366 assert_eq!(&id, &id3);
1367
1368 let mut found = HashSet::new();
1370 let usage = crate::GuardUsageBuilder::default()
1371 .kind(crate::GuardUsageKind::OneHopDirectory)
1372 .build()
1373 .unwrap();
1374 for _ in 0..64 {
1375 let (src, id) = guards
1376 .pick_guard_id(&usage, ¶ms, i1 + sec * 10)
1377 .unwrap();
1378 assert_eq!(src, ListKind::Primary);
1379 assert_eq!(
1380 guards.circ_usability_status(&id, &usage, ¶ms, i1 + sec * 10),
1381 Some(true)
1382 );
1383 guards.record_attempt_abandoned(&id);
1384 found.insert(id);
1385 }
1386 assert!(found.len() == 2);
1387 assert!(found.contains(&id3));
1388 assert!(found.contains(&id4));
1389
1390 assert_eq!(
1392 guards.circ_usability_status(&id1, &usage, ¶ms, i1 + sec * 12),
1393 Some(false)
1394 );
1395 assert_eq!(
1396 guards.circ_usability_status(&id2, &usage, ¶ms, i1 + sec * 12),
1397 Some(false)
1398 );
1399 }
1400
1401 #[test]
1402 fn everybodys_down() {
1403 let netdir = netdir();
1404 let params = GuardParams {
1405 min_filtered_sample_size: 5,
1406 n_primary: 2,
1407 max_sample_bw_fraction: 1.0,
1408 ..GuardParams::default()
1409 };
1410 let mut st = SystemTime::get();
1411 let mut inst = Instant::get();
1412 let sec = Duration::from_secs(1);
1413 let usage = crate::GuardUsageBuilder::default().build().unwrap();
1414
1415 let mut guards = GuardSet::default();
1416
1417 guards.extend_sample_as_needed(st, ¶ms, &netdir);
1418 guards.select_primary_guards(¶ms);
1419
1420 assert_eq!(guards.sample.len(), 5);
1421 for _ in 0..5 {
1422 let (_, id) = guards.pick_guard_id(&usage, ¶ms, inst).unwrap();
1423 guards.record_attempt(&id, inst);
1424 guards.record_failure(&id, None, inst + sec);
1425
1426 inst += sec * 2;
1427 st += sec * 2;
1428 }
1429
1430 let e = guards.pick_guard_id(&usage, ¶ms, inst);
1431 assert!(matches!(e, Err(PickGuardError::AllGuardsDown { .. })));
1432
1433 guards.extend_sample_as_needed(st, ¶ms, &netdir);
1435 guards.select_primary_guards(¶ms);
1436 assert_eq!(guards.sample.len(), 10);
1437 }
1438
1439 #[test]
1440 fn retry_primary() {
1441 let netdir = netdir();
1442 let params = GuardParams {
1443 min_filtered_sample_size: 5,
1444 n_primary: 2,
1445 max_sample_bw_fraction: 1.0,
1446 ..GuardParams::default()
1447 };
1448 let usage = crate::GuardUsageBuilder::default().build().unwrap();
1449
1450 let mut guards = GuardSet::default();
1451
1452 guards.extend_sample_as_needed(SystemTime::get(), ¶ms, &netdir);
1453 guards.select_primary_guards(¶ms);
1454
1455 assert_eq!(guards.primary.len(), 2);
1456 assert!(!guards.all_primary_guards_are_unreachable());
1457
1458 let (kind, p_id1) = guards
1460 .pick_guard_id(&usage, ¶ms, Instant::get())
1461 .unwrap();
1462 assert_eq!(kind, ListKind::Primary);
1463 guards.record_failure(&p_id1, None, Instant::get());
1464 assert!(!guards.all_primary_guards_are_unreachable());
1465
1466 let (kind, p_id2) = guards
1468 .pick_guard_id(&usage, ¶ms, Instant::get())
1469 .unwrap();
1470 assert_eq!(kind, ListKind::Primary);
1471 guards.record_failure(&p_id2, None, Instant::get());
1472 assert!(guards.all_primary_guards_are_unreachable());
1473
1474 guards.mark_primary_guards_retriable();
1476 assert!(!guards.all_primary_guards_are_unreachable());
1477 let (kind, p_id3) = guards
1478 .pick_guard_id(&usage, ¶ms, Instant::get())
1479 .unwrap();
1480 assert_eq!(kind, ListKind::Primary);
1481 assert_eq!(p_id3, p_id1);
1482 }
1483
1484 #[test]
1485 fn count_missing_mds() {
1486 let netdir = netdir();
1487 let params = GuardParams {
1488 min_filtered_sample_size: 5,
1489 n_primary: 2,
1490 max_sample_bw_fraction: 1.0,
1491 ..GuardParams::default()
1492 };
1493 let usage = crate::GuardUsageBuilder::default().build().unwrap();
1494 let mut guards = GuardSet::default();
1495 guards.extend_sample_as_needed(SystemTime::get(), ¶ms, &netdir);
1496 guards.select_primary_guards(¶ms);
1497 assert_eq!(guards.primary.len(), 2);
1498
1499 let (_kind, p_id1) = guards
1500 .pick_guard_id(&usage, ¶ms, Instant::get())
1501 .unwrap();
1502 guards.record_success(&p_id1, ¶ms, None, SystemTime::get());
1503 assert_eq!(guards.n_primary_without_id_info_in(&netdir), 0);
1504
1505 use tor_netdir::testnet;
1506 let netdir2 = testnet::construct_custom_netdir(|_idx, bld, _| {
1507 let md_so_far = bld.md.testing_md().expect("Couldn't build md?");
1508 if &p_id1.0.identity(RelayIdType::Ed25519).unwrap() == md_so_far.ed25519_id() {
1509 bld.omit_md = true;
1510 }
1511 })
1512 .unwrap()
1513 .unwrap_if_sufficient()
1514 .unwrap();
1515
1516 assert_eq!(guards.n_primary_without_id_info_in(&netdir2), 1);
1517 }
1518
1519 #[test]
1520 fn copy_status() {
1521 let netdir = netdir();
1522 let params = GuardParams {
1523 min_filtered_sample_size: 5,
1524 n_primary: 2,
1525 max_sample_bw_fraction: 1.0,
1526 ..GuardParams::default()
1527 };
1528 let mut guards1 = GuardSet::default();
1529 guards1.extend_sample_as_needed(SystemTime::get(), ¶ms, &netdir);
1530 guards1.select_primary_guards(¶ms);
1531 let mut guards2 = guards1.clone();
1532
1533 let id1 = guards1.primary[0].clone();
1535 let id2 = guards1.primary[1].clone();
1536 guards1.record_success(&id1, ¶ms, None, SystemTime::get());
1537 guards2.record_success(&id2, ¶ms, None, SystemTime::get());
1538 guards2.record_failure(&id2, None, Instant::get());
1540
1541 guards1.copy_ephemeral_status_into_newly_loaded_state(guards2);
1543 {
1544 let g1 = guards1.get(&id1).unwrap();
1545 let g2 = guards1.get(&id2).unwrap();
1546 assert!(g1.confirmed());
1547 assert!(!g2.confirmed());
1548 assert_eq!(g1.reachable(), Reachable::Untried);
1549 assert_eq!(g2.reachable(), Reachable::Unreachable);
1550 }
1551
1552 let mut guards3 = GuardSet::default();
1555 let g1_set: HashSet<_> = guards1
1556 .guards
1557 .values()
1558 .map(|g| g.guard_id().clone())
1559 .collect();
1560 let mut g3_set: HashSet<_> = HashSet::new();
1561 for _ in 0..4 {
1562 guards3.extend_sample_as_needed(SystemTime::get(), ¶ms, &netdir);
1565 guards3.select_primary_guards(¶ms);
1566 g3_set = guards3
1567 .guards
1568 .values()
1569 .map(|g| g.guard_id().clone())
1570 .collect();
1571
1572 if g1_set == g3_set {
1574 guards3 = GuardSet::default();
1575 continue;
1576 }
1577 break;
1578 }
1579 assert_ne!(g1_set, g3_set);
1580 guards1.copy_ephemeral_status_into_newly_loaded_state(guards3);
1582 let g1_set_new: HashSet<_> = guards1
1583 .guards
1584 .values()
1585 .map(|g| g.guard_id().clone())
1586 .collect();
1587 assert_eq!(g1_set, g1_set_new);
1588 }
1589}