1
//! Support for unit tests, in this crate and elsewhere.
2
//!
3
//! This module is only enabled when the `testing` feature is enabled.
4
//!
5
//! It is not covered by semver for the `tor-netdir` crate: see notes
6
//! on [`construct_network`].
7
//!
8
//! # Panics
9
//!
10
//! These functions can panic on numerous possible internal failures:
11
//! only use these functions for testing.
12

            
13
#![allow(clippy::unwrap_used)]
14

            
15
use crate::{MdDigest, MdReceiver, PartialNetDir};
16
use std::iter;
17
use std::net::SocketAddr;
18
use std::time::{Duration, SystemTime};
19
#[cfg(feature = "geoip")]
20
use tor_geoip::GeoipDb;
21
use tor_netdoc::doc::microdesc::{Microdesc, MicrodescBuilder};
22
use tor_netdoc::doc::netstatus::MdConsensus;
23
use tor_netdoc::doc::netstatus::{Lifetime, RelayFlags, RelayWeight, RouterStatusBuilder};
24

            
25
pub use tor_netdoc::{BuildError, BuildResult};
26

            
27
/// A set of builder objects for a single node.
28
#[derive(Debug, Clone)]
29
#[non_exhaustive]
30
pub struct NodeBuilders {
31
    /// Builds a routerstatus for a single node.
32
    ///
33
    /// Adjust fields in this builder to change the node's properties.
34
    pub rs: RouterStatusBuilder<MdDigest>,
35

            
36
    /// Builds a microdescriptor for a single node.
37
    ///
38
    /// Adjust fields in this builder in order to change the node's
39
    /// properties.
40
    pub md: MicrodescBuilder,
41

            
42
    /// Set this value to `true` to omit the microdesc from the network.
43
    pub omit_md: bool,
44

            
45
    /// Set this value to `true` to omit the routerdesc from the network.
46
    pub omit_rs: bool,
47
}
48

            
49
/// Helper: a customization function that does nothing.
50
107200
pub fn simple_net_func(_idx: usize, _nb: &mut NodeBuilders) {}
51

            
52
/// As [`construct_network()`], but return a [`PartialNetDir`].
53
1967
pub fn construct_netdir() -> PartialNetDir {
54
1967
    construct_custom_netdir(simple_net_func).expect("failed to build default testing netdir")
55
1967
}
56

            
57
/// As [`construct_custom_network()`], but return a [`PartialNetDir`],
58
/// and allow network parameter customisation.
59
2040
pub fn construct_custom_netdir_with_params<F, P, PK>(
60
2040
    func: F,
61
2040
    params: P,
62
2040
    lifetime: Option<Lifetime>,
63
2040
) -> BuildResult<PartialNetDir>
64
2040
where
65
2040
    F: FnMut(usize, &mut NodeBuilders),
66
2040
    P: IntoIterator<Item = (PK, i32)>,
67
2040
    PK: Into<String>,
68
2040
{
69
2040
    construct_custom_netdir_with_params_inner(
70
2040
        func,
71
2040
        params,
72
2040
        lifetime,
73
2040
        #[cfg(feature = "geoip")]
74
2040
        None,
75
2040
    )
76
2040
}
77

            
78
/// Implementation of `construct_custom_netdir_with_params`, written this way to avoid
79
/// the GeoIP argument crossing a crate API boundary.
80
2042
fn construct_custom_netdir_with_params_inner<F, P, PK>(
81
2042
    func: F,
82
2042
    params: P,
83
2042
    lifetime: Option<Lifetime>,
84
2042
    #[cfg(feature = "geoip")] geoip_db: Option<&GeoipDb>,
85
2042
) -> BuildResult<PartialNetDir>
86
2042
where
87
2042
    F: FnMut(usize, &mut NodeBuilders),
88
2042
    P: IntoIterator<Item = (PK, i32)>,
89
2042
    PK: Into<String>,
90
2042
{
91
2042
    let (consensus, microdescs) = construct_custom_network(func, lifetime)?;
92
    #[cfg(feature = "geoip")]
93
2042
    let mut dir = if let Some(db) = geoip_db {
94
2
        PartialNetDir::new_with_geoip(consensus, Some(&params.into_iter().collect()), db)
95
    } else {
96
2040
        PartialNetDir::new(consensus, Some(&params.into_iter().collect()))
97
    };
98
    #[cfg(not(feature = "geoip"))]
99
    let mut dir = PartialNetDir::new(consensus, Some(&params.into_iter().collect()));
100
83714
    for md in microdescs {
101
81672
        dir.add_microdesc(md);
102
81672
    }
103

            
104
2042
    Ok(dir)
105
2042
}
106

            
107
/// As [`construct_custom_network()`], but return a [`PartialNetDir`].
108
1991
pub fn construct_custom_netdir<F>(func: F) -> BuildResult<PartialNetDir>
109
1991
where
110
1991
    F: FnMut(usize, &mut NodeBuilders),
