Merge branch 'guards_as_bridges_part2' into 'main'

Use ByRelayIds to hold guards in GuardSet

See merge request tpo/core/arti!808
This commit is contained in:
Nick Mathewson 2022-10-27 21:09:47 +00:00
commit 29c46d08b5
6 changed files with 373 additions and 141 deletions

View File

@ -15,7 +15,7 @@ use crate::skew::SkewObservation;
use crate::util::randomize_time;
use crate::{ids::GuardId, GuardParams, GuardRestriction, GuardUsage};
use crate::{ExternalActivity, GuardSetSelector, GuardUsageKind};
use tor_linkspec::{HasAddrs, HasRelayIds};
use tor_linkspec::{HasAddrs, HasRelayIds, RelayIds};
use tor_persist::{Futureproof, JsonValue};
/// Tri-state to represent whether a guard is believed to be reachable or not.
@ -68,24 +68,28 @@ impl CrateId {
/// A single guard node, as held by the guard manager.
///
/// A Guard is a Tor relay that clients use for the first hop of their
/// circuits. It doesn't need to be a relay that's currently on the
/// network (that is, one that we could represent as a [`Relay`]):
/// guards might be temporarily unlisted.
/// A Guard is a Tor relay that clients use for the first hop of their circuits.
/// It doesn't need to be a relay that's currently on the network (that is, one
/// that we could represent as a [`Relay`]): guards might be temporarily
/// unlisted.
///
/// Some fields in guards are persistent; others are reset with every
/// process.
/// Some fields in guards are persistent; others are reset with every process.
///
/// # Identity
///
/// Every guard has at least one `RelayId`. A guard may _gain_ identities over
/// time, as we learn more about it, but it should never _lose_ or _change_ its
/// identities of a given type.
///
/// # TODO
///
/// This structure uses [`Instant`] to represent non-persistent points
/// in time, and [`SystemTime`] to represent points in time that need
/// to be persistent. That's possibly undesirable; maybe we should
/// come up with a better solution.
/// This structure uses [`Instant`] to represent non-persistent points in time,
/// and [`SystemTime`] to represent points in time that need to be persistent.
/// That's possibly undesirable; maybe we should come up with a better solution.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub(crate) struct Guard {
/// The identity keys for this guard.
id: GuardId, // TODO: Maybe refactor this out as redundant someday.
id: GuardId,
/// The most recently seen addresses for making OR connections to this
/// guard.
@ -298,12 +302,27 @@ impl Guard {
/// Copy all _non-persistent_ status from `other` to self.
///
/// Requires that the two `Guard`s have the same ID.
pub(crate) fn copy_status_from(self, other: Guard) -> Guard {
debug_assert_eq!(self.id, other.id);
/// We do this when we were not the owner of our persistent state, and we
/// have just reloaded it (as `self`), but we have some ephemeral knowledge
/// about this guard (as `other`).
///
/// You should not invent new uses for this function; instead we should come
/// up with alternatives.
///
/// # Panics
///
/// Panics if the identities in `self` are not exactly the same as the
/// identities in `other`.
pub(crate) fn copy_ephemeral_status_into_newly_loaded_state(self, other: Guard) -> Guard {
// It is not safe to copy failure information unless these identities
// are a superset of those in `other`; but it is not safe to copy success
// information unless these identities are a subset of those in `other`.
//
// To simplify matters, we just insist that the identities have to be the same.
assert!(self.same_relay_ids(&other));
Guard {
// All persistent fields are taken from `self`.
// All other persistent fields are taken from `self`.
id: self.id,
orports: self.orports,
added_at: self.added_at,
@ -428,14 +447,17 @@ impl Guard {
netdir.ids_listed(&self.id.0)
}
/// Change this guard's status based on a newly received or newly
/// updated [`NetDir`].
/// Change this guard's status based on a newly received or newly updated
/// [`NetDir`].
///
/// A guard may become "listed" or "unlisted": a listed guard is
/// one that appears in the consensus with the Guard flag.
/// A guard may become "listed" or "unlisted": a listed guard is one that
/// appears in the consensus with the Guard flag.
///
/// Additionally, a guard's orports may change, if the directory
/// lists a new address for the relay.
/// A guard may acquire additional identities if we learned them from the
/// netdir.
///
/// Additionally, a guard's orports may change, if the directory lists a new
/// address for the relay.
pub(crate) fn update_from_netdir(&mut self, netdir: &NetDir) {
// This is a tricky check, since if we're missing a microdescriptor
// for the RSA id, we won't know whether the ed25519 id is listed or
@ -450,6 +472,9 @@ impl Guard {
self.orports = relay.addrs().into();
// Check whether we can currently use it as a directory cache.
self.is_dir_cache = relay.is_dir_cache();
// Update our IDs: the Relay will have strictly more.
assert!(relay.has_all_relay_ids_from(self));
self.id = GuardId(RelayIds::from_relay_ids(&relay));
relay.is_flagged_guard()
}

View File

@ -64,6 +64,15 @@ impl GuardId {
}
}
impl HasRelayIds for GuardId {
fn identity(
&self,
key_type: tor_linkspec::RelayIdType,
) -> Option<tor_linkspec::RelayIdRef<'_>> {
self.0.identity(key_type)
}
}
/// Implementation type held inside of FirstHopId.
///
/// This exists as a separate type from FirstHopId because Rust requires that a pub enum's variants are all public.
@ -79,11 +88,6 @@ pub(crate) enum FirstHopIdInner {
/// directory.
///
/// (This is implemented internally using all of the guard's known identities.)
///
/// TODO(nickm): we may want a fuzzier match for this type in the future in our
/// maps, if we ever learn about more identity types. Right now we don't
/// recognize two `FirstHopId`s as "the same" if one has more IDs than the
/// other, even if all the IDs that they do have are the same.
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct FirstHopId(pub(crate) FirstHopIdInner);

View File

@ -641,7 +641,9 @@ impl GuardSets {
use strum::IntoEnumIterator;
for sample in GuardSetSelector::iter() {
self.guards_mut(&sample)
.copy_status_from(std::mem::take(other.guards_mut(&sample)));
.copy_ephemeral_status_into_newly_loaded_state(std::mem::take(
other.guards_mut(&sample),
));
}
}
}
@ -1055,7 +1057,11 @@ impl GuardMgrInner {
}
/// Return every currently extant FirstHopId for a guard or fallback
/// directory matching the provided keys.
/// directory matching (or possibly matching) the provided keys.
///
/// An identity is _possibly matching_ if it contains some of the IDs in the
/// provided identity, and it has no _contradictory_ identities, but it does
/// not necessarily contain _all_ of those identities.
///
/// # TODO
///
@ -1064,6 +1070,10 @@ impl GuardMgrInner {
/// doesn't know whether its circuit came from a guard or a fallback. To
/// solve that, we'll need CircMgr to record and report which one it was
/// using, which will take some more plumbing.
///
/// TODO relay: we will have to make the change above when we implement
/// relays; otherwise, it would be possible for an attacker to exploit it to
/// mislead us about our guard status.
fn lookup_ids<T>(&self, identity: &T) -> Vec<FirstHopId>
where
T: tor_linkspec::HasRelayIds + ?Sized,
@ -1073,9 +1083,12 @@ impl GuardMgrInner {
let id = ids::GuardId::from_relay_ids(identity);
for sample in GuardSetSelector::iter() {
if self.guards.guards(&sample).contains(&id) {
vec.push(FirstHopId(FirstHopIdInner::Guard(sample, id.clone())));
}
let guard_id = match self.guards.guards(&sample).contains(&id) {
Ok(true) => &id,
Err(other) => other,
Ok(false) => continue,
};
vec.push(FirstHopId(FirstHopIdInner::Guard(sample, guard_id.clone())));
}
let id = ids::FallbackId::from_relay_ids(identity);

View File

@ -13,6 +13,7 @@ use crate::{
};
use crate::{FirstHop, GuardSetSelector};
use tor_basic_utils::iter::{FilterCount, IteratorExt as _};
use tor_linkspec::{ByRelayIds, HasRelayIds};
use tor_netdir::{NetDir, Relay};
use itertools::Itertools;
@ -47,21 +48,78 @@ use tracing::{debug, info};
#[serde(from = "GuardSample")]
pub(crate) struct GuardSet {
/// Map from identities to guards, for every guard in this sample.
guards: HashMap<GuardId, Guard>,
///
/// The key for each entry is a set of identities which we have
/// good (trustworthy-enough) reason to link together.
///
/// When we connect to a guard we require it to demonstrate
/// that it has *all* of these identities;
/// and we do pinning, so that we note down the other identities we discover it has,
/// with the intent that we will require them in future.
///
/// ### Sources of linkage:
///
/// * If we connect to a relay and it proves a set of identities,
/// that necessarily will include at least the ones we have already.
/// We can add any other identities we have discovered.
/// Justification: the owners of the old ids have made a statement
/// (via the connection protocols) that these other ids are also theirs,
/// and should be required in future.
///
/// * If we obtain a (full) descriptor for a relay, and check the
/// self-signatures by all the identities we have already,
/// we can add any other identities listed in the descriptor.
/// Justification: the owners of the old ids have made an explicit statement
/// that these other ids are also theirs,
/// and should be required in future.
///
/// * For a relay in the netdir, if the netdir links some ids together,
/// we can combine the entries.
/// Justification: the netdir is authoritative for netdir-based relays.
///
/// * For a configured bridge, if our configuration links some identities,
/// we must insist on all those identities.
/// So we combine them.
///
/// ### Handling of conflicting entries:
///
/// `ByRelayIds` will implicitly delete conflicting entries,
/// simply forgetting about them.
/// This is OK for netdir relays, since we do not expect this to occur in practice.
///
/// For bridges, conflicts may in fact occur,
/// since bridge lines are not issued by a single authority,
/// and should be afforded limited trust.
///
/// * If the configuration contains bridge lines that mutually conflict,
/// affected bridge lines should be disregarded,
/// or the configuration rejected.
///
/// * If the configuration contains information which is inconsistent with
/// our past experience, we should discard the past experiences which
/// aren't reconcilable with the configuration.
///
/// * We may discover a linkage which demonstrates that the configuration
/// is wrong: for example, two bridge lines for identities X and Y,
/// but in fact there is only one bridge with both identities.
/// In this situation it is OK to effectively disregard some the configuration
/// entries which are at variance with reality, maybe with a warning,
/// but keeping at least one of every usable id set (actually existing bridge)
/// would be good.
guards: ByRelayIds<Guard>,
/// Identities of all the guards in the sample, in sample order.
///
/// This contains the same elements as `self.guards.keys()`, and
/// only exists to define an ordering on the guards.
/// This contains the same elements as the keys of `guards`
sample: Vec<GuardId>,
/// Identities of all the confirmed guards in the sample, in
/// confirmed order.
///
/// This contains a subset of the values in `self.guards.keys()`.
/// This contains a subset of the values in `sample`.
confirmed: Vec<GuardId>,
/// Identities of all the primary guards, in preference order
/// (from best to worst).
///
/// This contains a subset of the values in `self.guards.keys()`.
/// This contains a subset of the values in `sample`.
primary: Vec<GuardId>,
/// Currently active filter that restricts which guards we can use.
///
@ -126,21 +184,34 @@ impl GuardSet {
)
}
/// Remove all elements from this `GuardSet` that ought to be
/// referenced by another element, but which are not.
/// Remove all elements from this `GuardSet` that ought to be referenced by
/// another element, but which are not.
///
/// This method only removes corrupted elements; it doesn't add or
/// fix anything. It won't do anything if the `GuardSet` is
/// well-formed.
/// This method only removes corrupted elements and updates IDs in the ID
/// list (possibly adding new IDs); it doesn't add guards or other data.
/// It won't do anything if the `GuardSet` is well-formed.
fn fix_consistency(&mut self) {
/// Remove every element of `id_list` that does not belong to some guard
/// in `guards`, and update the others to have any extra identities
/// listed in `guards`.
fn fix_id_list(guards: &ByRelayIds<Guard>, id_list: &mut Vec<GuardId>) {
// TODO: Use Vec::retain_mut when our MSRV >= 1.61
#![allow(deprecated)]
use retain_mut::RetainMut;
RetainMut::retain_mut(id_list, |id| match guards.by_all_ids(id) {
Some(guard) => {
*id = guard.guard_id().clone();
true
}
None => false,
});
}
let sample_set: HashSet<_> = self.sample.iter().collect();
self.guards
.retain(|id, g| g.guard_id() == id && sample_set.contains(id));
let guards = &self.guards; // avoid borrow issues
// TODO: We should potentially de-duplicate these.
self.sample.retain(|id| guards.contains_key(id));
self.confirmed.retain(|id| guards.contains_key(id));
self.primary.retain(|id| guards.contains_key(id));
self.guards.retain(|g| sample_set.contains(g.guard_id()));
fix_id_list(&self.guards, &mut self.sample);
fix_id_list(&self.guards, &mut self.confirmed);
fix_id_list(&self.guards, &mut self.primary);
}
/// Assert that this `GuardSet` is internally consistent.
@ -153,9 +224,9 @@ impl GuardSet {
assert_eq!(len_pre, len_post);
}
/// Return the guard whose id is `id`, if any.
/// Return the guard that has every identity in `id`, if any.
pub(crate) fn get(&self, id: &GuardId) -> Option<&Guard> {
self.guards.get(id)
self.guards.by_all_ids(id)
}
/// Replace the filter used by this `GuardSet` with `filter`.
@ -173,7 +244,7 @@ impl GuardSet {
let filt = &self.active_filter;
self.primary.retain(|id| {
guards
.get(id)
.by_all_ids(id)
.map(|g| g.usable() && filt.permits(g))
.unwrap_or(false)
});
@ -187,16 +258,23 @@ impl GuardSet {
}
/// Copy non-persistent status from every guard shared with `other`.
pub(crate) fn copy_status_from(&mut self, mut other: GuardSet) {
let mut old_guards = HashMap::new();
std::mem::swap(&mut old_guards, &mut self.guards);
///
/// This is used as part of our reload process when we don't own our state
/// files, and we're reloading in order to find out what the other Arti
/// instance thinks the guards are. At that point, `self` is the set of
/// guards that we just loaded from state, and `other` is our old guards,
/// which we are using only for their status information.
pub(crate) fn copy_ephemeral_status_into_newly_loaded_state(&mut self, mut other: GuardSet) {
let old_guards = std::mem::take(&mut self.guards);
self.guards = old_guards
.into_iter()
.map(|(id, guard)| {
if let Some(other_guard) = other.guards.remove(&id) {
(id, guard.copy_status_from(other_guard))
.into_values()
.map(|guard| {
let id = guard.guard_id();
if let Some(other_guard) = other.guards.remove_exact(id) {
guard.copy_ephemeral_status_into_newly_loaded_state(other_guard)
} else {
(id, guard)
guard
}
})
.collect();
@ -208,7 +286,7 @@ impl GuardSet {
let guards = self
.sample
.iter()
.map(|id| Cow::Borrowed(self.guards.get(id).expect("Inconsistent state")))
.map(|id| Cow::Borrowed(self.guards.by_all_ids(id).expect("Inconsistent state")))
.collect();
GuardSample {
@ -220,11 +298,11 @@ impl GuardSet {
/// Reconstruct a guard state from its serialized representation.
fn from_state(state: GuardSample<'_>) -> Self {
let mut guards = HashMap::new();
let mut guards = ByRelayIds::new();
let mut sample = Vec::new();
for guard in state.guards {
sample.push(guard.guard_id().clone());
guards.insert(guard.guard_id().clone(), guard.into_owned());
guards.insert(guard.into_owned());
}
let confirmed = state.confirmed.into_owned();
let primary = Vec::new();
@ -258,17 +336,30 @@ impl GuardSet {
guard_set
}
/// Return true if `relay` is a member of this set.
fn contains_relay(&self, relay: &Relay<'_>) -> bool {
// Note: Could implement Borrow instead, but I don't think it'll
// matter.
let id = GuardId::from_relay_ids(relay);
self.contains(&id)
/// Return false if `relay` (or some other relay that shares an ID with it)
/// is a member if this set.
fn can_add_relay(&self, relay: &Relay<'_>) -> bool {
self.guards.all_overlapping(relay).is_empty()
}
/// Return true if `id` is a member of this set.
pub(crate) fn contains(&self, id: &GuardId) -> bool {
self.guards.contains_key(id)
/// Return `Ok(true)` if `id` is definitely a member of this set, and
/// `Ok(false)` if it is definitely not a member.
///
/// If we cannot tell, it's because there is a guard in this sample that has
/// a _subset_ of the IDs in `id`. In that case, we return
/// `Err(guard_ident)`, where `guard_ident` is the identity of that guard.
pub(crate) fn contains(&self, id: &GuardId) -> Result<bool, &GuardId> {
let overlapping = self.guards.all_overlapping(id);
match &overlapping[..] {
[singleton] => {
if singleton.has_all_relay_ids_from(id) {
Ok(true)
} else {
Err(singleton.guard_id())
}
}
_ => Ok(false),
}
}
/// If there are not enough filter-permitted usable guards in this
@ -367,7 +458,7 @@ impl GuardSet {
filter_ok
&& relay.is_flagged_guard()
&& relay.is_dir_cache()
&& !self.contains_relay(relay)
&& self.can_add_relay(relay)
},
);
@ -409,12 +500,12 @@ impl GuardSet {
/// Does nothing if it is already a guard.
fn add_guard(&mut self, relay: &Relay<'_>, now: SystemTime, params: &GuardParams) {
let id = GuardId::from_relay_ids(relay);
if self.guards.contains_key(&id) {
if self.guards.by_all_ids(&id).is_some() {
return;
}
debug!(guard_id=?id, "Adding guard to sample.");
let guard = Guard::from_relay(relay, now, params);
self.guards.insert(id.clone(), guard);
self.guards.insert(guard);
self.sample.push(id);
self.primary_guards_invalidated = true;
}
@ -425,7 +516,10 @@ impl GuardSet {
self.primary
.iter()
.filter(|id| {
let g = self.guards.get(id).expect("Inconsistent guard state");
let g = self
.guards
.by_all_ids(*id)
.expect("Inconsistent guard state");
g.listed_in(dir).is_none()
})
.count()
@ -434,9 +528,16 @@ impl GuardSet {
/// Update the status of every guard in this sample from a network
/// directory.
pub(crate) fn update_status_from_netdir(&mut self, dir: &NetDir) {
for g in self.guards.values_mut() {
g.update_from_netdir(dir);
}
let old_guards = std::mem::take(&mut self.guards);
self.guards = old_guards
.into_values()
.map(|mut guard| {
guard.update_from_netdir(dir);
guard
})
.collect();
// Call "fix consistency", in case any guards got a new ID.
self.fix_consistency();
}
/// Re-build the list of primary guards.
@ -469,7 +570,10 @@ impl GuardSet {
.unique()
// We only consider usable guards that the filter allows.
.filter_map(|id| {
let g = self.guards.get(id).expect("Inconsistent guard state");
let g = self
.guards
.by_all_ids(id)
.expect("Inconsistent guard state");
if g.usable() && self.active_filter.permits(g) {
Some(id.clone())
} else {
@ -486,9 +590,9 @@ impl GuardSet {
// Clear exploratory_circ_pending for all primary guards.
for id in &self.primary {
if let Some(guard) = self.guards.get_mut(id) {
self.guards.modify_by_all_ids(id, |guard| {
guard.note_exploratory_circ(false);
}
});
}
// TODO: Recalculate retry times, perhaps, since we may have changed
@ -503,11 +607,11 @@ impl GuardSet {
pub(crate) fn expire_old_guards(&mut self, params: &GuardParams, now: SystemTime) {
self.assert_consistency();
let n_pre = self.guards.len();
self.guards.retain(|_, g| !g.is_expired(params, now));
let guards = &self.guards; // to avoid borrowing issue
self.sample.retain(|id| guards.contains_key(id));
self.confirmed.retain(|id| guards.contains_key(id));
self.primary.retain(|id| guards.contains_key(id));
self.guards.retain(|g| !g.is_expired(params, now));
let guards = &self.guards;
self.sample.retain(|id| guards.by_all_ids(id).is_some());
self.confirmed.retain(|id| guards.by_all_ids(id).is_some());
self.primary.retain(|id| guards.by_all_ids(id).is_some());
self.assert_consistency();
if self.guards.len() < n_pre {
@ -521,7 +625,10 @@ impl GuardSet {
/// is not known to be Unreachable.
fn reachable_sample_ids(&self) -> impl Iterator<Item = &GuardId> {
self.sample.iter().filter(move |id| {
let g = self.guards.get(id).expect("Inconsistent guard state");
let g = self
.guards
.by_all_ids(*id)
.expect("Inconsistent guard state");
g.reachable() != Reachable::Unreachable
})
}
@ -547,21 +654,30 @@ impl GuardSet {
/// Like `preference_order_ids`, but yields `&Guard` instead of `&GuardId`.
fn preference_order(&self) -> impl Iterator<Item = (ListKind, &Guard)> + '_ {
self.preference_order_ids()
.filter_map(move |(p, id)| self.guards.get(id).map(|g| (p, g)))
.filter_map(move |(p, id)| self.guards.by_all_ids(id).map(|g| (p, g)))
}
/// Return true if `guard_id` is the identity for a primary guard.
/// Return true if `guard_id` is an identity subset for any primary guard in this set.
fn guard_is_primary(&self, guard_id: &GuardId) -> bool {
// (This could be yes/no/maybe.)
// This is O(n), but the list is short.
self.primary.contains(guard_id)
self.primary
.iter()
.any(|p| p.has_all_relay_ids_from(guard_id))
}
/// For every guard that has been marked as `Unreachable` for too long,
/// mark it as `Unknown`.
pub(crate) fn consider_all_retries(&mut self, now: Instant) {
for guard in self.guards.values_mut() {
guard.consider_retry(now);
}
let old_guards = std::mem::take(&mut self.guards);
self.guards = old_guards
.into_values()
.map(|mut guard| {
guard.consider_retry(now);
guard
})
.collect();
}
/// Return the earliest time at which any guard will be retriable.
@ -575,9 +691,8 @@ impl GuardSet {
/// Mark every `Unreachable` primary guard as `Unknown`.
pub(crate) fn mark_primary_guards_retriable(&mut self) {
for id in &self.primary {
if let Some(g) = self.guards.get_mut(id) {
g.mark_retriable();
}
self.guards
.modify_by_all_ids(id, |guard| guard.mark_retriable());
}
}
@ -586,28 +701,33 @@ impl GuardSet {
pub(crate) fn all_primary_guards_are_unreachable(&mut self) -> bool {
self.primary
.iter()
.flat_map(|id| self.guards.get(id))
.flat_map(|id| self.guards.by_all_ids(id))
.all(|g| g.reachable() == Reachable::Unreachable)
}
/// Mark every `Unreachable` guard as `Unknown`.
pub(crate) fn mark_all_guards_retriable(&mut self) {
for g in self.guards.values_mut() {
g.mark_retriable();
}
let old_guards = std::mem::take(&mut self.guards);
self.guards = old_guards
.into_values()
.map(|mut guard| {
guard.mark_retriable();
guard
})
.collect();
}
/// Record that an attempt has begun to use the guard with
/// `guard_id`.
pub(crate) fn record_attempt(&mut self, guard_id: &GuardId, now: Instant) {
let is_primary = self.guard_is_primary(guard_id);
if let Some(guard) = self.guards.get_mut(guard_id) {
self.guards.modify_by_all_ids(guard_id, |guard| {
guard.record_attempt(now);
if !is_primary {
guard.note_exploratory_circ(true);
}
}
});
}
/// Record that an attempt to use the guard with `guard_id` has just
@ -623,20 +743,18 @@ impl GuardSet {
now: SystemTime,
) {
self.assert_consistency();
if let Some(guard) = self.guards.get_mut(guard_id) {
match how {
Some(external) => guard.record_external_success(external),
None => {
let newly_confirmed = guard.record_success(now, params);
self.guards.modify_by_all_ids(guard_id, |guard| match how {
Some(external) => guard.record_external_success(external),
None => {
let newly_confirmed = guard.record_success(now, params);
if newly_confirmed == NewlyConfirmed::Yes {
self.confirmed.push(guard_id.clone());
self.primary_guards_invalidated = true;
}
if newly_confirmed == NewlyConfirmed::Yes {
self.confirmed.push(guard_id.clone());
self.primary_guards_invalidated = true;
}
}
self.assert_consistency();
}
});
self.assert_consistency();
}
/// Record that an attempt to use the guard with `guard_id` has just failed.
@ -649,37 +767,33 @@ impl GuardSet {
) {
// TODO use instant uniformly for in-process, and systemtime for storage?
let is_primary = self.guard_is_primary(guard_id);
if let Some(guard) = self.guards.get_mut(guard_id) {
match how {
Some(external) => guard.record_external_failure(external, now),
None => guard.record_failure(now, is_primary),
}
}
self.guards.modify_by_all_ids(guard_id, |guard| match how {
Some(external) => guard.record_external_failure(external, now),
None => guard.record_failure(now, is_primary),
});
}
/// Record that an attempt to use the guard with `guard_id` has
/// just been abandoned, without learning whether it succeeded or failed.
pub(crate) fn record_attempt_abandoned(&mut self, guard_id: &GuardId) {
if let Some(guard) = self.guards.get_mut(guard_id) {
guard.note_exploratory_circ(false);
}
self.guards
.modify_by_all_ids(guard_id, |guard| guard.note_exploratory_circ(false));
}
/// Record that an attempt to use the guard with `guard_id` has
/// just failed in a way that we could not definitively attribute to
/// the guard.
pub(crate) fn record_indeterminate_result(&mut self, guard_id: &GuardId) {
if let Some(guard) = self.guards.get_mut(guard_id) {
self.guards.modify_by_all_ids(guard_id, |guard| {
guard.note_exploratory_circ(false);
guard.record_indeterminate_result();
}
});
}
/// Record that a given guard has told us about clock skew.
pub(crate) fn record_skew(&mut self, guard_id: &GuardId, observation: SkewObservation) {
if let Some(guard) = self.guards.get_mut(guard_id) {
guard.note_skew(observation);
}
self.guards
.modify_by_all_ids(guard_id, |guard| guard.note_skew(observation));
}
/// Return an iterator over all stored clock skew observations.
@ -956,12 +1070,12 @@ mod test {
guards.assert_consistency();
// make sure all the guards are okay.
for (g, guard) in &guards.guards {
let id = FirstHopId::in_sample(GuardSetSelector::Default, g.clone());
for guard in guards.guards.values() {
let id = FirstHopId::in_sample(GuardSetSelector::Default, guard.guard_id().clone());
let relay = id.get_relay(&netdir).unwrap();
assert!(relay.is_flagged_guard());
assert!(relay.is_dir_cache());
assert!(guards.contains_relay(&relay));
assert!(guards.guards.by_all_ids(&relay).is_some());
{
assert!(!guard.is_expired(&params, SystemTime::now()));
}
@ -1007,11 +1121,19 @@ mod test {
assert_eq!(&guards2.confirmed, &guards.confirmed);
assert_eq!(&guards2.confirmed, &[id1]);
assert_eq!(
guards.guards.keys().collect::<HashSet<_>>(),
guards2.guards.keys().collect::<HashSet<_>>()
guards
.guards
.values()
.map(Guard::guard_id)
.collect::<HashSet<_>>(),
guards2
.guards
.values()
.map(Guard::guard_id)
.collect::<HashSet<_>>()
);
for (k, g) in &guards.guards {
let g2 = guards2.guards.get(k).unwrap();
for g in guards.guards.values() {
let g2 = guards2.guards.by_all_ids(g.guard_id()).unwrap();
assert_eq!(format!("{:?}", g), format!("{:?}", g2));
}
}
@ -1363,7 +1485,7 @@ mod test {
guards2.record_failure(&id2, None, Instant::now());
// Copy status: make sure non-persistent status changed, and persistent didn't.
guards1.copy_status_from(guards2);
guards1.copy_ephemeral_status_into_newly_loaded_state(guards2);
{
let g1 = guards1.get(&id1).unwrap();
let g2 = guards1.get(&id2).unwrap();
@ -1376,14 +1498,22 @@ mod test {
// Now make a new set of unrelated guards, and make sure that copying
// from it doesn't change the membership of guards1.
let mut guards3 = GuardSet::default();
let g1_set: HashSet<_> = guards1.guards.keys().map(Clone::clone).collect();
let g1_set: HashSet<_> = guards1
.guards
.values()
.map(|g| g.guard_id().clone())
.collect();
let mut g3_set: HashSet<_> = HashSet::new();
for _ in 0..4 {
// There is roughly a 1-in-5000 chance of getting the same set
// twice, so we loop until that doesn't happen.
guards3.extend_sample_as_needed(SystemTime::now(), &params, &netdir);
guards3.select_primary_guards(&params);
g3_set = guards3.guards.keys().map(Clone::clone).collect();
g3_set = guards3
.guards
.values()
.map(|g| g.guard_id().clone())
.collect();
// There is roughly a 1-in-5000 chance of getting the same set twice, so
if g1_set == g3_set {
@ -1394,8 +1524,12 @@ mod test {
}
assert_ne!(g1_set, g3_set);
// Do the copy; make sure that the membership is unchanged.
guards1.copy_status_from(guards3);
let g1_set_new: HashSet<_> = guards1.guards.keys().map(Clone::clone).collect();
guards1.copy_ephemeral_status_into_newly_loaded_state(guards3);
let g1_set_new: HashSet<_> = guards1
.guards
.values()
.map(|g| g.guard_id().clone())
.collect();
assert_eq!(g1_set, g1_set_new);
}
}

View File

@ -51,6 +51,20 @@ impl<H: HasRelayIds> ByRelayIds<H> {
}
}
/// Modify the value in this set (if any) that has the key `key`.
///
/// Return values are as for [`modify_by_ed25519`](Self::modify_by_ed25519)
pub fn modify_by_id<'a, T, F>(&mut self, key: T, func: F) -> Vec<H>
where
T: Into<RelayIdRef<'a>>,
F: FnOnce(&mut H),
{
match key.into() {
RelayIdRef::Ed25519(ed) => self.modify_by_ed25519(ed, func),
RelayIdRef::Rsa(rsa) => self.modify_by_rsa(rsa, func),
}
}
/// Return the value in this set (if any) that has _all_ the relay IDs
/// that `key` does.
///
@ -64,6 +78,26 @@ impl<H: HasRelayIds> ByRelayIds<H> {
.filter(|val| val.has_all_relay_ids_from(key))
}
/// Modify the value in this set (if any) that has _all_ the relay IDs
/// that `key` does.
///
/// Return values are as for [`modify_by_ed25519`](Self::modify_by_ed25519)
pub fn modify_by_all_ids<T, F>(&mut self, key: &T, func: F) -> Vec<H>
where
T: HasRelayIds,
F: FnOnce(&mut H),
{
let any_id = match key.identities().next() {
Some(id) => id,
None => return Vec::new(),
};
self.modify_by_id(any_id, |val| {
if val.has_all_relay_ids_from(key) {
func(val);
}
})
}
/// Remove the single value in this set (if any) that has _exactly the same_
/// relay IDs that `key` does
pub fn remove_exact<T>(&mut self, key: &T) -> Option<H>
@ -82,6 +116,23 @@ impl<H: HasRelayIds> ByRelayIds<H> {
}
}
/// Remove the single value in this set (if any) that has all the same relay ids that
/// relay IDs that `key` does.
pub fn remove_by_all_ids<T>(&mut self, key: &T) -> Option<H>
where
T: HasRelayIds,
{
let any_id = key.identities().next()?;
if self
.by_id(any_id)
.filter(|ent| ent.has_all_relay_ids_from(key))
.is_some()
{
self.remove_by_id(any_id)
} else {
None
}
}
/// Return a reference to every element in this set that shares _any_ ID
/// with `key`.
///

View File

@ -2,6 +2,7 @@
//! that Tor can connect to, directly or indirectly.
use std::{iter::FusedIterator, net::SocketAddr};
use strum::IntoEnumIterator;
use tor_llcrypto::pk;
use crate::{ChannelMethod, OwnedChanTarget, RelayIdRef, RelayIdType, RelayIdTypeIter};
@ -61,6 +62,11 @@ pub trait HasRelayIds {
self.identity(id.id_type()).map(|my_id| my_id == id) == Some(true)
}
/// Return true if this object has any known identity.
fn has_any_identity(&self) -> bool {
RelayIdType::iter().any(|id_type| self.identity(id_type).is_some())
}
/// Return true if this object has exactly the same relay IDs as `other`.
//
// TODO: Once we make it so particular identity key types are optional, we
@ -101,7 +107,6 @@ pub trait HasRelayIds {
/// If additional identities are added in the future, they may taken into
/// consideration before _or_ after the current identity types.
fn cmp_by_relay_ids<T: HasRelayIds + ?Sized>(&self, other: &T) -> std::cmp::Ordering {
use strum::IntoEnumIterator;
for key_type in RelayIdType::iter() {
let ordering = Ord::cmp(&self.identity(key_type), &other.identity(key_type));
if ordering.is_ne() {