1
1
//! Represents a clients'-eye view of the Tor network.
2
//!
3
//! # Overview
4
//!
5
//! The `tor-netdir` crate wraps objects from tor-netdoc, and combines
6
//! them to provide a unified view of the relays on the network.
7
//! It is responsible for representing a client's knowledge of the
8
//! network's state and who is on it.
9
//!
10
//! This crate is part of
11
//! [Arti](https://gitlab.torproject.org/tpo/core/arti/), a project to
12
//! implement [Tor](https://www.torproject.org/) in Rust.  Its purpose
13
//! is to expose an abstract view of a Tor network and the relays in
14
//! it, so that higher-level crates don't need to know about the
15
//! particular documents that describe the network and its properties.
16
//!
17
//! There are two intended users for this crate.  First, producers
18
//! like [`tor-dirmgr`] create [`NetDir`] objects fill them with
19
//! information from the Tor network directory.  Later, consumers
20
//! like [`tor-circmgr`] use [`NetDir`]s to select relays for random
21
//! paths through the Tor network.
22
//!
23
//! # Limitations
24
//!
25
//! Only modern consensus methods and microdescriptor consensuses are
26
//! supported.
27

            
28
#![deny(missing_docs)]
29
#![warn(noop_method_call)]
30
#![deny(unreachable_pub)]
31
#![warn(clippy::all)]
32
#![deny(clippy::await_holding_lock)]
33
#![deny(clippy::cargo_common_metadata)]
34
#![deny(clippy::cast_lossless)]
35
#![deny(clippy::checked_conversions)]
36
#![warn(clippy::cognitive_complexity)]
37
#![deny(clippy::debug_assert_with_mut_call)]
38
#![deny(clippy::exhaustive_enums)]
39
#![deny(clippy::exhaustive_structs)]
40
#![deny(clippy::expl_impl_clone_on_copy)]
41
#![deny(clippy::fallible_impl_from)]
42
#![deny(clippy::implicit_clone)]
43
#![deny(clippy::large_stack_arrays)]
44
#![warn(clippy::manual_ok_or)]
45
#![deny(clippy::missing_docs_in_private_items)]
46
#![deny(clippy::missing_panics_doc)]
47
#![warn(clippy::needless_borrow)]
48
#![warn(clippy::needless_pass_by_value)]
49
#![warn(clippy::option_option)]
50
#![warn(clippy::rc_buffer)]
51
#![deny(clippy::ref_option_ref)]
52
#![warn(clippy::semicolon_if_nothing_returned)]
53
#![warn(clippy::trait_duplication_in_bounds)]
54
#![deny(clippy::unnecessary_wraps)]
55
#![warn(clippy::unseparated_literal_suffix)]
56
#![deny(clippy::unwrap_used)]
57

            
58
mod err;
59
pub mod fallback;
60
pub mod params;
61
#[cfg(test)]
62
mod testing;
63
mod weight;
64

            
65
#[cfg(any(test, feature = "testing"))]
66
pub mod testnet;
67

            
68
use tor_linkspec::ChanTarget;
69
use tor_llcrypto as ll;
70
use tor_llcrypto::pk::{ed25519::Ed25519Identity, rsa::RsaIdentity};
71
use tor_netdoc::doc::microdesc::{MdDigest, Microdesc};
72
use tor_netdoc::doc::netstatus::{self, MdConsensus, RouterStatus};
73
use tor_netdoc::types::policy::PortPolicy;
74

            
75
use serde::Deserialize;
76
use std::collections::{HashMap, HashSet};
77
use std::net::IpAddr;
78
use std::sync::Arc;
79
use tracing::warn;
80

            
81
pub use err::Error;
82
pub use weight::WeightRole;
83
/// A Result using the Error type from the tor-netdir crate
84
pub type Result<T> = std::result::Result<T, Error>;
85

            
86
use params::NetParameters;
87

            
88
/// Configuration for determining when two relays have addresses "too close" in
89
/// the network.
90
///
91
/// Used by [`Relay::in_same_subnet()`].
92
#[derive(Deserialize, Debug, Clone, Copy)]
93
#[serde(deny_unknown_fields)]
94
pub struct SubnetConfig {
95
    /// Consider IPv4 nodes in the same /x to be the same family.
96
    ///
97
    /// If this value is 0, all nodes with IPv4 addresses will be in the
98
    /// same family.  If this value is above 32, then no nodes will be
99
    /// placed im the same family based on their IPv4 addresses.
100
    subnets_family_v4: u8,
101
    /// Consider IPv6 nodes in the same /x to be the same family.
102
    ///
103
    /// If this value is 0, all nodes with IPv6 addresses will be in the
104
    /// same family.  If this value is above 128, then no nodes will be
105
    /// placed im the same family based on their IPv6 addresses.
106
    subnets_family_v6: u8,
107
}
108

            
109
impl Default for SubnetConfig {
110
49330
    fn default() -> Self {
111
49330
        Self::new(16, 32)
112
49330
    }
113
}
114

            
115
impl SubnetConfig {
116
    /// Construct a new SubnetConfig from a pair of bit prefix lengths.
117
    ///
118
    /// The values are clamped to the appropriate ranges if they are
119
    /// out-of-bounds.
120
98834
    pub fn new(subnets_family_v4: u8, subnets_family_v6: u8) -> Self {
121
98834
        Self {
122
98834
            subnets_family_v4,
123
98834
            subnets_family_v6,
124
98834
        }
125
98834
    }
126

            
127
    /// Are two addresses in the same subnet according to this configuration
128
    fn addrs_in_same_subnet(&self, a: &IpAddr, b: &IpAddr) -> bool {
129
4716028
        match (a, b) {
130
4715999
            (IpAddr::V4(a), IpAddr::V4(b)) => {
131
4715999
                let bits = self.subnets_family_v4;
132
4715999
                if bits > 32 {
133
4
                    return false;
134
4715115
                }
135
4715115
                let a = u32::from_be_bytes(a.octets());
136
4715115
                let b = u32::from_be_bytes(b.octets());
137
4715115
                (a >> (32 - bits)) == (b >> (32 - bits))
138
            }
139
7
            (IpAddr::V6(a), IpAddr::V6(b)) => {
140
7
                let bits = self.subnets_family_v6;
141
7
                if bits > 128 {
142
2
                    return false;
143
5
                }
144
5
                let a = u128::from_be_bytes(a.octets());
145
5
                let b = u128::from_be_bytes(b.octets());
146
5
                (a >> (128 - bits)) == (b >> (128 - bits))
147
            }
148
6
            _ => false,
149
        }
150
4715132
    }
151
}
152

            
153
/// Internal type: either a microdescriptor, or the digest for a
154
/// microdescriptor that we want.
155
///
156
/// This is a separate type so we can use a HashSet instead of
157
/// HashMap.
158
#[derive(Clone, Debug)]
159
enum MdEntry {
160
    /// A microdescriptor that is wanted but not present.
161
    Absent {
162
        /// Index of the routerstatus entry that wants this microdesc.
163
        rs_idx: usize,
164
        /// Content digest of the microdescriptor.
165
        // TODO: I wanted to make this a reference at first, but that's
166
        // nontrival.  At this point I'm not 100% sure it would be a good
167
        // idea.
168
        d: MdDigest,
169
    },
170
    /// A microdescriptor that we have.
171
    Present {
172
        /// The microdescriptor itself.
173
        md: Arc<Microdesc>,
174
    },
175
}
176

            
177
impl std::borrow::Borrow<MdDigest> for MdEntry {
178
6649078
    fn borrow(&self) -> &MdDigest {
179
6649078
        self.digest()
180
6649078
    }
181
}
182

            
183
impl MdEntry {
184
    /// Return the digest for this entry.
185
6708971
    fn digest(&self) -> &MdDigest {
186
6708971
        match self {
187
55335
            MdEntry::Absent { d, .. } => d,
188
6653636
            MdEntry::Present { md, .. } => md.digest(),
189
        }
190
6708971
    }
191
}
192

            
193
impl PartialEq for MdEntry {
194
3189
    fn eq(&self, rhs: &MdEntry) -> bool {
195
3189
        self.digest() == rhs.digest()
196
3189
    }
197
}
198
impl Eq for MdEntry {}
199

            
200
impl std::hash::Hash for MdEntry {
201
54873
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
202
54873
        self.digest().hash(state);
203
54873
    }
204
}
205

            
206
/// An opaque type representing the weight with which a relay or set of
207
/// relays will be selected for a given role.
208
///
209
/// Most users should ignore this type, and just use pick_relay instead.
210
#[derive(
211
    Copy,