111
1991
{
112
1991
    construct_custom_netdir_with_params(func, iter::empty::<(&str, _)>(), None)
113
1991
}
114

            
115
#[cfg(feature = "geoip")]
116
/// As [`construct_custom_netdir()`], but with a `GeoipDb`.
117
2
pub fn construct_custom_netdir_with_geoip<F>(func: F, db: &GeoipDb) -> BuildResult<PartialNetDir>
118
2
where
119
2
    F: FnMut(usize, &mut NodeBuilders),
120
2
{
121
2
    construct_custom_netdir_with_params_inner(func, iter::empty::<(&str, _)>(), None, Some(db))
122
2
}
123

            
124
/// As [`construct_custom_network`], but do not require a
125
/// customization function.
126
676
pub fn construct_network() -> BuildResult<(MdConsensus, Vec<Microdesc>)> {
127
676
    construct_custom_network(simple_net_func, None)
128
676
}
129

            
130
/// Build a fake network with enough information to enable some basic
131
/// tests.
132
///
133
/// By default, the constructed network will contain 40 relays,
134
/// numbered 0 through 39. They will have with RSA and Ed25519
135
/// identity fingerprints set to 0x0000...00 through 0x2727...27.
136
/// Each pair of relays is in a family with one another: 0x00..00 with
137
/// 0x01..01, and so on.
138
///
139
/// All relays are marked as usable.  The first ten are marked with no
140
/// additional flags.  The next ten are marked with the exit flag.
141
/// The next ten are marked with the guard flag.  The last ten are
142
/// marked with the exit _and_ guard flags.
143
///
144
/// TAP and Ntor onion keys are present, but unusable.
145
///
146
/// Odd-numbered exit relays are set to allow ports 80 and 443 on
147
/// IPv4.  Even-numbered exit relays are set to allow ports 1-65535
148
/// on IPv4.  No exit relays are marked to support IPv6.
149
///
150
/// Even-numbered relays support the `DirCache=2` protocol.
151
///
152
/// Every relay is given a measured weight based on its position
153
/// within its group of ten.  The weights for the ten relays in each
154
/// group are: 1000, 2000, 3000, ... 10000.  There is no additional
155
/// flag-based bandwidth weighting.
156
///
157
/// The consensus is declared as using method 34, and as being valid for
158
/// one day (in realtime) after the current `SystemTime`.
159
///
160
/// # Customization
161
///
162
/// Before each relay is added to the consensus or the network, it is
163
/// passed through the provided filtering function.  This function
164
/// receives as its arguments the current index (in range 0..40), a
165
/// [`RouterStatusBuilder`], and a [`MicrodescBuilder`].  If it
166
/// returns a `RouterStatusBuilder`, the corresponding router status
167
/// is added to the consensus.  If it returns a `MicrodescBuilder`,
168
/// the corresponding microdescriptor is added to the vector of
169
/// microdescriptor.
170
///
171
/// # Notes for future expansion
172
///
173
/// _Resist the temptation to make unconditional changes to this
174
/// function._ If the network generated by this function gets more and
175
/// more complex, then it will become harder and harder over time to
176
/// make it support new test cases and new behavior, and eventually
177
/// we'll have to throw the whole thing away.  (We ran into this
178
/// problem with Tor's unit tests.)
179
///
180
/// Instead, refactor this function so that it takes a
181
/// description of what kind of network to build, and then builds it from
182
/// that description.
183
2720
pub fn construct_custom_network<F>(
184
2720
    mut func: F,
185
2720
    lifetime: Option<Lifetime>,
186
2720
) -> BuildResult<(MdConsensus, Vec<Microdesc>)>
187
2720
where
188
2720
    F: FnMut(usize, &mut NodeBuilders),
