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
        #[allow(deprecated)] // TODO(#2386)
231
306
        let messages = iter::from_fn(|| match self.recv.try_next() {
232
108
            Ok(Some(t)) => Some(Ok(t)),
233
            Ok(None) => Some(Err(())),
234
132
            Err(_) => None,
235
240
        })
236
132
        .collect_vec();
237

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

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

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

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

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

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

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

            
309
    // ---- reduced padding ----
310

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

            
324
    // ---- dormant ----
325

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

            
339
    // ---- more complicated evolution ----
340

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

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

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

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

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

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

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

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

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

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