212
    Clone,
213
    Debug,
214
78316
    derive_more::Add,
215
7976
    derive_more::Sum,
216
1320
    derive_more::AddAssign,
217
    Eq,
218
7
    PartialEq,
219
    Ord,
220
5307
    PartialOrd,
221
)]
222
pub struct RelayWeight(u64);
223

            
224
impl RelayWeight {
225
    /// Try to divide this weight by `rhs`.
226
    ///
227
    /// Return a ratio on success, or None on division-by-zero.
228
2
    pub fn checked_div(&self, rhs: RelayWeight) -> Option<f64> {
229
2
        if rhs.0 == 0 {
230
1
            None
231
        } else {
232
1
            Some((self.0 as f64) / (rhs.0 as f64))
233
        }
234
2
    }
235

            
236
    /// Compute a ratio `frac` of this weight.
237
    ///
238
    /// Return None if frac is less than zero, since negative weights
239
    /// are impossible.
240
4658
    pub fn ratio(&self, frac: f64) -> Option<RelayWeight> {
241
4658
        let product = (self.0 as f64) * frac;
242
4658
        if product >= 0.0 && product.is_finite() {
243
4641
            Some(RelayWeight(product as u64))
244
        } else {
245
1
            None
246
        }
247
4642
    }
248
}
249

            
250
impl From<u64> for RelayWeight {
251
16
    fn from(val: u64) -> Self {
252
16
        RelayWeight(val)
253
16
    }
254
}
255

            
256
/// A view of the Tor directory, suitable for use in building
257
/// circuits.
258
///
259
/// Abstractly, a [`NetDir`] is a set of usable public [`Relay`]s,
260
/// each of which has its own properties, identity, and correct weighted
261
/// probability for use under different circumstances.
262
///
263
/// A [`NetDir`] is constructed by making a [`PartialNetDir`] from a
264
/// consensus document, and then adding enough microdescriptors to
265
/// that `PartialNetDir` so that it can be used to build paths.
266
/// (Thus, if you have a NetDir, it is definitely adequate to build
267
/// paths.)
268
#[derive(Debug, Clone)]
269
pub struct NetDir {
270
    /// A microdescriptor consensus that lists the members of the network,
271
    /// and maps each one to a 'microdescriptor' that has more information
272
    /// about it
273
    consensus: Arc<MdConsensus>,
274
    /// A map from keys to integer values, distributed in the consensus,
275
    /// and clamped to certain defaults.
276
    params: NetParameters,
277
    /// Map from SHA256 digest of microdescriptors to the
278
    /// microdescriptors themselves.
279
    mds: HashSet<MdEntry>,
280
    /// Map from ed25519 identity to index of the routerstatus within
281
    /// `self.consensus.relays()`.
282
    ///
283
    /// Note that we don't know the ed25519 identity of a relay until
284
    /// we get the microdescriptor for it, so this won't be filled in
285
    /// until we get the microdescriptors.
286
    ///
287
    /// # Implementation note
288
    ///
289
    /// For this field, and for `rs_idx_by_rsa`, and for
290
    /// `MdEntry::*::rsa_idx`, it might be cool to have references instead.
291
    /// But that would make this into a self-referential structure,
292
    /// which isn't possible in safe rust.
293
    rs_idx_by_ed: HashMap<Ed25519Identity, usize>,
294
    /// Map from RSA identity to index of the routerstatus within
295
    /// `self.consensus.relays()`.
296
    ///
297
    /// This is constructed at the same time as the NetDir object, so it
298
    /// can be immutable.
299
    rs_idx_by_rsa: Arc<HashMap<RsaIdentity, usize>>,
300

            
301
    /// Weight values to apply to a given relay when deciding how frequently
302
    /// to choose it for a given role.
303
    weights: weight::WeightSet,
304
}
305

            
306
/// A partially build NetDir -- it can't be unwrapped until it has
307
/// enough information to build safe paths.
308
#[derive(Debug, Clone)]
309
pub struct PartialNetDir {
310
    /// The netdir that's under construction.
311
    netdir: NetDir,
312
}
313

            
314
/// A view of a relay on the Tor network, suitable for building circuits.
315
// TODO: This should probably be a more specific struct, with a trait
316
// that implements it.
317
22500
#[derive(Clone)]
318
pub struct Relay<'a> {
319
    /// A router descriptor for this relay.
320
    rs: &'a netstatus::MdConsensusRouterStatus,
321
    /// A microdescriptor for this relay.
322
    md: &'a Microdesc,
323
}
324

            
325
/// A relay that we haven't checked for validity or usability in
326
/// routing.
327
#[derive(Debug)]
328
pub struct UncheckedRelay<'a> {
329
    /// A router descriptor for this relay.
330
    rs: &'a netstatus::MdConsensusRouterStatus,
331
    /// A microdescriptor for this relay, if there is one.
332
    md: Option<&'a Microdesc>,
333
}
334

            
335
/// A partial or full network directory that we can download
336
/// microdescriptors for.
337
pub trait MdReceiver {
338
    /// Return an iterator over the digests for all of the microdescriptors
339
    /// that this netdir is missing.
340
    fn missing_microdescs(&self) -> Box<dyn Iterator<Item = &MdDigest> + '_>;
341
    /// Add a microdescriptor to this netdir, if it was wanted.
342
    ///
343
    /// Return true if it was indeed wanted.
344
    fn add_microdesc(&mut self, md: Microdesc) -> bool;
345
}
346

            
347
impl PartialNetDir {
348
    /// Create a new PartialNetDir with a given consensus, and no
349
    /// microdescriptors loaded.
350
    ///
351
    /// If `replacement_params` is provided, override network parameters from
352
    /// the consensus with those from `replacement_params`.
353
752
    pub fn new(
354
752
        consensus: MdConsensus,
355
752
        replacement_params: Option<&netstatus::NetParams<i32>>,
356
752
    ) -> Self {
357
752
        let mut params = NetParameters::default();
358
752

            
359
752
        // (We ignore unrecognized options here, since they come from
360
752
        // the consensus, and we don't expect to recognize everything
361
752
        // there.)
362
752
        let _ = params.saturating_update(consensus.params().iter());
363

            
364
        // Now see if the user has any parameters to override.
365
        // (We have to do this now, or else changes won't be reflected in our
366
        // weights.)
367
752
        if let Some(replacement) = replacement_params {
368
291
            for u in params.saturating_update(replacement.iter()) {
369
1
                warn!("Unrecognized option: override_net_params.{}", u);
370
            }
371
461
        }
372

            
373
        // Compute the weights we'll want to use for these relays.
374
736
        let weights = weight::WeightSet::from_consensus(&consensus, &params);
375
736

            
376
736
        let mds = consensus
377
736
            .relays()
378
736
            .iter()
379
736
            .enumerate()
380
27557
            .map(|(rs_idx, rs)| MdEntry::Absent {
381
27557
                rs_idx,
382
27557
                d: *rs.md_digest(),
383
27557
            })
384
736
            .collect();
385
736

            
386
736
        let rs_idx_by_rsa = consensus
387
736
            .relays()
388
736
            .iter()
389
736
            .enumerate()
390
27667
            .map(|(rs_idx, rs)| (*rs.rsa_identity(), rs_idx))
391
736
            .collect();
392
736

            
393
736
        let netdir = NetDir {
394
736
            consensus: Arc::new(consensus),
395
736
            params,
396
736
            mds,
397
736
            rs_idx_by_rsa: Arc::new(rs_idx_by_rsa),
398
736
            rs_idx_by_ed: HashMap::new(),
399
736
            weights,
400
736
        };
401
736

            
402
736
        PartialNetDir { netdir }
403
736
    }
404

            
405
    /// Return the declared lifetime of this PartialNetDir.
406
1
    pub fn lifetime(&self) -> &netstatus::Lifetime {
407
1
        self.netdir.lifetime()
408
1
    }
409

            
410
    /// Fill in as many missing microdescriptors as possible in this
411
    /// netdir, using the microdescriptors from the previous netdir.
412
1
    pub fn fill_from_previous_netdir<'a>(&mut self, prev: &'a NetDir) -> Vec<&'a MdDigest> {
413
1
        let mut loaded = Vec::new();
414
41
        for ent in &prev.mds {
415
40
            if let MdEntry::Present { md, .. } = ent {
416
38
                if self.netdir.mds.contains(md.digest()) {
417
38
                    loaded.push(md.digest());
418
38
                    self.netdir.add_arc_microdesc(Arc::clone(md));
419
38
                }
420
2
            }
421
        }
