1
//! Types related to stream isolation
2
use downcast_rs::{Downcast, impl_downcast};
3
use dyn_clone::{DynClone, clone_trait_object};
4
use std::sync::atomic::{AtomicU64, Ordering};
5

            
6
/// A type that can make isolation decisions about streams it is attached to.
7
///
8
/// Types that implement `Isolation` contain properties about a stream that are
9
/// used to make decisions about whether that stream can share the same circuit
10
/// as other streams. You may pass in any type implementing `Isolation` when
11
/// creating a stream via `TorClient::connect_with_prefs`, or constructing a
12
/// circuit with [`CircMgr::get_or_launch_exit()`](crate::CircMgr::get_or_launch_exit).
13
///
14
/// You typically do not want to implement this trait directly.  Instead, most
15
/// users should implement [`IsolationHelper`].
16
pub trait Isolation:
17
    seal::Sealed + Downcast + DynClone + std::fmt::Debug + Send + Sync + 'static
18
{
19
    /// Return true if this Isolation is compatible with another.
20
    ///
21
    /// Two streams may share a circuit if and only if they have compatible
22
    /// `Isolation`s.
23
    ///
24
    /// # Requirements
25
    ///
26
    /// For correctness, this relation must be symmetrical and reflexive:
27
    /// `self.compatible(other)` must equal `other.compatible(self)`, and
28
    /// `self.compatible(self)` must be true.
29
    ///
30
    /// For correctness, this function must always give the same result as
31
    /// `self.join(other).is_some()`.
32
    ///
33
    /// This relationship does **not** have to be transitive: it's possible that
34
    /// stream A can share a circuit with either stream B or stream C, but not
35
    /// with both.
36
    fn compatible(&self, other: &dyn Isolation) -> bool;
37

            
38
    /// Join two [`Isolation`] into the intersection of what each allows.
39
    ///
40
    /// A circuit's isolation is the `join` of the isolation values of all of
41
    /// the streams that have _ever_ used that circuit.  A circuit's isolation
42
    /// can never be `None`: streams that would cause it to be `None` can't be
43
    /// attached to the circuit.
44
    ///
45
    /// When a stream is added to a circuit, `join` is used to calculate the
46
    /// circuit's new isolation.
47
    ///
48
    /// # Requirements
49
    ///
50
    /// For correctness, this function must be commutative: `self.join(other)`
51
    /// must equal `other.join(self)`.  Also, it must be idempotent:
52
    /// `self.join(self)` must equal self.
53
    //
54
    // TODO: (This function probably should be associative too, but we haven't done
55
    // all the math.)
56
    fn join(&self, other: &dyn Isolation) -> Option<Box<dyn Isolation>>;
57

            
58
    /// Return true if this `Isolation` object should be considered sufficiently strong
59
    /// as to enable long-lived circuits.
60
    ///
61
    /// By default, once a circuit has been in use for long enough,
62
    /// it is considered no longer usable for new circuits.
63
    /// But if the circuit's isolation is sufficiently strong
64
    /// (and this method returns true)
65
    /// then a circuit will keep being used for new streams indefinitely.
66
    ///
67
    /// The default implementation of this method returns false.
68
    fn enables_long_lived_circuits(&self) -> bool {
69
        false
70
    }
71
}
72

            
73
/// Seal preventing implementation of Isolation not relying on IsolationHelper
74
mod seal {
75
    /// Seal preventing implementation of Isolation not relying on IsolationHelper
76
    pub trait Sealed {}
77
    impl<T: super::IsolationHelper> Sealed for T {}
78
}
79

            
80
impl_downcast!(Isolation);
81
clone_trait_object!(Isolation);
82
impl<T: Isolation> From<T> for Box<dyn Isolation> {
83
52
    fn from(isolation: T) -> Self {
84
52
        Box::new(isolation)
85
52
    }
86
}
87

            
88
impl<T: IsolationHelper + Clone + std::fmt::Debug + Send + Sync + 'static> Isolation for T {
89
512
    fn compatible(&self, other: &dyn Isolation) -> bool {
90
512
        if let Some(other) = other.as_any().downcast_ref() {
91
476
            self.compatible_same_type(other)
92
        } else {
93
36
            false
94
        }
95
512
    }
