tor_guardmgr/bridge/descs.rs
1//! Code for working with bridge descriptors.
2//!
3//! Here we need to keep track of which bridge descriptors we need, and inform
4//! the directory manager of them.
5
6use std::collections::HashMap;
7use std::sync::Arc;
8
9use crate::{
10 bridge::BridgeConfig,
11 sample::{Candidate, CandidateStatus, Universe, WeightThreshold},
12};
13use dyn_clone::DynClone;
14use futures::stream::BoxStream;
15use num_enum::{IntoPrimitive, TryFromPrimitive};
16use strum::{EnumCount, EnumIter};
17use tor_error::{HasKind, HasRetryTime};
18use tor_linkspec::{ChanTarget, HasChanMethod, HasRelayIds, OwnedChanTarget};
19use tor_llcrypto::pk::{ed25519::Ed25519Identity, rsa::RsaIdentity};
20use tor_netdir::RelayWeight;
21use tor_netdoc::doc::routerdesc::RouterDesc;
22use web_time_compat::{SystemTime, SystemTimeExt};
23
24use super::BridgeRelay;
25
26/// A router descriptor that can be used to build circuits through a bridge.
27///
28/// These descriptors are fetched from the bridges themselves, and used in
29/// conjunction with configured bridge information and pluggable transports to
30/// contact bridges and build circuits through them.
31#[derive(Clone, Debug)]
32pub struct BridgeDesc {
33 /// The inner descriptor.
34 ///
35 /// NOTE: This is wrapped in an `Arc<>` because we expect to pass BridgeDesc
36 /// around a bit and clone it frequently. If that doesn't actually happen,
37 /// we can remove the Arc here.
38 desc: Arc<RouterDesc>,
39
40 /// The RsaIdentity of the [`RouterDesc`] found in [`BridgeDesc::desc`].
41 // It is unfortunate that we have to store this in an extra field, but the
42 // alternatives are not satisfying either:
43 // * Include the [`RsaIdentity`] inside [`RouterDesc`] so we can return
44 // a reference to it.
45 // * This is not nice because it would involve `netdoc(skip)`.
46 // * Change [`tor_linkspec`] to use the values directly rather than
47 // returning references to it.
48 // * This is the more clean solution, especially because both types
49 // implement [`Copy`] anyways.
50 // * However, [`tor_linkspec`] and its traits and types are deeply
51 // integrated into the codebase and changing them would mean lots of
52 // fixes everywhere.
53 rsa_identity: RsaIdentity,
54}
55
56impl AsRef<RouterDesc> for BridgeDesc {
57 fn as_ref(&self) -> &RouterDesc {
58 self.desc.as_ref()
59 }
60}
61
62impl BridgeDesc {
63 /// Construct a new BridgeDesc from `desc`.
64 ///
65 /// The provided `desc` must be a descriptor retrieved from the bridge
66 /// itself.
67 pub fn new(desc: Arc<RouterDesc>) -> Self {
68 let rsa_identity = desc.rsa_identity();
69 Self { desc, rsa_identity }
70 }
71}
72
73impl tor_linkspec::HasRelayIdsLegacy for BridgeDesc {
74 fn ed_identity(&self) -> &Ed25519Identity {
75 self.desc.ed_identity()
76 }
77
78 fn rsa_identity(&self) -> &RsaIdentity {
79 &self.rsa_identity
80 }
81}
82
83/// Trait for an object that knows how to fetch bridge descriptors as needed.
84///
85/// A "bridge descriptor" (represented by [`BridgeDesc`]) is a self-signed
86/// representation of a bridge's keys, capabilities, and other information. We
87/// can connect to a bridge without a descriptor, but we need to have one before
88/// we can build a multi-hop circuit through a bridge.
89///
90/// In arti, the implementor of this trait is `BridgeDescMgr`. We define this
91/// trait here so that we can avoid a circularity in our crate dependencies.
92/// (Since `BridgeDescMgr` uses circuits, it needs `CircMgr`, which needs
93/// `GuardMgr`, which in turn needs `BridgeDescMgr` again. We break this
94/// circularity by having `GuardMgr` use `BridgeDescMgr` only through this
95/// trait's API.)
96pub trait BridgeDescProvider: DynClone + Send + Sync {
97 /// Return the current set of bridge descriptors.
98 fn bridges(&self) -> Arc<BridgeDescList>;
99
100 /// Return a stream that gets a notification when the set of bridge
101 /// descriptors has changed.
102 fn events(&self) -> BoxStream<'static, BridgeDescEvent>;
103
104 /// Change the set of bridges that we want to download descriptors for.
105 ///
106 /// Bridges outside of this set will not have their descriptors updated,
107 /// and will not be revealed in the BridgeDescList.
108 fn set_bridges(&self, bridges: &[BridgeConfig]);
109}
110
111dyn_clone::clone_trait_object!(BridgeDescProvider);
112
113/// An event describing a change in a `BridgeDescList`.
114///
115/// Currently changes are always reported as `BridgeDescEvent::SomethingChanged`.
116///
117/// In the future, as an optimization, more fine-grained information may be provided.
118/// Unrecognized variants should be handled the same way as `SomethingChanged`.
119/// (So right now, it is not necessary to match on the variant at all.)
120#[derive(
121 Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCount, IntoPrimitive, TryFromPrimitive,
122)]
123#[non_exhaustive]
124#[repr(u16)]
125pub enum BridgeDescEvent {
126 /// Some change occurred to the set of descriptors
127 ///
128 /// The return value from [`bridges()`](BridgeDescProvider::bridges)
129 /// may have changed.
130 ///
131 /// The nature of the change is not specified; it might affect multiple descriptors,
132 /// and include multiple different kinds of change.
133 ///
134 /// This event may also be generated spuriously, if nothing has changed,
135 /// but this will usually be avoided for performance reasons.
136 SomethingChanged,
137}
138
139/// An error caused while fetching bridge descriptors
140///
141/// Note that when this appears in `BridgeDescList`, as returned by `BridgeDescMgr`,
142/// the fact that this is `HasRetryTime` does *not* mean the caller should retry.
143/// Retries will be handled by the `BridgeDescMgr`.
144/// The `HasRetryTime` impl can be used as a guide to
145/// whether the situation is likely to improve soon.
146///
147/// Does *not* include the information about which bridge we were trying to
148/// get a descriptor for.
149pub trait BridgeDescError:
150 std::error::Error + DynClone + HasKind + HasRetryTime + Send + Sync + 'static
151{
152}
153
154dyn_clone::clone_trait_object!(BridgeDescError);
155
156/// A set of bridge descriptors, managed and modified by a BridgeDescProvider.
157pub type BridgeDescList = HashMap<BridgeConfig, Result<BridgeDesc, Box<dyn BridgeDescError>>>;
158
159/// A collection of bridges, possibly with their descriptors.
160#[derive(Debug, Clone)]
161pub(crate) struct BridgeSet {
162 /// The configured bridges.
163 config: Arc<[BridgeConfig]>,
164 /// A map from those bridges to their descriptors. It may contain elements
165 /// that are not in `config`.
166 descs: Option<Arc<BridgeDescList>>,
167}
168
169impl BridgeSet {
170 /// Create a new `BridgeSet` from its configuration.
171 pub(crate) fn new(config: Arc<[BridgeConfig]>, descs: Option<Arc<BridgeDescList>>) -> Self {
172 Self { config, descs }
173 }
174
175 /// Returns the bridge that best matches a given guard.
176 ///
177 /// Note that since the guard may have more identities than the bridge the
178 /// match may not be perfect: the caller needs to check for a closer match
179 /// if they want to be certain.
180 ///
181 /// We check for a match by identity _and_ channel method, since channel
182 /// method is part of what makes two bridge lines different.
183 pub(crate) fn bridge_by_guard<T>(&self, guard: &T) -> Option<&BridgeConfig>
184 where
185 T: ChanTarget,
186 {
187 self.config.iter().find(|bridge| {
188 guard.has_all_relay_ids_from(*bridge)
189 // The Guard could have more addresses than the BridgeConfig if
190 // we happen to know its descriptor, it is using a direct
191 // connection, and it has listed more addresses there.
192 && bridge.chan_method().contained_by(&guard.chan_method())
193 })
194 }
195
196 /// Return a BridgeRelay wrapping the provided configuration, plus any known
197 /// descriptor for that configuration.
198 fn relay_by_bridge<'a>(&'a self, bridge: &'a BridgeConfig) -> BridgeRelay<'a> {
199 let desc = match self.descs.as_ref().and_then(|d| d.get(bridge)) {
200 Some(Ok(b)) => Some(b.clone()),
201 _ => None,
202 };
203 BridgeRelay::new(bridge, desc)
204 }
205
206 /// Look up a BridgeRelay corresponding to a given guard.
207 pub(crate) fn bridge_relay_by_guard<T: tor_linkspec::ChanTarget>(
208 &self,
209 guard: &T,
210 ) -> CandidateStatus<BridgeRelay> {
211 match self.bridge_by_guard(guard) {
212 Some(bridge) => {
213 let bridge_relay = self.relay_by_bridge(bridge);
214 if bridge_relay.has_all_relay_ids_from(guard) {
215 // We have all the IDs from the guard, either in the bridge
216 // line or in the descriptor, so the match is exact.
217 CandidateStatus::Present(bridge_relay)
218 } else if bridge_relay.has_descriptor() {
219 // We don't have an exact match and we have have a
220 // descriptor, so we know that this is _not_ a real match.
221 CandidateStatus::Absent
222 } else {
223 // We don't have a descriptor; finding it might make our
224 // match precise.
225 CandidateStatus::Uncertain
226 }
227 }
228 // We found no bridge that matches this guard's identities, so we
229 // can declare it absent.
230 None => CandidateStatus::Absent,
231 }
232 }
233}
234
235impl Universe for BridgeSet {
236 fn contains<T: tor_linkspec::ChanTarget>(&self, guard: &T) -> Option<bool> {
237 match self.bridge_relay_by_guard(guard) {
238 CandidateStatus::Present(_) => Some(true),
239 CandidateStatus::Absent => Some(false),
240 CandidateStatus::Uncertain => None,
241 }
242 }
243
244 fn status<T: tor_linkspec::ChanTarget>(&self, guard: &T) -> CandidateStatus<Candidate> {
245 match self.bridge_relay_by_guard(guard) {
246 CandidateStatus::Present(bridge_relay) => CandidateStatus::Present(Candidate {
247 listed_as_guard: true,
248 is_dir_cache: true, // all bridges are directory caches.
249 full_dir_info: bridge_relay.has_descriptor(),
250 owned_target: OwnedChanTarget::from_chan_target(&bridge_relay),
251 sensitivity: crate::guard::DisplayRule::Redacted,
252 }),
253 CandidateStatus::Absent => CandidateStatus::Absent,
254 CandidateStatus::Uncertain => CandidateStatus::Uncertain,
255 }
256 }
257
258 fn timestamp(&self) -> SystemTime {
259 // We just use the current time as the timestamp of this BridgeSet.
260 // This makes the guard code treat a BridgeSet as _continuously updated_:
261 // anything listed in the guard set is treated as listed right up to this
262 // moment, and anything unlisted is treated as unlisted right up to this
263 // moment.
264 SystemTime::get()
265 }
266
267 /// Note that for a BridgeSet, we always treat the current weight as 0 and
268 /// the maximum weight as "unlimited". That's because we don't have
269 /// bandwidth measurements for bridges, and so `max_sample_bw_fraction`
270 /// doesn't apply to them.
271 fn weight_threshold<T>(
272 &self,
273 _sample: &tor_linkspec::ByRelayIds<T>,
274 _params: &crate::GuardParams,
275 ) -> WeightThreshold
276 where
277 T: HasRelayIds,
278 {
279 WeightThreshold {
280 current_weight: RelayWeight::from(0),
281 maximum_weight: RelayWeight::from(u64::MAX),
282 }
283 }
284
285 fn sample<T>(
286 &self,
287 pre_existing: &tor_linkspec::ByRelayIds<T>,
288 filter: &crate::GuardFilter,
289 n: usize,
290 ) -> Vec<(Candidate, tor_netdir::RelayWeight)>
291 where
292 T: HasRelayIds,
293 {
294 use rand::seq::IteratorRandom;
295 self.config
296 .iter()
297 .filter(|bridge_conf| {
298 filter.permits(*bridge_conf)
299 && pre_existing.all_overlapping(*bridge_conf).is_empty()
300 })
301 .sample(&mut rand::rng(), n)
302 .into_iter()
303 .map(|bridge_config| {
304 let relay = self.relay_by_bridge(bridge_config);
305 (
306 Candidate {
307 listed_as_guard: true,
308 is_dir_cache: true,
309 full_dir_info: relay.has_descriptor(),
310 owned_target: OwnedChanTarget::from_chan_target(&relay),
311 sensitivity: crate::guard::DisplayRule::Redacted,
312 },
313 RelayWeight::from(0),
314 )
315 })
316 .collect()
317 }
318}