422
1
        loaded
423
1
    }
424

            
425
    /// Return true if this are enough information in this directory
426
    /// to build multihop paths.
427
2
    pub fn have_enough_paths(&self) -> bool {
428
2
        self.netdir.have_enough_paths()
429
2
    }
430
    /// If this directory has enough information to build multihop
431
    /// circuits, return it.
432
781
    pub fn unwrap_if_sufficient(self) -> std::result::Result<NetDir, PartialNetDir> {
433
781
        if self.netdir.have_enough_paths() {
434
715
            Ok(self.netdir)
435
        } else {
436
66
            Err(self)
437
        }
438
781
    }
439
    /// Return true if we are currently missing a microdescriptor for the
440
    /// given RSA identity.
441
    ///
442
    /// A descriptor is `missing` only if it is listed in the consensus,
443
    /// but we don't have it downloaded.
444
2
    pub fn missing_descriptor_for(&self, rsa_id: &RsaIdentity) -> bool {
445
2
        self.netdir.missing_descriptor_for(rsa_id)
446
2
    }
447
}
448

            
449
impl MdReceiver for PartialNetDir {
450
52
    fn missing_microdescs(&self) -> Box<dyn Iterator<Item = &MdDigest> + '_> {
451
52
        self.netdir.missing_microdescs()
452
52
    }
453
27807
    fn add_microdesc(&mut self, md: Microdesc) -> bool {
454
27807
        self.netdir.add_microdesc(md)
455
27807
    }
456
}
457

            
458
impl NetDir {
459
    /// Return the declared lifetime of this NetDir.
460
49585
    pub fn lifetime(&self) -> &netstatus::Lifetime {
461
49585
        self.consensus.lifetime()
462
49585
    }
463

            
464
    /// Add `md` to this NetDir.
465
    ///
466
    /// Return true if we wanted it, and false otherwise.
467
    #[allow(clippy::missing_panics_doc)] // Can't panic on valid object.
468
27993
    fn add_arc_microdesc(&mut self, md: Arc<Microdesc>) -> bool {
469
27993
        if let Some(prev_ent) = self.mds.take(md.digest()) {
470
27961
            if let MdEntry::Absent { rs_idx, .. } = prev_ent {
471
27961
                assert_eq!(self.consensus.relays()[rs_idx].md_digest(), md.digest());
472

            
473
                // There should never be two approved MDs in the same
474
                // consensus listing the same ID... but if there is,
475
                // we'll let the most recent one win.
476
27773
                self.rs_idx_by_ed.insert(*md.ed25519_id(), rs_idx);
477
27773

            
478
27773
                // Happy path: we did indeed want this one.
479
27773
                self.mds.insert(MdEntry::Present { md });
480
27773

            
481
27773
                return true;
482
            } else {
483
                // We already had this.
484
                self.mds.insert(prev_ent);
485
            }
486
32
        }
487

            
488
        // Either we already had it, or we never wanted it at all.
489
32
        false
490
27805
    }
491

            
492
    /// Construct a (possibly invalid) Relay object from a routerstatus and its
493
    /// microdescriptor (if any).
494
6545286
    fn relay_from_rs<'a>(
495
6545286
        &'a self,
496
6545286
        rs: &'a netstatus::MdConsensusRouterStatus,
497
6545286
    ) -> UncheckedRelay<'a> {
498
6563368
        let md = match self.mds.get(rs.md_digest()) {
499
6561992
            Some(MdEntry::Present { md }) => Some(Arc::as_ref(md)),
500
1376
            _ => None,
501
        };
502
6563368
        UncheckedRelay { rs, md }
503
6563368
    }
504

            
505
    /// Replace the overridden parameters in this netdir with `new_replacement`.
506
    ///
507
    /// After this function is done, the netdir's parameters will be those in
508
    /// the consensus, overridden by settings from `new_replacement`.  Any
509
    /// settings in the old replacement parameters will be discarded.
510
16
    pub fn replace_overridden_parameters(&mut self, new_replacement: &netstatus::NetParams<i32>) {
511
16
        // TODO(nickm): This is largely duplicate code from PartialNetDir::new().
512
16
        let mut new_params = NetParameters::default();
513
16
        let _ = new_params.saturating_update(self.consensus.params().iter());
514
16
        for u in new_params.saturating_update(new_replacement.iter()) {
515
            warn!("Unrecognized option: override_net_params.{}", u);
516
        }
517

            
518
16
        self.params = new_params;
519
16
    }
520

            
521
    /// Return an iterator over all Relay objects, including invalid ones
522
    /// that we can't use.
523
161615
    pub fn all_relays(&self) -> impl Iterator<Item = UncheckedRelay<'_>> {
524
161615
        // TODO: I'd like if if we could memoize this so we don't have to
525
161615
        // do so many hashtable lookups.
526
161615
        self.consensus
527
161615
            .relays()
528
161615
            .iter()
529
6410887
            .map(move |rs| self.relay_from_rs(rs))
530
161615
    }
531
    /// Return an iterator over all usable Relays.
532
153801
    pub fn relays(&self) -> impl Iterator<Item = Relay<'_>> {
533
153801
        self.all_relays().filter_map(UncheckedRelay::into_relay)
534
153801
    }
535
    /// Return a relay matching a given Ed25519 identity, if we have a
536
    /// _usable_ relay with that key.
537
    ///
538
    /// (Does not return unusable relays.)
539
    ///
540
    /// Note that if a microdescriptor is subsequently added for a relay
541
    /// with this ID, the ID may become usable.
542
    #[allow(clippy::missing_panics_doc)] // Can't panic on valid object.
543
44867
    pub fn by_id(&self, id: &Ed25519Identity) -> Option<Relay<'_>> {
544
44867
        let rs_idx = self.rs_idx_by_ed.get(id)?;
545
44849
        let rs = self.consensus.relays().get(*rs_idx).expect("Corrupt index");
546

            
547
44849
        let relay = self.relay_from_rs(rs).into_relay()?;
548
44849
        assert_eq!(id, relay.id());
549
44785
        Some(relay)
550
44803
    }
551

            
552
    /// Return a relay matching a given Ed25519 identity and RSA identity,
553
    /// if we have a usable relay with _both_ keys.
554
    ///
555
    /// (Does not return unusable relays.)
556
    ///
557
    /// Note that if a microdescriptor is subsequently added for a relay
558
    /// with this ID, the ID may become usable.
559
44563
    pub fn by_id_pair(&self, ed_id: &Ed25519Identity, rsa_id: &RsaIdentity) -> Option<Relay<'_>> {
560
44563
        self.by_id(ed_id).filter(|r| r.rs.rsa_identity() == rsa_id)
561
44563
    }
562

            
563
    /// Return the usable relay matching a given [`ChanTarget`]'s
564
    /// identities, if any.
565
    ///
566
    /// (Does not return unusable relays.)
567
    pub fn by_chantarget(&self, chan_target: &impl tor_linkspec::ChanTarget) -> Option<Relay<'_>> {
568
        self.by_id_pair(chan_target.ed_identity(), chan_target.rsa_identity())
569
    }
570

            
571
    /// Return a boolean if this consensus definitely has (or does not
572
    /// have) a relay matching both the given Ed25519 and RSA
573
    /// identity.
574
    ///
575
    /// If we can't yet tell for sure, return None.
576
    ///
577
    /// Once function has returned `Some(b)`, it will always return that
578
    /// value for the same `ed_id` and `rsa_id` on this `NetDir`.  A `None`
579
    /// answer may later become `Some(b)` if a microdescriptor arrives.
580
52499
    pub fn id_pair_listed(&self, ed_id: &Ed25519Identity, rsa_id: &RsaIdentity) -> Option<bool> {
581
52499
        let r = self.by_rsa_id_unchecked(rsa_id);
582
52499
        match r {
583
52450
            Some(unchecked) => {
584
52450
                if !unchecked.rs.ed25519_id_is_usable() {
585
                    return Some(false);
586
52418
                }
587
52418
                // If md is present, then it's listed iff we have the right
588
52418
                // ed id.  Otherwise we don't know if it's listed.
589
52418
                unchecked.md.map(|md| md.ed25519_id() == ed_id)
590
            }
591
            None => {
592
                // Definitely not listed.
593
49
                Some(false)
594
            }
595
        }
596
52467
    }
597

            
598
    /// Return true if we are currently missing a micro descriptor for the
599
    /// given RSA identity.
