1
//! Tests for padding
2

            
3
// @@ begin test lint list maintained by maint/add_warning @@
4
#![allow(clippy::bool_assert_comparison)]
5
#![allow(clippy::clone_on_copy)]
6
#![allow(clippy::dbg_macro)]
7
#![allow(clippy::mixed_attributes_style)]
8
#![allow(clippy::print_stderr)]
9
#![allow(clippy::print_stdout)]
10
#![allow(clippy::single_char_pattern)]
11
#![allow(clippy::unwrap_used)]
12
#![allow(clippy::unchecked_time_subtraction)]
13
#![allow(clippy::useless_vec)]
14
#![allow(clippy::needless_pass_by_value)]
15
#![allow(clippy::string_slice)] // See arti#2571
16
//! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
17

            
18
use super::*;
19

            
20
use std::iter;
21

            
22
use async_trait::async_trait;
23
use futures::channel::mpsc;
24
use itertools::{Itertools, zip_eq};
25
#[cfg(feature = "relay")]
26
use safelog::Sensitive;
27

            
28
use tor_cell::chancell::msg::PaddingNegotiateCmd;
29
use tor_config::PaddingLevel;
30
use tor_linkspec::{HasRelayIds, OwnedChanTarget};
31
use tor_memquota::ArcMemoryQuotaTrackerExt as _;
32
use tor_netdir::NetDir;
33
use tor_proto::channel::{Channel, CtrlMsg};
34
use tor_proto::memquota::{ChannelAccount, ToplevelAccount};
35
use tor_rtcompat::{Runtime, test_with_all_runtimes};
36

            
37
use crate::ChannelUsage;
38
use crate::mgr::{AbstractChanMgr, AbstractChannelFactory};
39

            
40
use crate::factory::BootstrapReporter;
41
use PaddingLevel as PL;
42

            
43
const DEF_MS: [u32; 2] = [1500, 9500];
44
const REDUCED_MS: [u32; 2] = [9000, 14000];
45

            
46
const ADJ_MS: [u32; 2] = [1499, 9499];
47
const ADJ_REDUCED_MS: [u32; 2] = [8999, 13999];
48

            
49
/// Returns a NetDir that has consensus parameters as specified
50
28
fn some_interesting_netdir<'v, V>(values: V) -> Arc<NetDir>
51
28
where
52
28
    V: IntoIterator<Item = (&'v str, i32)>,
53
{
54
1120
    tor_netdir::testnet::construct_custom_netdir_with_params(|_, _, _| {}, values, None)
55
28
        .unwrap()
56
28
        .unwrap_if_sufficient()
57
28
        .unwrap()
58
28
        .into()
59
28
}
60

            
61
/// Returns a NetDir that has consensus parameters different from the protocol default
62
16
fn interesting_netdir() -> Arc<NetDir> {
63
16
    some_interesting_netdir(
64
16
        [
65
16
            ("nf_ito_low", ADJ_MS[0]),
66
16
            ("nf_ito_high", ADJ_MS[1]),
67
16
            ("nf_ito_low_reduced", ADJ_REDUCED_MS[0]),
68
16
            ("nf_ito_high_reduced", ADJ_REDUCED_MS[1]),
69
16
        ]
70
16
        .into_iter()
71
72
        .map(|(k, v)| (k, v as _)),
72
    )
73
16
}
74

            
75
#[test]
76
2
fn padding_parameters_calculation() {
77
6
    fn one(pconfig: PaddingLevel, netparams: &NetParamsExtract, exp: Option<[u32; 2]>) {
78
6
        eprintln!(
79
            "### {:?} {:?}",
80
6
            &pconfig,
81
27
            netparams.nf_ito.map(|l| l.map(|v| v.as_millis().get())),
82
        );
83
6
        let got = padding_parameters(pconfig, netparams).unwrap();
84
9
        let exp = exp.map(|exp| {
85
6
            PaddingParameters::builder()
86
6
                .low(exp[0].into())
87
6
                .high(exp[1].into())
88
6
                .build()
89
6
                .unwrap()
90
6
        });
91
6
        assert_eq!(got, exp);
92
6
    }
93

            
94
2
    one(
95
2
        PL::default(),
96
2
        &NetParamsExtract::from(interesting_netdir().params()),
97
2
        Some(ADJ_MS),
98
    );
99

            
100
2
    one(
101
2
        PL::Reduced,
102
2
        &NetParamsExtract::from(interesting_netdir().params()),
103
2
        Some(ADJ_REDUCED_MS),
104
    );
105

            
106
3
    let make_bogus_netdir = |values: &[(&str, i32)]| {
107
2
        NetParamsExtract::from(
108
2
            tor_netdir::testnet::construct_custom_netdir_with_params(
109
80
                |_, _, _| {},
110
2
                values.iter().cloned(),
111
2
                None,
112
            )
113
2
            .unwrap()
114
2
            .unwrap_if_sufficient()
115
2
            .unwrap()
116
2
            .params(),
117
        )
118
2
    };
119

            
120
2
    let bogus_netdir = make_bogus_netdir(&[
121
2
        // for testing low > high
122
2
        ("nf_ito_low", ADJ_REDUCED_MS[1] as _),
123
2
        ("nf_ito_high", ADJ_REDUCED_MS[0] as _),
124
2
    ]);
125
2
    one(PL::default(), &bogus_netdir, Some(DEF_MS));
126
2
}
127

            
128
#[derive(Clone)]
129
struct FakeChannelFactory {
130
    channel: Arc<Channel>,
131
}
132

            
133
#[async_trait]
134
impl AbstractChannelFactory for FakeChannelFactory {
135
    type Channel = Channel;
136
    type BuildSpec = tor_linkspec::OwnedChanTarget;
137
    type Stream = ();
138

            
139
    async fn build_channel(
140
        &self,
141
        _target: &Self::BuildSpec,
142
        _reporter: BootstrapReporter,
143
        _memquota: ChannelAccount,
144
48
    ) -> Result<Arc<Self::Channel>> {
145
        Ok(self.channel.clone())
146
48
    }
147

            
148
    #[cfg(feature = "relay")]
149
    async fn build_channel_using_incoming(
150
        &self,
151
        _peer: Sensitive<std::net::SocketAddr>,
152
        _stream: Self::Stream,
153
        _memquota: ChannelAccount,
154
    ) -> Result<Arc<Self::Channel>> {
155
        unimplemented!()
156
    }
157
}
158

            
159
struct CaseContext {
160
    channel: Arc<Channel>,
161
    recv: mpsc::UnboundedReceiver<CtrlMsg>,
162
    chanmgr: AbstractChanMgr<FakeChannelFactory>,
163
    netparams: Arc<dyn AsRef<NetParameters>>,
164
}
165

            
166
/// Details of an expected control message
167
#[derive(Debug, Clone, Default)]
168
struct Expected {
169
    enabled: Option<bool>,
170
    timing: Option<[u32; 2]>,
171
    nego: Option<(PaddingNegotiateCmd, [u32; 2])>,
172
}
173

            
174
48
async fn case(
175
48
    rt: &impl Runtime,
176
48
    level: PaddingLevel,
177
48
    dormancy: Dormancy,
178
48
    usage: ChannelUsage,
179
48
) -> CaseContext {
180
48
    let mut cconfig = ChannelConfig::builder();
181
48
    cconfig.padding(level);
182
48
    let cconfig = cconfig.build().unwrap();
183

            
184
48
    eprintln!("\n---- {:?} {:?} {:?} ----", &cconfig, &dormancy, &usage);
185

            
186
48
    let (channel, recv) =
187
48
        Channel::new_fake(rt.clone(), tor_proto::channel::ChannelType::ClientInitiator);
188
48
    let peer_id = channel.target().ed_identity().unwrap().clone();
189
48
    let relay_ids = OwnedChanTarget::builder()
190
48
        .ed_identity(peer_id.clone())
191
48
        .build()
192
48
        .unwrap();
193
48
    let factory = FakeChannelFactory {
194
48
        channel: Arc::new(channel),
195
48
    };
196

            
197
48
    let netparams = Arc::new(NetParameters::default());
198

            
199
48
    let chanmgr = AbstractChanMgr::new(
200
48
        factory,
201
48
        cconfig,
202
48
        dormancy,
203
48
        &netparams,
204
48
        BootstrapReporter::fake(),
205
48
        ToplevelAccount::new_noop(),
206
    );
207

            
208
48
    let (channel, _prov) = chanmgr.get_or_launch(relay_ids, usage).await.unwrap();
209

            
210
48
    CaseContext {
211
48
        channel,
212
48
        recv,
213
48
        chanmgr,
214
48
        netparams,
215
48
    }
216
48
}
217

            
218
impl CaseContext {
219
72
    fn netparams(&self) -> Arc<dyn AsRef<NetParameters>> {
220
72
        self.netparams.clone()
221
72
    }
222

            
223
108
    fn expect_1(&mut self, exp: Expected) {
224
108
        self.expect(vec![exp]);
225
108
    }
226
24
    fn expect_0(&mut self) {
227
24
        self.expect(vec![]);
228
24
    }
229

            
230
132
    fn expect(&mut self, expected: Vec<Expected>) {
231
        #[allow(deprecated)] // TODO(#2386)
232
306
        let messages = iter::from_fn(|| match self.recv.try_next() {
233
108
            Ok(Some(t)) => Some(Ok(t)),
234
            Ok(None) => Some(Err(())),
235
132
            Err(_) => None,
236
240
        })
237
132
        .collect_vec();
238

            
239
132
        eprintln!("{:#?}", &messages);
240

            
241
132
        for (i, (got, exp)) in zip_eq(messages, expected).enumerate() {
242
108
            eprintln!("{} {:?} {:?}", i, got, exp);
243
108
            let got: ChannelPaddingInstructionsUpdates = match got {
244
108
                Ok(CtrlMsg::ConfigUpdate(u)) => (*u).clone(),
245
                _ => panic!("wrong message {:?}", got),
246
            };
247

            
248
            let Expected {
249
108
                enabled,
250
108
                timing,
251
108
                nego,
252
108
            } = exp;
253
108
            let nego =
254
132
                nego.map(|(cmd, [low, high])| PaddingNegotiate::from_raw(cmd, low as _, high as _));
255
144
            let timing = timing.map(|[low, high]| {
256
72
                PaddingParameters::builder()
257
72
                    .low(low.into())
258
72
                    .high(high.into())
259
72
                    .build()
260
72
                    .unwrap()
261
72
            });
262
108
            assert_eq!(got.padding_enable(), enabled.as_ref());
263
108
            assert_eq!(got.padding_parameters(), timing.as_ref());
264
108
            assert_eq!(got.padding_negotiate(), nego.as_ref());
265
        }
266
132
    }
267
}
268

            
269
/// Test padding control from the top of chanmgr through to just before the channel reactor
270
///
271
/// The rules about when to send padding and what negotiation cells to send are super complex.
272
/// Furthermore, our implementation is spread across several layers, mostly for performance
273
/// reasons (in particular, to do as much of the work centrally, in
274
/// the channel manager, as possible).
275
///
276
/// So here we test what happens if we call the various channel manager methods (the methods on
277
/// `AbstractChanMgr`, not `ChanMgr`, because our channel factory is strange, but the methods of
278
/// `ChanMgr` are simple passthroughs).
279
///
280
/// We observe the effect by pretending that we are the channel reactor, and reading out
281
/// the control messages.  The channel reactor logic is very simple: it just does as it's
282
/// told.  For example each PaddingNegotiation in a control message will be sent precisely
283
/// once (assuming it can be sent before the channel is closed or the next one arrives).
284
/// The timing parameters, and enablement, are passed directly to the padding timer.
285
#[test]
286
2
fn padding_control_through_layer() {
287
2
    test_with_all_runtimes!(padding_control_through_layer_impl);
288
2
}
289

            
290
/// Helper for padding_control_through_layer: takes a runtime as an argument.
291
12
async fn padding_control_through_layer_impl(rt: impl tor_rtcompat::Runtime) {
292
    const STOP_MSG: (PaddingNegotiateCmd, [u32; 2]) = (PaddingNegotiateCmd::STOP, [0, 0]);
293
    const START_CMD: PaddingNegotiateCmd = PaddingNegotiateCmd::START;
294

            
295
    // ---- simple case, active exit, defaults ----
296

            
297
12
    let mut c = case(
298
12
        &rt,
299
12
        PL::default(),
300
12
        Dormancy::Active,
301
12
        ChannelUsage::UserTraffic,
302
12
    )
303
12
    .await;
304
12
    c.expect_1(Expected {
305
12
        enabled: Some(true),
306
12
        timing: Some(DEF_MS),
307
12
        nego: None,
308
12
    });
309

            
310
    // ---- reduced padding ----
311

            
312
12
    let mut c = case(
313
12
        &rt,
314
12
        PL::Reduced,
315
12
        Dormancy::Active,
316
12
        ChannelUsage::UserTraffic,
317
12
    )
318
12
    .await;
319
12
    c.expect_1(Expected {
320
12
        enabled: Some(true),
321
12
        timing: Some(REDUCED_MS),
322
12
        nego: Some(STOP_MSG),
323
12
    });
324

            
325
    // ---- dormant ----
326

            
327
12
    let mut c = case(
328
12
        &rt,
329
12
        PL::default(),
330
12
        Dormancy::Dormant,
331
12
        ChannelUsage::UserTraffic,
332
12
    )
333
12
    .await;
334
12
    c.expect_1(Expected {
335
12
        enabled: None,
336
12
        timing: None,
337
12
        nego: Some(STOP_MSG),
338
12
    });
339

            
340
    // ---- more complicated evolution ----
341

            
342
12
    let cconfig_reduced = {
343
12
        let mut cconfig = ChannelConfig::builder();
344
12
        cconfig.padding(PL::Reduced);
345
12
        cconfig.build().unwrap()
346
    };
347

            
348
12
    let mut c = case(&rt, PL::default(), Dormancy::Active, ChannelUsage::Dir).await;
349
    // directory circuits don't get padding (and we don't need to tell the peer to disable)
350
12
    c.expect_0();
351

            
352
12
    eprintln!("### UserTraffic ###");
353
12
    c.channel.engage_padding_activities();
354
12
    c.expect_1(Expected {
355
12
        enabled: Some(true),  // we now turn on our padding sender
356
12
        timing: Some(DEF_MS), // with default parameters
357
12
        nego: None,           // the peer will start padding when it sees us do non-dir stuff
358
12
    });
359

            
360
12
    eprintln!("### set_dormancy - Dormant ###");
361
12
    c.chanmgr
362
12
        .set_dormancy(Dormancy::Dormant, c.netparams())
363
12
        .unwrap();
364
12
    c.expect_1(Expected {
365
12
        enabled: Some(false), // we now must turn off our padding sender
366
12
        timing: None,
367
12
        nego: Some(STOP_MSG), // and tell the peer to stop
368
12
    });
369

            
370
12
    eprintln!("### change to reduced padding while dormant ###");
371
12
    c.chanmgr
372
12
        .reconfigure(&cconfig_reduced, c.netparams())
373
12
        .unwrap();
374
12
    c.expect_0();
375

            
376
12
    eprintln!("### set_dormancy - Active ###");
377
12
    c.chanmgr
378
12
        .set_dormancy(Dormancy::Active, c.netparams())
379
12
        .unwrap();
380
12
    c.expect_1(Expected {
381
12
        enabled: Some(true),
382
12
        timing: Some(REDUCED_MS),
383
12
        nego: None, // don't enable inbound padding again
384
12
    });
385

            
386
12
    eprintln!("### imagine a netdir turns up, with some different parameters ###");
387
12
    c.netparams = interesting_netdir();
388
12
    c.chanmgr.update_netparams(c.netparams()).unwrap();
389
12
    c.expect_1(Expected {
390
12
        enabled: None,                // still enabled
391
12
        timing: Some(ADJ_REDUCED_MS), // parameters adjusted a bit
392
12
        nego: None,                   // no need to send an update
393
12
    });
394

            
395
12
    eprintln!("### change back to normal padding ###");
396
12
    c.chanmgr
397
12
        .reconfigure(&ChannelConfig::default(), c.netparams())
398
12
        .unwrap();
399
12
    c.expect_1(Expected {
400
12
        enabled: None,                   // still enabled
401
12
        timing: Some(ADJ_MS),            // parameters adjusted
402
12
        nego: Some((START_CMD, [0, 0])), // ask peer to use consensus default
403
12
    });
404

            
405
12
    eprintln!("### consensus changes to no padding ###");
406
    // ---- consensus is no padding ----
407
12
    c.netparams = some_interesting_netdir(
408
12
        [
409
12
            "nf_ito_low",
410
12
            "nf_ito_high",
411
12
            "nf_ito_low_reduced",
412
12
            "nf_ito_high_reduced",
413
12
        ]
414
12
        .into_iter()
415
48
        .map(|k| (k, 0)),
416
    );
417
12
    c.chanmgr.update_netparams(c.netparams()).unwrap();
418
12
    c.expect_1(Expected {
419
12
        enabled: Some(false),
420
12
        timing: None,
421
12
        nego: None,
422
12
    });
423

            
424
    // Ideally we would somehow test the sending of a START message with nonzero parameters.
425
    //
426
    // However, that can only occur if we want the peer to send some padding which is not the
427
    // consensus default.  And we get our own desired parameters from our idea of the consensus:
428
    // the config can only enable/disable/reduce (and for reduced, we ask our peer not to send
429
    // padding at all).
430
    //
431
    // The only current arrangements for providing alternative parameters are via netdir overrides,
432
    // which (because they override our view of the netdir) alter not only our idea of what to do,
433
    // but also our idea of what our peer will do.
434
    //
435
    // Possibly at some future point we might support specifying padding parameters
436
    // separately in the config.
437
12
}