diff --git a/crates/tor-guardmgr/src/guard.rs b/crates/tor-guardmgr/src/guard.rs index 9f860d084..82ce4bf3f 100644 --- a/crates/tor-guardmgr/src/guard.rs +++ b/crates/tor-guardmgr/src/guard.rs @@ -449,6 +449,14 @@ impl Guard { pub(crate) fn get_weight(&self, dir: &NetDir) -> Option { dir.weight_by_rsa_id(&self.id.rsa, tor_netdir::WeightRole::Guard) } + + /// Return a [`crate::Guard`] object to represent this guard. + pub(crate) fn get_external_rep(&self) -> crate::Guard { + crate::Guard { + id: self.id.clone(), + orports: self.orports.clone(), + } + } } impl tor_linkspec::ChanTarget for Guard { diff --git a/crates/tor-guardmgr/src/lib.rs b/crates/tor-guardmgr/src/lib.rs index 9dc2762cd..ef01fc321 100644 --- a/crates/tor-guardmgr/src/lib.rs +++ b/crates/tor-guardmgr/src/lib.rs @@ -133,6 +133,7 @@ use futures::task::{SpawnError, SpawnExt}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::convert::{TryFrom, TryInto}; +use std::net::SocketAddr; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant, SystemTime}; use tracing::{debug, info, trace, warn}; @@ -388,7 +389,7 @@ impl GuardMgr { &self, usage: GuardUsage, netdir: Option<&NetDir>, - ) -> Result<(GuardId, GuardMonitor, GuardUsable), PickGuardError> { + ) -> Result<(Guard, GuardMonitor, GuardUsable), PickGuardError> { let now = self.runtime.now(); let wallclock = self.runtime.wallclock(); @@ -399,6 +400,11 @@ impl GuardMgr { inner.active_guards.consider_all_retries(now); let (origin, guard_id) = inner.select_guard_with_retries(&usage, netdir, wallclock)?; + let guard = inner + .active_guards + .get(&guard_id) + .expect("Selected guard that wasn't in our sample!?") + .get_external_rep(); trace!(?guard_id, ?usage, "Guard selected"); @@ -422,7 +428,7 @@ impl GuardMgr { .unbounded_send(Ok(daemon::Msg::Observe(rcv))) .expect("Guard observer task exited prematurely"); - Ok((guard_id, monitor, usable)) + Ok((guard, monitor, usable)) } /// Ensure that the message queue is flushed before proceding to @@ -774,6 +780,39 @@ impl GuardId { } } +/// Representation of a guard, as returned by [`GuardMgr::select_guard()`]. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Guard { + /// The guard's identities + id: GuardId, + /// The addresses at which the guard can be contacted. + orports: Vec, +} + +impl Guard { + /// Return the identities of this guard. + pub fn id(&self) -> &GuardId { + &self.id + } + /// Look up this guard in `netdir`. + pub fn get_relay<'a>(&self, netdir: &'a NetDir) -> Option> { + self.id().get_relay(netdir) + } +} + +// This is somewhat redundant with the implementation in crate::guard::Guard. +impl tor_linkspec::ChanTarget for Guard { + fn addrs(&self) -> &[SocketAddr] { + &self.orports[..] + } + fn ed_identity(&self) -> &pk::ed25519::Ed25519Identity { + &self.id.ed25519 + } + fn rsa_identity(&self) -> &pk::rsa::RsaIdentity { + &self.id.rsa + } +} + /// The purpose for which we plan to use a guard. /// /// This can affect the guard selection algorithm. @@ -968,9 +1007,9 @@ mod test { guardmgr.update_network(&netdir); guardmgr.set_filter(GuardFilter::TestingLimitKeys, &netdir); - let (id1, _mon, _usable) = guardmgr.select_guard(u, Some(&netdir)).unwrap(); + let (guard, _mon, _usable) = guardmgr.select_guard(u, Some(&netdir)).unwrap(); // Make sure that the filter worked. - assert_eq!(id1.rsa.as_bytes()[0] % 4, 0); + assert_eq!(guard.id().rsa.as_bytes()[0] % 4, 0); }) } } diff --git a/crates/tor-guardmgr/src/sample.rs b/crates/tor-guardmgr/src/sample.rs index 0f3d341ee..54f1762ca 100644 --- a/crates/tor-guardmgr/src/sample.rs +++ b/crates/tor-guardmgr/src/sample.rs @@ -139,6 +139,11 @@ impl GuardSet { assert_eq!(len_pre, len_post); } + /// Return the guard whose id is `id`, if any. + pub(crate) fn get(&self, id: &GuardId) -> Option<&Guard> { + self.guards.get(id) + } + /// Replace the filter used by this `GuardSet` with `filter`. /// /// Removes all primary guards that the filter doesn't permit.