600
    ///
601
    /// A descriptor is `missing` only if it is listed in the consensus,
602
    /// but we don't have it downloaded.
603
2
    pub fn missing_descriptor_for(&self, rsa_id: &RsaIdentity) -> bool {
604
2
        match self.by_rsa_id_unchecked(rsa_id) {
605
2
            Some(unchecked) => unchecked.md.is_none(),
606
            None => false,
607
        }
608
2
    }
609

            
610
    /// Return a (possibly unusable) relay with a given RSA identity.
611
    #[allow(clippy::missing_panics_doc)] // Can't panic on valid object.
612
94034
    pub fn by_rsa_id_unchecked(&self, rsa_id: &RsaIdentity) -> Option<UncheckedRelay<'_>> {
613
94034
        let rs_idx = self.rs_idx_by_rsa.get(rsa_id)?;
614
93950
        let rs = self.consensus.relays().get(*rs_idx).expect("Corrupt index");
615
93950
        assert_eq!(rs.rsa_identity(), rsa_id);
616
93902
        Some(self.relay_from_rs(rs))
617
93986
    }
618
    /// Return the relay with a given RSA identity, if we have one
619
    /// and it is usable.
620
    pub fn by_rsa_id(&self, rsa_id: &RsaIdentity) -> Option<Relay<'_>> {
621
134
        self.by_rsa_id_unchecked(rsa_id)?.into_relay()
622
134
    }
623
    /// Return true if `rsa_id` is listed in this directory, even if it
624
    /// isn't currently usable.
625
3
    pub fn rsa_id_is_listed(&self, rsa_id: &RsaIdentity) -> bool {
626
3
        self.by_rsa_id_unchecked(rsa_id).is_some()
627
3
    }
628
    /// Return the parameters from the consensus, clamped to the
629
    /// correct ranges, with defaults filled in.
630
    ///
631
    /// NOTE: that unsupported parameters aren't returned here; only those
632
    /// values configured in the `params` module are available.
633
5295
    pub fn params(&self) -> &NetParameters {
634
5295
        &self.params
635
5295
    }
636
    /// Return weighted the fraction of relays we can use.  We only
637
    /// consider relays that match the predicate `usable`.  We weight
638
    /// this bandwidth according to the provided `role`.
639
    ///
640
    /// If _no_ matching relays in the consensus have a nonzero
641
    /// weighted bandwidth value, we fall back to looking at the
642
    /// unweighted fraction of matching relays.
643
    ///
644
    /// If there are no matching relays in the consensus, we return 0.0.
645
2355
    fn frac_for_role<'a, F>(&'a self, role: WeightRole, usable: F) -> f64
646
2355
    where
647
2355
        F: Fn(&UncheckedRelay<'a>) -> bool,
648
2355
    {
649
2355
        let mut total_weight = 0_u64;
650
2355
        let mut have_weight = 0_u64;
651
2355
        let mut have_count = 0_usize;
652
2355
        let mut total_count = 0_usize;
653

            
654
82665
        for r in self.all_relays() {
655
82665
            if !usable(&r) {
656
27206
                continue;
657
55459
            }
658
55459
            let w = self.weights.weight_rs_for_role(r.rs, role);
659
55459
            total_weight += w;
660
55459
            total_count += 1;
661
55459
            if r.is_usable() {
662
54909
                have_weight += w;
663
54909
                have_count += 1;
664
54909
            }
665
        }
666

            
667
2355
        if total_weight > 0 {
668
            // The consensus lists some weighted bandwidth so return the
669
            // fraction of the weighted bandwidth for which we have
670
            // descriptors.
671
2355
            (have_weight as f64) / (total_weight as f64)
672
        } else if total_count > 0 {
673
            // The consensus lists no weighted bandwidth for these relays,
674
            // but at least it does list relays. Return the fraction of
675
            // relays for which it we have descriptors.
676
            (have_count as f64) / (total_count as f64)
677
        } else {
678
            // There are no relays of this kind in the consensus.  Return
679
            // 0.0, to avoid dividing by zero and giving NaN.
680
            0.0
681
        }
682
2355
    }
683
    /// Return the estimated fraction of possible paths that we have
684
    /// enough microdescriptors to build.
685
784
    fn frac_usable_paths(&self) -> f64 {
686
27495
        let f_g = self.frac_for_role(WeightRole::Guard, |u| u.rs.is_flagged_guard());
687
27524
        let f_m = self.frac_for_role(WeightRole::Middle, |_| true);
688
7760
        let f_e = if self.all_relays().any(|u| u.rs.is_flagged_exit()) {
689
27394
            self.frac_for_role(WeightRole::Exit, |u| u.rs.is_flagged_exit())
690
        } else {
691
            // If there are no exits at all, we use f_m here.
692
            f_m
693
        };
694
784
        f_g * f_m * f_e
695
784
    }
696
    /// Return true if there is enough information in this NetDir to build
697
    /// multihop circuits.
698

            
699
783
    fn have_enough_paths(&self) -> bool {
700
783
        // TODO-A001: This should check for our guards as well, and
701
783
        // make sure that if they're listed in the consensus, we have
702
783
        // the descriptors for them.
703
783

            
704
783
        // If we can build a randomly chosen path with at least this
705
783
        // probability, we know enough information to participate
706
783
        // on the network.
707
783

            
708
783
        let min_frac_paths: f64 = self.params().min_circuit_path_threshold.as_fraction();
709
783

            
710
783
        // What fraction of paths can we build?
711
783
        let available = self.frac_usable_paths();
712
783

            
713
783
        available >= min_frac_paths
714
783
    }
715
    /// Choose a relay at random.
716
    ///
717
    /// Each relay is chosen with probability proportional to its weight
718
    /// in the role `role`, and is only selected if the predicate `usable`
719
    /// returns true for it.
720
    ///
721
    /// This function returns None if (and only if) there are no relays
722
    /// with nonzero weight where `usable` returned true.
723
14187
    pub fn pick_relay<'a, R, P>(
724
14187
        &'a self,
725
14187
        rng: &mut R,
726
14187
        role: WeightRole,
727
14187
        usable: P,
728
14187
    ) -> Option<Relay<'a>>
729
14187
    where
730
14187
        R: rand::Rng,
731
14187
        P: Fn(&Relay<'a>) -> bool,
732
14187
    {
733
14187
        use rand::seq::SliceRandom;
734
14187
        let relays: Vec<_> = self.relays().filter(usable).collect();
735
14187
        // This algorithm uses rand::distributions::WeightedIndex, and uses
736
14187
        // gives O(n) time and space  to build the index, plus O(log n)
737
14187
        // sampling time.
738
14187
        //
739
14187
        // We might be better off building a WeightedIndex in advance
740
14187
        // for each `role`, and then sampling it repeatedly until we
741
14187
        // get a relay that satisfies `usable`.  Or we might not --
742
14187
        // that depends heavily on the actual particulars of our
743
14187
        // inputs.  We probably shouldn't make any changes there
744
14187
        // unless profiling tells us that this function is in a hot
745
14187
        // path.
746
14187
        //
747
14187
        // The C Tor sampling implementation goes through some trouble
748
14187
        // here to try to make its path selection constant-time.  I
749
14187
        // believe that there is no actual remotely exploitable
750
14187
        // side-channel here however.  It could be worth analyzing in
751
14187
        // the future.
752
14187
        //
753
14187
        // This code will give the wrong result if the total of all weights
754
14187
        // can exceed u64::MAX.  We make sure that can't happen when we
755
14187
        // set up `self.weights`.
756
14187
        relays[..]
757
269174
            .choose_weighted(rng, |r| self.weights.weight_rs_for_role(r.rs, role))
758
14187
            .ok()
759
14187
            .cloned()
760
14187
    }
761

            
762
    /// Choose `n` relay at random.
763
    ///
764
    /// Each relay is chosen with probability proportional to its weight
765
    /// in the role `role`, and is only selected if the predicate `usable`
766
    /// returns true for it.
767
    ///
768
    /// Relays are chosen without replacement: no relay will be
769
    /// returned twice. Therefore, the resulting vector may be smaller
770
    /// than `n` if we happen to have fewer than `n` appropriate relays.
771
    ///
772
    /// This function returns an empty vector if (and only if) there
773
    /// are no relays with nonzero weight where `usable` returned
774
    /// true.
775
1393
    pub fn pick_n_relays<'a, R, P>(
776
1393
        &'a self,
777
1393
        rng: &mut R,
778
1393
        n: usize,
779
1393
        role: WeightRole,
780
1393
        usable: P,
781
1393
    ) -> Vec<Relay<'a>>