96

            
97
452
    fn join(&self, other: &dyn Isolation) -> Option<Box<dyn Isolation>> {
98
452
        if let Some(other) = other.as_any().downcast_ref() {
99
448
            self.join_same_type(other)
100
459
                .map(|res| Box::new(res) as Box<dyn Isolation>)
101
        } else {
102
4
            None
103
        }
104
452
    }
105

            
106
448
    fn enables_long_lived_circuits(&self) -> bool {
107
448
        IsolationHelper::enables_long_lived_circuits(self)
108
448
    }
109
}
110

            
111
/// Trait to help implement [`Isolation`].
112
///
113
/// You should generally implement this trait whenever you need to implement a
114
/// new set of stream isolation rules: it takes care of down-casting and type
115
/// checking for you.
116
///
117
/// When you implement this trait for some type T, isolation objects of that
118
/// type will be incompatible (unable to share circuits) with objects of _any
119
/// other type_.  (That's usually what you want; if you're defining a new type
120
/// of Isolation rules, then you probably don't want streams using different
121
/// rules to share circuits with yours.)
122
pub trait IsolationHelper: Sized {
123
    /// Returns whether self and other are compatible.
124
    ///
125
    /// Two streams may share a circuit if and only if they have compatible
126
    /// `Isolation`s.
127
    ///
128
    /// (See [`Isolation::compatible`] for more information and requirements.)
129
    fn compatible_same_type(&self, other: &Self) -> bool;
130

            
131
    /// Join self and other into the intersection of what they allows.
132
    ///
133
    /// (See [`Isolation::join`] for more information and requirements.)
134
    fn join_same_type(&self, other: &Self) -> Option<Self>;
135

            
136
    /// Return true if this `Isolation` object should be considered sufficiently strong
137
    /// as to permit long-lived circuits.
138
    ///
139
    /// (See [`Isolation::enables_long_lived_circuits`] for more information.)
140
    fn enables_long_lived_circuits(&self) -> bool {
141
        false
142
    }
143
}
144

            
145
/// A token used to isolate unrelated streams on different circuits.
146
///
147
/// When two streams are associated with different isolation tokens, they
148
/// can never share the same circuit.
149
///
150
/// Tokens created with [`IsolationToken::new`] are all different from
151
/// one another, and different from tokens created with
152
/// [`IsolationToken::no_isolation`]. However, tokens created with
153
/// [`IsolationToken::no_isolation`] are all equal to one another.
154
///
155
/// # Examples
156
///
157
/// Creating distinct isolation tokens:
158
///
159
/// ```rust
160
/// # use tor_circmgr::IsolationToken;
161
/// let token_1 = IsolationToken::new();
162
/// let token_2 = IsolationToken::new();
163
///
164
/// assert_ne!(token_1, token_2);
165
///
166
/// // Demonstrating the behaviour of no_isolation() tokens:
167
/// assert_ne!(token_1, IsolationToken::no_isolation());
168
/// assert_eq!(IsolationToken::no_isolation(), IsolationToken::no_isolation());
169
/// ```
170
///
171
/// Using an isolation token to route streams differently over the Tor network:
172
///
173
/// ```ignore
174
/// use arti_client::StreamPrefs;
175
///
176
/// let token_1 = IsolationToken::new();
177
/// let token_2 = IsolationToken::new();
178
///
179
/// let mut prefs_1 = StreamPrefs::new();
180
/// prefs_1.set_isolation(token_1);
181
///
182
/// let mut prefs_2 = StreamPrefs::new();
183
/// prefs_2.set_isolation(token_2);
184
///
185
/// // These two connections will come from different source IP addresses.
186
/// tor_client.connect(("example.com", 80), Some(prefs_1)).await?;
187
/// tor_client.connect(("example.com", 80), Some(prefs_2)).await?;
188
/// ```
189
// # Semver note
190
//
191
// This type is re-exported by `arti-client`: any changes to it must be
192
// reflected in `arti-client`'s version.
193
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
194
pub struct IsolationToken(u64);
195

            
196
#[allow(clippy::new_without_default)]
197
impl IsolationToken {
198
    /// Create a new IsolationToken, unequal to any other token this function
199
    /// has created.
200
    ///
201
    /// # Panics
202
    ///
203
    /// Panics if we have already allocated 2^64 isolation tokens: in that
204
    /// case, we have exhausted the space of possible tokens, and it is
205
    /// no longer possible to ensure isolation.
206
1008
    pub fn new() -> Self {
207
        /// Internal counter used to generate different tokens each time
208
        static COUNTER: AtomicU64 = AtomicU64::new(1);
209
        // Ordering::Relaxed is fine because we don't care about causality, we just want a
210
        // different number each time
211
1008
        let token = COUNTER.fetch_add(1, Ordering::Relaxed);
212
1008
        assert!(token < u64::MAX);
213
1008
        IsolationToken(token)
214
1008
    }
215

            
216
    /// Create a new IsolationToken equal to every other token created
217
    /// with this function, but different from all tokens created with
218
    /// `new`.
219
    ///
220
    /// This can be used when no isolation is wanted for some streams.
221
628
    pub fn no_isolation() -> Self {
222
628
        IsolationToken(0)
223
628
    }
224
}
225

            
226
impl IsolationHelper for IsolationToken {
227
900
    fn compatible_same_type(&self, other: &Self) -> bool {
228
900
        self == other
229
900
    }
230
428
    fn join_same_type(&self, other: &Self) -> Option<Self> {
231
428
        if self.compatible_same_type(other) {
232
424
            Some(*self)
233
        } else {
234
4
            None
235
        }
236
428
    }
237

            
238
448
    fn enables_long_lived_circuits(&self) -> bool {
239
448
        false
240
448
    }
241
}
242

            
243
/// Helper macro to implement IsolationHelper for tuple of IsolationHelper
244
macro_rules! tuple_impls {
245
    ($(
246
        $Tuple:ident {
247
            $(($idx:tt) -> $T:ident)+
248
        }
249
    )+) => {
250
        $(
251
            impl<$($T:IsolationHelper),+> IsolationHelper for ($($T,)+) {
252
22
                fn compatible_same_type(&self, other: &Self) -> bool {
253
18
                    $(self.$idx.compatible_same_type(&other.$idx))&&+
254
22
                }
255

            
256
4
                fn join_same_type(&self, other: &Self) -> Option<Self> {
257
                    Some((
258
4
                    $(self.$idx.join_same_type(&other.$idx)?,)+
259
                    ))
260
4
                }
261

            
262
                fn enables_long_lived_circuits(&self) -> bool {
263
                    $(self.$idx.enables_long_lived_circuits() || )+ false
264
                }
265
            }
266
        )+
267
    }
268
}
269

            
270
tuple_impls! {
271
    Tuple1 {
272
        (0) -> A
273
    }
274
    Tuple2 {
275
        (0) -> A
276
        (1) -> B
277
    }
278
    Tuple3 {
279
        (0) -> A
280
        (1) -> B
281
        (2) -> C
282
    }
283
    Tuple4 {
284
        (0) -> A
285
        (1) -> B
286
        (2) -> C
287
        (3) -> D
288
    }
289
    Tuple5 {
290
        (0) -> A
291
        (1) -> B
292
        (2) -> C
293
        (3) -> D
294
        (4) -> E
295
    }
296
    Tuple6 {
297
        (0) -> A
298
        (1) -> B
299
        (2) -> C
300
        (3) -> D
301
        (4) -> E
302
        (5) -> F
303
    }
304
    Tuple7 {
305
        (0) -> A
306
        (1) -> B
307
        (2) -> C
308
        (3) -> D
309
        (4) -> E
310
        (5) -> F
311
        (6) -> G
312
    }
313
    Tuple8 {
314
        (0) -> A
315
        (1) -> B
316
        (2) -> C
317
        (3) -> D
318
        (4) -> E
319
        (5) -> F
320
        (6) -> G
321
        (7) -> H
322
    }
323
    Tuple9 {
324
        (0) -> A
325
        (1) -> B
326
        (2) -> C
327
        (3) -> D
328
        (4) -> E
329
        (5) -> F
330
        (6) -> G
331
        (7) -> H
332
        (8) -> I
333
    }
334
    Tuple10 {
335
        (0) -> A
336
        (1) -> B
337
        (2) -> C
338
        (3) -> D
339
        (4) -> E
340
        (5) -> F
341
        (6) -> G
342
        (7) -> H
343
        (8) -> I
344
        (9) -> J
345
    }
346
    Tuple11 {
347
        (0) -> A
348
        (1) -> B
349
        (2) -> C
350
        (3) -> D
351
        (4) -> E
352
        (5) -> F
353
        (6) -> G
354
        (7) -> H
355
        (8) -> I
356
        (9) -> J
357
        (10) -> K
358
    }
359
    Tuple12 {
360
        (0) -> A
361
        (1) -> B
362
        (2) -> C
363
        (3) -> D
364
        (4) -> E
365
        (5) -> F
366
        (6) -> G
367
        (7) -> H
368
        (8) -> I
369
        (9) -> J
370
        (10) -> K
371
        (11) -> L
372
    }
373
}
374

            
375
/// A set of information about how a stream should be isolated.
376
///
377
/// If two streams are isolated from one another, they may not share
378
/// a circuit.
379
#[derive(Clone, Debug, derive_builder::Builder)]
380
pub struct StreamIsolation {
381
    /// Any isolation set on the stream.
382
    #[builder(default = "Box::new(IsolationToken::no_isolation())")]
383
    stream_isolation: Box<dyn Isolation>,
384
    /// Any additional isolation token set on an object that "owns" this
385
    /// stream.  This is typically owned by a `TorClient`.
386
    #[builder(default = "IsolationToken::no_isolation()")]
387
    owner_token: IsolationToken,
388
}
389

            
390
impl StreamIsolation {
391
    /// Construct a new StreamIsolation with no isolation enabled.
392
292
    pub fn no_isolation() -> Self {
393
292
        StreamIsolationBuilder::new()
394
292
            .build()
395
292
            .expect("Bug constructing StreamIsolation")
396
292
    }
397

            
398
    /// Return a new StreamIsolationBuilder for constructing
399
    /// StreamIsolation objects.
400
18
    pub fn builder() -> StreamIsolationBuilder {
401
18
        StreamIsolationBuilder::new()
402
18
    }
403
}
404

            
405
impl IsolationHelper for StreamIsolation {
406
1216
    fn compatible_same_type(&self, other: &StreamIsolation) -> bool {
407
1216
        self.owner_token == other.owner_token
408
432
            && self
409
432
                .stream_isolation
410
432
                .compatible(other.stream_isolation.as_ref())
411
1216
    }
412

            
413
416
    fn join_same_type(&self, other: &StreamIsolation) -> Option<StreamIsolation> {
414
416
        if self.owner_token != other.owner_token {
415
4
            return None;
416
412
        }
417
412
        self.stream_isolation
418
412
            .join(other.stream_isolation.as_ref())
419
412
            .map(|stream_isolation| StreamIsolation {
420
412
                stream_isolation,
421
412
                owner_token: self.owner_token,
422
412
            })
423
416
    }
424

            
425
448
    fn enables_long_lived_circuits(&self) -> bool {
426
448
        self.stream_isolation.enables_long_lived_circuits()
427
448
    }
428
}
429

            
430
impl StreamIsolationBuilder {
431
    /// Construct a builder with no items set.
432
330
    pub fn new() -> Self {
433
330
        StreamIsolationBuilder::default()
434
330
    }
435
}
436

            
437
#[cfg(test)]
438
pub(crate) mod test {
439
    #![allow(clippy::unwrap_used)]
440
    use super::*;
441

            
442
    /// Trait for testing use only. Much like PartialEq, but for type containing an dyn Isolation
443
    /// which is known to be an IsolationToken.
444
    pub(crate) trait IsolationTokenEq {
445
        /// Compare two values, returning true if they are equals and all dyn Isolation they contain
446
        /// are IsolationToken (which are equal too).
447
        fn isol_eq(&self, other: &Self) -> bool;
448
    }
449

            
450
    macro_rules! assert_isoleq {
451
        { $arg1:expr, $arg2:expr } => {
452
            assert!($arg1.isol_eq(&$arg2))
453
        }
454
    }
455
    pub(crate) use assert_isoleq;
456

            
457
    impl IsolationTokenEq for IsolationToken {
458
        fn isol_eq(&self, other: &Self) -> bool {
459
            self == other
460
        }
461
    }
462

            
463
    impl<T: IsolationTokenEq> IsolationTokenEq for Option<T> {
464
        fn isol_eq(&self, other: &Self) -> bool {
465
            match (self, other) {
466
                (Some(this), Some(other)) => this.isol_eq(other),
467
                (None, None) => true,
468
                _ => false,
469
            }
470
        }
471
    }
472

            
473
    impl<T: IsolationTokenEq + std::fmt::Debug> IsolationTokenEq for Vec<T> {
474
        fn isol_eq(&self, other: &Self) -> bool {
475
            if self.len() != other.len() {
476
                return false;
477
            }
478
            self.iter()
479
                .zip(other.iter())
480
                .all(|(this, other)| this.isol_eq(other))
481
        }
482
    }
483

            
484
    impl IsolationTokenEq for dyn Isolation {
485
        fn isol_eq(&self, other: &Self) -> bool {
486
            let this = self.as_any().downcast_ref::<IsolationToken>();
487
            let other = other.as_any().downcast_ref::<IsolationToken>();
488
            match (this, other) {
489
                (Some(this), Some(other)) => this == other,
490
                _ => false,
491
            }
492
        }
493
    }
494

            
495
    impl IsolationTokenEq for StreamIsolation {
496
        fn isol_eq(&self, other: &Self) -> bool {
497
            self.stream_isolation
498
                .isol_eq(other.stream_isolation.as_ref())
499
                && self.owner_token == other.owner_token
500
        }
501
    }
502

            
503
    #[derive(PartialEq, Clone, Copy, Debug, Eq)]
504
    struct OtherIsolation(usize);
505

            
506
    impl IsolationHelper for OtherIsolation {
507
        fn compatible_same_type(&self, other: &Self) -> bool {
508
            self == other
509
        }
510
        fn join_same_type(&self, other: &Self) -> Option<Self> {
511
            if self.compatible_same_type(other) {
512
                Some(*self)
513
            } else {
514
                None
515
            }
516
        }
517
    }
518

            
519
    #[test]
520
    fn isolation_token() {
521
        let token_1 = IsolationToken::new();
522
        let token_2 = IsolationToken::new();
523

            
524
        assert!(token_1.compatible_same_type(&token_1));
525
        assert!(token_2.compatible_same_type(&token_2));
526
        assert!(!token_1.compatible_same_type(&token_2));
527

            
528
        assert_eq!(token_1.join_same_type(&token_1), Some(token_1));
529
        assert_eq!(token_2.join_same_type(&token_2), Some(token_2));
530
        assert_eq!(token_1.join_same_type(&token_2), None);
531
    }
532

            
533
    #[test]
534
    fn isolation_trait() {
535
        let token_1: Box<dyn Isolation> = Box::new(IsolationToken::new());
536
        let token_2: Box<dyn Isolation> = Box::new(IsolationToken::new());
537
        let other_1: Box<dyn Isolation> = Box::new(OtherIsolation(0));
538
        let other_2: Box<dyn Isolation> = Box::new(OtherIsolation(1));
539

            
540
        assert!(token_1.compatible(token_1.as_ref()));
541
        assert!(token_2.compatible(token_2.as_ref()));
542
        assert!(!token_1.compatible(token_2.as_ref()));
543

            
544
        assert!(other_1.compatible(other_1.as_ref()));
545
        assert!(other_2.compatible(other_2.as_ref()));
546
        assert!(!other_1.compatible(other_2.as_ref()));
547

            
548
        assert!(!token_1.compatible(other_1.as_ref()));
549
        assert!(!other_1.compatible(token_1.as_ref()));
550

            
551
        assert!(token_1.join(token_1.as_ref()).is_some());
552
        assert!(token_1.join(token_2.as_ref()).is_none());
553

            
554
        assert!(other_1.join(other_1.as_ref()).is_some());
555
        assert!(other_1.join(other_2.as_ref()).is_none());
556

            
557
        assert!(token_1.join(other_1.as_ref()).is_none());
558
        assert!(other_1.join(token_1.as_ref()).is_none());
559
    }
560

            
561
    #[test]
562
    fn isolation_tuple() {
563
        let token_1 = IsolationToken::new();
564
        let token_2 = IsolationToken::new();
565
        let other_1 = OtherIsolation(0);
566
        let other_2 = OtherIsolation(1);
567

            
568
        let token_12: Box<dyn Isolation> = Box::new((token_1, token_2));
569
        let token_21: Box<dyn Isolation> = Box::new((token_2, token_1));
570
        let mix_11: Box<dyn Isolation> = Box::new((token_1, other_1));
571
        let mix_12: Box<dyn Isolation> = Box::new((token_1, other_2));
572
        let revmix_11: Box<dyn Isolation> = Box::new((other_1, token_1));
573

            
574
        let join_token = token_12.join(token_12.as_ref()).unwrap();
575
        assert!(join_token.compatible(token_12.as_ref()));
576
        let join_mix = mix_12.join(mix_12.as_ref()).unwrap();
577
        assert!(join_mix.compatible(mix_12.as_ref()));
578

            
579
        let isol_list = [token_12, token_21, mix_11, mix_12, revmix_11];
580

            
581
        for (i, isol1) in isol_list.iter().enumerate() {
582
            for (j, isol2) in isol_list.iter().enumerate() {
583
                assert_eq!(isol1.compatible(isol2.as_ref()), i == j);
584
            }
585
        }
586
    }
587

            
588
    #[test]
589
    fn build_isolation() {
590
        let no_isolation = StreamIsolation::no_isolation();
591
        let no_isolation2 = StreamIsolation::builder()
592
            .owner_token(IsolationToken::no_isolation())
593
            .stream_isolation(Box::new(IsolationToken::no_isolation()))
594
            .build()
595
            .unwrap();
596
        assert_eq!(no_isolation.owner_token, no_isolation2.owner_token);
597
        assert_eq!(
598
            no_isolation
599
                .stream_isolation
600
                .as_ref()
601
                .as_any()
602
                .downcast_ref::<IsolationToken>(),
603
            no_isolation2
604
                .stream_isolation
605
                .as_ref()
606
                .as_any()
607
                .downcast_ref::<IsolationToken>()
608
        );
609
        assert!(no_isolation.compatible(&no_isolation2));
610

            
611
        let tok = IsolationToken::new();
612
        let some_isolation = StreamIsolation::builder().owner_token(tok).build().unwrap();
613
        let some_isolation2 = StreamIsolation::builder()
614
            .stream_isolation(Box::new(tok))
615
            .build()
616
            .unwrap();
617
        assert!(!no_isolation.compatible(&some_isolation));
618
        assert!(!no_isolation.compatible(&some_isolation2));
619
        assert!(!some_isolation.compatible(&some_isolation2));
620
        assert!(some_isolation.compatible(&some_isolation));
621
    }
622
}