guardmgr: Implement Universe for bridges.

Now we can use a group of bridges as the basis for a sample of
guards.
This commit is contained in:
Nick Mathewson 2022-10-28 11:55:59 -04:00
parent 79dade342a
commit 5867318a35
3 changed files with 161 additions and 2 deletions

View File

@ -5,16 +5,24 @@
use std::collections::HashMap;
use std::sync::Arc;
use std::time::SystemTime;
use crate::bridge::BridgeConfig;
use crate::{
bridge::BridgeConfig,
sample::{CandidateStatus, Universe, WeightThreshold},
};
use dyn_clone::DynClone;
use futures::stream::BoxStream;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use strum::{EnumCount, EnumIter};
use tor_error::{HasKind, HasRetryTime};
use tor_linkspec::{ChanTarget, HasChanMethod, HasRelayIds, OwnedChanTarget};
use tor_llcrypto::pk::{ed25519::Ed25519Identity, rsa::RsaIdentity};
use tor_netdir::RelayWeight;
use tor_netdoc::doc::routerdesc::RouterDesc;
use super::BridgeRelay;
/// A router descriptor that can be used to build circuits through a bridge.
///
/// These descriptors are fetched from the bridges themselves, and used in
@ -122,3 +130,149 @@ dyn_clone::clone_trait_object!(BridgeDescError);
/// A set of bridge descriptors, managed and modified by a BridgeDescProvider.
pub type BridgeDescList = HashMap<Arc<BridgeConfig>, Result<BridgeDesc, Box<dyn BridgeDescError>>>;
/// A collection of bridges, possibly with their descriptors.
//
// TODO pt-client: I doubt that this type is in its final form.
#[derive(Debug, Clone)]
pub(crate) struct BridgeSet {
/// When did this BridgeSet last change its listed bridges?
config_last_changed: SystemTime,
/// The configured bridges.
config: Vec<Arc<BridgeConfig>>,
/// A map from those bridges to their descriptors. It may contain elements
/// that are not in `config`.
descs: Arc<BridgeDescList>,
}
impl BridgeSet {
/// Create a new `BridgeSet` from its configuration.
#[allow(dead_code)] // TODO pt-client remove
pub(crate) fn new(config: Vec<Arc<BridgeConfig>>) -> Self {
Self {
config_last_changed: SystemTime::now(),
config,
descs: Arc::new(BridgeDescList::default()),
}
}
/// Returns the bridge that best matches a given guard.
///
/// Note that since the guard may have more identities than the bridge the
/// match may not be perfect: the caller needs to check for a closer match
/// if they want to be certain.
///
/// We check for a match by identity _and_ channel method, since channel
/// method is part of what makes two bridge lines different.
fn bridge_by_guard<T>(&self, guard: &T) -> Option<&Arc<BridgeConfig>>
where
T: ChanTarget,
{
self.config.iter().find(|bridge| {
guard.has_all_relay_ids_from(bridge.as_ref())
&& guard.chan_method() == bridge.chan_method()
})
}
/// Return a BridgeRelay wrapping the provided configuration, plus any known
/// descriptor for that configuration.
fn relay_by_bridge(&self, bridge: &Arc<BridgeConfig>) -> BridgeRelay {
let desc = match self.descs.get(bridge) {
Some(Ok(b)) => Some(b.clone()),
_ => None,
};
BridgeRelay::new(bridge.clone(), desc)
}
}
impl Universe for BridgeSet {
fn contains<T: tor_linkspec::ChanTarget>(&self, guard: &T) -> Option<bool> {
match self.bridge_by_guard(guard) {
Some(bridge) => {
let bridge_relay = self.relay_by_bridge(bridge);
if bridge_relay.has_all_relay_ids_from(guard) {
// We have all the IDs from the guard, either in the bridge
// line or in the descriptor, so the match is exact.
Some(true)
} else if bridge_relay.has_descriptor() {
// We don't have an exact match and we have have a
// descriptor, so we know that this is _not_ a real match.
Some(false)
} else {
// We don't have a descriptor; finding it might make our
// match precise.
None
}
}
// We found no bridge that matches this guard's
None => Some(false),
}
}
fn status<T: tor_linkspec::ChanTarget>(&self, guard: &T) -> CandidateStatus {
if let Some(bridge) = self.bridge_by_guard(guard) {
let bridge_relay = self.relay_by_bridge(bridge);
// Logic here is similar to that of "contains"
if bridge_relay.has_all_relay_ids_from(guard) {
CandidateStatus::Present {
listed_as_guard: true,
is_dir_cache: true, // all bridges are directory caches.
owned_target: OwnedChanTarget::from_chan_target(&bridge_relay),
}
} else if bridge_relay.has_descriptor() {
CandidateStatus::Absent
} else {
CandidateStatus::Uncertain
}
} else {
CandidateStatus::Absent
}
}
fn timestamp(&self) -> std::time::SystemTime {
self.config_last_changed
}
fn weight_threshold<T>(
&self,
_sample: &tor_linkspec::ByRelayIds<T>,
_params: &crate::GuardParams,
) -> WeightThreshold
where
T: HasRelayIds,
{
WeightThreshold {
current_weight: RelayWeight::from(0),
maximum_weight: RelayWeight::from(u64::MAX),
}
}
fn sample<T>(
&self,
pre_existing: &tor_linkspec::ByRelayIds<T>,
filter: &crate::GuardFilter,
n: usize,
) -> Vec<(tor_linkspec::OwnedChanTarget, tor_netdir::RelayWeight)>
where
T: HasRelayIds,
{
use rand::seq::IteratorRandom;
self.config
.iter()
.filter(|bridge_conf| {
filter.permits(bridge_conf.as_ref())
&& pre_existing
.all_overlapping(bridge_conf.as_ref())
.is_empty()
})
.choose_multiple(&mut rand::thread_rng(), n)
.into_iter()
.map(|bridge_config| {
(
OwnedChanTarget::from_chan_target(&self.relay_by_bridge(bridge_config)),
RelayWeight::from(0),
)
})
.collect()
}
}

View File

@ -38,6 +38,11 @@ pub struct BridgeRelayWithDesc<'a>(
);
impl BridgeRelay {
/// Construct a new BridgeRelay from its parts.
pub(crate) fn new(bridge_line: Arc<BridgeConfig>, desc: Option<BridgeDesc>) -> Self {
Self { bridge_line, desc }
}
/// Return true if this BridgeRelay has a known descriptor and can be used for relays.
pub fn has_descriptor(&self) -> bool {
self.desc.is_some()

View File

@ -268,7 +268,7 @@ impl Guard {
disabled: None,
confirmed_at: None,
unlisted_since: None,
dir_info_missing: false,
dir_info_missing: false, // TODO pt-client this can be wrong for bridges.
last_tried_to_connect_at: None,
reachable: Reachable::Unknown,
retry_at: None,