782
1393
    where
783
1393
        R: rand::Rng,
784
1393
        P: Fn(&Relay<'a>) -> bool,
785
1393
    {
786
1393
        use rand::seq::SliceRandom;
787
1393
        let relays: Vec<_> = self.relays().filter(usable).collect();
788
        // NOTE: See discussion in pick_relay().
789
26455
        let mut relays = match relays[..].choose_multiple_weighted(rng, n, |r| {
790
26455
            self.weights.weight_rs_for_role(r.rs, role) as f64
791
26455
        }) {
792
            Err(_) => Vec::new(),
793
1393
            Ok(iter) => iter.map(Relay::clone).collect(),
794
        };
795
1393
        relays.shuffle(rng);
796
1393
        relays
797
1393
    }
798

            
799
    /// Compute the weight with which `relay` will be selected for a given
800
    /// `role`.
801
3201
    pub fn relay_weight<'a>(&'a self, relay: &Relay<'a>, role: WeightRole) -> RelayWeight {
802
3201
        RelayWeight(self.weights.weight_rs_for_role(relay.rs, role))
803
3201
    }
804

            
805
    /// Compute the total weight with which any relay matching `usable`
806
    /// will be selected for a given `role`.
807
    ///
808
    /// Note: because this function is used to assess the total
809
    /// properties of the consensus, the `usable` predicate takes a
810
    /// [`RouterStatus`] rather than a [`Relay`].
811
3986
    pub fn total_weight<P>(&self, role: WeightRole, usable: P) -> RelayWeight
812
3986
    where
813
3986
        P: Fn(&UncheckedRelay<'_>) -> bool,
814
3986
    {
815
3986
        self.all_relays()
816
159494
            .filter_map(|unchecked| {
817
159494
                if usable(&unchecked) {
818
39920
                    Some(RelayWeight(
819
39920
                        self.weights.weight_rs_for_role(unchecked.rs, role),
820
39920
                    ))
821
                } else {
822
119574
                    None
823
                }
824
159494
            })
825
3986
            .sum()
826
3986
    }
827

            
828
    /// Compute the weight with which a relay with ID `rsa_id` would be
829
    /// selected for a given `role`.
830
    ///
831
    /// Note that weight returned by this function assumes that the
832
    /// relay with that ID is actually usable; if it isn't usable,
833
    /// then other weight-related functions will call its weight zero.
834
41394
    pub fn weight_by_rsa_id(&self, rsa_id: &RsaIdentity, role: WeightRole) -> Option<RelayWeight> {
835
41394
        self.by_rsa_id_unchecked(rsa_id)
836
41394
            .map(|unchecked| RelayWeight(self.weights.weight_rs_for_role(unchecked.rs, role)))
837
41394
    }
838

            
839
    /// Return all relays in this NetDir known to be in the same family as
840
    /// `relay`.
841
    ///
842
    /// This list of members will **not** necessarily include `relay` itself.
843
    ///
844
    /// # Limitations
845
    ///
846
    /// Two relays only belong to the same family if _each_ relay
847
    /// claims to share a family with the other.  But if we are
848
    /// missing a microdescriptor for one of the relays listed by this
849
    /// relay, we cannot know whether it acknowledges family
850
    /// membership with this relay or not.  Therefore, this function
851
    /// can omit family members for which there is not (as yet) any
852
    /// Relay object.
853
130
    pub fn known_family_members<'a>(
854
130
        &'a self,
855
130
        relay: &'a Relay<'a>,
856
130
    ) -> impl Iterator<Item = Relay<'a>> {
857
130
        let relay_rsa_id = relay.rsa_id();
858
132
        relay.md.family().members().filter_map(move |other_rsa_id| {
859
12
            self.by_rsa_id(other_rsa_id)
860
12
                .filter(|other_relay| other_relay.md.family().contains(relay_rsa_id))
861
132
        })
862
130
    }
863
}
864

            
865
impl MdReceiver for NetDir {
866
53
    fn missing_microdescs(&self) -> Box<dyn Iterator<Item = &MdDigest> + '_> {
867
424
        Box::new(self.consensus.relays().iter().filter_map(move |rs| {
868
424
            let d = rs.md_digest();
869
424
            match self.mds.get(d) {
870
308
                Some(MdEntry::Absent { d, .. }) => Some(d),
871
116
                _ => None,
872
            }
873
424
        }))
874
53
    }
875
27870
    fn add_microdesc(&mut self, md: Microdesc) -> bool {
876
27870
        self.add_arc_microdesc(Arc::new(md))
877
27870
    }
878
}
879

            
880
impl<'a> UncheckedRelay<'a> {
881
    /// Return true if this relay is valid and usable.
882
    ///
883
    /// This function should return `true` for every Relay we expose
884
    /// to the user.
885
6244509
    pub fn is_usable(&self) -> bool {
886
6244509
        // No need to check for 'valid' or 'running': they are implicit.
887
6245197
        self.md.is_some() && self.rs.ed25519_id_is_usable()
888
6244509
    }
889
    /// If this is usable, return a corresponding Relay object.
890
6188300
    pub fn into_relay(self) -> Option<Relay<'a>> {
891
6188300
        if self.is_usable() {
892
            Some(Relay {
893
6185577
                rs: self.rs,
894
6185577
                md: self.md?,
895
            })
896
        } else {
897
2723
            None
898
        }
899
6187675
    }
900
    /// Return true if this relay has the guard flag.
901
186088
    pub fn is_flagged_guard(&self) -> bool {
902
186088
        self.rs.is_flagged_guard()
903
186088
    }
904
    /// Return true if this relay is a potential directory cache.
905
93488
    pub fn is_dir_cache(&self) -> bool {
906
93488
        rs_is_dir_cache(self.rs)
907
93488
    }
908
}
909

            
910
impl<'a> Relay<'a> {
911
    /// Return the Ed25519 ID for this relay.
912
10657950
    pub fn id(&self) -> &Ed25519Identity {
913
10657950
        self.md.ed25519_id()
914
10657950
    }
915
    /// Return the RsaIdentity for this relay.
916
5433957
    pub fn rsa_id(&self) -> &RsaIdentity {
917
5433957
        self.rs.rsa_identity()
918
5433957
    }
919
    /// Return true if this relay and `other` seem to be the same relay.
920
    ///
921
    /// (Two relays are the same if they have the same identity.)
922
4979547
    pub fn same_relay<'b>(&self, other: &Relay<'b>) -> bool {
923
4979547
        self.id() == other.id() && self.rsa_id() == other.rsa_id()
924
4979547
    }
925
    /// Return true if this relay allows exiting to `port` on IPv4.
926
1034540
    pub fn supports_exit_port_ipv4(&self, port: u16) -> bool {
927
1034540
        self.ipv4_policy().allows_port(port)
928
1034540
    }
929
    /// Return true if this relay allows exiting to `port` on IPv6.
930
35
    pub fn supports_exit_port_ipv6(&self, port: u16) -> bool {
931
35
        self.ipv6_policy().allows_port(port)
932
35
    }
933
    /// Return true if this relay is suitable for use as a directory
934
    /// cache.
935
707844
    pub fn is_dir_cache(&self) -> bool {
936
707844
        rs_is_dir_cache(self.rs)
937
707844
    }
938
    /// Return true if this relay is marked as usable as a new Guard node.
939
1988849
    pub fn is_flagged_guard(&self) -> bool {
940
1988849
        self.rs.is_flagged_guard()
941
1988849
    }
942
    /// Return true if both relays are in the same subnet, as configured by
943
    /// `subnet_config`.
944
    ///
945
    /// Two relays are considered to be in the same subnet if they
946
    /// have IPv4 addresses with the same `subnets_family_v4`-bit
947
    /// prefix, or if they have IPv6 addresses with the same
948
    /// `subnets_family_v6`-bit prefix.
949
4707641
    pub fn in_same_subnet<'b>(&self, other: &Relay<'b>, subnet_config: &SubnetConfig) -> bool {
950
4715900
        self.rs.orport_addrs().any(|addr| {
951
4715900
            other
952
4715900
                .rs
953
4715900
                .orport_addrs()
954
4717760
                .any(|other| subnet_config.addrs_in_same_subnet(&addr.ip(), &other.ip()))
955
4715900
        })
956
4707641
    }
957
    /// Return true if both relays are in the same family.
958
    ///
959
    /// (Every relay is considered to be in the same family as itself.)