189
2720
{
190
2720
    let f = RelayFlags::RUNNING
191
2720
        | RelayFlags::VALID
192
2720
        | RelayFlags::V2DIR
193
2720
        | RelayFlags::FAST
194
2720
        | RelayFlags::STABLE;
195
2720
    // define 4 groups of flags
196
2720
    let flags = [
197
2720
        f | RelayFlags::HSDIR,
198
2720
        f | RelayFlags::EXIT,
199
2720
        f | RelayFlags::GUARD,
200
2720
        f | RelayFlags::EXIT | RelayFlags::GUARD,
201
2720
    ];
202

            
203
2720
    let lifetime = lifetime.map(Ok).unwrap_or_else(|| {
204
2683
        let now = SystemTime::now();
205
2683
        let one_day = Duration::new(86400, 0);
206
2683

            
207
2683
        Lifetime::new(now, now + one_day / 2, now + one_day)
208
2720
    })?;
209

            
210
2720
    let mut bld = MdConsensus::builder();
211
2720
    bld.consensus_method(34)
212
2720
        .lifetime(lifetime)
213
2720
        .param("bwweightscale", 1)
214
2720
        .weights("".parse()?);
215

            
216
2720
    let mut microdescs = Vec::new();
217
111520
    for idx in 0..40_u8 {
218
        // Each relay gets a couple of no-good onion keys.
219
        // Its identity fingerprints are set to `idx`, repeating.
220
        // They all get the same address.
221
108800
        let flags = flags[(idx / 10) as usize];
222
108800
        let policy = if flags.contains(RelayFlags::EXIT) {
223
54400
            if idx % 2 == 1 {
224
27200
                "accept 80,443"
225
            } else {
226
27200
                "accept 1-65535"
227
            }
228
        } else {
229
54400
            "reject 1-65535"
230
        };
231
        // everybody is family with the adjacent relay.
232
108800
        let fam_id = [idx ^ 1; 20];
233
108800
        let family = hex::encode(fam_id);
234
108800

            
235
108800
        let mut md_builder = Microdesc::builder();
236
108800
        md_builder
237
108800
            .ntor_key((*b"----nothing in dirmgr uses this-").into())
238
108800
            .ed25519_id([idx; 32].into())
239
108800
            .family(family.parse().unwrap())
240
108800
            .parse_ipv4_policy(policy)
241
108800
            .unwrap();
242
108800
        let protocols = if idx % 2 == 0 {
243
            // even-numbered relays are dircaches.
244
54400
            "DirCache=2".parse().unwrap()
245
        } else {
246
54400
            "".parse().unwrap()
247
        };
248
108800
        let weight = RelayWeight::Measured(1000 * u32::from(idx % 10 + 1));
249
108800
        let mut rs_builder = bld.rs();
250
108800
        rs_builder
251
108800
            .identity([idx; 20].into())
252
108800
            .add_or_port(SocketAddr::from(([idx % 5, 0, 0, 3], 9001)))
253
108800
            .protos(protocols)
254
108800
            .set_flags(flags)
255
108800
            .weight(weight);
256
108800

            
257
108800
        let mut node_builders = NodeBuilders {
258
108800
            rs: rs_builder,
259
108800
            md: md_builder,
260
108800
            omit_rs: false,
261
108800
            omit_md: false,
262
108800
        };
263
108800

            
264
108800
        func(idx as usize, &mut node_builders);
265

            
266
108800
        let md = node_builders.md.testing_md()?;
267
108800
        let md_digest = *md.digest();
268
108800
        if !node_builders.omit_md {
269
108792
            microdescs.push(md);
270
108792
        }
271

            
272
108800
        if !node_builders.omit_rs {
273
108796
            node_builders
274
108796
                .rs
275
108796
                .doc_digest(md_digest)
276
108796
                .build_into(&mut bld)?;
277
4
        }
278
    }
279

            
280
2720
    let consensus = bld.testing_consensus()?;
281

            
282
2720
    Ok((consensus, microdescs))
283
2720
}
284

            
285
#[cfg(test)]
286
mod test {
287
    // @@ begin test lint list maintained by maint/add_warning @@
288
    #![allow(clippy::bool_assert_comparison)]
289
    #![allow(clippy::clone_on_copy)]
290
    #![allow(clippy::dbg_macro)]
291
    #![allow(clippy::print_stderr)]
292
    #![allow(clippy::print_stdout)]
293
    #![allow(clippy::single_char_pattern)]
294
    #![allow(clippy::unwrap_used)]
295
    #![allow(clippy::unchecked_duration_subtraction)]
296
    #![allow(clippy::useless_vec)]
297
    #![allow(clippy::needless_pass_by_value)]
298
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
299
    use super::*;
300
    #[test]
301
    fn try_with_function() {
302
        let mut val = 0_u32;
303
        let _net = construct_custom_netdir(|_idx, _nb| {
304
            val += 1;
305
        });
306
        assert_eq!(val, 40);
307
    }
308
}