tor_netdir/lib.rs
1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3// @@ begin lint list maintained by maint/add_warning @@
4#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
5#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
6#![warn(missing_docs)]
7#![warn(noop_method_call)]
8#![warn(unreachable_pub)]
9#![warn(clippy::all)]
10#![deny(clippy::await_holding_lock)]
11#![deny(clippy::cargo_common_metadata)]
12#![deny(clippy::cast_lossless)]
13#![deny(clippy::checked_conversions)]
14#![warn(clippy::cognitive_complexity)]
15#![deny(clippy::debug_assert_with_mut_call)]
16#![deny(clippy::exhaustive_enums)]
17#![deny(clippy::exhaustive_structs)]
18#![deny(clippy::expl_impl_clone_on_copy)]
19#![deny(clippy::fallible_impl_from)]
20#![deny(clippy::implicit_clone)]
21#![deny(clippy::large_stack_arrays)]
22#![warn(clippy::manual_ok_or)]
23#![deny(clippy::missing_docs_in_private_items)]
24#![warn(clippy::needless_borrow)]
25#![warn(clippy::needless_pass_by_value)]
26#![warn(clippy::option_option)]
27#![deny(clippy::print_stderr)]
28#![deny(clippy::print_stdout)]
29#![warn(clippy::rc_buffer)]
30#![deny(clippy::ref_option_ref)]
31#![warn(clippy::semicolon_if_nothing_returned)]
32#![warn(clippy::trait_duplication_in_bounds)]
33#![deny(clippy::unchecked_time_subtraction)]
34#![deny(clippy::unnecessary_wraps)]
35#![warn(clippy::unseparated_literal_suffix)]
36#![deny(clippy::unwrap_used)]
37#![deny(clippy::mod_module_files)]
38#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
39#![allow(clippy::uninlined_format_args)]
40#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
41#![allow(clippy::result_large_err)] // temporary workaround for arti#587
42#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
43#![allow(clippy::needless_lifetimes)] // See arti#1765
44#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
45#![allow(clippy::collapsible_if)] // See arti#2342
46#![deny(clippy::unused_async)]
47#![deny(clippy::string_slice)] // See arti#2571
48//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
49
50pub mod details;
51mod err;
52#[cfg(feature = "hs-common")]
53mod hsdir_params;
54#[cfg(feature = "hs-common")]
55mod hsdir_ring;
56pub mod params;
57mod weight;
58
59#[cfg(any(test, feature = "testing"))]
60pub mod testnet;
61#[cfg(feature = "testing")]
62pub mod testprovider;
63
64use async_trait::async_trait;
65#[cfg(feature = "hs-service")]
66use itertools::chain;
67use tor_error::warn_report;
68#[cfg(feature = "hs-common")]
69use tor_linkspec::OwnedCircTarget;
70use tor_linkspec::{
71 ChanTarget, DirectChanMethodsHelper, HasAddrs, HasRelayIds, RelayIdRef, RelayIdType,
72};
73use tor_llcrypto as ll;
74use tor_llcrypto::pk::{ed25519::Ed25519Identity, rsa::RsaIdentity};
75use tor_netdoc::doc::microdesc::{MdDigest, MicrodescAndHash};
76use tor_netdoc::doc::netstatus::{self, MdConsensus, MdRouterStatus};
77#[cfg(feature = "hs-common")]
78use {hsdir_ring::HsDirRing, std::iter};
79
80use derive_more::{From, Into};
81use futures::{StreamExt, stream::BoxStream};
82use num_enum::{IntoPrimitive, TryFromPrimitive};
83use rand::seq::{IndexedRandom as _, SliceRandom as _, WeightError};
84use serde::Deserialize;
85use std::collections::HashMap;
86use std::net::IpAddr;
87use std::ops::Deref;
88use std::sync::Arc;
89use std::time::SystemTime;
90use strum::{EnumCount, EnumIter};
91use tracing::warn;
92use typed_index_collections::{TiSlice, TiVec};
93
94#[cfg(feature = "hs-common")]
95use {
96 itertools::Itertools,
97 std::collections::HashSet,
98 std::result::Result as StdResult,
99 tor_error::{Bug, internal},
100 tor_hscrypto::{pk::HsBlindId, time::TimePeriod},
101 tor_linkspec::{OwnedChanTargetBuilder, verbatim::VerbatimLinkSpecCircTarget},
102 tor_llcrypto::pk::curve25519,
103};
104
105pub use err::Error;
106pub use weight::WeightRole;
107/// A Result using the Error type from the tor-netdir crate
108pub type Result<T> = std::result::Result<T, Error>;
109
110#[cfg(feature = "hs-common")]
111pub use err::{OnionDirLookupError, VerbatimCircTargetDecodeError};
112
113use params::NetParameters;
114#[cfg(feature = "geoip")]
115use tor_geoip::{CountryCode, GeoipDb, HasCountryCode};
116
117#[cfg(feature = "hs-common")]
118pub use hsdir_params::HsDirParams;
119
120/// Index into the consensus relays
121///
122/// This is an index into the list of relays returned by
123/// [`.c_relays()`](ConsensusRelays::c_relays)
124/// (on the corresponding consensus or netdir).
125///
126/// This is just a `usize` inside, but using a newtype prevents getting a relay index
127/// confused with other kinds of slice indices or counts.
128///
129/// If you are in a part of the code which needs to work with multiple consensuses,
130/// the typechecking cannot tell if you try to index into the wrong consensus.
131#[derive(Debug, From, Into, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
132pub(crate) struct RouterStatusIdx(usize);
133
134/// Extension trait to provide index-type-safe `.c_relays()` method
135//
136// TODO: Really it would be better to have MdConsensns::relays() return TiSlice,
137// but that would be an API break there.
138pub(crate) trait ConsensusRelays {
139 /// Obtain the list of relays in the consensus
140 //
141 fn c_relays(&self) -> &TiSlice<RouterStatusIdx, MdRouterStatus>;
142}
143impl ConsensusRelays for MdConsensus {
144 fn c_relays(&self) -> &TiSlice<RouterStatusIdx, MdRouterStatus> {
145 TiSlice::from_ref(MdConsensus::relays(self))
146 }
147}
148impl ConsensusRelays for NetDir {
149 fn c_relays(&self) -> &TiSlice<RouterStatusIdx, MdRouterStatus> {
150 self.consensus.c_relays()
151 }
152}
153
154/// Configuration for determining when two relays have addresses "too close" in
155/// the network.
156///
157/// Used by `Relay::low_level_details().in_same_subnet()`.
158#[derive(Deserialize, Debug, Clone, Copy, Eq, PartialEq)]
159#[serde(deny_unknown_fields)]
160pub struct SubnetConfig {
161 /// Consider IPv4 nodes in the same /x to be the same family.
162 ///
163 /// If this value is 0, all nodes with IPv4 addresses will be in the
164 /// same family. If this value is above 32, then no nodes will be
165 /// placed im the same family based on their IPv4 addresses.
166 subnets_family_v4: u8,
167 /// Consider IPv6 nodes in the same /x to be the same family.
168 ///
169 /// If this value is 0, all nodes with IPv6 addresses will be in the
170 /// same family. If this value is above 128, then no nodes will be
171 /// placed im the same family based on their IPv6 addresses.
172 subnets_family_v6: u8,
173}
174
175impl Default for SubnetConfig {
176 fn default() -> Self {
177 Self::new(16, 32)
178 }
179}
180
181impl SubnetConfig {
182 /// Construct a new SubnetConfig from a pair of bit prefix lengths.
183 ///
184 /// The values are clamped to the appropriate ranges if they are
185 /// out-of-bounds.
186 pub fn new(subnets_family_v4: u8, subnets_family_v6: u8) -> Self {
187 Self {
188 subnets_family_v4,
189 subnets_family_v6,
190 }
191 }
192
193 /// Construct a new SubnetConfig such that addresses are not in the same
194 /// family with anything--not even with themselves.
195 pub fn no_addresses_match() -> SubnetConfig {
196 SubnetConfig {
197 subnets_family_v4: 33,
198 subnets_family_v6: 129,
199 }
200 }
201
202 /// Return true if the two addresses in the same subnet, according to this
203 /// configuration.
204 pub fn addrs_in_same_subnet(&self, a: &IpAddr, b: &IpAddr) -> bool {
205 match (a, b) {
206 (IpAddr::V4(a), IpAddr::V4(b)) => {
207 let bits = self.subnets_family_v4;
208 if bits > 32 {
209 return false;
210 }
211 let a = u32::from_be_bytes(a.octets());
212 let b = u32::from_be_bytes(b.octets());
213 (a >> (32 - bits)) == (b >> (32 - bits))
214 }
215 (IpAddr::V6(a), IpAddr::V6(b)) => {
216 let bits = self.subnets_family_v6;
217 if bits > 128 {
218 return false;
219 }
220 let a = u128::from_be_bytes(a.octets());
221 let b = u128::from_be_bytes(b.octets());
222 (a >> (128 - bits)) == (b >> (128 - bits))
223 }
224 _ => false,
225 }
226 }
227
228 /// Return true if any of the addresses in `a` shares a subnet with any of
229 /// the addresses in `b`, according to this configuration.
230 pub fn any_addrs_in_same_subnet<T, U>(&self, a: &T, b: &U) -> bool
231 where
232 T: tor_linkspec::HasAddrs,
233 U: tor_linkspec::HasAddrs,
234 {
235 a.addrs().any(|aa| {
236 b.addrs()
237 .any(|bb| self.addrs_in_same_subnet(&aa.ip(), &bb.ip()))
238 })
239 }
240
241 /// Return a new subnet configuration that is the union of `self` and
242 /// `other`.
243 ///
244 /// That is, return a subnet configuration that puts all addresses in the
245 /// same subnet if and only if at least one of `self` and `other` would put
246 /// them in the same subnet.
247 pub fn union(&self, other: &Self) -> Self {
248 use std::cmp::min;
249 Self {
250 subnets_family_v4: min(self.subnets_family_v4, other.subnets_family_v4),
251 subnets_family_v6: min(self.subnets_family_v6, other.subnets_family_v6),
252 }
253 }
254}
255
256/// Configuration for which listed family information to use when deciding
257/// whether relays belong to the same family.
258///
259/// Derived from network parameters.
260#[derive(Clone, Copy, Debug)]
261pub struct FamilyRules {
262 /// If true, we use family information from lists of family members.
263 use_family_lists: bool,
264 /// If true, we use family information from lists of family IDs and from family certs.
265 use_family_ids: bool,
266}
267
268impl<'a> From<&'a NetParameters> for FamilyRules {
269 fn from(params: &'a NetParameters) -> Self {
270 FamilyRules {
271 use_family_lists: bool::from(params.use_family_lists),
272 use_family_ids: bool::from(params.use_family_ids),
273 }
274 }
275}
276
277impl FamilyRules {
278 /// Return a `FamilyRules` that will use all recognized kinds of family information.
279 pub fn all_family_info() -> Self {
280 Self {
281 use_family_lists: true,
282 use_family_ids: true,
283 }
284 }
285
286 /// Return a `FamilyRules` that will ignore all family information declared by relays.
287 pub fn ignore_declared_families() -> Self {
288 Self {
289 use_family_lists: false,
290 use_family_ids: false,
291 }
292 }
293
294 /// Configure this `FamilyRules` to use (or not use) family information from
295 /// lists of family members.
296 pub fn use_family_lists(&mut self, val: bool) -> &mut Self {
297 self.use_family_lists = val;
298 self
299 }
300
301 /// Configure this `FamilyRules` to use (or not use) family information from
302 /// family IDs and family certs.
303 pub fn use_family_ids(&mut self, val: bool) -> &mut Self {
304 self.use_family_ids = val;
305 self
306 }
307
308 /// Return a `FamilyRules` that will look at every source of information
309 /// requested by `self` or by `other`.
310 pub fn union(&self, other: &Self) -> Self {
311 Self {
312 use_family_lists: self.use_family_lists || other.use_family_lists,
313 use_family_ids: self.use_family_ids || other.use_family_ids,
314 }
315 }
316}
317
318/// An opaque type representing the weight with which a relay or set of
319/// relays will be selected for a given role.
320///
321/// Most users should ignore this type, and just use pick_relay instead.
322#[derive(
323 Copy,
324 Clone,
325 Debug,
326 derive_more::Add,
327 derive_more::Sum,
328 derive_more::AddAssign,
329 Eq,
330 PartialEq,
331 Ord,
332 PartialOrd,
333)]
334pub struct RelayWeight(u64);
335
336impl RelayWeight {
337 /// Try to divide this weight by `rhs`.
338 ///
339 /// Return a ratio on success, or None on division-by-zero.
340 pub fn checked_div(&self, rhs: RelayWeight) -> Option<f64> {
341 if rhs.0 == 0 {
342 None
343 } else {
344 Some((self.0 as f64) / (rhs.0 as f64))
345 }
346 }
347
348 /// Compute a ratio `frac` of this weight.
349 ///
350 /// Return None if frac is less than zero, since negative weights
351 /// are impossible.
352 pub fn ratio(&self, frac: f64) -> Option<RelayWeight> {
353 let product = (self.0 as f64) * frac;
354 if product >= 0.0 && product.is_finite() {
355 Some(RelayWeight(product as u64))
356 } else {
357 None
358 }
359 }
360}
361
362impl From<u64> for RelayWeight {
363 fn from(val: u64) -> Self {
364 RelayWeight(val)
365 }
366}
367
368/// An operation for which we might be requesting a hidden service directory.
369#[derive(Copy, Clone, Debug, PartialEq)]
370// TODO: make this pub(crate) once NetDir::hs_dirs is removed
371#[non_exhaustive]
372pub enum HsDirOp {
373 /// Uploading an onion service descriptor.
374 #[cfg(feature = "hs-service")]
375 Upload,
376 /// Downloading an onion service descriptor.
377 Download,
378}
379
380/// A view of the Tor directory, suitable for use in building circuits.
381///
382/// Abstractly, a [`NetDir`] is a set of usable public [`Relay`]s, each of which
383/// has its own properties, identity, and correct weighted probability for use
384/// under different circumstances.
385///
386/// A [`NetDir`] is constructed by making a [`PartialNetDir`] from a consensus
387/// document, and then adding enough microdescriptors to that `PartialNetDir` so
388/// that it can be used to build paths. (Thus, if you have a NetDir, it is
389/// definitely adequate to build paths.)
390///
391/// # "Usable" relays
392///
393/// Many methods on NetDir are defined in terms of <a name="usable">"Usable"</a> relays. Unless
394/// otherwise stated, a relay is "usable" if it is listed in the consensus,
395/// if we have full directory information for that relay (including a
396/// microdescriptor), and if that relay does not have any flags indicating that
397/// we should never use it. (Currently, `NoEdConsensus` is the only such flag.)
398///
399/// # Limitations
400///
401/// The current NetDir implementation assumes fairly strongly that every relay
402/// has an Ed25519 identity and an RSA identity, that the consensus is indexed
403/// by RSA identities, and that the Ed25519 identities are stored in
404/// microdescriptors.
405///
406/// If these assumptions someday change, then we'll have to revise the
407/// implementation.
408#[derive(Debug, Clone)]
409pub struct NetDir {
410 /// A microdescriptor consensus that lists the members of the network,
411 /// and maps each one to a 'microdescriptor' that has more information
412 /// about it
413 consensus: Arc<MdConsensus>,
414 /// A map from keys to integer values, distributed in the consensus,
415 /// and clamped to certain defaults.
416 params: NetParameters,
417 /// Map from routerstatus index, to that routerstatus's microdescriptor (if we have one.)
418 mds: TiVec<RouterStatusIdx, Option<Arc<MicrodescAndHash>>>,
419 /// Map from SHA256 of _missing_ microdescriptors to the index of their
420 /// corresponding routerstatus.
421 rsidx_by_missing: HashMap<MdDigest, RouterStatusIdx>,
422 /// Map from ed25519 identity to index of the routerstatus.
423 ///
424 /// Note that we don't know the ed25519 identity of a relay until
425 /// we get the microdescriptor for it, so this won't be filled in
426 /// until we get the microdescriptors.
427 ///
428 /// # Implementation note
429 ///
430 /// For this field, and for `rsidx_by_rsa`,
431 /// it might be cool to have references instead.
432 /// But that would make this into a self-referential structure,
433 /// which isn't possible in safe rust.
434 rsidx_by_ed: HashMap<Ed25519Identity, RouterStatusIdx>,
435 /// Map from RSA identity to index of the routerstatus.
436 ///
437 /// This is constructed at the same time as the NetDir object, so it
438 /// can be immutable.
439 rsidx_by_rsa: Arc<HashMap<RsaIdentity, RouterStatusIdx>>,
440
441 /// Hash ring(s) describing the onion service directory.
442 ///
443 /// This is empty in a PartialNetDir, and is filled in before the NetDir is
444 /// built.
445 //
446 // TODO hs: It is ugly to have this exist in a partially constructed state
447 // in a PartialNetDir.
448 // Ideally, a PartialNetDir would contain only an HsDirs<HsDirParams>,
449 // or perhaps nothing at all, here.
450 #[cfg(feature = "hs-common")]
451 hsdir_rings: Arc<HsDirs<HsDirRing>>,
452
453 /// Weight values to apply to a given relay when deciding how frequently
454 /// to choose it for a given role.
455 weights: weight::WeightSet,
456
457 #[cfg(feature = "geoip")]
458 /// Country codes for each router in our consensus.
459 ///
460 /// This is indexed by the `RouterStatusIdx` (i.e. a router idx of zero has
461 /// the country code at position zero in this array).
462 country_codes: Vec<Option<CountryCode>>,
463}
464
465/// Collection of hidden service directories (or parameters for them)
466///
467/// In [`NetDir`] this is used to store the actual hash rings.
468/// (But, in a NetDir in a [`PartialNetDir`], it contains [`HsDirRing`]s
469/// where only the `params` are populated, and the `ring` is empty.)
470///
471/// This same generic type is used as the return type from
472/// [`HsDirParams::compute`](HsDirParams::compute),
473/// where it contains the *parameters* for the primary and secondary rings.
474#[derive(Debug, Clone)]
475#[cfg(feature = "hs-common")]
476pub(crate) struct HsDirs<D> {
477 /// The current ring
478 ///
479 /// It corresponds to the time period containing the `valid-after` time in
480 /// the consensus. Its SRV is whatever SRV was most current at the time when
481 /// that time period began.
482 ///
483 /// This is the hash ring that we should use whenever we are fetching an
484 /// onion service descriptor.
485 current: D,
486
487 /// Secondary rings (based on the parameters for the previous and next time periods)
488 ///
489 /// Onion services upload to positions on these ring as well, based on how
490 /// far into the current time period this directory is, so that
491 /// not-synchronized clients can still find their descriptor.
492 ///
493 /// Note that with the current (2023) network parameters, with
494 /// `hsdir_interval = SRV lifetime = 24 hours` at most one of these
495 /// secondary rings will be active at a time. We have two here in order
496 /// to conform with a more flexible regime in proposal 342.
497 //
498 // TODO: hs clients never need this; so I've made it not-present for them.
499 // But does that risk too much with respect to side channels?
500 //
501 // TODO: Perhaps we should refactor this so that it is clear that these
502 // are immutable? On the other hand, the documentation for this type
503 // declares that it is immutable, so we are likely okay.
504 //
505 // TODO: this `Vec` is only ever 0,1,2 elements.
506 // Maybe it should be an ArrayVec or something.
507 #[cfg(feature = "hs-service")]
508 secondary: Vec<D>,
509}
510
511#[cfg(feature = "hs-common")]
512impl<D> HsDirs<D> {
513 /// Convert an `HsDirs<D>` to `HsDirs<D2>` by mapping each contained `D`
514 pub(crate) fn map<D2>(self, mut f: impl FnMut(D) -> D2) -> HsDirs<D2> {
515 HsDirs {
516 current: f(self.current),
517 #[cfg(feature = "hs-service")]
518 secondary: self.secondary.into_iter().map(f).collect(),
519 }
520 }
521
522 /// Iterate over some of the contained hsdirs, according to `secondary`
523 ///
524 /// The current ring is always included.
525 /// Secondary rings are included iff `secondary` and the `hs-service` feature is enabled.
526 fn iter_filter_secondary(&self, secondary: bool) -> impl Iterator<Item = &D> {
527 let i = iter::once(&self.current);
528
529 // With "hs-service" disabled, there are no secondary rings,
530 // so we don't care.
531 let _ = secondary;
532
533 #[cfg(feature = "hs-service")]
534 let i = chain!(i, self.secondary.iter().filter(move |_| secondary));
535
536 i
537 }
538
539 /// Iterate over all the contained hsdirs
540 pub(crate) fn iter(&self) -> impl Iterator<Item = &D> {
541 self.iter_filter_secondary(true)
542 }
543
544 /// Iterate over the hsdirs relevant for `op`
545 pub(crate) fn iter_for_op(&self, op: HsDirOp) -> impl Iterator<Item = &D> {
546 self.iter_filter_secondary(match op {
547 #[cfg(feature = "hs-service")]
548 HsDirOp::Upload => true,
549 HsDirOp::Download => false,
550 })
551 }
552}
553
554/// An event that a [`NetDirProvider`] can broadcast to indicate that a change in
555/// the status of its directory.
556#[derive(
557 Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumCount, IntoPrimitive, TryFromPrimitive,
558)]
559#[non_exhaustive]
560#[repr(u16)]
561pub enum DirEvent {
562 /// A new consensus has been received, and has enough information to be
563 /// used.
564 ///
565 /// This event is also broadcast when a new set of consensus parameters is
566 /// available, even if that set of parameters comes from a configuration
567 /// change rather than from the latest consensus.
568 NewConsensus,
569
570 /// New descriptors have been received for the current consensus.
571 ///
572 /// (This event is _not_ broadcast when receiving new descriptors for a
573 /// consensus which is not yet ready to replace the current consensus.)
574 NewDescriptors,
575
576 /// We have received updated recommendations and requirements
577 /// for which subprotocols we should have to use the network.
578 NewProtocolRecommendation,
579}
580
581/// The network directory provider is shutting down without giving us the
582/// netdir we asked for.
583#[derive(Clone, Copy, Debug, thiserror::Error)]
584#[error("Network directory provider is shutting down")]
585#[non_exhaustive]
586pub struct NetdirProviderShutdown;
587
588impl tor_error::HasKind for NetdirProviderShutdown {
589 fn kind(&self) -> tor_error::ErrorKind {
590 tor_error::ErrorKind::ArtiShuttingDown
591 }
592}
593
594/// How "timely" must a network directory be?
595///
596/// This enum is used as an argument when requesting a [`NetDir`] object from
597/// [`NetDirProvider`] and other APIs, to specify how recent the information
598/// must be in order to be useful.
599#[derive(Copy, Clone, Eq, PartialEq, Debug)]
600#[allow(clippy::exhaustive_enums)]
601pub enum Timeliness {
602 /// The network directory must be strictly timely.
603 ///
604 /// That is, it must be based on a consensus that valid right now, with no
605 /// tolerance for skew or consensus problems.
606 ///
607 /// Avoid using this option if you could use [`Timeliness::Timely`] instead.
608 Strict,
609 /// The network directory must be roughly timely.
610 ///
611 /// This is, it must be based on a consensus that is not _too_ far in the
612 /// future, and not _too_ far in the past.
613 ///
614 /// (The tolerances for "too far" will depend on configuration.)
615 ///
616 /// This is almost always the option that you want to use.
617 Timely,
618 /// Any network directory is permissible, regardless of how untimely.
619 ///
620 /// Avoid using this option if you could use [`Timeliness::Timely`] instead.
621 Unchecked,
622}
623
624/// An object that can provide [`NetDir`]s, as well as inform consumers when
625/// they might have changed.
626///
627/// It is the responsibility of the implementor of `NetDirProvider`
628/// to try to obtain an up-to-date `NetDir`,
629/// and continuously to maintain and update it.
630///
631/// In usual configurations, Arti uses `tor_dirmgr::DirMgr`
632/// as its `NetDirProvider`.
633#[async_trait]
634pub trait NetDirProvider: UpcastArcNetDirProvider + Send + Sync {
635 /// Return a network directory that's live according to the provided
636 /// `timeliness`.
637 fn netdir(&self, timeliness: Timeliness) -> Result<Arc<NetDir>>;
638
639 /// Return a reasonable netdir for general usage.
640 ///
641 /// This is an alias for
642 /// [`NetDirProvider::netdir`]`(`[`Timeliness::Timely`]`)`.
643 fn timely_netdir(&self) -> Result<Arc<NetDir>> {
644 self.netdir(Timeliness::Timely)
645 }
646
647 /// Return a new asynchronous stream that will receive notification
648 /// whenever the consensus has changed.
649 ///
650 /// Multiple events may be batched up into a single item: each time
651 /// this stream yields an event, all you can assume is that the event has
652 /// occurred at least once.
653 fn events(&self) -> BoxStream<'static, DirEvent>;
654
655 /// Return the latest network parameters.
656 ///
657 /// If we have no directory, return a reasonable set of defaults.
658 fn params(&self) -> Arc<dyn AsRef<NetParameters>>;
659
660 /// Get a NetDir from `provider`, waiting until one exists.
661 async fn wait_for_netdir(
662 &self,
663 timeliness: Timeliness,
664 ) -> std::result::Result<Arc<NetDir>, NetdirProviderShutdown> {
665 if let Ok(nd) = self.netdir(timeliness) {
666 return Ok(nd);
667 }
668
669 let mut stream = self.events();
670 loop {
671 // We need to retry `self.netdir()` before waiting for any stream events, to
672 // avoid deadlock.
673 //
674 // We ignore all errors here: they can all potentially be fixed by
675 // getting a fresh consensus, and they will all get warned about
676 // by the NetDirProvider itself.
677 if let Ok(nd) = self.netdir(timeliness) {
678 return Ok(nd);
679 }
680 match stream.next().await {
681 Some(_) => {}
682 None => {
683 return Err(NetdirProviderShutdown);
684 }
685 }
686 }
687 }
688
689 /// Wait until `provider` lists `target`.
690 ///
691 /// NOTE: This might potentially wait indefinitely, if `target` is never actually
692 /// becomes listed in the directory. It will exit if the `NetDirProvider` shuts down.
693 async fn wait_for_netdir_to_list(
694 &self,
695 target: &tor_linkspec::RelayIds,
696 timeliness: Timeliness,
697 ) -> std::result::Result<(), NetdirProviderShutdown> {
698 let mut events = self.events();
699 loop {
700 // See if the desired relay is in the netdir.
701 //
702 // We do this before waiting for any events, to avoid race conditions.
703 {
704 let netdir = self.wait_for_netdir(timeliness).await?;
705 if netdir.ids_listed(target) == Some(true) {
706 return Ok(());
707 }
708 // If we reach this point, then ids_listed returned `Some(false)`,
709 // meaning "This relay is definitely not in the current directory";
710 // or it returned `None`, meaning "waiting for more information
711 // about this network directory.
712 // In both cases, it's reasonable to just wait for another netdir
713 // event and try again.
714 }
715 // We didn't find the relay; wait for the provider to have a new netdir
716 // or more netdir information.
717 if events.next().await.is_none() {
718 // The event stream is closed; the provider has shut down.
719 return Err(NetdirProviderShutdown);
720 }
721 }
722 }
723
724 /// Return the latest set of recommended and required protocols, if there is one.
725 ///
726 /// This may be more recent (or more available) than this provider's associated NetDir.
727 fn protocol_statuses(&self) -> Option<(SystemTime, Arc<netstatus::ProtoStatuses>)>;
728}
729
730impl<T> NetDirProvider for Arc<T>
731where
732 T: NetDirProvider,
733{
734 fn netdir(&self, timeliness: Timeliness) -> Result<Arc<NetDir>> {
735 self.deref().netdir(timeliness)
736 }
737
738 fn timely_netdir(&self) -> Result<Arc<NetDir>> {
739 self.deref().timely_netdir()
740 }
741
742 fn events(&self) -> BoxStream<'static, DirEvent> {
743 self.deref().events()
744 }
745
746 fn params(&self) -> Arc<dyn AsRef<NetParameters>> {
747 self.deref().params()
748 }
749
750 fn protocol_statuses(&self) -> Option<(SystemTime, Arc<netstatus::ProtoStatuses>)> {
751 self.deref().protocol_statuses()
752 }
753}
754
755/// Helper trait: allows any `Arc<X>` to be upcast to a `Arc<dyn
756/// NetDirProvider>` if X is an implementation or supertrait of NetDirProvider.
757///
758/// This trait exists to work around a limitation in rust: when trait upcasting
759/// coercion is stable, this will be unnecessary.
760///
761/// The Rust tracking issue is <https://github.com/rust-lang/rust/issues/65991>.
762pub trait UpcastArcNetDirProvider {
763 /// Return a view of this object as an `Arc<dyn NetDirProvider>`
764 fn upcast_arc<'a>(self: Arc<Self>) -> Arc<dyn NetDirProvider + 'a>
765 where
766 Self: 'a;
767}
768
769impl<T> UpcastArcNetDirProvider for T
770where
771 T: NetDirProvider + Sized,
772{
773 fn upcast_arc<'a>(self: Arc<Self>) -> Arc<dyn NetDirProvider + 'a>
774 where
775 Self: 'a,
776 {
777 self
778 }
779}
780
781impl AsRef<NetParameters> for NetDir {
782 fn as_ref(&self) -> &NetParameters {
783 self.params()
784 }
785}
786
787/// A partially build NetDir -- it can't be unwrapped until it has
788/// enough information to build safe paths.
789#[derive(Debug, Clone)]
790pub struct PartialNetDir {
791 /// The netdir that's under construction.
792 netdir: NetDir,
793
794 /// The previous netdir, if we had one
795 ///
796 /// Used as a cache, so we can reuse information
797 #[cfg(feature = "hs-common")]
798 prev_netdir: Option<Arc<NetDir>>,
799}
800
801/// A view of a relay on the Tor network, suitable for building circuits.
802// TODO: This should probably be a more specific struct, with a trait
803// that implements it.
804#[derive(Clone)]
805pub struct Relay<'a> {
806 /// A router descriptor for this relay.
807 rs: &'a netstatus::MdRouterStatus,
808 /// A microdescriptor for this relay.
809 md: &'a MicrodescAndHash,
810 /// The country code this relay is in, if we know one.
811 #[cfg(feature = "geoip")]
812 cc: Option<CountryCode>,
813}
814
815/// A relay that we haven't checked for validity or usability in
816/// routing.
817#[derive(Debug)]
818pub struct UncheckedRelay<'a> {
819 /// A router descriptor for this relay.
820 rs: &'a netstatus::MdRouterStatus,
821 /// A microdescriptor for this relay, if there is one.
822 md: Option<&'a MicrodescAndHash>,
823 /// The country code this relay is in, if we know one.
824 #[cfg(feature = "geoip")]
825 cc: Option<CountryCode>,
826}
827
828/// A partial or full network directory that we can download
829/// microdescriptors for.
830pub trait MdReceiver {
831 /// Return an iterator over the digests for all of the microdescriptors
832 /// that this netdir is missing.
833 fn missing_microdescs(&self) -> Box<dyn Iterator<Item = &MdDigest> + '_>;
834 /// Add a microdescriptor to this netdir, if it was wanted.
835 ///
836 /// Return true if it was indeed wanted.
837 fn add_microdesc(&mut self, md: MicrodescAndHash) -> bool;
838 /// Return the number of missing microdescriptors.
839 fn n_missing(&self) -> usize;
840}
841
842impl PartialNetDir {
843 /// Create a new PartialNetDir with a given consensus, and no
844 /// microdescriptors loaded.
845 ///
846 /// If `replacement_params` is provided, override network parameters from
847 /// the consensus with those from `replacement_params`.
848 pub fn new(
849 consensus: MdConsensus,
850 replacement_params: Option<&netstatus::NetParams<i32>>,
851 ) -> Self {
852 Self::new_inner(
853 consensus,
854 replacement_params,
855 #[cfg(feature = "geoip")]
856 None,
857 )
858 }
859
860 /// Create a new PartialNetDir with GeoIP support.
861 ///
862 /// This does the same thing as `new()`, except the provided GeoIP database is used to add
863 /// country codes to relays.
864 #[cfg(feature = "geoip")]
865 pub fn new_with_geoip(
866 consensus: MdConsensus,
867 replacement_params: Option<&netstatus::NetParams<i32>>,
868 geoip_db: &GeoipDb,
869 ) -> Self {
870 Self::new_inner(consensus, replacement_params, Some(geoip_db))
871 }
872
873 /// Implementation of the `new()` functions.
874 fn new_inner(
875 consensus: MdConsensus,
876 replacement_params: Option<&netstatus::NetParams<i32>>,
877 #[cfg(feature = "geoip")] geoip_db: Option<&GeoipDb>,
878 ) -> Self {
879 let mut params = NetParameters::default();
880
881 // (We ignore unrecognized options here, since they come from
882 // the consensus, and we don't expect to recognize everything
883 // there.)
884 let _ = params.saturating_update(consensus.params().iter());
885
886 // Now see if the user has any parameters to override.
887 // (We have to do this now, or else changes won't be reflected in our
888 // weights.)
889 if let Some(replacement) = replacement_params {
890 for u in params.saturating_update(replacement.iter()) {
891 warn!("Unrecognized option: override_net_params.{}", u);
892 }
893 }
894
895 // Compute the weights we'll want to use for these relays.
896 let weights = weight::WeightSet::from_consensus(&consensus, ¶ms);
897
898 let n_relays = consensus.c_relays().len();
899
900 let rsidx_by_missing = consensus
901 .c_relays()
902 .iter_enumerated()
903 .map(|(rsidx, rs)| (*rs.md_digest(), rsidx))
904 .collect();
905
906 let rsidx_by_rsa = consensus
907 .c_relays()
908 .iter_enumerated()
909 .map(|(rsidx, rs)| (*rs.rsa_identity(), rsidx))
910 .collect();
911
912 #[cfg(feature = "geoip")]
913 let country_codes = if let Some(db) = geoip_db {
914 consensus
915 .c_relays()
916 .iter()
917 .map(|rs| {
918 db.lookup_country_code_multi(rs.addrs().map(|x| x.ip()))
919 .cloned()
920 })
921 .collect()
922 } else {
923 Default::default()
924 };
925
926 #[cfg(feature = "hs-common")]
927 let hsdir_rings = Arc::new({
928 let params = HsDirParams::compute(&consensus, ¶ms).expect("Invalid consensus!");
929 // TODO: It's a bit ugly to use expect above, but this function does
930 // not return a Result. On the other hand, the error conditions under which
931 // HsDirParams::compute can return Err are _very_ narrow and hard to
932 // hit; see documentation in that function. As such, we probably
933 // don't need to have this return a Result.
934
935 params.map(HsDirRing::empty_from_params)
936 });
937
938 let netdir = NetDir {
939 consensus: Arc::new(consensus),
940 params,
941 mds: vec![None; n_relays].into(),
942 rsidx_by_missing,
943 rsidx_by_rsa: Arc::new(rsidx_by_rsa),
944 rsidx_by_ed: HashMap::with_capacity(n_relays),
945 #[cfg(feature = "hs-common")]
946 hsdir_rings,
947 weights,
948 #[cfg(feature = "geoip")]
949 country_codes,
950 };
951
952 PartialNetDir {
953 netdir,
954 #[cfg(feature = "hs-common")]
955 prev_netdir: None,
956 }
957 }
958
959 /// Return the declared lifetime of this PartialNetDir.
960 pub fn lifetime(&self) -> &netstatus::Lifetime {
961 self.netdir.lifetime()
962 }
963
964 /// Record a previous netdir, which can be used for reusing cached information
965 //
966 // Fills in as many missing microdescriptors as possible in this
967 // netdir, using the microdescriptors from the previous netdir.
968 //
969 // With HS enabled, stores the netdir for reuse of relay hash ring index values.
970 #[allow(clippy::needless_pass_by_value)] // prev might, or might not, be stored
971 pub fn fill_from_previous_netdir(&mut self, prev: Arc<NetDir>) {
972 for md in prev.mds.iter().flatten() {
973 self.netdir.add_arc_microdesc(md.clone());
974 }
975
976 #[cfg(feature = "hs-common")]
977 {
978 self.prev_netdir = Some(prev);
979 }
980 }
981
982 /// Compute the hash ring(s) for this NetDir
983 #[cfg(feature = "hs-common")]
984 fn compute_rings(&mut self) {
985 let params = HsDirParams::compute(&self.netdir.consensus, &self.netdir.params)
986 .expect("Invalid consensus");
987 // TODO: see TODO by similar expect in new()
988
989 self.netdir.hsdir_rings =
990 Arc::new(params.map(|params| {
991 HsDirRing::compute(params, &self.netdir, self.prev_netdir.as_deref())
992 }));
993 }
994
995 /// Return true if this are enough information in this directory
996 /// to build multihop paths.
997 pub fn have_enough_paths(&self) -> bool {
998 self.netdir.have_enough_paths()
999 }
1000 /// If this directory has enough information to build multihop
1001 /// circuits, return it.
1002 pub fn unwrap_if_sufficient(
1003 #[allow(unused_mut)] mut self,
1004 ) -> std::result::Result<NetDir, PartialNetDir> {
1005 if self.netdir.have_enough_paths() {
1006 #[cfg(feature = "hs-common")]
1007 self.compute_rings();
1008 Ok(self.netdir)
1009 } else {
1010 Err(self)
1011 }
1012 }
1013}
1014
1015impl MdReceiver for PartialNetDir {
1016 fn missing_microdescs(&self) -> Box<dyn Iterator<Item = &MdDigest> + '_> {
1017 self.netdir.missing_microdescs()
1018 }
1019 fn add_microdesc(&mut self, md: MicrodescAndHash) -> bool {
1020 self.netdir.add_microdesc(md)
1021 }
1022 fn n_missing(&self) -> usize {
1023 self.netdir.n_missing()
1024 }
1025}
1026
1027impl NetDir {
1028 /// Return the declared lifetime of this NetDir.
1029 pub fn lifetime(&self) -> &netstatus::Lifetime {
1030 self.consensus.lifetime()
1031 }
1032
1033 /// Add `md` to this NetDir.
1034 ///
1035 /// Return true if we wanted it, and false otherwise.
1036 fn add_arc_microdesc(&mut self, md: Arc<MicrodescAndHash>) -> bool {
1037 if let Some(rsidx) = self.rsidx_by_missing.remove(md.digest()) {
1038 assert_eq!(self.c_relays()[rsidx].md_digest(), md.digest());
1039
1040 // There should never be two approved MDs in the same
1041 // consensus listing the same ID... but if there is,
1042 // we'll let the most recent one win.
1043 self.rsidx_by_ed.insert(*md.ed25519_id(), rsidx);
1044
1045 // Happy path: we did indeed want this one.
1046 self.mds[rsidx] = Some(md);
1047
1048 // Save some space in the missing-descriptor list.
1049 if self.rsidx_by_missing.len() < self.rsidx_by_missing.capacity() / 4 {
1050 self.rsidx_by_missing.shrink_to_fit();
1051 }
1052
1053 return true;
1054 }
1055
1056 // Either we already had it, or we never wanted it at all.
1057 false
1058 }
1059
1060 /// Construct a (possibly invalid) Relay object from a routerstatus and its
1061 /// index within the consensus.
1062 fn relay_from_rs_and_rsidx<'a>(
1063 &'a self,
1064 rs: &'a netstatus::MdRouterStatus,
1065 rsidx: RouterStatusIdx,
1066 ) -> UncheckedRelay<'a> {
1067 debug_assert_eq!(self.c_relays()[rsidx].rsa_identity(), rs.rsa_identity());
1068 let md = self.mds[rsidx].as_deref();
1069 if let Some(md) = md {
1070 debug_assert_eq!(rs.md_digest(), md.digest());
1071 }
1072
1073 UncheckedRelay {
1074 rs,
1075 md,
1076 #[cfg(feature = "geoip")]
1077 cc: self.country_codes.get(rsidx.0).copied().flatten(),
1078 }
1079 }
1080
1081 /// Return the value of the hsdir_n_replicas param.
1082 #[cfg(feature = "hs-common")]
1083 fn n_replicas(&self) -> u8 {
1084 self.params
1085 .hsdir_n_replicas
1086 .get()
1087 .try_into()
1088 .expect("BoundedInt did not enforce bounds")
1089 }
1090
1091 /// Return the spread parameter for the specified `op`.
1092 #[cfg(feature = "hs-common")]
1093 fn spread(&self, op: HsDirOp) -> usize {
1094 let spread = match op {
1095 HsDirOp::Download => self.params.hsdir_spread_fetch,
1096 #[cfg(feature = "hs-service")]
1097 HsDirOp::Upload => self.params.hsdir_spread_store,
1098 };
1099
1100 spread
1101 .get()
1102 .try_into()
1103 .expect("BoundedInt did not enforce bounds!")
1104 }
1105
1106 /// Select `spread` hsdir relays for the specified `hsid` from a given `ring`.
1107 ///
1108 /// Algorithm:
1109 ///
1110 /// for idx in 1..=n_replicas:
1111 /// - let H = hsdir_ring::onion_service_index(id, replica, rand,
1112 /// period).
1113 /// - Find the position of H within hsdir_ring.
1114 /// - Take elements from hsdir_ring starting at that position,
1115 /// adding them to Dirs until we have added `spread` new elements
1116 /// that were not there before.
1117 #[cfg(feature = "hs-common")]
1118 fn select_hsdirs<'h, 'r: 'h>(
1119 &'r self,
1120 hsid: HsBlindId,
1121 ring: &'h HsDirRing,
1122 spread: usize,
1123 ) -> impl Iterator<Item = Relay<'r>> + 'h {
1124 let n_replicas = self.n_replicas();
1125
1126 (1..=n_replicas) // 1-indexed !
1127 .flat_map({
1128 let mut selected_nodes = HashSet::new();
1129
1130 move |replica: u8| {
1131 let hsdir_idx = hsdir_ring::service_hsdir_index(&hsid, replica, ring.params());
1132
1133 ring.ring_items_at(hsdir_idx, spread, |(hsdir_idx, _)| {
1134 // According to rend-spec 2.2.3:
1135 // ... If any of those
1136 // nodes have already been selected for a lower-numbered replica of the
1137 // service, any nodes already chosen are disregarded (i.e. skipped over)
1138 // when choosing a replica's hsdir_spread_store nodes.
1139 selected_nodes.insert(*hsdir_idx)
1140 })
1141 .collect::<Vec<_>>()
1142 }
1143 })
1144 .filter_map(move |(_hsdir_idx, rs_idx)| {
1145 // This ought not to be None but let's not panic or bail if it is
1146 self.relay_by_rs_idx(*rs_idx)
1147 })
1148 }
1149
1150 /// Replace the overridden parameters in this netdir with `new_replacement`.
1151 ///
1152 /// After this function is done, the netdir's parameters will be those in
1153 /// the consensus, overridden by settings from `new_replacement`. Any
1154 /// settings in the old replacement parameters will be discarded.
1155 pub fn replace_overridden_parameters(&mut self, new_replacement: &netstatus::NetParams<i32>) {
1156 // TODO(nickm): This is largely duplicate code from PartialNetDir::new().
1157 let mut new_params = NetParameters::default();
1158 let _ = new_params.saturating_update(self.consensus.params().iter());
1159 for u in new_params.saturating_update(new_replacement.iter()) {
1160 warn!("Unrecognized option: override_net_params.{}", u);
1161 }
1162
1163 self.params = new_params;
1164 }
1165
1166 /// Return an iterator over all Relay objects, including invalid ones
1167 /// that we can't use.
1168 pub fn all_relays(&self) -> impl Iterator<Item = UncheckedRelay<'_>> {
1169 // TODO: I'd like if we could memoize this so we don't have to
1170 // do so many hashtable lookups.
1171 self.c_relays()
1172 .iter_enumerated()
1173 .map(move |(rsidx, rs)| self.relay_from_rs_and_rsidx(rs, rsidx))
1174 }
1175 /// Return an iterator over all [usable](NetDir#usable) Relays.
1176 pub fn relays(&self) -> impl Iterator<Item = Relay<'_>> {
1177 self.all_relays().filter_map(UncheckedRelay::into_relay)
1178 }
1179
1180 /// Look up a relay's [`MicrodescAndHash`] by its [`RouterStatusIdx`]
1181 #[cfg_attr(not(feature = "hs-common"), allow(dead_code))]
1182 pub(crate) fn md_by_rsidx(&self, rsidx: RouterStatusIdx) -> Option<&MicrodescAndHash> {
1183 self.mds.get(rsidx)?.as_deref()
1184 }
1185
1186 /// Return a relay matching a given identity, if we have a
1187 /// _usable_ relay with that key.
1188 ///
1189 /// (Does not return [unusable](NetDir#usable) relays.)
1190 ///
1191 ///
1192 /// Note that a `None` answer is not always permanent: if a microdescriptor
1193 /// is subsequently added for a relay with this ID, the ID may become usable
1194 /// even if it was not usable before.
1195 pub fn by_id<'a, T>(&self, id: T) -> Option<Relay<'_>>
1196 where
1197 T: Into<RelayIdRef<'a>>,
1198 {
1199 let id = id.into();
1200 let answer = match id {
1201 RelayIdRef::Ed25519(ed25519) => {
1202 let rsidx = *self.rsidx_by_ed.get(ed25519)?;
1203 let rs = self.c_relays().get(rsidx).expect("Corrupt index");
1204
1205 self.relay_from_rs_and_rsidx(rs, rsidx).into_relay()?
1206 }
1207 RelayIdRef::Rsa(rsa) => self
1208 .by_rsa_id_unchecked(rsa)
1209 .and_then(UncheckedRelay::into_relay)?,
1210 other_type => self.relays().find(|r| r.has_identity(other_type))?,
1211 };
1212 assert!(answer.has_identity(id));
1213 Some(answer)
1214 }
1215
1216 /// Obtain a `Relay` given a `RouterStatusIdx`
1217 ///
1218 /// Differs from `relay_from_rs_and_rsi` as follows:
1219 /// * That function expects the caller to already have an `MdRouterStatus`;
1220 /// it checks with `debug_assert` that the relay in the netdir matches.
1221 /// * That function panics if the `RouterStatusIdx` is invalid; this one returns `None`.
1222 /// * That function returns an `UncheckedRelay`; this one a `Relay`.
1223 ///
1224 /// `None` could be returned here, even with a valid `rsi`,
1225 /// if `rsi` refers to an [unusable](NetDir#usable) relay.
1226 #[cfg_attr(not(feature = "hs-common"), allow(dead_code))]
1227 pub(crate) fn relay_by_rs_idx(&self, rs_idx: RouterStatusIdx) -> Option<Relay<'_>> {
1228 let rs = self.c_relays().get(rs_idx)?;
1229 let md = self.mds.get(rs_idx)?.as_deref();
1230 UncheckedRelay {
1231 rs,
1232 md,
1233 #[cfg(feature = "geoip")]
1234 cc: self.country_codes.get(rs_idx.0).copied().flatten(),
1235 }
1236 .into_relay()
1237 }
1238
1239 /// Return a relay with the same identities as those in `target`, if one
1240 /// exists.
1241 ///
1242 /// Does not return [unusable](NetDir#usable) relays.
1243 ///
1244 /// Note that a negative result from this method is not necessarily permanent:
1245 /// it may be the case that a relay exists,
1246 /// but we don't yet have enough information about it to know all of its IDs.
1247 /// To test whether a relay is *definitely* absent,
1248 /// use [`by_ids_detailed`](Self::by_ids_detailed)
1249 /// or [`ids_listed`](Self::ids_listed).
1250 ///
1251 /// # Limitations
1252 ///
1253 /// This will be very slow if `target` does not have an Ed25519 or RSA
1254 /// identity.
1255 pub fn by_ids<T>(&self, target: &T) -> Option<Relay<'_>>
1256 where
1257 T: HasRelayIds + ?Sized,
1258 {
1259 let mut identities = target.identities();
1260 // Don't try if there are no identities.
1261 let first_id = identities.next()?;
1262
1263 // Since there is at most one relay with each given ID type,
1264 // we only need to check the first relay we find.
1265 let candidate = self.by_id(first_id)?;
1266 if identities.all(|wanted_id| candidate.has_identity(wanted_id)) {
1267 Some(candidate)
1268 } else {
1269 None
1270 }
1271 }
1272
1273 /// Check whether there is a relay that has at least one identity from
1274 /// `target`, and which _could_ have every identity from `target`.
1275 /// If so, return such a relay.
1276 ///
1277 /// Return `Ok(None)` if we did not find a relay with any identity from `target`.
1278 ///
1279 /// Return `RelayLookupError::Impossible` if we found a relay with at least
1280 /// one identity from `target`, but that relay's other identities contradict
1281 /// what we learned from `target`.
1282 ///
1283 /// Does not return [unusable](NetDir#usable) relays.
1284 ///
1285 /// (This function is only useful if you need to distinguish the
1286 /// "impossible" case from the "no such relay known" case.)
1287 ///
1288 /// # Limitations
1289 ///
1290 /// This will be very slow if `target` does not have an Ed25519 or RSA
1291 /// identity.
1292 //
1293 // TODO HS: This function could use a better name.
1294 //
1295 // TODO: We could remove the feature restriction here once we think this API is
1296 // stable.
1297 #[cfg(feature = "hs-common")]
1298 pub fn by_ids_detailed<T>(
1299 &self,
1300 target: &T,
1301 ) -> std::result::Result<Option<Relay<'_>>, RelayLookupError>
1302 where
1303 T: HasRelayIds + ?Sized,
1304 {
1305 let candidate = target
1306 .identities()
1307 // Find all the relays that share any identity with this set of identities.
1308 .filter_map(|id| self.by_id(id))
1309 // We might find the same relay more than once under a different
1310 // identity, so we remove the duplicates.
1311 //
1312 // Since there is at most one relay per rsa identity per consensus,
1313 // this is a true uniqueness check under current construction rules.
1314 .unique_by(|r| r.rs.rsa_identity())
1315 // If we find two or more distinct relays, then have a contradiction.
1316 .at_most_one()
1317 .map_err(|_| RelayLookupError::Impossible)?;
1318
1319 // If we have no candidate, return None early.
1320 let candidate = match candidate {
1321 Some(relay) => relay,
1322 None => return Ok(None),
1323 };
1324
1325 // Now we know we have a single candidate. Make sure that it does not have any
1326 // identity that does not match the target.
1327 if target
1328 .identities()
1329 .all(|wanted_id| match candidate.identity(wanted_id.id_type()) {
1330 None => true,
1331 Some(id) => id == wanted_id,
1332 })
1333 {
1334 Ok(Some(candidate))
1335 } else {
1336 Err(RelayLookupError::Impossible)
1337 }
1338 }
1339
1340 /// Return a boolean if this consensus definitely has (or does not have) a
1341 /// relay matching the listed identities.
1342 ///
1343 /// `Some(true)` indicates that the relay exists.
1344 /// `Some(false)` indicates that the relay definitely does not exist.
1345 /// `None` indicates that we can't yet tell whether such a relay exists,
1346 /// due to missing information.
1347 fn id_pair_listed(&self, ed_id: &Ed25519Identity, rsa_id: &RsaIdentity) -> Option<bool> {
1348 let r = self.by_rsa_id_unchecked(rsa_id);
1349 match r {
1350 Some(unchecked) => {
1351 if !unchecked.rs.ed25519_id_is_usable() {
1352 return Some(false);
1353 }
1354 // If md is present, then it's listed iff we have the right
1355 // ed id. Otherwise we don't know if it's listed.
1356 unchecked.md.map(|md| md.ed25519_id() == ed_id)
1357 }
1358 None => {
1359 // Definitely not listed.
1360 Some(false)
1361 }
1362 }
1363 }
1364
1365 /// Check whether a relay exists (or may exist)
1366 /// with the same identities as those in `target`.
1367 ///
1368 /// `Some(true)` indicates that the relay exists.
1369 /// `Some(false)` indicates that the relay definitely does not exist.
1370 /// `None` indicates that we can't yet tell whether such a relay exists,
1371 /// due to missing information.
1372 pub fn ids_listed<T>(&self, target: &T) -> Option<bool>
1373 where
1374 T: HasRelayIds + ?Sized,
1375 {
1376 let rsa_id = target.rsa_identity();
1377 let ed25519_id = target.ed_identity();
1378
1379 // TODO: If we later support more identity key types, this will
1380 // become incorrect. This assertion might help us recognize that case.
1381 const _: () = assert!(RelayIdType::COUNT == 2);
1382
1383 match (rsa_id, ed25519_id) {
1384 (Some(r), Some(e)) => self.id_pair_listed(e, r),
1385 (Some(r), None) => Some(self.rsa_id_is_listed(r)),
1386 (None, Some(e)) => {
1387 if self.rsidx_by_ed.contains_key(e) {
1388 Some(true)
1389 } else {
1390 None
1391 }
1392 }
1393 (None, None) => None,
1394 }
1395 }
1396
1397 /// Return a (possibly [unusable](NetDir#usable)) relay with a given RSA identity.
1398 ///
1399 /// This API can be used to find information about a relay that is listed in
1400 /// the current consensus, even if we don't yet have enough information
1401 /// (like a microdescriptor) about the relay to use it.
1402 #[cfg_attr(feature = "experimental-api", visibility::make(pub))]
1403 #[cfg_attr(docsrs, doc(cfg(feature = "experimental-api")))]
1404 fn by_rsa_id_unchecked(&self, rsa_id: &RsaIdentity) -> Option<UncheckedRelay<'_>> {
1405 let rsidx = *self.rsidx_by_rsa.get(rsa_id)?;
1406 let rs = self.c_relays().get(rsidx).expect("Corrupt index");
1407 assert_eq!(rs.rsa_identity(), rsa_id);
1408 Some(self.relay_from_rs_and_rsidx(rs, rsidx))
1409 }
1410 /// Return the relay with a given RSA identity, if we have one
1411 /// and it is [usable](NetDir#usable).
1412 fn by_rsa_id(&self, rsa_id: &RsaIdentity) -> Option<Relay<'_>> {
1413 self.by_rsa_id_unchecked(rsa_id)?.into_relay()
1414 }
1415 /// Return true if `rsa_id` is listed in this directory, even if it isn't
1416 /// currently usable.
1417 ///
1418 /// (An "[unusable](NetDir#usable)" relay in this context is one for which we don't have full
1419 /// directory information.)
1420 #[cfg_attr(feature = "experimental-api", visibility::make(pub))]
1421 #[cfg_attr(docsrs, doc(cfg(feature = "experimental-api")))]
1422 fn rsa_id_is_listed(&self, rsa_id: &RsaIdentity) -> bool {
1423 self.by_rsa_id_unchecked(rsa_id).is_some()
1424 }
1425
1426 /// List the hsdirs in this NetDir, that should be in the HSDir rings
1427 ///
1428 /// The results are not returned in any particular order.
1429 #[cfg(feature = "hs-common")]
1430 fn all_hsdirs(&self) -> impl Iterator<Item = (RouterStatusIdx, Relay<'_>)> {
1431 self.c_relays().iter_enumerated().filter_map(|(rsidx, rs)| {
1432 let relay = self.relay_from_rs_and_rsidx(rs, rsidx);
1433 relay.is_hsdir_for_ring().then_some(())?;
1434 let relay = relay.into_relay()?;
1435 Some((rsidx, relay))
1436 })
1437 }
1438
1439 /// Return the parameters from the consensus, clamped to the
1440 /// correct ranges, with defaults filled in.
1441 ///
1442 /// NOTE: that unsupported parameters aren't returned here; only those
1443 /// values configured in the `params` module are available.
1444 pub fn params(&self) -> &NetParameters {
1445 &self.params
1446 }
1447
1448 /// Return a [`ProtoStatus`](netstatus::ProtoStatus) that lists the
1449 /// network's current requirements and recommendations for the list of
1450 /// protocols that every relay must implement.
1451 //
1452 // TODO HS: I am not sure this is the right API; other alternatives would be:
1453 // * To expose the _required_ relay protocol list instead (since that's all that
1454 // onion service implementations need).
1455 // * To expose the client protocol list as well (for symmetry).
1456 // * To expose the MdConsensus instead (since that's more general, although
1457 // it restricts the future evolution of this API).
1458 //
1459 // I think that this is a reasonably good compromise for now, but I'm going
1460 // to put it behind the `hs-common` feature to give us time to consider more.
1461 #[cfg(feature = "hs-common")]
1462 pub fn relay_protocol_status(&self) -> &netstatus::ProtoStatus {
1463 self.consensus.relay_protocol_status()
1464 }
1465
1466 /// Return a [`ProtoStatus`](netstatus::ProtoStatus) that lists the
1467 /// network's current requirements and recommendations for the list of
1468 /// protocols that every relay must implement.
1469 //
1470 // TODO HS: See notes on relay_protocol_status above.
1471 #[cfg(feature = "hs-common")]
1472 pub fn client_protocol_status(&self) -> &netstatus::ProtoStatus {
1473 self.consensus.client_protocol_status()
1474 }
1475
1476 /// Construct a `CircTarget` from an externally provided list of link specifiers,
1477 /// and an externally provided onion key.
1478 ///
1479 /// This method is used in the onion service protocol,
1480 /// where introduction points and rendezvous points are specified using these inputs.
1481 ///
1482 /// This function is a member of `NetDir` so that it can provide a reasonable list of
1483 /// [`Protocols`](tor_protover::Protocols) capabilities for the generated `CircTarget`.
1484 /// It does not (and should not!) look up anything else from the directory.
1485 #[cfg(feature = "hs-common")]
1486 pub fn circ_target_from_verbatim_linkspecs(
1487 &self,
1488 linkspecs: &[tor_linkspec::EncodedLinkSpec],
1489 ntor_onion_key: &curve25519::PublicKey,
1490 ) -> StdResult<VerbatimLinkSpecCircTarget<OwnedCircTarget>, VerbatimCircTargetDecodeError> {
1491 use VerbatimCircTargetDecodeError as E;
1492 use tor_linkspec::CircTarget as _;
1493 use tor_linkspec::decode::Strictness;
1494
1495 let mut bld = OwnedCircTarget::builder();
1496 use tor_error::into_internal;
1497
1498 *bld.chan_target() =
1499 OwnedChanTargetBuilder::from_encoded_linkspecs(Strictness::Standard, linkspecs)?;
1500 let protocols = {
1501 let chan_target = bld.chan_target().build().map_err(into_internal!(
1502 "from_encoded_linkspecs gave an invalid output"
1503 ))?;
1504 match self
1505 .by_ids_detailed(&chan_target)
1506 .map_err(E::ImpossibleIds)?
1507 {
1508 Some(relay) => relay.protovers().clone(),
1509 None => self.relay_protocol_status().required_protocols().clone(),
1510 }
1511 };
1512 bld.protocols(protocols);
1513 bld.ntor_onion_key(*ntor_onion_key);
1514 Ok(VerbatimLinkSpecCircTarget::new(
1515 bld.build()
1516 .map_err(into_internal!("Failed to construct a valid circtarget"))?,
1517 linkspecs.to_vec(),
1518 ))
1519 }
1520
1521 /// Return weighted the fraction of relays we can use. We only
1522 /// consider relays that match the predicate `usable`. We weight
1523 /// this bandwidth according to the provided `role`.
1524 ///
1525 /// If _no_ matching relays in the consensus have a nonzero
1526 /// weighted bandwidth value, we fall back to looking at the
1527 /// unweighted fraction of matching relays.
1528 ///
1529 /// If there are no matching relays in the consensus, we return 0.0.
1530 fn frac_for_role<'a, F>(&'a self, role: WeightRole, usable: F) -> f64
1531 where
1532 F: Fn(&UncheckedRelay<'a>) -> bool,
1533 {
1534 let mut total_weight = 0_u64;
1535 let mut have_weight = 0_u64;
1536 let mut have_count = 0_usize;
1537 let mut total_count = 0_usize;
1538
1539 for r in self.all_relays() {
1540 if !usable(&r) {
1541 continue;
1542 }
1543 let w = self.weights.weight_rs_for_role(r.rs, role);
1544 total_weight += w;
1545 total_count += 1;
1546 if r.is_usable() {
1547 have_weight += w;
1548 have_count += 1;
1549 }
1550 }
1551
1552 if total_weight > 0 {
1553 // The consensus lists some weighted bandwidth so return the
1554 // fraction of the weighted bandwidth for which we have
1555 // descriptors.
1556 (have_weight as f64) / (total_weight as f64)
1557 } else if total_count > 0 {
1558 // The consensus lists no weighted bandwidth for these relays,
1559 // but at least it does list relays. Return the fraction of
1560 // relays for which it we have descriptors.
1561 (have_count as f64) / (total_count as f64)
1562 } else {
1563 // There are no relays of this kind in the consensus. Return
1564 // 0.0, to avoid dividing by zero and giving NaN.
1565 0.0
1566 }
1567 }
1568 /// Return the estimated fraction of possible paths that we have
1569 /// enough microdescriptors to build.
1570 fn frac_usable_paths(&self) -> f64 {
1571 // TODO #504, TODO SPEC: We may want to add a set of is_flagged_fast() and/or
1572 // is_flagged_stable() checks here. This will require spec clarification.
1573 let f_g = self.frac_for_role(WeightRole::Guard, |u| {
1574 u.low_level_details().is_suitable_as_guard()
1575 });
1576 let f_m = self.frac_for_role(WeightRole::Middle, |_| true);
1577 let f_e = if self.all_relays().any(|u| u.rs.is_flagged_exit()) {
1578 self.frac_for_role(WeightRole::Exit, |u| u.rs.is_flagged_exit())
1579 } else {
1580 // If there are no exits at all, we use f_m here.
1581 f_m
1582 };
1583 f_g * f_m * f_e
1584 }
1585 /// Return true if there is enough information in this NetDir to build
1586 /// multihop circuits.
1587 fn have_enough_paths(&self) -> bool {
1588 // TODO-A001: This should check for our guards as well, and
1589 // make sure that if they're listed in the consensus, we have
1590 // the descriptors for them.
1591
1592 // If we can build a randomly chosen path with at least this
1593 // probability, we know enough information to participate
1594 // on the network.
1595
1596 let min_frac_paths: f64 = self.params().min_circuit_path_threshold.as_fraction();
1597
1598 // What fraction of paths can we build?
1599 let available = self.frac_usable_paths();
1600
1601 available >= min_frac_paths
1602 }
1603 /// Choose a relay at random.
1604 ///
1605 /// Each relay is chosen with probability proportional to its weight
1606 /// in the role `role`, and is only selected if the predicate `usable`
1607 /// returns true for it.
1608 ///
1609 /// This function returns None if (and only if) there are no relays
1610 /// where `usable` returned true.
1611 ///
1612 /// A relay with zero weight will be chosen iff all `usable` relays have
1613 /// zero weight.
1614 //
1615 // TODO this API, with the `usable` closure, invites mistakes where we fail to
1616 // check conditions that are implied by the role we have selected for the relay:
1617 // call sites must include a call to `Relay::is_polarity_inverter()` or whatever.
1618 // IMO the `WeightRole` ought to imply a condition (and it should therefore probably
1619 // be renamed.) -Diziet
1620 #[allow(clippy::cognitive_complexity)]
1621 pub fn pick_relay<'a, R, P>(
1622 &'a self,
1623 rng: &mut R,
1624 role: WeightRole,
1625 usable: P,
1626 ) -> Option<Relay<'a>>
1627 where
1628 R: rand::Rng,
1629 P: FnMut(&Relay<'a>) -> bool,
1630 {
1631 let relays: Vec<_> = self.relays().filter(usable).collect();
1632
1633 tracing::trace!(?role, "picking from {} relays", relays.len());
1634
1635 // Preemptively check for and handle an empty sequence ourselves, since it's
1636 // cheap to do so and the `choose_weighted` behavior for this edge-case
1637 // is a bit unpredictable.
1638 // See e.g. <https://github.com/rust-random/rand/issues/1783>
1639 if relays.is_empty() {
1640 tracing::debug!(?role, "No eligible relays");
1641 return None;
1642 }
1643
1644 // This algorithm uses rand::distr::WeightedIndex, and uses
1645 // gives O(n) time and space to build the index, plus O(log n)
1646 // sampling time.
1647 //
1648 // We might be better off building a WeightedIndex in advance
1649 // for each `role`, and then sampling it repeatedly until we
1650 // get a relay that satisfies `usable`. Or we might not --
1651 // that depends heavily on the actual particulars of our
1652 // inputs. We probably shouldn't make any changes there
1653 // unless profiling tells us that this function is in a hot
1654 // path.
1655 //
1656 // The C Tor sampling implementation goes through some trouble
1657 // here to try to make its path selection constant-time. I
1658 // believe that there is no actual remotely exploitable
1659 // side-channel here however. It could be worth analyzing in
1660 // the future.
1661 //
1662 // This code will give the wrong result if the total of all weights
1663 // can exceed u64::MAX. We make sure that can't happen when we
1664 // set up `self.weights`.
1665 match relays[..].choose_weighted(rng, |r| {
1666 let weight = self.weights.weight_rs_for_role(r.rs, role);
1667 tracing::trace!("relay:{id:?} role:{role:?} weight:{weight}", id = r.id());
1668 weight
1669 }) {
1670 Ok(relay) => Some(relay.clone()),
1671 Err(WeightError::InsufficientNonZero) => {
1672 warn!(?self.weights, ?role,
1673 "After filtering, all {} relays had zero weight. Choosing one at random. See bug #1907.",
1674 relays.len());
1675 relays.choose(rng).cloned()
1676 }
1677 Err(e) => {
1678 warn_report!(
1679 e,
1680 "Unexpected error while choosing from {} relays for role {:?}",
1681 relays.len(),
1682 role
1683 );
1684 None
1685 }
1686 }
1687 }
1688
1689 /// Choose `n` items (relays) at random, using the provided weights.
1690 ///
1691 /// This is intended as an internal, easier-to-test, implementation of
1692 /// `pick_n_relays`. `T` is generic for testing, but intended to be `Relay`.
1693 ///
1694 /// Items are chosen without replacement: no item will be returned twice.
1695 ///
1696 /// If *all* items have zero-weight, then up to `n` will be chosen randomly
1697 /// and returned. Otherwise, never returns items with zero-weight.
1698 ///
1699 /// May return fewer than `n` items if there are fewer than `n` with non-zero weight
1700 /// (or all have zero-weight but there are fewer than `n` total).
1701 #[allow(clippy::cognitive_complexity)] // all due to tracing crate.
1702 fn pick_n_weighted<R, T>(rng: &mut R, n: usize, weighted_items: &[(T, u64)]) -> Vec<T>
1703 where
1704 R: rand::Rng,
1705 T: Clone,
1706 {
1707 let mut sampled_items = match weighted_items[..]
1708 .sample_weighted(rng, n, |(_r, w)| *w as f64)
1709 {
1710 Err(e) => {
1711 warn_report!(e, "Unexpected error while sampling a set of items");
1712 Vec::new()
1713 }
1714 Ok(sampled_items) => {
1715 if sampled_items.len() < n {
1716 // Too few items had nonzero weights: return all of those that are okay.
1717 let nonzero_weight_items: Vec<_> = weighted_items
1718 .iter()
1719 .filter_map(|(i, w)| if *w > 0 { Some(i) } else { None })
1720 .cloned()
1721 .collect();
1722 if nonzero_weight_items.is_empty() {
1723 tracing::debug!(
1724 "All {} items had zero weight! Picking some at random. See bug #1907.",
1725 weighted_items.len()
1726 );
1727 let items: Vec<_> =
1728 weighted_items.iter().map(|(i, _w)| i.clone()).collect();
1729 if items.len() >= n {
1730 items.sample(rng, n).cloned().collect()
1731 } else {
1732 items
1733 }
1734 } else {
1735 tracing::debug!(
1736 "After filtering, only had {}/{} items with nonzero weight. Returning them all. See bug #1907.",
1737 nonzero_weight_items.len(),
1738 weighted_items.len()
1739 );
1740 nonzero_weight_items
1741 }
1742 } else {
1743 sampled_items.map(|(i, _w)| i.clone()).collect()
1744 }
1745 }
1746 };
1747 sampled_items.shuffle(rng);
1748 sampled_items
1749 }
1750
1751 /// Choose `n` relay at random.
1752 ///
1753 /// Each relay is chosen with probability proportional to its weight
1754 /// in the role `role`, and is only selected if the predicate `usable`
1755 /// returns true for it.
1756 ///
1757 /// Relays are chosen without replacement: no relay will be
1758 /// returned twice. Therefore, the resulting vector may be smaller
1759 /// than `n` if we happen to have fewer than `n` appropriate relays.
1760 ///
1761 /// Relays with zero-weight will be chosen only if there are *no* usable
1762 /// relays with nonzero-weight.
1763 ///
1764 /// This function returns an empty vector if (and only if) there are no
1765 /// relays where `usable` returned true.
1766 #[allow(clippy::cognitive_complexity)] // all due to tracing crate.
1767 pub fn pick_n_relays<'a, R, P>(
1768 &'a self,
1769 rng: &mut R,
1770 n: usize,
1771 role: WeightRole,
1772 usable: P,
1773 ) -> Vec<Relay<'a>>
1774 where
1775 R: rand::Rng,
1776 P: FnMut(&Relay<'a>) -> bool,
1777 {
1778 let filtered_weighted_relays: Vec<(Relay<'a>, u64)> = self
1779 .relays()
1780 .filter(usable)
1781 .map(|r| (r.clone(), self.weights.weight_rs_for_role(r.rs, role)))
1782 .collect();
1783 let res = NetDir::pick_n_weighted(rng, n, filtered_weighted_relays.as_slice());
1784 let n_found = res.len();
1785 if n_found < n {
1786 warn!(?self.weights, ?role,
1787 "Requested {n} relays, but only {n_usable} were usable, and only {n_found} were chosen after weighting {role:?}.",
1788 n_usable=filtered_weighted_relays.len(),
1789 );
1790 }
1791 res
1792 }
1793
1794 /// Compute the weight with which `relay` will be selected for a given
1795 /// `role`.
1796 pub fn relay_weight<'a>(&'a self, relay: &Relay<'a>, role: WeightRole) -> RelayWeight {
1797 RelayWeight(self.weights.weight_rs_for_role(relay.rs, role))
1798 }
1799
1800 /// Compute the total weight with which any relay matching `usable`
1801 /// will be selected for a given `role`.
1802 ///
1803 /// Note: because this function is used to assess the total
1804 /// properties of the consensus, the `usable` predicate takes a
1805 /// [`MdRouterStatus`] rather than a [`Relay`].
1806 pub fn total_weight<P>(&self, role: WeightRole, usable: P) -> RelayWeight
1807 where
1808 P: Fn(&UncheckedRelay<'_>) -> bool,
1809 {
1810 self.all_relays()
1811 .filter_map(|unchecked| {
1812 if usable(&unchecked) {
1813 Some(RelayWeight(
1814 self.weights.weight_rs_for_role(unchecked.rs, role),
1815 ))
1816 } else {
1817 None
1818 }
1819 })
1820 .sum()
1821 }
1822
1823 /// Compute the weight with which a relay with ID `rsa_id` would be
1824 /// selected for a given `role`.
1825 ///
1826 /// Note that weight returned by this function assumes that the
1827 /// relay with that ID is actually [usable](NetDir#usable); if it isn't usable,
1828 /// then other weight-related functions will call its weight zero.
1829 pub fn weight_by_rsa_id(&self, rsa_id: &RsaIdentity, role: WeightRole) -> Option<RelayWeight> {
1830 self.by_rsa_id_unchecked(rsa_id)
1831 .map(|unchecked| RelayWeight(self.weights.weight_rs_for_role(unchecked.rs, role)))
1832 }
1833
1834 /// Return all relays in this NetDir known to be in the same family as
1835 /// `relay`.
1836 ///
1837 /// This list of members will **not** necessarily include `relay` itself.
1838 ///
1839 /// # Limitations
1840 ///
1841 /// Two relays only belong to the same family if _each_ relay
1842 /// claims to share a family with the other. But if we are
1843 /// missing a microdescriptor for one of the relays listed by this
1844 /// relay, we cannot know whether it acknowledges family
1845 /// membership with this relay or not. Therefore, this function
1846 /// can omit family members for which there is not (as yet) any
1847 /// Relay object.
1848 pub fn known_family_members<'a>(
1849 &'a self,
1850 relay: &'a Relay<'a>,
1851 ) -> impl Iterator<Item = Relay<'a>> {
1852 let relay_rsa_id = relay.rsa_id();
1853 relay.md.family().members().filter_map(move |other_rsa_id| {
1854 self.by_rsa_id(other_rsa_id)
1855 .filter(|other_relay| other_relay.md.family().contains(relay_rsa_id))
1856 })
1857 }
1858
1859 /// Return the current hidden service directory "time period".
1860 ///
1861 /// Specifically, this returns the time period that contains the beginning
1862 /// of the validity period of this `NetDir`'s consensus. That time period
1863 /// is the one we use when acting as an hidden service client.
1864 #[cfg(feature = "hs-common")]
1865 pub fn hs_time_period(&self) -> TimePeriod {
1866 self.hsdir_rings.current.time_period()
1867 }
1868
1869 /// Return the [`HsDirParams`] of all the relevant hidden service directory "time periods"
1870 ///
1871 /// This includes the current time period (as from
1872 /// [`.hs_time_period`](NetDir::hs_time_period))
1873 /// plus additional time periods that we publish descriptors for when we are
1874 /// acting as a hidden service.
1875 #[cfg(feature = "hs-service")]
1876 pub fn hs_all_time_periods(&self) -> Vec<HsDirParams> {
1877 self.hsdir_rings
1878 .iter()
1879 .map(|r| r.params().clone())
1880 .collect()
1881 }
1882
1883 /// Return the relays in this network directory that will be used as hidden service directories
1884 ///
1885 /// These are suitable to retrieve a given onion service's descriptor at a given time period.
1886 #[cfg(feature = "hs-common")]
1887 pub fn hs_dirs_download<'r, R>(
1888 &'r self,
1889 hsid: HsBlindId,
1890 period: TimePeriod,
1891 rng: &mut R,
1892 ) -> std::result::Result<Vec<Relay<'r>>, Bug>
1893 where
1894 R: rand::Rng,
1895 {
1896 // Algorithm:
1897 //
1898 // 1. Determine which HsDirRing to use, based on the time period.
1899 // 2. Find the shared random value that's associated with that HsDirRing.
1900 // 3. Choose spread = the parameter `hsdir_spread_fetch`
1901 // 4. Let n_replicas = the parameter `hsdir_n_replicas`.
1902 // 5. Initialize Dirs = []
1903 // 6. for idx in 1..=n_replicas:
1904 // - let H = hsdir_ring::onion_service_index(id, replica, rand,
1905 // period).
1906 // - Find the position of H within hsdir_ring.
1907 // - Take elements from hsdir_ring starting at that position,
1908 // adding them to Dirs until we have added `spread` new elements
1909 // that were not there before.
1910 // 7. Shuffle Dirs
1911 // 8. return Dirs.
1912
1913 let spread = self.spread(HsDirOp::Download);
1914
1915 // When downloading, only look at relays on current ring.
1916 let ring = &self.hsdir_rings.current;
1917
1918 if ring.params().time_period != period {
1919 return Err(internal!(
1920 "our current ring is not associated with the requested time period!"
1921 ));
1922 }
1923
1924 let mut hs_dirs = self.select_hsdirs(hsid, ring, spread).collect_vec();
1925
1926 // When downloading, the order of the returned relays is random.
1927 hs_dirs.shuffle(rng);
1928
1929 Ok(hs_dirs)
1930 }
1931
1932 /// Return the relays in this network directory that will be used as hidden service directories
1933 ///
1934 /// Returns the relays that are suitable for storing a given onion service's descriptors at the
1935 /// given time period.
1936 #[cfg(feature = "hs-service")]
1937 pub fn hs_dirs_upload(
1938 &self,
1939 hsid: HsBlindId,
1940 period: TimePeriod,
1941 ) -> std::result::Result<impl Iterator<Item = Relay<'_>>, Bug> {
1942 // Algorithm:
1943 //
1944 // 1. Choose spread = the parameter `hsdir_spread_store`
1945 // 2. Determine which HsDirRing to use, based on the time period.
1946 // 3. Find the shared random value that's associated with that HsDirRing.
1947 // 4. Let n_replicas = the parameter `hsdir_n_replicas`.
1948 // 5. Initialize Dirs = []
1949 // 6. for idx in 1..=n_replicas:
1950 // - let H = hsdir_ring::onion_service_index(id, replica, rand,
1951 // period).
1952 // - Find the position of H within hsdir_ring.
1953 // - Take elements from hsdir_ring starting at that position,
1954 // adding them to Dirs until we have added `spread` new elements
1955 // that were not there before.
1956 // 3. return Dirs.
1957 let spread = self.spread(HsDirOp::Upload);
1958
1959 // For each HsBlindId, determine which HsDirRing to use.
1960 let rings = self
1961 .hsdir_rings
1962 .iter()
1963 .filter_map(move |ring| {
1964 // Make sure the ring matches the TP of the hsid it's matched with.
1965 (ring.params().time_period == period).then_some((ring, hsid, period))
1966 })
1967 .collect::<Vec<_>>();
1968
1969 // The specified period should have an associated ring.
1970 if !rings.iter().any(|(_, _, tp)| *tp == period) {
1971 return Err(internal!(
1972 "the specified time period does not have an associated ring"
1973 ));
1974 };
1975
1976 // Now that we've matched each `hsid` with the ring associated with its TP, we can start
1977 // selecting replicas from each ring.
1978 Ok(rings.into_iter().flat_map(move |(ring, hsid, period)| {
1979 assert_eq!(period, ring.params().time_period());
1980 self.select_hsdirs(hsid, ring, spread)
1981 }))
1982 }
1983
1984 /// Return the relays in this network directory that will be used as hidden service directories
1985 ///
1986 /// Depending on `op`,
1987 /// these are suitable to either store, or retrieve, a
1988 /// given onion service's descriptor at a given time period.
1989 ///
1990 /// When `op` is `Download`, the order is random.
1991 /// When `op` is `Upload`, the order is not specified.
1992 ///
1993 /// Return an error if the time period is not one returned by
1994 /// `onion_service_time_period` or `onion_service_secondary_time_periods`.
1995 //
1996 // TODO: make HsDirOp pub(crate) once this is removed
1997 #[cfg(feature = "hs-common")]
1998 #[deprecated(note = "Use hs_dirs_upload or hs_dirs_download instead")]
1999 pub fn hs_dirs<'r, R>(&'r self, hsid: &HsBlindId, op: HsDirOp, rng: &mut R) -> Vec<Relay<'r>>
2000 where
2001 R: rand::Rng,
2002 {
2003 // Algorithm:
2004 //
2005 // 1. Determine which HsDirRing to use, based on the time period.
2006 // 2. Find the shared random value that's associated with that HsDirRing.
2007 // 3. Choose spread = the parameter `hsdir_spread_store` or
2008 // `hsdir_spread_fetch` based on `op`.
2009 // 4. Let n_replicas = the parameter `hsdir_n_replicas`.
2010 // 5. Initialize Dirs = []
2011 // 6. for idx in 1..=n_replicas:
2012 // - let H = hsdir_ring::onion_service_index(id, replica, rand,
2013 // period).
2014 // - Find the position of H within hsdir_ring.
2015 // - Take elements from hsdir_ring starting at that position,
2016 // adding them to Dirs until we have added `spread` new elements
2017 // that were not there before.
2018 // 7. return Dirs.
2019 let n_replicas = self
2020 .params
2021 .hsdir_n_replicas
2022 .get()
2023 .try_into()
2024 .expect("BoundedInt did not enforce bounds");
2025
2026 let spread = match op {
2027 HsDirOp::Download => self.params.hsdir_spread_fetch,
2028 #[cfg(feature = "hs-service")]
2029 HsDirOp::Upload => self.params.hsdir_spread_store,
2030 };
2031
2032 let spread = spread
2033 .get()
2034 .try_into()
2035 .expect("BoundedInt did not enforce bounds!");
2036
2037 // TODO: I may be wrong here but I suspect that this function may
2038 // need refactoring so that it does not look at _all_ of the HsDirRings,
2039 // but only at the ones that corresponds to time periods for which
2040 // HsBlindId is valid. Or I could be mistaken, in which case we should
2041 // have a comment to explain why I am, since the logic is subtle.
2042 // (For clients, there is only one ring.) -nickm
2043 //
2044 // (Actually, there is no need to follow through with the above TODO,
2045 // since this function is deprecated, and not used anywhere but the
2046 // tests.)
2047
2048 let mut hs_dirs = self
2049 .hsdir_rings
2050 .iter_for_op(op)
2051 .cartesian_product(1..=n_replicas) // 1-indexed !
2052 .flat_map({
2053 let mut selected_nodes = HashSet::new();
2054
2055 move |(ring, replica): (&HsDirRing, u8)| {
2056 let hsdir_idx = hsdir_ring::service_hsdir_index(hsid, replica, ring.params());
2057
2058 ring.ring_items_at(hsdir_idx, spread, |(hsdir_idx, _)| {
2059 // According to rend-spec 2.2.3:
2060 // ... If any of those
2061 // nodes have already been selected for a lower-numbered replica of the
2062 // service, any nodes already chosen are disregarded (i.e. skipped over)
2063 // when choosing a replica's hsdir_spread_store nodes.
2064 selected_nodes.insert(*hsdir_idx)
2065 })
2066 .collect::<Vec<_>>()
2067 }
2068 })
2069 .filter_map(|(_hsdir_idx, rs_idx)| {
2070 // This ought not to be None but let's not panic or bail if it is
2071 self.relay_by_rs_idx(*rs_idx)
2072 })
2073 .collect_vec();
2074
2075 match op {
2076 HsDirOp::Download => {
2077 // When `op` is `Download`, the order is random.
2078 hs_dirs.shuffle(rng);
2079 }
2080 #[cfg(feature = "hs-service")]
2081 HsDirOp::Upload => {
2082 // When `op` is `Upload`, the order is not specified.
2083 }
2084 }
2085
2086 hs_dirs
2087 }
2088}
2089
2090impl MdReceiver for NetDir {
2091 fn missing_microdescs(&self) -> Box<dyn Iterator<Item = &MdDigest> + '_> {
2092 Box::new(self.rsidx_by_missing.keys())
2093 }
2094 fn add_microdesc(&mut self, md: MicrodescAndHash) -> bool {
2095 self.add_arc_microdesc(Arc::new(md))
2096 }
2097 fn n_missing(&self) -> usize {
2098 self.rsidx_by_missing.len()
2099 }
2100}
2101
2102impl<'a> UncheckedRelay<'a> {
2103 /// Return an [`UncheckedRelayDetails`](details::UncheckedRelayDetails) for this relay.
2104 ///
2105 /// Callers should generally avoid using this information directly if they can;
2106 /// it's better to use a higher-level function that exposes semantic information
2107 /// rather than these properties.
2108 pub fn low_level_details(&self) -> details::UncheckedRelayDetails<'_> {
2109 details::UncheckedRelayDetails(self)
2110 }
2111
2112 /// Return true if this relay is valid and [usable](NetDir#usable).
2113 ///
2114 /// This function should return `true` for every Relay we expose
2115 /// to the user.
2116 pub fn is_usable(&self) -> bool {
2117 // No need to check for 'valid' or 'running': they are implicit.
2118 self.md.is_some() && self.rs.ed25519_id_is_usable()
2119 }
2120 /// If this is [usable](NetDir#usable), return a corresponding Relay object.
2121 pub fn into_relay(self) -> Option<Relay<'a>> {
2122 if self.is_usable() {
2123 Some(Relay {
2124 rs: self.rs,
2125 md: self.md?,
2126 #[cfg(feature = "geoip")]
2127 cc: self.cc,
2128 })
2129 } else {
2130 None
2131 }
2132 }
2133
2134 /// Return true if this relay is a hidden service directory
2135 ///
2136 /// Ie, if it is to be included in the hsdir ring.
2137 #[cfg(feature = "hs-common")]
2138 pub(crate) fn is_hsdir_for_ring(&self) -> bool {
2139 // TODO are there any other flags should we check?
2140 // rend-spec-v3 2.2.3 says just
2141 // "each node listed in the current consensus with the HSDir flag"
2142 // Do we need to check ed25519_id_is_usable ?
2143 // See also https://gitlab.torproject.org/tpo/core/arti/-/issues/504
2144 self.rs.is_flagged_hsdir()
2145 }
2146}
2147
2148impl<'a> Relay<'a> {
2149 /// Return a [`RelayDetails`](details::RelayDetails) for this relay.
2150 ///
2151 /// Callers should generally avoid using this information directly if they can;
2152 /// it's better to use a higher-level function that exposes semantic information
2153 /// rather than these properties.
2154 pub fn low_level_details(&self) -> details::RelayDetails<'_> {
2155 details::RelayDetails(self)
2156 }
2157
2158 /// Return the Ed25519 ID for this relay.
2159 pub fn id(&self) -> &Ed25519Identity {
2160 self.md.ed25519_id()
2161 }
2162 /// Return the RsaIdentity for this relay.
2163 pub fn rsa_id(&self) -> &RsaIdentity {
2164 self.rs.rsa_identity()
2165 }
2166
2167 /// Return a reference to this relay's "router status" entry in
2168 /// the consensus.
2169 ///
2170 /// The router status entry contains information about the relay
2171 /// that the authorities voted on directly. For most use cases,
2172 /// you shouldn't need them.
2173 ///
2174 /// This function is only available if the crate was built with
2175 /// its `experimental-api` feature.
2176 #[cfg(feature = "experimental-api")]
2177 pub fn rs(&self) -> &netstatus::MdRouterStatus {
2178 self.rs
2179 }
2180 /// Return a reference to this relay's "microdescriptor" entry in
2181 /// the consensus.
2182 ///
2183 /// A "microdescriptor" is a synopsis of the information about a relay,
2184 /// used to determine its capabilities and route traffic through it.
2185 /// For most use cases, you shouldn't need it.
2186 ///
2187 /// This function is only available if the crate was built with
2188 /// its `experimental-api` feature.
2189 #[cfg(feature = "experimental-api")]
2190 pub fn md(&self) -> &MicrodescAndHash {
2191 self.md
2192 }
2193}
2194
2195/// An error value returned from [`NetDir::by_ids_detailed`].
2196#[cfg(feature = "hs-common")]
2197#[derive(Clone, Debug, thiserror::Error)]
2198#[non_exhaustive]
2199pub enum RelayLookupError {
2200 /// We found a relay whose presence indicates that the provided set of
2201 /// identities is impossible to resolve.
2202 #[error("Provided set of identities is impossible according to consensus.")]
2203 Impossible,
2204}
2205
2206impl<'a> HasAddrs for Relay<'a> {
2207 fn addrs(&self) -> impl Iterator<Item = std::net::SocketAddr> {
2208 self.rs.addrs()
2209 }
2210}
2211#[cfg(feature = "geoip")]
2212impl<'a> HasCountryCode for Relay<'a> {
2213 fn country_code(&self) -> Option<CountryCode> {
2214 self.cc
2215 }
2216}
2217impl<'a> tor_linkspec::HasRelayIdsLegacy for Relay<'a> {
2218 fn ed_identity(&self) -> &Ed25519Identity {
2219 self.id()
2220 }
2221 fn rsa_identity(&self) -> &RsaIdentity {
2222 self.rsa_id()
2223 }
2224}
2225
2226impl<'a> HasRelayIds for UncheckedRelay<'a> {
2227 fn identity(&self, key_type: RelayIdType) -> Option<RelayIdRef<'_>> {
2228 match key_type {
2229 RelayIdType::Ed25519 if self.rs.ed25519_id_is_usable() => {
2230 self.md.map(|m| m.ed25519_id().into())
2231 }
2232 RelayIdType::Rsa => Some(self.rs.rsa_identity().into()),
2233 _ => None,
2234 }
2235 }
2236}
2237#[cfg(feature = "geoip")]
2238impl<'a> HasCountryCode for UncheckedRelay<'a> {
2239 fn country_code(&self) -> Option<CountryCode> {
2240 self.cc
2241 }
2242}
2243
2244impl<'a> DirectChanMethodsHelper for Relay<'a> {}
2245impl<'a> ChanTarget for Relay<'a> {}
2246
2247impl<'a> tor_linkspec::CircTarget for Relay<'a> {
2248 fn ntor_onion_key(&self) -> &ll::pk::curve25519::PublicKey {
2249 self.md.ntor_key()
2250 }
2251 fn protovers(&self) -> &tor_protover::Protocols {
2252 self.rs.protovers()
2253 }
2254}
2255
2256#[cfg(test)]
2257mod test {
2258 // @@ begin test lint list maintained by maint/add_warning @@
2259 #![allow(clippy::bool_assert_comparison)]
2260 #![allow(clippy::clone_on_copy)]
2261 #![allow(clippy::dbg_macro)]
2262 #![allow(clippy::mixed_attributes_style)]
2263 #![allow(clippy::print_stderr)]
2264 #![allow(clippy::print_stdout)]
2265 #![allow(clippy::single_char_pattern)]
2266 #![allow(clippy::unwrap_used)]
2267 #![allow(clippy::unchecked_time_subtraction)]
2268 #![allow(clippy::useless_vec)]
2269 #![allow(clippy::needless_pass_by_value)]
2270 #![allow(clippy::string_slice)] // See arti#2571
2271 //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
2272 #![allow(clippy::cognitive_complexity)]
2273 use super::*;
2274 use crate::testnet::*;
2275 use float_eq::assert_float_eq;
2276 use std::collections::HashSet;
2277 use std::time::Duration;
2278 use tor_basic_utils::test_rng::{self, testing_rng};
2279 use tor_linkspec::{RelayIdType, RelayIds};
2280
2281 #[cfg(feature = "hs-common")]
2282 fn dummy_hs_blind_id() -> HsBlindId {
2283 let hsid = [2, 1, 1, 1].iter().cycle().take(32).cloned().collect_vec();
2284 let hsid = Ed25519Identity::new(hsid[..].try_into().unwrap());
2285 HsBlindId::from(hsid)
2286 }
2287
2288 // Basic functionality for a partial netdir: Add microdescriptors,
2289 // then you have a netdir.
2290 #[test]
2291 fn partial_netdir() {
2292 let (consensus, microdescs) = construct_network().unwrap();
2293 let dir = PartialNetDir::new(consensus, None);
2294
2295 // Check the lifetime
2296 let lifetime = dir.lifetime();
2297 assert_eq!(
2298 lifetime
2299 .valid_until()
2300 .duration_since(lifetime.valid_after())
2301 .unwrap(),
2302 Duration::new(86400, 0)
2303 );
2304
2305 // No microdescriptors, so we don't have enough paths, and can't
2306 // advance.
2307 assert!(!dir.have_enough_paths());
2308 let mut dir = match dir.unwrap_if_sufficient() {
2309 Ok(_) => panic!(),
2310 Err(d) => d,
2311 };
2312
2313 let missing: HashSet<_> = dir.missing_microdescs().collect();
2314 assert_eq!(missing.len(), 40);
2315 assert_eq!(missing.len(), dir.netdir.c_relays().len());
2316 for md in µdescs {
2317 assert!(missing.contains(md.digest()));
2318 }
2319
2320 // Now add all the mds and try again.
2321 for md in microdescs {
2322 let wanted = dir.add_microdesc(md);
2323 assert!(wanted);
2324 }
2325
2326 let missing: HashSet<_> = dir.missing_microdescs().collect();
2327 assert!(missing.is_empty());
2328 assert!(dir.have_enough_paths());
2329 let _complete = match dir.unwrap_if_sufficient() {
2330 Ok(d) => d,
2331 Err(_) => panic!(),
2332 };
2333 }
2334
2335 #[test]
2336 fn override_params() {
2337 let (consensus, _microdescs) = construct_network().unwrap();
2338 let override_p = "bwweightscale=2 doesnotexist=77 circwindow=500"
2339 .parse()
2340 .unwrap();
2341 let dir = PartialNetDir::new(consensus.clone(), Some(&override_p));
2342 let params = &dir.netdir.params;
2343 assert_eq!(params.bw_weight_scale.get(), 2);
2344 assert_eq!(params.circuit_window.get(), 500_i32);
2345
2346 // try again without the override.
2347 let dir = PartialNetDir::new(consensus, None);
2348 let params = &dir.netdir.params;
2349 assert_eq!(params.bw_weight_scale.get(), 1_i32);
2350 assert_eq!(params.circuit_window.get(), 1000_i32);
2351 }
2352
2353 #[test]
2354 fn fill_from_previous() {
2355 let (consensus, microdescs) = construct_network().unwrap();
2356
2357 let mut dir = PartialNetDir::new(consensus.clone(), None);
2358 for md in microdescs.iter().skip(2) {
2359 let wanted = dir.add_microdesc(md.clone());
2360 assert!(wanted);
2361 }
2362 let dir1 = dir.unwrap_if_sufficient().unwrap();
2363 assert_eq!(dir1.missing_microdescs().count(), 2);
2364
2365 let mut dir = PartialNetDir::new(consensus, None);
2366 assert_eq!(dir.missing_microdescs().count(), 40);
2367 dir.fill_from_previous_netdir(Arc::new(dir1));
2368 assert_eq!(dir.missing_microdescs().count(), 2);
2369 }
2370
2371 #[test]
2372 fn path_count() {
2373 let low_threshold = "min_paths_for_circs_pct=64".parse().unwrap();
2374 let high_threshold = "min_paths_for_circs_pct=65".parse().unwrap();
2375
2376 let (consensus, microdescs) = construct_network().unwrap();
2377
2378 let mut dir = PartialNetDir::new(consensus.clone(), Some(&low_threshold));
2379 for (pos, md) in microdescs.iter().enumerate() {
2380 if pos % 7 == 2 {
2381 continue; // skip a few relays.
2382 }
2383 dir.add_microdesc(md.clone());
2384 }
2385 let dir = dir.unwrap_if_sufficient().unwrap();
2386
2387 // We have 40 relays that we know about from the consensus.
2388 assert_eq!(dir.all_relays().count(), 40);
2389
2390 // But only 34 are usable.
2391 assert_eq!(dir.relays().count(), 34);
2392
2393 // For guards: mds 20..=39 correspond to Guard relays.
2394 // Their bandwidth is 2*(1000+2000+...10000) = 110_000.
2395 // We skipped 23, 30, and 37. They have bandwidth
2396 // 4000 + 1000 + 8000 = 13_000. So our fractional bandwidth
2397 // should be (110-13)/110.
2398 let f = dir.frac_for_role(WeightRole::Guard, |u| u.rs.is_flagged_guard());
2399 assert!(((97.0 / 110.0) - f).abs() < 0.000001);
2400
2401 // For exits: mds 10..=19 and 30..=39 correspond to Exit relays.
2402 // We skipped 16, 30, and 37. Per above our fractional bandwidth is
2403 // (110-16)/110.
2404 let f = dir.frac_for_role(WeightRole::Exit, |u| u.rs.is_flagged_exit());
2405 assert!(((94.0 / 110.0) - f).abs() < 0.000001);
2406
2407 // For middles: all relays are middles. We skipped 2, 9, 16,
2408 // 23, 30, and 37. Per above our fractional bandwidth is
2409 // (220-33)/220
2410 let f = dir.frac_for_role(WeightRole::Middle, |_| true);
2411 assert!(((187.0 / 220.0) - f).abs() < 0.000001);
2412
2413 // Multiplying those together, we get the fraction of paths we can
2414 // build at ~0.64052066, which is above the threshold we set above for
2415 // MinPathsForCircsPct.
2416 let f = dir.frac_usable_paths();
2417 assert!((f - 0.64052066).abs() < 0.000001);
2418
2419 // But if we try again with a slightly higher threshold...
2420 let mut dir = PartialNetDir::new(consensus, Some(&high_threshold));
2421 for (pos, md) in microdescs.into_iter().enumerate() {
2422 if pos % 7 == 2 {
2423 continue; // skip a few relays.
2424 }
2425 dir.add_microdesc(md);
2426 }
2427 assert!(dir.unwrap_if_sufficient().is_err());
2428 }
2429
2430 /// Return a 3-tuple for use by `test_pick_*()` of an Rng, a number of
2431 /// iterations, and a tolerance.
2432 ///
2433 /// If the Rng is deterministic (the default), we can use a faster setup,
2434 /// with a higher tolerance and fewer iterations. But if you've explicitly
2435 /// opted into randomization (or are replaying a seed from an earlier
2436 /// randomized test), we give you more iterations and a tighter tolerance.
2437 fn testing_rng_with_tolerances() -> (impl rand::Rng, usize, f64) {
2438 // Use a deterministic RNG if none is specified, since this is slow otherwise.
2439 let config = test_rng::Config::from_env().unwrap_or(test_rng::Config::Deterministic);
2440 let (iters, tolerance) = match config {
2441 test_rng::Config::Deterministic => (5000, 0.02),
2442 _ => (50000, 0.01),
2443 };
2444 (config.into_rng(), iters, tolerance)
2445 }
2446
2447 #[test]
2448 fn test_pick() {
2449 let (consensus, microdescs) = construct_network().unwrap();
2450 let mut dir = PartialNetDir::new(consensus, None);
2451 for md in microdescs.into_iter() {
2452 let wanted = dir.add_microdesc(md.clone());
2453 assert!(wanted);
2454 }
2455 let dir = dir.unwrap_if_sufficient().unwrap();
2456
2457 let (mut rng, total, tolerance) = testing_rng_with_tolerances();
2458
2459 let mut picked = [0_isize; 40];
2460 for _ in 0..total {
2461 let r = dir.pick_relay(&mut rng, WeightRole::Middle, |r| {
2462 r.low_level_details().supports_exit_port_ipv4(80)
2463 });
2464 let r = r.unwrap();
2465 let id_byte = r.identity(RelayIdType::Rsa).unwrap().as_bytes()[0];
2466 picked[id_byte as usize] += 1;
2467 }
2468 // non-exits should never get picked.
2469 picked[0..10].iter().for_each(|x| assert_eq!(*x, 0));
2470 picked[20..30].iter().for_each(|x| assert_eq!(*x, 0));
2471
2472 let picked_f: Vec<_> = picked.iter().map(|x| *x as f64 / total as f64).collect();
2473
2474 // We didn't we any non-default weights, so the other relays get
2475 // weighted proportional to their bandwidth.
2476 assert_float_eq!(picked_f[19], (10.0 / 110.0), abs <= tolerance);
2477 assert_float_eq!(picked_f[38], (9.0 / 110.0), abs <= tolerance);
2478 assert_float_eq!(picked_f[39], (10.0 / 110.0), abs <= tolerance);
2479 }
2480
2481 #[test]
2482 fn test_pick_multiple() {
2483 // This is mostly a copy of test_pick, except that it uses
2484 // pick_n_relays to pick several relays at once.
2485
2486 let dir = construct_netdir().unwrap_if_sufficient().unwrap();
2487
2488 let (mut rng, total, tolerance) = testing_rng_with_tolerances();
2489
2490 let mut picked = [0_isize; 40];
2491 for _ in 0..total / 4 {
2492 let relays = dir.pick_n_relays(&mut rng, 4, WeightRole::Middle, |r| {
2493 r.low_level_details().supports_exit_port_ipv4(80)
2494 });
2495 assert_eq!(relays.len(), 4);
2496 for r in relays {
2497 let id_byte = r.identity(RelayIdType::Rsa).unwrap().as_bytes()[0];
2498 picked[id_byte as usize] += 1;
2499 }
2500 }
2501 // non-exits should never get picked.
2502 picked[0..10].iter().for_each(|x| assert_eq!(*x, 0));
2503 picked[20..30].iter().for_each(|x| assert_eq!(*x, 0));
2504
2505 let picked_f: Vec<_> = picked.iter().map(|x| *x as f64 / total as f64).collect();
2506
2507 // We didn't we any non-default weights, so the other relays get
2508 // weighted proportional to their bandwidth.
2509 assert_float_eq!(picked_f[19], (10.0 / 110.0), abs <= tolerance);
2510 assert_float_eq!(picked_f[36], (7.0 / 110.0), abs <= tolerance);
2511 assert_float_eq!(picked_f[39], (10.0 / 110.0), abs <= tolerance);
2512 }
2513
2514 #[test]
2515 fn test_pick_multiple_from_insufficient() {
2516 // This is intended to test `NetDir::pick_n_relays`, but targets the internal,
2517 // easier-to-test `NetDir::pick_n_weighted` that is used to implement it.
2518
2519 let mut rng = testing_rng();
2520
2521 // If *any* item (relay) has non-zero weight, then we return only those, even if we asked for more.
2522 let mostly_zeros = vec![("dud1", 0), ("dud2", 0), ("ok", 1), ("dud3", 0)];
2523 for n in [1, 2, 10] {
2524 assert_eq!(
2525 NetDir::pick_n_weighted(&mut rng, n, &mostly_zeros[..]),
2526 vec!["ok"],
2527 "where n={n}"
2528 );
2529 }
2530
2531 // If *all* items have zero weight, and we ask for as many or more than
2532 // we have, we get back all of them.
2533 let all_zeros = vec![("dud1", 0), ("dud2", 0), ("dud3", 0)];
2534 let all_zeros_items = all_zeros.iter().map(|(x, _w)| *x).collect::<Vec<_>>();
2535 for n in [all_zeros.len(), all_zeros.len() + 10] {
2536 let mut res = NetDir::pick_n_weighted(&mut rng, n, &all_zeros[..]);
2537 res.sort();
2538 assert_eq!(res, all_zeros_items, "where n={n}");
2539 }
2540
2541 // If *all* items have zero weight, and we ask for fewer than we have,
2542 // we get back as many as we asked for.
2543 let n = all_zeros.len() / 2;
2544 let res = NetDir::pick_n_weighted(&mut rng, n, &all_zeros[..]);
2545 assert_eq!(res.len(), n);
2546 }
2547
2548 #[test]
2549 fn subnets() {
2550 let cfg = SubnetConfig::default();
2551
2552 fn same_net(cfg: &SubnetConfig, a: &str, b: &str) -> bool {
2553 cfg.addrs_in_same_subnet(&a.parse().unwrap(), &b.parse().unwrap())
2554 }
2555
2556 assert!(same_net(&cfg, "127.15.3.3", "127.15.9.9"));
2557 assert!(!same_net(&cfg, "127.15.3.3", "127.16.9.9"));
2558
2559 assert!(!same_net(&cfg, "127.15.3.3", "127::"));
2560
2561 assert!(same_net(&cfg, "ffff:ffff:90:33::", "ffff:ffff:91:34::"));
2562 assert!(!same_net(&cfg, "ffff:ffff:90:33::", "ffff:fffe:91:34::"));
2563
2564 let cfg = SubnetConfig {
2565 subnets_family_v4: 32,
2566 subnets_family_v6: 128,
2567 };
2568 assert!(!same_net(&cfg, "127.15.3.3", "127.15.9.9"));
2569 assert!(!same_net(&cfg, "ffff:ffff:90:33::", "ffff:ffff:91:34::"));
2570
2571 assert!(same_net(&cfg, "127.0.0.1", "127.0.0.1"));
2572 assert!(!same_net(&cfg, "127.0.0.1", "127.0.0.2"));
2573 assert!(same_net(&cfg, "ffff:ffff:90:33::", "ffff:ffff:90:33::"));
2574
2575 let cfg = SubnetConfig {
2576 subnets_family_v4: 33,
2577 subnets_family_v6: 129,
2578 };
2579 assert!(!same_net(&cfg, "127.0.0.1", "127.0.0.1"));
2580 assert!(!same_net(&cfg, "::", "::"));
2581 }
2582
2583 #[test]
2584 fn subnet_union() {
2585 let cfg1 = SubnetConfig {
2586 subnets_family_v4: 16,
2587 subnets_family_v6: 64,
2588 };
2589 let cfg2 = SubnetConfig {
2590 subnets_family_v4: 24,
2591 subnets_family_v6: 32,
2592 };
2593 let a1 = "1.2.3.4".parse().unwrap();
2594 let a2 = "1.2.10.10".parse().unwrap();
2595
2596 let a3 = "ffff:ffff::7".parse().unwrap();
2597 let a4 = "ffff:ffff:1234::8".parse().unwrap();
2598
2599 assert_eq!(cfg1.addrs_in_same_subnet(&a1, &a2), true);
2600 assert_eq!(cfg2.addrs_in_same_subnet(&a1, &a2), false);
2601
2602 assert_eq!(cfg1.addrs_in_same_subnet(&a3, &a4), false);
2603 assert_eq!(cfg2.addrs_in_same_subnet(&a3, &a4), true);
2604
2605 let cfg_u = cfg1.union(&cfg2);
2606 assert_eq!(
2607 cfg_u,
2608 SubnetConfig {
2609 subnets_family_v4: 16,
2610 subnets_family_v6: 32,
2611 }
2612 );
2613 assert_eq!(cfg_u.addrs_in_same_subnet(&a1, &a2), true);
2614 assert_eq!(cfg_u.addrs_in_same_subnet(&a3, &a4), true);
2615
2616 assert_eq!(cfg1.union(&cfg1), cfg1);
2617
2618 assert_eq!(cfg1.union(&SubnetConfig::no_addresses_match()), cfg1);
2619 }
2620
2621 #[test]
2622 fn relay_funcs() {
2623 let (consensus, microdescs) = construct_custom_network(
2624 |pos, nb, _| {
2625 if pos == 15 {
2626 nb.rs.add_or_port("[f0f0::30]:9001".parse().unwrap());
2627 } else if pos == 20 {
2628 nb.rs.add_or_port("[f0f0::3131]:9001".parse().unwrap());
2629 }
2630 },
2631 None,
2632 )
2633 .unwrap();
2634 let subnet_config = SubnetConfig::default();
2635 let all_family_info = FamilyRules::all_family_info();
2636 let mut dir = PartialNetDir::new(consensus, None);
2637 for md in microdescs.into_iter() {
2638 let wanted = dir.add_microdesc(md.clone());
2639 assert!(wanted);
2640 }
2641 let dir = dir.unwrap_if_sufficient().unwrap();
2642
2643 // Pick out a few relays by ID.
2644 let k0 = Ed25519Identity::from([0; 32]);
2645 let k1 = Ed25519Identity::from([1; 32]);
2646 let k2 = Ed25519Identity::from([2; 32]);
2647 let k3 = Ed25519Identity::from([3; 32]);
2648 let k10 = Ed25519Identity::from([10; 32]);
2649 let k15 = Ed25519Identity::from([15; 32]);
2650 let k20 = Ed25519Identity::from([20; 32]);
2651
2652 let r0 = dir.by_id(&k0).unwrap();
2653 let r1 = dir.by_id(&k1).unwrap();
2654 let r2 = dir.by_id(&k2).unwrap();
2655 let r3 = dir.by_id(&k3).unwrap();
2656 let r10 = dir.by_id(&k10).unwrap();
2657 let r15 = dir.by_id(&k15).unwrap();
2658 let r20 = dir.by_id(&k20).unwrap();
2659
2660 assert_eq!(r0.id(), &[0; 32].into());
2661 assert_eq!(r0.rsa_id(), &[0; 20].into());
2662 assert_eq!(r1.id(), &[1; 32].into());
2663 assert_eq!(r1.rsa_id(), &[1; 20].into());
2664
2665 assert!(r0.same_relay_ids(&r0));
2666 assert!(r1.same_relay_ids(&r1));
2667 assert!(!r1.same_relay_ids(&r0));
2668
2669 assert!(r0.low_level_details().is_dir_cache());
2670 assert!(!r1.low_level_details().is_dir_cache());
2671 assert!(r2.low_level_details().is_dir_cache());
2672 assert!(!r3.low_level_details().is_dir_cache());
2673
2674 assert!(!r0.low_level_details().supports_exit_port_ipv4(80));
2675 assert!(!r1.low_level_details().supports_exit_port_ipv4(80));
2676 assert!(!r2.low_level_details().supports_exit_port_ipv4(80));
2677 assert!(!r3.low_level_details().supports_exit_port_ipv4(80));
2678
2679 assert!(!r0.low_level_details().policies_allow_some_port());
2680 assert!(!r1.low_level_details().policies_allow_some_port());
2681 assert!(!r2.low_level_details().policies_allow_some_port());
2682 assert!(!r3.low_level_details().policies_allow_some_port());
2683 assert!(r10.low_level_details().policies_allow_some_port());
2684
2685 assert!(r0.low_level_details().in_same_family(&r0, all_family_info));
2686 assert!(r0.low_level_details().in_same_family(&r1, all_family_info));
2687 assert!(r1.low_level_details().in_same_family(&r0, all_family_info));
2688 assert!(r1.low_level_details().in_same_family(&r1, all_family_info));
2689 assert!(!r0.low_level_details().in_same_family(&r2, all_family_info));
2690 assert!(!r2.low_level_details().in_same_family(&r0, all_family_info));
2691 assert!(r2.low_level_details().in_same_family(&r2, all_family_info));
2692 assert!(r2.low_level_details().in_same_family(&r3, all_family_info));
2693
2694 assert!(r0.low_level_details().in_same_subnet(&r10, &subnet_config));
2695 assert!(r10.low_level_details().in_same_subnet(&r10, &subnet_config));
2696 assert!(r0.low_level_details().in_same_subnet(&r0, &subnet_config));
2697 assert!(r1.low_level_details().in_same_subnet(&r1, &subnet_config));
2698 assert!(!r1.low_level_details().in_same_subnet(&r2, &subnet_config));
2699 assert!(!r2.low_level_details().in_same_subnet(&r3, &subnet_config));
2700
2701 // Make sure IPv6 families work.
2702 let subnet_config = SubnetConfig {
2703 subnets_family_v4: 128,
2704 subnets_family_v6: 96,
2705 };
2706 assert!(r15.low_level_details().in_same_subnet(&r20, &subnet_config));
2707 assert!(!r15.low_level_details().in_same_subnet(&r1, &subnet_config));
2708
2709 // Make sure that subnet configs can be disabled.
2710 let subnet_config = SubnetConfig {
2711 subnets_family_v4: 255,
2712 subnets_family_v6: 255,
2713 };
2714 assert!(!r15.low_level_details().in_same_subnet(&r20, &subnet_config));
2715 }
2716
2717 #[test]
2718 fn test_badexit() {
2719 // make a netdir where relays 10-19 are badexit, and everybody
2720 // exits to 443 on IPv6.
2721 use tor_netdoc::types::relay_flags::RelayFlag;
2722 let netdir = construct_custom_netdir(|pos, nb, _| {
2723 if (10..20).contains(&pos) {
2724 nb.rs.add_flags(RelayFlag::BadExit);
2725 }
2726 nb.md.parse_ipv6_policy("accept 443").unwrap();
2727 })
2728 .unwrap()
2729 .unwrap_if_sufficient()
2730 .unwrap();
2731
2732 let e12 = netdir.by_id(&Ed25519Identity::from([12; 32])).unwrap();
2733 let e32 = netdir.by_id(&Ed25519Identity::from([32; 32])).unwrap();
2734
2735 assert!(!e12.low_level_details().supports_exit_port_ipv4(80));
2736 assert!(e32.low_level_details().supports_exit_port_ipv4(80));
2737
2738 assert!(!e12.low_level_details().supports_exit_port_ipv6(443));
2739 assert!(e32.low_level_details().supports_exit_port_ipv6(443));
2740 assert!(!e32.low_level_details().supports_exit_port_ipv6(555));
2741
2742 assert!(!e12.low_level_details().policies_allow_some_port());
2743 assert!(e32.low_level_details().policies_allow_some_port());
2744
2745 assert!(!e12.low_level_details().ipv4_policy().allows_some_port());
2746 assert!(!e12.low_level_details().ipv6_policy().allows_some_port());
2747 assert!(e32.low_level_details().ipv4_policy().allows_some_port());
2748 assert!(e32.low_level_details().ipv6_policy().allows_some_port());
2749
2750 assert!(
2751 e12.low_level_details()
2752 .ipv4_declared_policy()
2753 .allows_some_port()
2754 );
2755 assert!(
2756 e12.low_level_details()
2757 .ipv6_declared_policy()
2758 .allows_some_port()
2759 );
2760 }
2761
2762 #[cfg(feature = "experimental-api")]
2763 #[test]
2764 fn test_accessors() {
2765 let netdir = construct_netdir().unwrap_if_sufficient().unwrap();
2766
2767 let r4 = netdir.by_id(&Ed25519Identity::from([4; 32])).unwrap();
2768 let r16 = netdir.by_id(&Ed25519Identity::from([16; 32])).unwrap();
2769
2770 assert!(!r4.md().ipv4_policy().allows_some_port());
2771 assert!(r16.md().ipv4_policy().allows_some_port());
2772
2773 assert!(!r4.rs().is_flagged_exit());
2774 assert!(r16.rs().is_flagged_exit());
2775 }
2776
2777 #[test]
2778 fn test_by_id() {
2779 // Make a netdir that omits the microdescriptor for 0xDDDDDD...
2780 let netdir = construct_custom_netdir(|pos, nb, _| {
2781 nb.omit_md = pos == 13;
2782 })
2783 .unwrap();
2784
2785 let netdir = netdir.unwrap_if_sufficient().unwrap();
2786
2787 let r = netdir.by_id(&Ed25519Identity::from([0; 32])).unwrap();
2788 assert_eq!(r.id().as_bytes(), &[0; 32]);
2789
2790 assert!(netdir.by_id(&Ed25519Identity::from([13; 32])).is_none());
2791
2792 let r = netdir.by_rsa_id(&[12; 20].into()).unwrap();
2793 assert_eq!(r.rsa_id().as_bytes(), &[12; 20]);
2794 assert!(netdir.rsa_id_is_listed(&[12; 20].into()));
2795
2796 assert!(netdir.by_rsa_id(&[13; 20].into()).is_none());
2797
2798 assert!(netdir.by_rsa_id_unchecked(&[99; 20].into()).is_none());
2799 assert!(!netdir.rsa_id_is_listed(&[99; 20].into()));
2800
2801 let r = netdir.by_rsa_id_unchecked(&[13; 20].into()).unwrap();
2802 assert_eq!(r.rs.rsa_identity().as_bytes(), &[13; 20]);
2803 assert!(netdir.rsa_id_is_listed(&[13; 20].into()));
2804
2805 let pair_13_13 = RelayIds::builder()
2806 .ed_identity([13; 32].into())
2807 .rsa_identity([13; 20].into())
2808 .build()
2809 .unwrap();
2810 let pair_14_14 = RelayIds::builder()
2811 .ed_identity([14; 32].into())
2812 .rsa_identity([14; 20].into())
2813 .build()
2814 .unwrap();
2815 let pair_14_99 = RelayIds::builder()
2816 .ed_identity([14; 32].into())
2817 .rsa_identity([99; 20].into())
2818 .build()
2819 .unwrap();
2820
2821 let r = netdir.by_ids(&pair_13_13);
2822 assert!(r.is_none());
2823 let r = netdir.by_ids(&pair_14_14).unwrap();
2824 assert_eq!(r.identity(RelayIdType::Rsa).unwrap().as_bytes(), &[14; 20]);
2825 assert_eq!(
2826 r.identity(RelayIdType::Ed25519).unwrap().as_bytes(),
2827 &[14; 32]
2828 );
2829 let r = netdir.by_ids(&pair_14_99);
2830 assert!(r.is_none());
2831
2832 assert_eq!(
2833 netdir.id_pair_listed(&[13; 32].into(), &[13; 20].into()),
2834 None
2835 );
2836 assert_eq!(
2837 netdir.id_pair_listed(&[15; 32].into(), &[15; 20].into()),
2838 Some(true)
2839 );
2840 assert_eq!(
2841 netdir.id_pair_listed(&[15; 32].into(), &[99; 20].into()),
2842 Some(false)
2843 );
2844 }
2845
2846 #[test]
2847 #[cfg(feature = "hs-common")]
2848 fn test_by_ids_detailed() {
2849 // Make a netdir that omits the microdescriptor for 0xDDDDDD...
2850 let netdir = construct_custom_netdir(|pos, nb, _| {
2851 nb.omit_md = pos == 13;
2852 })
2853 .unwrap();
2854
2855 let netdir = netdir.unwrap_if_sufficient().unwrap();
2856
2857 let id13_13 = RelayIds::builder()
2858 .ed_identity([13; 32].into())
2859 .rsa_identity([13; 20].into())
2860 .build()
2861 .unwrap();
2862 let id15_15 = RelayIds::builder()
2863 .ed_identity([15; 32].into())
2864 .rsa_identity([15; 20].into())
2865 .build()
2866 .unwrap();
2867 let id15_99 = RelayIds::builder()
2868 .ed_identity([15; 32].into())
2869 .rsa_identity([99; 20].into())
2870 .build()
2871 .unwrap();
2872 let id99_15 = RelayIds::builder()
2873 .ed_identity([99; 32].into())
2874 .rsa_identity([15; 20].into())
2875 .build()
2876 .unwrap();
2877 let id99_99 = RelayIds::builder()
2878 .ed_identity([99; 32].into())
2879 .rsa_identity([99; 20].into())
2880 .build()
2881 .unwrap();
2882 let id15_xx = RelayIds::builder()
2883 .ed_identity([15; 32].into())
2884 .build()
2885 .unwrap();
2886 let idxx_15 = RelayIds::builder()
2887 .rsa_identity([15; 20].into())
2888 .build()
2889 .unwrap();
2890
2891 assert!(matches!(netdir.by_ids_detailed(&id13_13), Ok(None)));
2892 assert!(matches!(netdir.by_ids_detailed(&id15_15), Ok(Some(_))));
2893 assert!(matches!(
2894 netdir.by_ids_detailed(&id15_99),
2895 Err(RelayLookupError::Impossible)
2896 ));
2897 assert!(matches!(
2898 netdir.by_ids_detailed(&id99_15),
2899 Err(RelayLookupError::Impossible)
2900 ));
2901 assert!(matches!(netdir.by_ids_detailed(&id99_99), Ok(None)));
2902 assert!(matches!(netdir.by_ids_detailed(&id15_xx), Ok(Some(_))));
2903 assert!(matches!(netdir.by_ids_detailed(&idxx_15), Ok(Some(_))));
2904 }
2905
2906 #[test]
2907 fn weight_type() {
2908 let r0 = RelayWeight(0);
2909 let r100 = RelayWeight(100);
2910 let r200 = RelayWeight(200);
2911 let r300 = RelayWeight(300);
2912 assert_eq!(r100 + r200, r300);
2913 assert_eq!(r100.checked_div(r200), Some(0.5));
2914 assert!(r100.checked_div(r0).is_none());
2915 assert_eq!(r200.ratio(0.5), Some(r100));
2916 assert!(r200.ratio(-1.0).is_none());
2917 }
2918
2919 #[test]
2920 fn weight_accessors() {
2921 // Make a netdir that omits the microdescriptor for 0xDDDDDD...
2922 let netdir = construct_netdir().unwrap_if_sufficient().unwrap();
2923
2924 let g_total = netdir.total_weight(WeightRole::Guard, |r| r.rs.is_flagged_guard());
2925 // This is just the total guard weight, since all our Wxy = 1.
2926 assert_eq!(g_total, RelayWeight(110_000));
2927
2928 let g_total = netdir.total_weight(WeightRole::Guard, |_| false);
2929 assert_eq!(g_total, RelayWeight(0));
2930
2931 let relay = netdir.by_id(&Ed25519Identity::from([35; 32])).unwrap();
2932 assert!(relay.rs.is_flagged_guard());
2933 let w = netdir.relay_weight(&relay, WeightRole::Guard);
2934 assert_eq!(w, RelayWeight(6_000));
2935
2936 let w = netdir
2937 .weight_by_rsa_id(&[33; 20].into(), WeightRole::Guard)
2938 .unwrap();
2939 assert_eq!(w, RelayWeight(4_000));
2940
2941 assert!(
2942 netdir
2943 .weight_by_rsa_id(&[99; 20].into(), WeightRole::Guard)
2944 .is_none()
2945 );
2946 }
2947
2948 #[test]
2949 fn family_list() {
2950 let netdir = construct_custom_netdir(|pos, n, _| {
2951 if pos == 0x0a {
2952 n.md.family(
2953 "$0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B \
2954 $0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C \
2955 $0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D"
2956 .parse()
2957 .unwrap(),
2958 );
2959 } else if pos == 0x0c {
2960 n.md.family("$0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A".parse().unwrap());
2961 }
2962 })
2963 .unwrap()
2964 .unwrap_if_sufficient()
2965 .unwrap();
2966
2967 // In the testing netdir, adjacent members are in the same family by default...
2968 let r0 = netdir.by_id(&Ed25519Identity::from([0; 32])).unwrap();
2969 let family: Vec<_> = netdir.known_family_members(&r0).collect();
2970 assert_eq!(family.len(), 1);
2971 assert_eq!(family[0].id(), &Ed25519Identity::from([1; 32]));
2972
2973 // But we've made this relay claim membership with several others.
2974 let r10 = netdir.by_id(&Ed25519Identity::from([10; 32])).unwrap();
2975 let family: HashSet<_> = netdir.known_family_members(&r10).map(|r| *r.id()).collect();
2976 assert_eq!(family.len(), 2);
2977 assert!(family.contains(&Ed25519Identity::from([11; 32])));
2978 assert!(family.contains(&Ed25519Identity::from([12; 32])));
2979 // Note that 13 doesn't get put in, even though it's listed, since it doesn't claim
2980 // membership with 10.
2981 }
2982 #[test]
2983 #[cfg(feature = "geoip")]
2984 fn relay_has_country_code() {
2985 let src_v6 = r#"
2986 fe80:dead:beef::,fe80:dead:ffff::,US
2987 fe80:feed:eeee::1,fe80:feed:eeee::1,AT
2988 fe80:feed:eeee::2,fe80:feed:ffff::,DE
2989 "#;
2990 let db = GeoipDb::new_from_legacy_format("", src_v6, true).unwrap();
2991
2992 let netdir = construct_custom_netdir_with_geoip(
2993 |pos, n, _| {
2994 if pos == 0x01 {
2995 n.rs.add_or_port("[fe80:dead:beef::1]:42".parse().unwrap());
2996 }
2997 if pos == 0x02 {
2998 n.rs.add_or_port("[fe80:feed:eeee::1]:42".parse().unwrap());
2999 n.rs.add_or_port("[fe80:feed:eeee::2]:42".parse().unwrap());
3000 }
3001 if pos == 0x03 {
3002 n.rs.add_or_port("[fe80:dead:beef::1]:42".parse().unwrap());
3003 n.rs.add_or_port("[fe80:dead:beef::2]:42".parse().unwrap());
3004 }
3005 },
3006 &db,
3007 )
3008 .unwrap()
3009 .unwrap_if_sufficient()
3010 .unwrap();
3011
3012 // No GeoIP data available -> None
3013 let r0 = netdir.by_id(&Ed25519Identity::from([0; 32])).unwrap();
3014 assert_eq!(r0.cc, None);
3015
3016 // Exactly one match -> Some
3017 let r1 = netdir.by_id(&Ed25519Identity::from([1; 32])).unwrap();
3018 assert_eq!(r1.cc.as_ref().map(|x| x.as_ref()), Some("US"));
3019
3020 // Conflicting matches -> None
3021 let r2 = netdir.by_id(&Ed25519Identity::from([2; 32])).unwrap();
3022 assert_eq!(r2.cc, None);
3023
3024 // Multiple agreeing matches -> Some
3025 let r3 = netdir.by_id(&Ed25519Identity::from([3; 32])).unwrap();
3026 assert_eq!(r3.cc.as_ref().map(|x| x.as_ref()), Some("US"));
3027 }
3028
3029 #[test]
3030 #[cfg(feature = "hs-common")]
3031 #[allow(deprecated)]
3032 fn hs_dirs_selection() {
3033 use tor_basic_utils::test_rng::testing_rng;
3034
3035 const HSDIR_SPREAD_STORE: i32 = 6;
3036 const HSDIR_SPREAD_FETCH: i32 = 2;
3037 const PARAMS: [(&str, i32); 2] = [
3038 ("hsdir_spread_store", HSDIR_SPREAD_STORE),
3039 ("hsdir_spread_fetch", HSDIR_SPREAD_FETCH),
3040 ];
3041
3042 let netdir: Arc<NetDir> =
3043 crate::testnet::construct_custom_netdir_with_params(|_, _, _| {}, PARAMS, None)
3044 .unwrap()
3045 .unwrap_if_sufficient()
3046 .unwrap()
3047 .into();
3048 let hsid = dummy_hs_blind_id();
3049
3050 const OP_RELAY_COUNT: &[(HsDirOp, usize)] = &[
3051 // We can't upload to (hsdir_n_replicas * hsdir_spread_store) = 12, relays because there
3052 // are only 10 relays with the HsDir flag in the consensus.
3053 #[cfg(feature = "hs-service")]
3054 (HsDirOp::Upload, 10),
3055 (HsDirOp::Download, 4),
3056 ];
3057
3058 for (op, relay_count) in OP_RELAY_COUNT {
3059 let relays = netdir.hs_dirs(&hsid, *op, &mut testing_rng());
3060
3061 assert_eq!(relays.len(), *relay_count);
3062
3063 // There should be no duplicates (the filtering function passed to
3064 // HsDirRing::ring_items_at() ensures the relays that are already in use for
3065 // lower-numbered replicas aren't considered a second time for a higher-numbered
3066 // replica).
3067 let unique = relays
3068 .iter()
3069 .map(|relay| relay.ed_identity())
3070 .collect::<HashSet<_>>();
3071 assert_eq!(unique.len(), relays.len());
3072 }
3073
3074 // TODO: come up with a test that checks that HsDirRing::ring_items_at() skips over the
3075 // expected relays.
3076 //
3077 // For example, let's say we have the following hsdir ring:
3078 //
3079 // A - B
3080 // / \
3081 // F C
3082 // \ /
3083 // E - D
3084 //
3085 // Let's also assume that:
3086 //
3087 // * hsdir_spread_store = 3
3088 // * the ordering of the relays on the ring is [A, B, C, D, E, F]
3089 //
3090 // If we use relays [A, B, C] for replica 1, and hs_index(2) = E, then replica 2 _must_ get
3091 // relays [E, F, D]. We should have a test that checks this.
3092 }
3093
3094 #[test]
3095 fn zero_weights() {
3096 // Here we check the behavior of IndexedRandom::choose_weighted
3097 // in the presence of items whose weight is 0.
3098 //
3099 // We think that the behavior is:
3100 // - If all items have weight 0, choose_weighted returns an error.
3101 // - If any items have non-zero weight, one of them will be returned.
3102 let items = vec![1, 2, 3];
3103 let mut rng = testing_rng();
3104
3105 let a = items.choose_weighted(&mut rng, |_| 0);
3106 assert!(matches!(a, Err(WeightError::InsufficientNonZero)));
3107
3108 let only_one = |n: &i32| if *n == 1 { 1 } else { 0 };
3109 for _ in 0..100 {
3110 let a = items.choose_weighted(&mut rng, only_one);
3111 assert_eq!(a.unwrap(), &1);
3112 }
3113 }
3114}