1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
//! Support for unit tests, in this crate and elsewhere.
//!
//! This module is only enabled when the `testing` feature is enabled.
//!
//! It is not covered by semver for the `tor-netdir` crate: see notes
//! on [`construct_network`].
//!
//! # Panics
//!
//! These functions can panic on numerous possible internal failures:
//! only use these functions for testing.
#![allow(clippy::unwrap_used)]
use crate::{MdDigest, MdReceiver, PartialNetDir};
use std::iter;
use std::net::SocketAddr;
use std::time::{Duration, SystemTime};
#[cfg(feature = "geoip")]
use tor_geoip::GeoipDb;
use tor_netdoc::doc::microdesc::{Microdesc, MicrodescBuilder};
use tor_netdoc::doc::netstatus::MdConsensus;
use tor_netdoc::doc::netstatus::{Lifetime, RelayFlags, RelayWeight, RouterStatusBuilder};
pub use tor_netdoc::{BuildError, BuildResult};
/// A set of builder objects for a single node.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct NodeBuilders {
/// Builds a routerstatus for a single node.
///
/// Adjust fields in this builder to change the node's properties.
pub rs: RouterStatusBuilder<MdDigest>,
/// Builds a microdescriptor for a single node.
///
/// Adjust fields in this builder in order to change the node's
/// properties.
pub md: MicrodescBuilder,
/// Set this value to `true` to omit the microdesc from the network.
pub omit_md: bool,
/// Set this value to `true` to omit the routerdesc from the network.
pub omit_rs: bool,
}
/// Helper: a customization function that does nothing.
pub fn simple_net_func(_idx: usize, _nb: &mut NodeBuilders) {}
/// As [`construct_network()`], but return a [`PartialNetDir`].
pub fn construct_netdir() -> PartialNetDir {
construct_custom_netdir(simple_net_func).expect("failed to build default testing netdir")
}
/// As [`construct_custom_network()`], but return a [`PartialNetDir`],
/// and allow network parameter customisation.
pub fn construct_custom_netdir_with_params<F, P, PK>(
func: F,
params: P,
lifetime: Option<Lifetime>,
) -> BuildResult<PartialNetDir>
where
F: FnMut(usize, &mut NodeBuilders),
P: IntoIterator<Item = (PK, i32)>,
PK: Into<String>,
{
construct_custom_netdir_with_params_inner(
func,
params,
lifetime,
#[cfg(feature = "geoip")]
None,
)
}
/// Implementation of `construct_custom_netdir_with_params`, written this way to avoid
/// the GeoIP argument crossing a crate API boundary.
fn construct_custom_netdir_with_params_inner<F, P, PK>(
func: F,
params: P,
lifetime: Option<Lifetime>,
#[cfg(feature = "geoip")] geoip_db: Option<&GeoipDb>,
) -> BuildResult<PartialNetDir>
where
F: FnMut(usize, &mut NodeBuilders),
P: IntoIterator<Item = (PK, i32)>,
PK: Into<String>,
{
let (consensus, microdescs) = construct_custom_network(func, lifetime)?;
#[cfg(feature = "geoip")]
let mut dir = if let Some(db) = geoip_db {
PartialNetDir::new_with_geoip(consensus, Some(¶ms.into_iter().collect()), db)
} else {
PartialNetDir::new(consensus, Some(¶ms.into_iter().collect()))
};
#[cfg(not(feature = "geoip"))]
let mut dir = PartialNetDir::new(consensus, Some(¶ms.into_iter().collect()));
for md in microdescs {
dir.add_microdesc(md);
}
Ok(dir)
}
/// As [`construct_custom_network()`], but return a [`PartialNetDir`].
pub fn construct_custom_netdir<F>(func: F) -> BuildResult<PartialNetDir>
where
F: FnMut(usize, &mut NodeBuilders),
{
construct_custom_netdir_with_params(func, iter::empty::<(&str, _)>(), None)
}
#[cfg(feature = "geoip")]
/// As [`construct_custom_netdir()`], but with a `GeoipDb`.
pub fn construct_custom_netdir_with_geoip<F>(func: F, db: &GeoipDb) -> BuildResult<PartialNetDir>
where
F: FnMut(usize, &mut NodeBuilders),
{
construct_custom_netdir_with_params_inner(func, iter::empty::<(&str, _)>(), None, Some(db))
}
/// As [`construct_custom_network`], but do not require a
/// customization function.
pub fn construct_network() -> BuildResult<(MdConsensus, Vec<Microdesc>)> {
construct_custom_network(simple_net_func, None)
}
/// Build a fake network with enough information to enable some basic
/// tests.
///
/// By default, the constructed network will contain 40 relays,
/// numbered 0 through 39. They will have with RSA and Ed25519
/// identity fingerprints set to 0x0000...00 through 0x2727...27.
/// Each pair of relays is in a family with one another: 0x00..00 with
/// 0x01..01, and so on.
///
/// All relays are marked as usable. The first ten are marked with no
/// additional flags. The next ten are marked with the exit flag.
/// The next ten are marked with the guard flag. The last ten are
/// marked with the exit _and_ guard flags.
///
/// TAP and Ntor onion keys are present, but unusable.
///
/// Odd-numbered exit relays are set to allow ports 80 and 443 on
/// IPv4. Even-numbered exit relays are set to allow ports 1-65535
/// on IPv4. No exit relays are marked to support IPv6.
///
/// Even-numbered relays support the `DirCache=2` protocol.
///
/// Every relay is given a measured weight based on its position
/// within its group of ten. The weights for the ten relays in each
/// group are: 1000, 2000, 3000, ... 10000. There is no additional
/// flag-based bandwidth weighting.
///
/// The consensus is declared as using method 34, and as being valid for
/// one day (in realtime) after the current `SystemTime`.
///
/// # Customization
///
/// Before each relay is added to the consensus or the network, it is
/// passed through the provided filtering function. This function
/// receives as its arguments the current index (in range 0..40), a
/// [`RouterStatusBuilder`], and a [`MicrodescBuilder`]. If it
/// returns a `RouterStatusBuilder`, the corresponding router status
/// is added to the consensus. If it returns a `MicrodescBuilder`,
/// the corresponding microdescriptor is added to the vector of
/// microdescriptor.
///
/// # Notes for future expansion
///
/// _Resist the temptation to make unconditional changes to this
/// function._ If the network generated by this function gets more and
/// more complex, then it will become harder and harder over time to
/// make it support new test cases and new behavior, and eventually
/// we'll have to throw the whole thing away. (We ran into this
/// problem with Tor's unit tests.)
///
/// Instead, refactor this function so that it takes a
/// description of what kind of network to build, and then builds it from
/// that description.
pub fn construct_custom_network<F>(
mut func: F,
lifetime: Option<Lifetime>,
) -> BuildResult<(MdConsensus, Vec<Microdesc>)>
where
F: FnMut(usize, &mut NodeBuilders),
{
let f = RelayFlags::RUNNING
| RelayFlags::VALID
| RelayFlags::V2DIR
| RelayFlags::FAST
| RelayFlags::STABLE;
// define 4 groups of flags
let flags = [
f | RelayFlags::HSDIR,
f | RelayFlags::EXIT,
f | RelayFlags::GUARD,
f | RelayFlags::EXIT | RelayFlags::GUARD,
];
let lifetime = lifetime.map(Ok).unwrap_or_else(|| {
let now = SystemTime::now();
let one_day = Duration::new(86400, 0);
Lifetime::new(now, now + one_day / 2, now + one_day)
})?;
let mut bld = MdConsensus::builder();
bld.consensus_method(34)
.lifetime(lifetime)
.param("bwweightscale", 1)
.weights("".parse()?);
let mut microdescs = Vec::new();
for idx in 0..40_u8 {
// Each relay gets a couple of no-good onion keys.
// Its identity fingerprints are set to `idx`, repeating.
// They all get the same address.
let flags = flags[(idx / 10) as usize];
let policy = if flags.contains(RelayFlags::EXIT) {
if idx % 2 == 1 {
"accept 80,443"
} else {
"accept 1-65535"
}
} else {
"reject 1-65535"
};
// everybody is family with the adjacent relay.
let fam_id = [idx ^ 1; 20];
let family = hex::encode(fam_id);
let mut md_builder = Microdesc::builder();
md_builder
.ntor_key((*b"----nothing in dirmgr uses this-").into())
.ed25519_id([idx; 32].into())
.family(family.parse().unwrap())
.parse_ipv4_policy(policy)
.unwrap();
let protocols = if idx % 2 == 0 {
// even-numbered relays are dircaches.
"DirCache=2".parse().unwrap()
} else {
"".parse().unwrap()
};
let weight = RelayWeight::Measured(1000 * u32::from(idx % 10 + 1));
let mut rs_builder = bld.rs();
rs_builder
.identity([idx; 20].into())
.add_or_port(SocketAddr::from(([idx % 5, 0, 0, 3], 9001)))
.protos(protocols)
.set_flags(flags)
.weight(weight);
let mut node_builders = NodeBuilders {
rs: rs_builder,
md: md_builder,
omit_rs: false,
omit_md: false,
};
func(idx as usize, &mut node_builders);
let md = node_builders.md.testing_md()?;
let md_digest = *md.digest();
if !node_builders.omit_md {
microdescs.push(md);
}
if !node_builders.omit_rs {
node_builders
.rs
.doc_digest(md_digest)
.build_into(&mut bld)?;
}
}
let consensus = bld.testing_consensus()?;
Ok((consensus, microdescs))
}
#[cfg(test)]
mod test {
// @@ begin test lint list maintained by maint/add_warning @@
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_duration_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
//! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
use super::*;
#[test]
fn try_with_function() {
let mut val = 0_u32;
let _net = construct_custom_netdir(|_idx, _nb| {
val += 1;
});
assert_eq!(val, 40);
}
}