960
4980824
    pub fn in_same_family<'b>(&self, other: &Relay<'b>) -> bool {
961
4980824
        if self.same_relay(other) {
962
140371
            return true;
963
4840453
        }
964
4840453
        self.md.family().contains(other.rsa_id()) && other.md.family().contains(self.rsa_id())
965
4973160
    }
966

            
967
    /// Return true if there are any ports for which this Relay can be
968
    /// used for exit traffic.
969
    ///
970
    /// (Returns false if this relay doesn't allow exit traffic, or if it
971
    /// has been flagged as a bad exit.)
972
658583
    pub fn policies_allow_some_port(&self) -> bool {
973
658583
        if self.rs.is_flagged_bad_exit() {
974
1
            return false;
975
658582
        }
976
658582

            
977
658582
        self.md.ipv4_policy().allows_some_port() || self.md.ipv6_policy().allows_some_port()
978
658583
    }
979

            
980
    /// Return the IPv4 exit policy for this relay. If the relay has been marked BadExit, return an
981
    /// empty policy
982
1050674
    pub fn ipv4_policy(&self) -> Arc<PortPolicy> {
983
1050674
        if !self.rs.is_flagged_bad_exit() {
984
1050647
            Arc::clone(self.md.ipv4_policy())
985
        } else {
986
27
            Arc::new(PortPolicy::new_reject_all())
987
        }
988
1050674
    }
989
    /// Return the IPv6 exit policy for this relay. If the relay has been marked BadExit, return an
990
    /// empty policy
991
213
    pub fn ipv6_policy(&self) -> Arc<PortPolicy> {
992
213
        if !self.rs.is_flagged_bad_exit() {
993
179
            Arc::clone(self.md.ipv6_policy())
994
        } else {
995
34
            Arc::new(PortPolicy::new_reject_all())
996
        }
997
213
    }
998
    /// Return the IPv4 exit policy declared by this relay. Contrary to [`Relay::ipv4_policy`],
999
    /// this does not verify if the relay is marked BadExit.
1
    pub fn ipv4_declared_policy(&self) -> &Arc<PortPolicy> {
1
        self.md.ipv4_policy()
1
    }
    /// Return the IPv6 exit policy declared by this relay. Contrary to [`Relay::ipv6_policy`],
    /// this does not verify if the relay is marked BadExit.
1
    pub fn ipv6_declared_policy(&self) -> &Arc<PortPolicy> {
1
        self.md.ipv6_policy()
1
    }

            
    /// Return a reference to this relay's "router status" entry in
    /// the consensus.
    ///
    /// The router status entry contains information about the relay
    /// that the authorities voted on directly.  For most use cases,
    /// you shouldn't need them.
    ///
    /// This function is only available if the crate was built with
    /// its `experimental-api` feature.
    #[cfg(feature = "experimental-api")]
2
    pub fn rs(&self) -> &netstatus::MdConsensusRouterStatus {
2
        self.rs
2
    }
    /// Return a reference to this relay's "microdescriptor" entry in
    /// the consensus.
    ///
    /// A "microdescriptor" is a synopsis of the information about a relay,
    /// used to determine its capabilities and route traffic through it.
    /// For most use cases, you shouldn't need it.
    ///
    /// This function is only available if the crate was built with
    /// its `experimental-api` feature.
    #[cfg(feature = "experimental-api")]
