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::missing_panics_doc)]
14
#![allow(clippy::unwrap_used)]
15

            
16
use crate::{MdDigest, MdReceiver, PartialNetDir};
17
use std::net::SocketAddr;
18
use std::time::{Duration, SystemTime};
19
use tor_netdoc::doc::microdesc::{Microdesc, MicrodescBuilder};
20
use tor_netdoc::doc::netstatus::MdConsensus;
21
use tor_netdoc::doc::netstatus::{Lifetime, RelayFlags, RelayWeight, RouterStatusBuilder};
22

            
23
pub use tor_netdoc::{BuildError, BuildResult};
24

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

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

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

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

            
47
/// Helper: a customization function that does nothing.
48
23005
fn simple_net_func(_idx: usize, _nb: &mut NodeBuilders) {}
49

            
50
/// As [`construct_network()`], but return a [`PartialNetDir`].
51
355
pub fn construct_netdir() -> BuildResult<PartialNetDir> {
52
355
    construct_custom_netdir(simple_net_func)
53
355
}
54

            
55
/// As [`construct_custom_network()`], but return a [`PartialNetDir`].
56
366
pub fn construct_custom_netdir<F>(func: F) -> BuildResult<PartialNetDir>
57
366
where
58
366
    F: FnMut(usize, &mut NodeBuilders),
59
366
{
60
366
    let (consensus, microdescs) = construct_custom_network(func)?;
61
366
    let mut dir = PartialNetDir::new(consensus, None);
62
14747
    for md in microdescs {
63
14381
        dir.add_microdesc(md);
64
14381
    }
65

            
66
366
    Ok(dir)
67
366
}
68

            
69
/// As [`construct_custom_network`], but do not require a
70
/// customization function.
71
229
pub fn construct_network() -> BuildResult<(MdConsensus, Vec<Microdesc>)> {
72
229
    construct_custom_network(simple_net_func)
73
229
}
74

            
75
/// Build a fake network with enough information to enable some basic
76
/// tests.
77
///
78
/// By default, the constructed network will contain 40 relays,
79
/// numbered 0 through 39. They will have with RSA and Ed25519
80
/// identity fingerprints set to 0x0000...00 through 0x2727...27.
81
/// Each pair of relays is in a family with one another: 0x00..00 with
82
/// 0x01..01, and so on.
83
///
84
/// All relays are marked as usable.  The first ten are marked with no
85
/// additional flags.  The next ten are marked with the exit flag.
86
/// The next ten are marked with the guard flag.  The last ten are
87
/// marked with the exit _and_ guard flags.
88
///
89
/// TAP and Ntor onion keys are present, but unusable.
90
///
91
/// Odd-numbered exit relays are set to allow ports 80 and 443 on
92
/// IPv4.  Even-numbered exit relays are set to allow ports 1-65535
93
/// on IPv4.  No exit relays are marked to support IPv6.
94
///
95
/// Even-numbered relays support the `DirCache=2` protocol.
96
///
97
/// Every relay is given a measured weight based on its position
98
/// within its group of ten.  The weights for the ten relays in each
99
/// group are: 1000, 2000, 3000, ... 10000.  There is no additional
100
/// flag-based bandwidth weighting.
101
///
102
/// The consensus is declared as using method 34, and as being valid for
103
/// one day (in realtime) after the current `SystemTime`.
104
///
105
/// # Customization
106
///
107
/// Before each relay is added to the consensus or the network, it is
108
/// passed through the provided filtering function.  This function
109
/// receives as its arguments the current index (in range 0..40), a
110
/// [`RouterStatusBuilder`], and a [`MicrodescBuilder`].  If it
111
/// returns a `RouterStatusBuilder`, the corresponding router status
112
/// is added to the consensus.  If it returns a `MicrodescBuilder`,
113
/// the corresponding microdescriptor is added to the vector of
114
/// microdescriptor.
115
///
116
/// # Notes for future expansion
117
///
118
/// _Resist the temptation to make unconditional changes to this
119
/// function._ If the network generated by this function gets more and
120
/// more complex, then it will become harder and harder over time to
121
/// make it support new test cases and new behavior, and eventually
122
/// we'll have to throw the whole thing away.  (We ran into this
123
/// problem with Tor's unit tests.)
124
///
125
/// Instead, refactor this function so that it takes a
126
/// description of what kind of network to build, and then builds it from
127
/// that description.
128
596
pub fn construct_custom_network<F>(mut func: F) -> BuildResult<(MdConsensus, Vec<Microdesc>)>
129
596
where
130
596
    F: FnMut(usize, &mut NodeBuilders),
