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
//! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
16

            
17
use super::*;
18

            
19
use std::iter;
20

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
237
132
        eprintln!("{:#?}", &messages);
238

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

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

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

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

            
293
    // ---- simple case, active exit, defaults ----
294

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

            
308
    // ---- reduced padding ----
309

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

            
323
    // ---- dormant ----
324

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

            
338
    // ---- more complicated evolution ----
339

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

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

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

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

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

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

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

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

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

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