2
    pub fn md(&self) -> &Microdesc {
2
        self.md
2
    }
}

            
impl<'a> ChanTarget for Relay<'a> {
207360
    fn addrs(&self) -> &[std::net::SocketAddr] {
207360
        self.rs.addrs()
207360
    }
662289
    fn ed_identity(&self) -> &Ed25519Identity {
662289
        self.id()
662289
    }
175102
    fn rsa_identity(&self) -> &RsaIdentity {
175102
        self.rsa_id()
175102
    }
}

            
impl<'a> tor_linkspec::CircTarget for Relay<'a> {
163808
    fn ntor_onion_key(&self) -> &ll::pk::curve25519::PublicKey {
163808
        self.md.ntor_key()
163808
    }
163824
    fn protovers(&self) -> &tor_protover::Protocols {
163824
        self.rs.protovers()
163824
    }
}

            
/// Return true if `rs` is usable as a directory cache.
801060
fn rs_is_dir_cache(rs: &netstatus::MdConsensusRouterStatus) -> bool {
801060
    use tor_protover::ProtoKind;
801060
    rs.is_flagged_v2dir() && rs.protovers().supports_known_subver(ProtoKind::DirCache, 2)
801060
}

            
#[cfg(test)]
mod test {
    #![allow(clippy::unwrap_used)]
    #![allow(clippy::cognitive_complexity)]
    use super::*;
    use crate::testnet::*;
    use std::collections::HashSet;
    use std::time::Duration;

            
    // Basic functionality for a partial netdir: Add microdescriptors,
    // then you have a netdir.
    #[test]
    fn partial_netdir() {
        let (consensus, microdescs) = construct_network().unwrap();
        let dir = PartialNetDir::new(consensus, None);

            
        // Check the lifetime
        let lifetime = dir.lifetime();
        assert_eq!(
            lifetime
                .valid_until()
                .duration_since(lifetime.valid_after())
                .unwrap(),
            Duration::new(86400, 0)
        );

            
        // No microdescriptors, so we don't have enough paths, and can't
        // advance.
        assert!(!dir.have_enough_paths());
        let mut dir = match dir.unwrap_if_sufficient() {
            Ok(_) => panic!(),
            Err(d) => d,
        };

            
        let missing: HashSet<_> = dir.missing_microdescs().collect();
        assert_eq!(missing.len(), 40);
        assert_eq!(missing.len(), dir.netdir.consensus.relays().len());
        for md in &microdescs {
            assert!(missing.contains(md.digest()));
        }

            
        // Now add all the mds and try again.
        for md in microdescs {
            let wanted = dir.add_microdesc(md);
            assert!(wanted);
        }

            
        let missing: HashSet<_> = dir.missing_microdescs().collect();
        assert!(missing.is_empty());
        assert!(dir.have_enough_paths());
        let _complete = match dir.unwrap_if_sufficient() {
            Ok(d) => d,
            Err(_) => panic!(),
        };
    }

            
    #[test]
    fn override_params() {
        let (consensus, _microdescs) = construct_network().unwrap();
        let override_p = "bwweightscale=2 doesnotexist=77 circwindow=500"
            .parse()
            .unwrap();
        let dir = PartialNetDir::new(consensus.clone(), Some(&override_p));
        let params = &dir.netdir.params;
        assert_eq!(params.bw_weight_scale.get(), 2);
        assert_eq!(params.circuit_window.get(), 500_i32);

            
        // try again without the override.
        let dir = PartialNetDir::new(consensus, None);
        let params = &dir.netdir.params;
        assert_eq!(params.bw_weight_scale.get(), 1_i32);
        assert_eq!(params.circuit_window.get(), 1000_i32);
    }

            
    #[test]
    fn fill_from_previous() {
        let (consensus, microdescs) = construct_network().unwrap();

            
        let mut dir = PartialNetDir::new(consensus.clone(), None);
        for md in microdescs.iter().skip(2) {
            let wanted = dir.add_microdesc(md.clone());
            assert!(wanted);
        }
        let dir1 = dir.unwrap_if_sufficient().unwrap();
        assert_eq!(dir1.missing_microdescs().count(), 2);

            
        let mut dir = PartialNetDir::new(consensus, None);
        assert_eq!(dir.missing_microdescs().count(), 40);
        dir.fill_from_previous_netdir(&dir1);
        assert_eq!(dir.missing_microdescs().count(), 2);
    }

            
    #[test]
    fn path_count() {
        let low_threshold = "min_paths_for_circs_pct=64".parse().unwrap();
        let high_threshold = "min_paths_for_circs_pct=65".parse().unwrap();

            
        let (consensus, microdescs) = construct_network().unwrap();

            
        let mut dir = PartialNetDir::new(consensus.clone(), Some(&low_threshold));
        for (idx, md) in microdescs.iter().enumerate() {
            if idx % 7 == 2 {
                continue; // skip a few relays.
            }
            dir.add_microdesc(md.clone());
        }
        let dir = dir.unwrap_if_sufficient().unwrap();

            
        // We  have 40 relays that we know about from the consensus.
        assert_eq!(dir.all_relays().count(), 40);

            
        // But only 34 are usable.
        assert_eq!(dir.relays().count(), 34);

            
        // For guards: mds 20..=39 correspond to Guard relays.
        // Their bandwidth is 2*(1000+2000+...10000) = 110_000.
        // We skipped 23, 30, and 37.  They have bandwidth
        // 4000 + 1000 + 8000 = 13_000.  So our fractional bandwidth
        // should be (110-13)/110.
        let f = dir.frac_for_role(WeightRole::Guard, |u| u.rs.is_flagged_guard());
        assert!(((97.0 / 110.0) - f).abs() < 0.000001);

            
        // For exits: mds 10..=19 and 30..=39 correspond to Exit relays.
        // We skipped 16, 30,  and 37. Per above our fractional bandwidth is
        // (110-16)/110.
        let f = dir.frac_for_role(WeightRole::Exit, |u| u.rs.is_flagged_exit());
        assert!(((94.0 / 110.0) - f).abs() < 0.000001);

            
        // For middles: all relays are middles. We skipped 2, 9, 16,
        // 23, 30, and 37. Per above our fractional bandwidth is
        // (220-33)/220
        let f = dir.frac_for_role(WeightRole::Middle, |_| true);
        assert!(((187.0 / 220.0) - f).abs() < 0.000001);

            
        // Multiplying those together, we get the fraction of paths we can
        // build at ~0.64052066, which is above the threshold we set above for
        // MinPathsForCircsPct.
        let f = dir.frac_usable_paths();
        assert!((f - 0.64052066).abs() < 0.000001);

            
        // But if we try again with a slightly higher threshold...
        let mut dir = PartialNetDir::new(consensus, Some(&high_threshold));
        for (idx, md) in microdescs.into_iter().enumerate() {
            if idx % 7 == 2 {
                continue; // skip a few relays.
            }
            dir.add_microdesc(md);
        }
        assert!(dir.unwrap_if_sufficient().is_err());
    }

            
    #[test]
    fn test_pick() {
        use crate::testing::*; // for stochastic testing

            
        let (consensus, microdescs) = construct_network().unwrap();
        let mut dir = PartialNetDir::new(consensus, None);
        for md in microdescs.into_iter() {
            let wanted = dir.add_microdesc(md.clone());
            assert!(wanted);
        }
        let dir = dir.unwrap_if_sufficient().unwrap();

            
        let total = get_iters() as isize;
        let mut picked = [0_isize; 40];
        let mut rng = get_rng();
        for _ in 0..get_iters() {
            let r = dir.pick_relay(&mut rng, WeightRole::Middle, |r| {
                r.supports_exit_port_ipv4(80)
            });
            let r = r.unwrap();
            let id_byte = r.rsa_identity().as_bytes()[0];
            picked[id_byte as usize] += 1;
        }
        // non-exits should never get picked.
        picked[0..10].iter().for_each(|x| assert_eq!(*x, 0));
        picked[20..30].iter().for_each(|x| assert_eq!(*x, 0));

            
        // We didn't we any non-default weights, so the other relays get
        // weighted proportional to their bandwidth.
        check_close(picked[19], (total * 10) / 110);
        check_close(picked[38], (total * 9) / 110);
        check_close(picked[39], (total * 10) / 110);
    }

            
    #[test]
    fn test_pick_multiple() {
        // This is mostly a copy of test_pick, except that it uses
        // pick_n_relays to pick several relays at once.

            
        use crate::testing::*; // for stochastic testing

            
        let dir = construct_netdir().unwrap().unwrap_if_sufficient().unwrap();

            
        let total = get_iters() as isize;
        let mut picked = [0_isize; 40];
        let mut rng = get_rng();
        for _ in 0..get_iters() / 4 {
            let relays = dir.pick_n_relays(&mut rng, 4, WeightRole::Middle, |r| {
                r.supports_exit_port_ipv4(80)
            });
            assert_eq!(relays.len(), 4);
            for r in relays {
                let id_byte = r.rsa_identity().as_bytes()[0];
                picked[id_byte as usize] += 1;
            }
        }
        // non-exits should never get picked.
        picked[0..10].iter().for_each(|x| assert_eq!(*x, 0));
        picked[20..30].iter().for_each(|x| assert_eq!(*x, 0));

            
        // We didn't we any non-default weights, so the other relays get
        // weighted proportional to their bandwidth.
        check_close(picked[19], (total * 10) / 110);
        check_close(picked[36], (total * 7) / 110);
        check_close(picked[39], (total * 10) / 110);
    }

            
    #[test]
    fn subnets() {
        let cfg = SubnetConfig::default();

            
        fn same_net(cfg: &SubnetConfig, a: &str, b: &str) -> bool {
            cfg.addrs_in_same_subnet(&a.parse().unwrap(), &b.parse().unwrap())
        }

            
        assert!(same_net(&cfg, "127.15.3.3", "127.15.9.9"));
        assert!(!same_net(&cfg, "127.15.3.3", "127.16.9.9"));

            
        assert!(!same_net(&cfg, "127.15.3.3", "127::"));

            
        assert!(same_net(&cfg, "ffff:ffff:90:33::", "ffff:ffff:91:34::"));
        assert!(!same_net(&cfg, "ffff:ffff:90:33::", "ffff:fffe:91:34::"));

            
        let cfg = SubnetConfig {
            subnets_family_v4: 32,
            subnets_family_v6: 128,
        };
        assert!(!same_net(&cfg, "127.15.3.3", "127.15.9.9"));
        assert!(!same_net(&cfg, "ffff:ffff:90:33::", "ffff:ffff:91:34::"));

            
        assert!(same_net(&cfg, "127.0.0.1", "127.0.0.1"));
        assert!(!same_net(&cfg, "127.0.0.1", "127.0.0.2"));
        assert!(same_net(&cfg, "ffff:ffff:90:33::", "ffff:ffff:90:33::"));

            
        let cfg = SubnetConfig {
            subnets_family_v4: 33,
            subnets_family_v6: 129,
        };
        assert!(!same_net(&cfg, "127.0.0.1", "127.0.0.1"));
        assert!(!same_net(&cfg, "::", "::"));
    }

            
    #[test]
    fn relay_funcs() {
        let (consensus, microdescs) = construct_custom_network(|idx, nb| {
            if idx == 15 {
                nb.rs.add_or_port("[f0f0::30]:9001".parse().unwrap());
            } else if idx == 20 {
                nb.rs.add_or_port("[f0f0::3131]:9001".parse().unwrap());
            }
        })
        .unwrap();
        let subnet_config = SubnetConfig::default();
        let mut dir = PartialNetDir::new(consensus, None);
        for md in microdescs.into_iter() {
            let wanted = dir.add_microdesc(md.clone());
            assert!(wanted);
        }
        let dir = dir.unwrap_if_sufficient().unwrap();

            
        // Pick out a few relays by ID.
        let r0 = dir.by_id(&[0; 32].into()).unwrap();
        let r1 = dir.by_id(&[1; 32].into()).unwrap();
        let r2 = dir.by_id(&[2; 32].into()).unwrap();
        let r3 = dir.by_id(&[3; 32].into()).unwrap();
        let r10 = dir.by_id(&[10; 32].into()).unwrap();
        let r15 = dir.by_id(&[15; 32].into()).unwrap();
        let r20 = dir.by_id(&[20; 32].into()).unwrap();

            
        assert_eq!(r0.id(), &[0; 32].into());
        assert_eq!(r0.rsa_id(), &[0; 20].into());
        assert_eq!(r1.id(), &[1; 32].into());
        assert_eq!(r1.rsa_id(), &[1; 20].into());

            
        assert!(r0.same_relay(&r0));
        assert!(r1.same_relay(&r1));
        assert!(!r1.same_relay(&r0));

            
        assert!(r0.is_dir_cache());
        assert!(!r1.is_dir_cache());
        assert!(r2.is_dir_cache());
        assert!(!r3.is_dir_cache());

            
        assert!(!r0.supports_exit_port_ipv4(80));
        assert!(!r1.supports_exit_port_ipv4(80));
        assert!(!r2.supports_exit_port_ipv4(80));
        assert!(!r3.supports_exit_port_ipv4(80));

            
        assert!(!r0.policies_allow_some_port());
        assert!(!r1.policies_allow_some_port());
        assert!(!r2.policies_allow_some_port());
        assert!(!r3.policies_allow_some_port());
        assert!(r10.policies_allow_some_port());

            
        assert!(r0.in_same_family(&r0));
        assert!(r0.in_same_family(&r1));
        assert!(r1.in_same_family(&r0));
        assert!(r1.in_same_family(&r1));
        assert!(!r0.in_same_family(&r2));
        assert!(!r2.in_same_family(&r0));
        assert!(r2.in_same_family(&r2));
        assert!(r2.in_same_family(&r3));

            
        assert!(r0.in_same_subnet(&r10, &subnet_config));
        assert!(r10.in_same_subnet(&r10, &subnet_config));
        assert!(r0.in_same_subnet(&r0, &subnet_config));
        assert!(r1.in_same_subnet(&r1, &subnet_config));
        assert!(!r1.in_same_subnet(&r2, &subnet_config));
        assert!(!r2.in_same_subnet(&r3, &subnet_config));

            
        // Make sure IPv6 families work.
        let subnet_config = SubnetConfig {
            subnets_family_v4: 128,
            subnets_family_v6: 96,
        };
        assert!(r15.in_same_subnet(&r20, &subnet_config));
        assert!(!r15.in_same_subnet(&r1, &subnet_config));

            
        // Make sure that subnet configs can be disabled.
        let subnet_config = SubnetConfig {
            subnets_family_v4: 255,
            subnets_family_v6: 255,
        };
        assert!(!r15.in_same_subnet(&r20, &subnet_config));
    }

            
    #[test]
    fn test_badexit() {
        // make a netdir where relays 10-19 are badexit, and everybody
        // exits to 443 on IPv6.
        use tor_netdoc::doc::netstatus::RelayFlags;
        let netdir = construct_custom_netdir(|idx, nb| {
            if (10..20).contains(&idx) {
                nb.rs.add_flags(RelayFlags::BAD_EXIT);
            }
            nb.md.parse_ipv6_policy("accept 443").unwrap();
        })
        .unwrap()
        .unwrap_if_sufficient()
        .unwrap();

            
        let e12 = netdir.by_id(&[12; 32].into()).unwrap();
        let e32 = netdir.by_id(&[32; 32].into()).unwrap();

            
        assert!(!e12.supports_exit_port_ipv4(80));
        assert!(e32.supports_exit_port_ipv4(80));

            
        assert!(!e12.supports_exit_port_ipv6(443));
        assert!(e32.supports_exit_port_ipv6(443));
        assert!(!e32.supports_exit_port_ipv6(555));

            
        assert!(!e12.policies_allow_some_port());
        assert!(e32.policies_allow_some_port());

            
        assert!(!e12.ipv4_policy().allows_some_port());
        assert!(!e12.ipv6_policy().allows_some_port());
        assert!(e32.ipv4_policy().allows_some_port());
        assert!(e32.ipv6_policy().allows_some_port());

            
        assert!(e12.ipv4_declared_policy().allows_some_port());
        assert!(e12.ipv6_declared_policy().allows_some_port());
    }

            
    #[cfg(feature = "experimental-api")]
    #[test]
    fn test_accessors() {
        let netdir = construct_netdir().unwrap().unwrap_if_sufficient().unwrap();

            
        let r4 = netdir.by_id(&[4; 32].into()).unwrap();
        let r16 = netdir.by_id(&[16; 32].into()).unwrap();

            
        assert!(!r4.md().ipv4_policy().allows_some_port());
        assert!(r16.md().ipv4_policy().allows_some_port());

            
        assert!(!r4.rs().is_flagged_exit());
        assert!(r16.rs().is_flagged_exit());
    }

            
    #[test]
    fn test_by_id() {
        // Make a netdir that omits the microdescriptor for 0xDDDDDD...
        let netdir = construct_custom_netdir(|idx, mut nb| {
            nb.omit_md = idx == 13;
        })
        .unwrap();

            
        assert!(netdir.missing_descriptor_for(&[13; 20].into()));
        assert!(!netdir.missing_descriptor_for(&[15; 20].into()));

            
        let netdir = netdir.unwrap_if_sufficient().unwrap();

            
        let r = netdir.by_id(&[0; 32].into()).unwrap();
        assert_eq!(r.id().as_bytes(), &[0; 32]);

            
        assert!(netdir.by_id(&[13; 32].into()).is_none());

            
        let r = netdir.by_rsa_id(&[12; 20].into()).unwrap();
        assert_eq!(r.rsa_id().as_bytes(), &[12; 20]);
        assert!(netdir.rsa_id_is_listed(&[12; 20].into()));

            
        assert!(netdir.by_rsa_id(&[13; 20].into()).is_none());

            
        assert!(netdir.by_rsa_id_unchecked(&[99; 20].into()).is_none());
        assert!(!netdir.rsa_id_is_listed(&[99; 20].into()));

            
        let r = netdir.by_rsa_id_unchecked(&[13; 20].into()).unwrap();
        assert_eq!(r.rs.rsa_identity().as_bytes(), &[13; 20]);
        assert!(netdir.rsa_id_is_listed(&[13; 20].into()));

            
        let r = netdir.by_id_pair(&[13; 32].into(), &[13; 20].into());
        assert!(r.is_none());
        let r = netdir
            .by_id_pair(&[14; 32].into(), &[14; 20].into())
            .unwrap();
        assert_eq!(r.rsa_identity(), &[14; 20].into());
        assert_eq!(r.ed_identity(), &[14; 32].into());
        let r = netdir.by_id_pair(&[14; 32].into(), &[99; 20].into());
        assert!(r.is_none());

            
        assert_eq!(
            netdir.id_pair_listed(&[13; 32].into(), &[13; 20].into()),
            None
        );
        assert_eq!(
            netdir.id_pair_listed(&[15; 32].into(), &[15; 20].into()),
            Some(true)
        );
        assert_eq!(
            netdir.id_pair_listed(&[15; 32].into(), &[99; 20].into()),
            Some(false)
        );
    }

            
    #[test]
    fn weight_type() {
        let r0 = RelayWeight(0);
        let r100 = RelayWeight(100);
        let r200 = RelayWeight(200);
        let r300 = RelayWeight(300);
        assert_eq!(r100 + r200, r300);
        assert_eq!(r100.checked_div(r200), Some(0.5));
        assert!(r100.checked_div(r0).is_none());
        assert_eq!(r200.ratio(0.5), Some(r100));
        assert!(r200.ratio(-1.0).is_none());
    }

            
    #[test]
    fn weight_accessors() {
        // Make a netdir that omits the microdescriptor for 0xDDDDDD...
        let netdir = construct_netdir().unwrap().unwrap_if_sufficient().unwrap();

            
        let g_total = netdir.total_weight(WeightRole::Guard, |r| r.is_flagged_guard());
        // This is just the total guard weight, since all our Wxy = 1.
        assert_eq!(g_total, RelayWeight(110_000));

            
        let g_total = netdir.total_weight(WeightRole::Guard, |_| false);
        assert_eq!(g_total, RelayWeight(0));

            
        let relay = netdir.by_id(&[35; 32].into()).unwrap();
        assert!(relay.is_flagged_guard());
        let w = netdir.relay_weight(&relay, WeightRole::Guard);
        assert_eq!(w, RelayWeight(6_000));

            
        let w = netdir
            .weight_by_rsa_id(&[33; 20].into(), WeightRole::Guard)
            .unwrap();
        assert_eq!(w, RelayWeight(4_000));

            
        assert!(netdir
            .weight_by_rsa_id(&[99; 20].into(), WeightRole::Guard)
            .is_none());
    }

            
    #[test]
    fn family_list() {
        let netdir = construct_custom_netdir(|idx, n| {
            if idx == 0x0a {
                n.md.family(
                    "$0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B \
                     $0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C \
                     $0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D0D"
                        .parse()
                        .unwrap(),
                );
            } else if idx == 0x0c {
                n.md.family("$0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A".parse().unwrap());
            }
        })
        .unwrap()
        .unwrap_if_sufficient()
        .unwrap();

            
        // In the testing netdir, adjacent members are in the same family by default...
        let r0 = netdir.by_id(&[0; 32].into()).unwrap();
        let family: Vec<_> = netdir.known_family_members(&r0).collect();
        assert_eq!(family.len(), 1);
        assert_eq!(family[0].id(), &Ed25519Identity::from([1; 32]));

            
        // But we've made this relay claim membership with several others.
        let r10 = netdir.by_id(&[10; 32].into()).unwrap();
        let family: HashSet<_> = netdir.known_family_members(&r10).map(|r| *r.id()).collect();
        assert_eq!(family.len(), 2);
        assert!(family.contains(&Ed25519Identity::from([11; 32])));
        assert!(family.contains(&Ed25519Identity::from([12; 32])));
        // Note that 13 doesn't get put in, even though it's listed, since it doesn't claim
        //  membership with 10.
    }
}