131
596
{
132
596
    let f = RelayFlags::RUNNING | RelayFlags::VALID | RelayFlags::V2DIR;
133
596
    // define 4 groups of flags
134
596
    let flags = [
135
596
        f,
136
596
        f | RelayFlags::EXIT,
137
596
        f | RelayFlags::GUARD,
138
596
        f | RelayFlags::EXIT | RelayFlags::GUARD,
139
596
    ];
140
596

            
141
596
    let now = SystemTime::now();
142
596
    let one_day = Duration::new(86400, 0);
143
596
    let mut bld = MdConsensus::builder();
144
596
    bld.consensus_method(34)
145
596
        .lifetime(Lifetime::new(now, now + one_day / 2, now + one_day)?)
146
596
        .param("bwweightscale", 1)
147
596
        .weights("".parse()?);
148

            
149
596
    let mut microdescs = Vec::new();
150
24420
    for idx in 0..40_u8 {
151
        // Each relay gets a couple of no-good onion keys.
152
        // Its identity fingerprints are set to `idx`, repeating.
153
        // They all get the same address.
154
23824
        let flags = flags[(idx / 10) as usize];
155
23824
        let policy = if flags.contains(RelayFlags::EXIT) {
156
11871
            if idx % 2 == 1 {
157
5943
                "accept 80,443"
158
            } else {
159
5928
                "accept 1-65535"
160
            }
161
        } else {
162
11953
            "reject 1-65535"
163
        };
164
        // everybody is family with the adjacent relay.
165
23824
        let fam_id = [idx ^ 1; 20];
166
23824
        let family = hex::encode(&fam_id);
167
23824

            
168
23824
        let mut md_builder = Microdesc::builder();
169
23824
        md_builder
170
23824
            .ntor_key((*b"----nothing in dirmgr uses this-").into())
171
23824
            .ed25519_id([idx; 32].into())
172
23824
            .family(family.parse().unwrap())
173
23824
            .parse_ipv4_policy(policy)
174
23824
            .unwrap();
175
23824
        let protocols = if idx % 2 == 0 {
176
            // even-numbered relays are dircaches.
177
11839
            "DirCache=2".parse().unwrap()
178
        } else {
179
11985
            "".parse().unwrap()
180
        };
181
23824
        let weight = RelayWeight::Measured(1000 * u32::from(idx % 10 + 1));
182
23824
        let mut rs_builder = bld.rs();
183
23824
        rs_builder
184
23824
            .identity([idx; 20].into())
185
23824
            .add_or_port(SocketAddr::from(([idx % 5, 0, 0, 3], 9001)))
186
23824
            .protos(protocols)
187
23824
            .set_flags(flags)
188
23824
            .weight(weight);
189
23824

            
190
23824
        let mut node_builders = NodeBuilders {
191
23824
            rs: rs_builder,
192
23824
            md: md_builder,
193
23824
            omit_rs: false,
194
23824
            omit_md: false,
195
23824
        };
196
23824

            
197
23824
        func(idx as usize, &mut node_builders);
198

            
199
23824
        let md = node_builders.md.testing_md()?;
200
23824
        let md_digest = *md.digest();
201
23839
        if !node_builders.omit_md {
202
23837
            microdescs.push(md);
203
        }
204

            
205
23824
        if !node_builders.omit_rs {
206
23822
            node_builders
207
23822
                .rs
208
23822
                .doc_digest(md_digest)
209
23822
                .build_into(&mut bld)?;
210
2
        }
211
    }
212

            
213
596
    let consensus = bld.testing_consensus()?;
214

            
215
596
    Ok((consensus, microdescs))
216
596
}
217

            
218
#[cfg(test)]
219
mod test {
220
    use super::*;
221
    #[test]
222
    fn try_with_function() {
223
        let mut val = 0_u32;
224
        let _net = construct_custom_netdir(|_idx, _nb| {
225
            val += 1;
226
        });
227
        assert_eq!(val, 40);
228
    }
229
}