tor_proto/client/circuit/padding/maybenot_padding.rs
1//! A `maybenot`-specific backend for padding.
2
3// Some of the circuit padding implementation isn't reachable unless
4// the extra-experimental circ-padding-manual feature is also present.
5//
6// TODO circpad: Remove this once we have circ-padding negotiation implemented.
7#![cfg_attr(
8 all(feature = "circ-padding", not(feature = "circ-padding-manual")),
9 expect(dead_code)
10)]
11
12mod backend;
13
14use std::collections::VecDeque;
15use std::num::NonZeroU16;
16use std::pin::Pin;
17use std::sync::{Arc, Mutex};
18use std::task::{Context, Poll, Waker};
19
20use bitvec::BitArr;
21use maybenot::MachineId;
22use smallvec::SmallVec;
23use tor_memquota::memory_cost_structural_copy;
24use tor_rtcompat::{DynTimeProvider, SleepProvider};
25
26use crate::HopNum;
27use crate::circuit::HOPS;
28use crate::util::err::ExcessPadding;
29use backend::PaddingBackend;
30
31/// The type of Instant that we'll use for our padding machines.
32///
33/// We use a separate type alias here in case we want to move to coarsetime.
34type Instant = web_time_compat::Instant;
35
36/// The type of Duration that we'll use for our padding machines.
37///
38/// We use a separate type alias here in case we want to move to coarsetime.
39type Duration = std::time::Duration;
40
41/// A type we use to generate a set of [`PaddingEvent`].
42///
43/// This is a separate type so we can tune it and make it into a smallvec if needed.
44type PaddingEventQueue = VecDeque<PaddingEvent>;
45
46/// A type we use to generate a set of [`PaddingEvent`].
47///
48/// This is a separate type so we can tune it and make it into a smallvec if needed.
49type PerHopPaddingEventVec = Vec<PerHopPaddingEvent>;
50
51/// Specifications for a set of maybenot padding machines as used in Arti: used to construct a `maybenot::Framework`.
52#[derive(Clone, Debug, derive_builder::Builder)]
53#[builder(build_fn(
54 validate = "Self::validate",
55 private,
56 error = "CircuitPadderConfigError"
57))]
58#[builder(name = "CircuitPadderConfig")]
59#[cfg_attr(not(feature = "circ-padding-manual"), builder(private))]
60#[cfg_attr(feature = "circ-padding-manual", builder(public))]
61pub(crate) struct PaddingRules {
62 /// List of padding machines to use for shaping traffic.
63 ///
64 /// Note that this list may be empty, if we only want to receive padding,
65 /// and never send it.
66 machines: Arc<[maybenot::Machine]>,
67 /// Maximum allowable outbound padding fraction.
68 ///
69 /// Passed directly to maybenot; not enforced in Arti.
70 /// See [`maybenot::Framework::new`] for details.
71 ///
72 /// Must be between 0.0 and 1.0
73 #[builder(default = "1.0")]
74 max_outbound_blocking_frac: f64,
75 /// Maximum allowable outbound blocking fraction.
76 ///
77 /// Passed directly to maybenot; not enforced in Arti.
78 /// See [`maybenot::Framework::new`] for details.
79 ///
80 /// Must be between 0.0 and 1.0.
81 #[builder(default = "1.0")]
82 max_outbound_padding_frac: f64,
83 /// Maximum allowable fraction of inbound padding
84 #[builder(default = "1.0")]
85 max_inbound_padding_frac: f64,
86 /// Number of cells before which we should not enforce max_inbound_padding_frac.
87 #[builder(default = "1")]
88 enforce_inbound_padding_after_cells: u16,
89}
90
91/// An error returned from validating a [`CircuitPadderConfig`].
92#[derive(Clone, Debug, thiserror::Error)]
93#[cfg_attr(feature = "circ-padding-manual", visibility::make(pub))]
94#[non_exhaustive]
95pub(crate) enum CircuitPadderConfigError {
96 /// A field needed to be given, but wasn't.
97 #[error("No value was given for {0}")]
98 UninitializedField(&'static str),
99 /// A field needed to be a proper fraction, but wasn't.
100 #[error("Value was out of range for {0}. (Must be between 0 and 1)")]
101 FractionOutOfRange(&'static str),
102 /// Maybenot gave us an error when initializing the framework.
103 #[error("Maybenot could not initialize framework for rules")]
104 MaybenotError(#[from] maybenot::Error),
105}
106
107impl From<derive_builder::UninitializedFieldError> for CircuitPadderConfigError {
108 fn from(value: derive_builder::UninitializedFieldError) -> Self {
109 Self::UninitializedField(value.field_name())
110 }
111}
112
113impl CircuitPadderConfig {
114 /// Helper: Return an error if this is not a valid Builder.
115 fn validate(&self) -> Result<(), CircuitPadderConfigError> {
116 macro_rules! enforce_frac {
117 { $field:ident } =>
118 {
119 if self.$field.is_some_and(|v| ! (0.0 .. 1.0).contains(&v)) {
120 return Err(CircuitPadderConfigError::FractionOutOfRange(stringify!($field)));
121 }
122 }
123 }
124 enforce_frac!(max_outbound_blocking_frac);
125 enforce_frac!(max_outbound_padding_frac);
126 enforce_frac!(max_inbound_padding_frac);
127
128 Ok(())
129 }
130
131 /// Construct a [`CircuitPadder`] based on this [`CircuitPadderConfig`].
132 ///
133 /// A [`CircuitPadderConfig`] is created its accessors, and used with this method to build a [`CircuitPadder`].
134 ///
135 /// That [`CircuitPadder`] can then be installed on a circuit using [`ClientCirc::start_padding_at_hop`](crate::client::circuit::ClientCirc::start_padding_at_hop).
136 #[cfg_attr(feature = "circ-padding-manual", visibility::make(pub))]
137 pub(crate) fn create_padder(
138 &self,
139 now: Instant,
140 ) -> Result<CircuitPadder, CircuitPadderConfigError> {
141 let rules = self.build()?;
142 let backend = rules.create_padding_backend(now)?;
143 let initial_stats = rules.initialize_stats();
144 Ok(CircuitPadder {
145 initial_stats,
146 backend,
147 })
148 }
149}
150
151impl PaddingRules {
152 /// Create a [`PaddingBackend`] for this [`PaddingRules`], so we can install it in a
153 /// [`PaddingShared`].
154 fn create_padding_backend(
155 &self,
156 now: Instant,
157 ) -> Result<Box<dyn PaddingBackend>, maybenot::Error> {
158 // TODO circpad: specialize this for particular values of n_machines,
159 // when we finally go to implement padding.
160 const OPTIMIZE_FOR_N_MACHINES: usize = 4;
161
162 let backend =
163 backend::MaybenotPadder::<OPTIMIZE_FOR_N_MACHINES>::from_framework_rules(self, now)?;
164 Ok(Box::new(backend))
165 }
166
167 /// Create a new `PaddingStats` to reflect the rules for inbound padding of this PaddingRules
168 fn initialize_stats(&self) -> PaddingStats {
169 PaddingStats {
170 n_padding: 0,
171 n_normal: 0,
172 max_padding_frac: self.max_inbound_padding_frac as f32,
173 // We just convert 0 to 1, since that's necessarily what was meant.
174 enforce_max_after: self
175 .enforce_inbound_padding_after_cells
176 .try_into()
177 .unwrap_or(1.try_into().expect("1 was not nonzero!?")),
178 }
179 }
180}
181
182/// A opaque handle to a padding implementation for a single hop.
183///
184/// This type is constructed with [`CircuitPadderConfig::create_padder`].
185#[derive(derive_more::Debug)]
186#[cfg_attr(feature = "circ-padding-manual", visibility::make(pub))]
187pub(crate) struct CircuitPadder {
188 /// The initial padding stats and restrictions for inbound padding.
189 initial_stats: PaddingStats,
190 /// The underlying backend to use.
191 #[debug(skip)]
192 backend: Box<dyn PaddingBackend>,
193}
194
195/// An instruction from the padding machine to the circuit.
196///
197/// These are returned from the [`PaddingEventStream`].
198///
199/// When the `circ-padding` feature is disabled, these won't actually be constructed.
200#[derive(Clone, Copy, Debug)]
201pub(crate) enum PaddingEvent {
202 /// An instruction to send padding.
203 SendPadding(SendPadding),
204 /// An instruction to start blocking outbound traffic,
205 /// or change the hop at which traffic is blocked.
206 StartBlocking(StartBlocking),
207 /// An instruction to stop all blocking.
208 StopBlocking,
209}
210
211/// An instruction from a single padding hop.
212///
213/// This will be turned into a [`PaddingEvent`] before it's given
214/// to the circuit reactor.
215#[derive(Clone, Copy, Debug)]
216enum PerHopPaddingEvent {
217 /// An instruction to send padding.
218 SendPadding {
219 /// The machine that told us to send the padding.
220 ///
221 /// (We need to use this when we report that we sent the padding.)
222 machine: MachineId,
223 /// Whether the padding can be replaced with regular data.
224 replace: Replace,
225 /// Whether the padding can bypass a bypassable block.
226 bypass: Bypass,
227 },
228 /// An instruction to start blocking traffic..
229 StartBlocking {
230 /// Whether this blocking instance may be bypassed by padding with
231 /// [`Bypass::BypassBlocking`].
232 ///
233 /// (Note that this is _not_ a `Bypass`, since that enum notes whether
234 /// or not a _padding_ cell can bypass blocking)
235 is_bypassable: bool,
236 },
237 /// An instruction to stop blocking.
238 StopBlocking,
239}
240
241/// Whether a given piece of padding can be replaced with queued data.
242///
243/// This is an enum to avoid confusing it with `Bypass`.
244#[derive(Clone, Copy, Debug, PartialEq, Eq)]
245pub(crate) enum Replace {
246 /// The padding can be replaced
247 /// either by packaging data in a regular data cell,
248 /// or with data currently queued but not yet sent.
249 Replaceable,
250 /// The padding must be queued; it can't be replaced with data.
251 NotReplaceable,
252}
253
254impl Replace {
255 /// Construct a [`Replace`] from a bool.
256 fn from_bool(replace: bool) -> Self {
257 match replace {
258 true => Replace::Replaceable,
259 false => Replace::NotReplaceable,
260 }
261 }
262}
263
264/// Whether a piece of padding can bypass a bypassable case of blocking.
265///
266/// This is an enum to avoid confusing it with `Release`.
267#[derive(Clone, Copy, Debug, PartialEq, Eq)]
268pub(crate) enum Bypass {
269 /// This padding may bypass the block, if the block is bypassable.
270 ///
271 /// Note that this case has complicated interactions with `Replace`; see the
272 /// `maybenot` documentation.
273 BypassBlocking,
274 /// The padding may not bypass the block.
275 DoNotBypass,
276}
277
278/// Information about a queued cell that we need to feed back into the padding
279/// subsystem.
280#[derive(Clone, Copy, Debug)]
281pub(crate) struct QueuedCellPaddingInfo {
282 /// The hop that will receive this cell.
283 pub(crate) target_hop: HopNum,
284}
285memory_cost_structural_copy!(QueuedCellPaddingInfo);
286
287impl Bypass {
288 /// Construct a [`Bypass`] from a bool.
289 fn from_bool(replace: bool) -> Self {
290 match replace {
291 true => Bypass::BypassBlocking,
292 false => Bypass::DoNotBypass,
293 }
294 }
295}
296
297/// An indication that we should send a padding cell.
298///
299/// Don't drop this: instead, once the cell is queued,
300/// pass this `SendPadding` object to the relevant [`PaddingController`]
301/// to report that the particular piece of padding has been queued.
302#[derive(Clone, Debug, Copy)]
303pub(crate) struct SendPadding {
304 /// The machine within a framework that told us to send the padding.
305 ///
306 /// We store this so we can tell the framework which machine's padding we sent.
307 machine: maybenot::MachineId,
308
309 /// The hop to which we need to send the padding.
310 pub(crate) hop: HopNum,
311
312 /// Whether this padding can be replaced by regular data.
313 pub(crate) replace: Replace,
314
315 /// Whether this padding cell should bypass any current blocking.
316 pub(crate) bypass: Bypass,
317}
318
319impl SendPadding {
320 /// Convert this SendPadding into a TriggerEvent for Maybenot,
321 /// to indicate that the padding was sent.
322 fn into_sent_event(self) -> maybenot::TriggerEvent {
323 maybenot::TriggerEvent::PaddingSent {
324 machine: self.machine,
325 }
326 }
327
328 /// If true, we are allowed to replace this padding cell
329 /// with a normal non-padding cell.
330 ///
331 /// (If we do, we should call [`PaddingController::queued_data_as_padding`])
332 pub(crate) fn may_replace_with_data(&self) -> Replace {
333 self.replace
334 }
335
336 /// Return whether this padding cell is allowed to bypass any current blocking.
337 pub(crate) fn may_bypass_block(&self) -> Bypass {
338 self.bypass
339 }
340}
341
342/// An instruction to start blocking traffic
343/// or to change the rules for blocking traffic.
344#[derive(Clone, Copy, Debug)]
345pub(crate) struct StartBlocking {
346 /// If true, then padding traffic _to the blocking hop_
347 /// can bypass this block, if it has [`Bypass::BypassBlocking`].
348 ///
349 /// (All traffic can be sent to earlier hops as normal.
350 /// No traffic may be sent to later hops.)
351 pub(crate) is_bypassable: bool,
352}
353
354/// Absolute upper bound for number of hops.
355const MAX_HOPS: usize = 64;
356
357/// A handle to the padding state of a single circuit.
358///
359/// Used to tell the padders about events that they need to react to.
360#[derive(Clone, derive_more::Debug)]
361pub(crate) struct PaddingController<S = DynTimeProvider>
362where
363 S: SleepProvider,
364{
365 /// The underlying shared state.
366 #[debug(skip)]
367 shared: Arc<Mutex<PaddingShared<S>>>,
368}
369
370/// The shared state for a single circuit's padding.
371///
372/// Used by both PaddingController and PaddingEventStream.
373struct PaddingShared<S: SleepProvider> {
374 /// A sleep provider for telling the time and creating sleep futures.
375 runtime: S,
376 /// Per-hop state for each hop that we have enabled padding with.
377 ///
378 /// INVARIANT: the length of this vector is no greater than `MAX_HOPS`.
379 hops: SmallVec<[Option<Box<dyn PaddingBackend>>; HOPS]>,
380 /// Records about how much padding and normal traffic we have received from each hop,
381 /// and how much padding is allowed.
382 stats: SmallVec<[Option<PaddingStats>; HOPS]>,
383 /// Which hops are currently blocking, and whether that blocking is bypassable.
384 blocking: BlockingState,
385 /// When will the currently pending sleep future next expire?
386 ///
387 /// We keep track of this so that we know when we need to reset the sleep future.
388 /// It gets updated by `PaddingStream::schedule_next_wakeup`,
389 /// which we call in `<PaddingStream as Stream>::poll_next` immediately
390 /// before we create a timer.
391 next_scheduled_wakeup: Option<Instant>,
392
393 /// A deque of `PaddingEvent` that we want to yield from our [`PaddingEventStream`].
394 ///
395 /// NOTE: If you put new items in this list from anywhere other than inside
396 /// `PaddingEventStream::poll_next`, you need to alert the `waker`.
397 pending_events: PaddingEventQueue,
398
399 /// A waker to alert if we've added any events to padding_events,
400 /// or if we need the stream to re-poll.
401 //
402 // TODO circpad: This waker is redundant with the one stored in every backend's `Timer`.
403 // When we revisit this code we may want to consider combining them somehow.
404 waker: Waker,
405}
406
407/// The number of padding and non-padding cells we have received from each hop,
408/// and the rules for how many are allowed.
409#[derive(Clone, Debug)]
410struct PaddingStats {
411 /// The number of padding cells we've received from this hop.
412 n_padding: u64,
413 /// The number of non-padding cells we've received from this hop.
414 n_normal: u64,
415 /// The maximum allowable fraction of padding cells.
416 max_padding_frac: f32,
417 /// A lower limit, below which we will not enforce `max_padding_frac`.
418 //
419 // This is a NonZero for several reasons:
420 // - It doesn't make sense to enforce a ratio when no cells have been received.
421 // - If we only check when the total is at above zero, we can avoid a division-by-zero check.
422 // - Having an impossible value here ensures that the niche optimization
423 // will work on PaddingStats.
424 enforce_max_after: NonZeroU16,
425}
426
427impl PaddingStats {
428 /// Return an error if this PaddingStats has exceeded its maximum.
429 fn validate(&self) -> Result<(), ExcessPadding> {
430 // Total number of cells.
431 // (It is impossible to get so many cells that this addition will overflow a u64.)
432 let total = self.n_padding + self.n_normal;
433
434 if total >= u16::from(self.enforce_max_after).into() {
435 // TODO: is there a way we can avoid a floating-point op here?
436 // Or can we limit the number of times that we need to check?
437 // (Tobias suggests randomization; I'm worried.)
438 //
439 // On the one hand, this may never appear on our profiles.
440 // But on the other hand, if it _does_ matter for performance,
441 // it is likely to be on some marginal platform with bad FP performance,
442 // where we are unlikely to be doing much testing.
443 //
444 // One promising possibility is to calculate a minimum amount of padding
445 // that we _know_ will be valid, given the current total,
446 // and then not check again until we at all until we reach that amount.
447 if self.n_padding as f32 > (total as f32 * self.max_padding_frac) {
448 return Err(ExcessPadding::PaddingExceedsLimit);
449 }
450 }
451 Ok(())
452 }
453}
454
455/// Current padding-related blocking status for a circuit.
456///
457/// We have to keep track of whether each hop is blocked or not,
458/// and whether its blocking is bypassable.
459/// But all we actually need to tell the reactor code
460/// is whether to block the _entire_ circuit or not.
461//
462// TODO circpad: It might beneficial
463// to block only the first blocking hop and its successors,
464// but that creates tricky starvation problems
465// in the case where we have queued traffic for a later, blocking, hop
466// that prevents us from flushing any messages to earlier hops.
467// We could solve this with tricky out-of-order designs,
468// but for now we're just treating "blocked" as a boolean.
469#[derive(Default)]
470struct BlockingState {
471 /// Whether each hop is currently blocked.
472 hop_blocked: BitArr![for MAX_HOPS],
473 /// Whether each hop's blocking is currently **not** bypassable.
474 blocking_non_bypassable: BitArr![for MAX_HOPS],
475}
476
477impl BlockingState {
478 /// Set the hop at position `idx` to blocked.
479 fn set_blocked(&mut self, idx: usize, is_bypassable: bool) {
480 self.hop_blocked.set(idx, true);
481 self.blocking_non_bypassable.set(idx, !is_bypassable);
482 }
483 /// Set the hop at position `idx` to unblocked.
484 fn set_unblocked(&mut self, idx: usize) {
485 self.hop_blocked.set(idx, false);
486 self.blocking_non_bypassable.set(idx, false);
487 }
488 /// Return a [`PaddingEvent`]
489 fn blocking_update_paddingevent(&self) -> PaddingEvent {
490 if self.blocking_non_bypassable.any() {
491 // At least one hop has non-bypassable blocking, so our blocking is non-bypassable.
492 PaddingEvent::StartBlocking(StartBlocking {
493 is_bypassable: false,
494 })
495 } else if self.hop_blocked.any() {
496 // At least one hop is blocking, but no hop has non-bypassable padding, so this padding
497 // is bypassable.
498 PaddingEvent::StartBlocking(StartBlocking {
499 is_bypassable: true,
500 })
501 } else {
502 // Nobody is blocking right now; it's time to unblock.
503 PaddingEvent::StopBlocking
504 }
505 }
506}
507
508#[allow(clippy::unnecessary_wraps)]
509impl<S: SleepProvider> PaddingController<S> {
510 /// Report that we've enqueued a non-padding cell for a given hop.
511 ///
512 /// Return a QueuedCellPaddingInfo if we need to alert the padding subsystem
513 /// when this cell is flushed.
514 pub(crate) fn queued_data(&self, hop: HopNum) -> Option<QueuedCellPaddingInfo> {
515 let mut shared = self.shared.lock().expect("Lock poisoned");
516 // Every hop up to and including the target hop will see this as normal data.
517 shared.trigger_events(hop, &[maybenot::TriggerEvent::NormalSent]);
518 shared.info_for_hop(hop)
519 }
520
521 /// Install the given [`CircuitPadder`] to start padding traffic to the listed `hop`.
522 ///
523 /// Stops padding if the provided padder is `None`.
524 ///
525 /// Replaces any previous [`CircuitPadder`].
526 pub(crate) fn install_padder_padding_at_hop(&self, hop: HopNum, padder: Option<CircuitPadder>) {
527 self.shared
528 .lock()
529 .expect("lock poisoned")
530 .set_hop_backend(hop, padder);
531 }
532
533 /// Report that we have enqueued a non-padding cell
534 /// in place of a replaceable padding cell
535 /// for a given hop.
536 ///
537 /// Return a QueuedCellPaddingInfo if we need to alert the padding subsystem
538 /// when this cell is flushed.
539 pub(crate) fn queued_data_as_padding(
540 &self,
541 hop: HopNum,
542 sendpadding: SendPadding,
543 ) -> Option<QueuedCellPaddingInfo> {
544 assert_eq!(hop, sendpadding.hop);
545 assert_eq!(Replace::Replaceable, sendpadding.replace);
546 let mut shared = self.shared.lock().expect("Lock poisoned");
547 shared.trigger_events_mixed(
548 hop,
549 // Each intermediate hop sees this as normal data.
550 &[maybenot::TriggerEvent::NormalSent],
551 // For the target hop, we treat this both as normal, _and_ as padding.
552 &[
553 maybenot::TriggerEvent::NormalSent,
554 sendpadding.into_sent_event(),
555 ],
556 );
557 shared.info_for_hop(hop)
558 }
559
560 /// Report that we have enqueued a padding cell to a given hop.
561 ///
562 /// Return a QueuedCellPaddingInfo if we need to alert the padding subsystem
563 /// when this cell is flushed.
564 pub(crate) fn queued_padding(
565 &self,
566 hop: HopNum,
567 sendpadding: SendPadding,
568 ) -> Option<QueuedCellPaddingInfo> {
569 assert_eq!(hop, sendpadding.hop);
570 let mut shared = self.shared.lock().expect("Lock poisoned");
571 shared.trigger_events_mixed(
572 hop,
573 // Each intermediate hop sees this as normal data.
574 &[maybenot::TriggerEvent::NormalSent],
575 // The target hop sees this as padding.
576 &[sendpadding.into_sent_event()],
577 );
578 shared.info_for_hop(hop)
579 }
580
581 /// Report that we are using an already-queued cell
582 /// as a substitute for sending padding to a given hop.
583 pub(crate) fn replaceable_padding_already_queued(&self, hop: HopNum, sendpadding: SendPadding) {
584 assert_eq!(hop, sendpadding.hop);
585 let mut shared = self.shared.lock().expect("Lock poisoned");
586 shared.trigger_events_mixed(
587 hop,
588 // No additional data will be seen for any intermediate hops.
589 &[],
590 // The target hop's machine sees this as padding.
591 &[sendpadding.into_sent_event()],
592 );
593 }
594
595 /// Report that we've flushed a cell from the queue for the given hop.
596 pub(crate) fn flushed_relay_cell(&self, info: QueuedCellPaddingInfo) {
597 // Every hop up to the last
598 let mut shared = self.shared.lock().expect("Lock poisoned");
599 shared.trigger_events(info.target_hop, &[maybenot::TriggerEvent::TunnelSent]);
600 }
601
602 /// Report that we've flushed a cell from the per-channel queue.
603 pub(crate) fn flushed_channel_cell(&self) {
604 let mut shared = self.shared.lock().expect("Lock poisoned");
605 shared.trigger_events(HopNum::from(0), &[maybenot::TriggerEvent::TunnelSent]);
606 }
607
608 /// Report that we have decrypted a non-padding cell from our queue
609 /// from a given hop.
610 ///
611 // Note that in theory, it would be better to trigger TunnelRecv as soon as
612 // possible after we receive and enqueue the data cell, and NormalRecv only
613 // once we've decrypted it and found it to be data. But we can't do that,
614 // since we won't know which hop actually originated the cell until we
615 // decrypt it.
616 pub(crate) fn decrypted_data(&self, hop: HopNum) {
617 let mut shared = self.shared.lock().expect("Lock poisoned");
618 shared.inc_normal_received(hop);
619 shared.trigger_events(
620 hop,
621 // We treat this as normal data from every hop.
622 &[
623 maybenot::TriggerEvent::TunnelRecv,
624 maybenot::TriggerEvent::NormalRecv,
625 ],
626 );
627 }
628 /// Report that we have decrypted a padding cell from our queue.
629 ///
630 /// Return an error if this padding cell is not acceptable
631 /// (because we have received too much padding from this hop,
632 /// or because we have not enabled padding with this hop.)
633 //
634 // See note above.
635 pub(crate) fn decrypted_padding(&self, hop: HopNum) -> Result<(), crate::Error> {
636 let mut shared = self.shared.lock().expect("Lock poisoned");
637 shared
638 .inc_padding_received(hop)
639 .map_err(|e| crate::Error::ExcessPadding(e, hop))?;
640 shared.trigger_events_mixed(
641 hop,
642 // We treat this as normal data from the intermediate hops.
643 &[
644 maybenot::TriggerEvent::TunnelRecv,
645 maybenot::TriggerEvent::NormalRecv,
646 ],
647 // But from the target hop, it counts as padding.
648 &[
649 maybenot::TriggerEvent::TunnelRecv,
650 maybenot::TriggerEvent::PaddingRecv,
651 ],
652 );
653 Ok(())
654 }
655}
656
657impl<S: SleepProvider> PaddingShared<S> {
658 /// Trigger a list of maybenot events on every hop up to and including `hop`.
659 fn trigger_events(&mut self, hop: HopNum, events: &[maybenot::TriggerEvent]) {
660 let final_idx = usize::from(hop);
661 let now = self.runtime.now();
662 let next_scheduled_wakeup = self.next_scheduled_wakeup;
663 for hop_controller in self.hops.iter_mut().take(final_idx + 1) {
664 let Some(hop_controller) = hop_controller else {
665 continue;
666 };
667 hop_controller.report_events_at(events, now, next_scheduled_wakeup);
668 }
669 }
670
671 /// Trigger `intermediate_hop_events` on every hop up to but _not_ including `hop`.
672 ///
673 /// Trigger `final_hop_events` on `hop`.
674 ///
675 /// (Don't trigger anything on any hops _after_ `hop`.)
676 fn trigger_events_mixed(
677 &mut self,
678 hop: HopNum,
679 intermediate_hop_events: &[maybenot::TriggerEvent],
680 final_hop_events: &[maybenot::TriggerEvent],
681 ) {
682 use itertools::Itertools as _;
683 let final_idx = usize::from(hop);
684 let now = self.runtime.now();
685 let next_scheduled_wakeup = self.next_scheduled_wakeup;
686 for (position, hop_controller) in self.hops.iter_mut().take(final_idx + 1).with_position() {
687 let Some(hop_controller) = hop_controller else {
688 continue;
689 };
690 let events = if position.is_last() {
691 final_hop_events
692 } else {
693 intermediate_hop_events
694 };
695 hop_controller.report_events_at(events, now, next_scheduled_wakeup);
696 }
697 }
698
699 /// Increment the normal cell count from every hop up to and including `hop`.
700 fn inc_normal_received(&mut self, hop: HopNum) {
701 let final_idx = usize::from(hop);
702 for stats in self.stats.iter_mut().take(final_idx + 1).flatten() {
703 stats.n_normal += 1;
704 }
705 }
706
707 /// Increment the padding count from `hop`, and the normal cell count from all earlier hops.
708 ///
709 /// Return an error if a padding cell from `hop` would not be acceptable.
710 fn inc_padding_received(&mut self, hop: HopNum) -> Result<(), ExcessPadding> {
711 use itertools::Itertools as _;
712 let final_idx = usize::from(hop);
713 for (position, stats) in self.stats.iter_mut().take(final_idx + 1).with_position() {
714 match (position.is_last(), stats) {
715 (false, Some(stats)) => stats.n_normal += 1,
716 (false, None) => {}
717 (true, Some(stats)) => {
718 stats.n_padding += 1;
719 stats.validate()?;
720 }
721 (true, None) => {
722 return Err(ExcessPadding::NoPaddingNegotiated);
723 }
724 }
725 }
726 Ok(())
727 }
728
729 /// Return the `QueuedCellPaddingInfo` to use when sending messages to `target_hop`
730 #[allow(clippy::unnecessary_wraps)]
731 fn info_for_hop(&self, target_hop: HopNum) -> Option<QueuedCellPaddingInfo> {
732 // TODO circpad optimization: This is always Some for now, but we
733 // could someday avoid creating this object
734 // when padding is not enabled on the circuit,
735 // or if padding is not enabled on any hop of the circuit <= target_hop.
736 Some(QueuedCellPaddingInfo { target_hop })
737 }
738}
739
740impl<S: SleepProvider> PaddingShared<S> {
741 /// Install or remove a [`CircuitPadder`] for a single hop.
742 fn set_hop_backend(&mut self, hop: HopNum, backend: Option<CircuitPadder>) {
743 let hop_idx: usize = hop.into();
744 assert!(hop_idx < MAX_HOPS);
745 let n_needed = hop_idx + 1;
746 // Make sure there are enough spaces in self.hops.
747 // We can't use "resize" or "extend", since Box<dyn<PaddingBackend>>
748 // doesn't implement Clone, which SmallVec requires.
749 while self.hops.len() < n_needed {
750 self.hops.push(None);
751 }
752 while self.stats.len() < n_needed {
753 self.stats.push(None);
754 }
755 // project through option...
756 let (hop_backend, stats) = if let Some(padder) = backend {
757 (Some(padder.backend), Some(padder.initial_stats))
758 } else {
759 (None, None)
760 };
761 self.hops[hop_idx] = hop_backend;
762 self.stats[hop_idx] = stats;
763
764 let was_blocked = self.blocking.hop_blocked[hop_idx];
765 self.blocking.set_unblocked(hop_idx);
766 if was_blocked {
767 self.pending_events
768 .push_back(self.blocking.blocking_update_paddingevent());
769 }
770
771 // We need to alert the stream, in case we added an event above, and so that it will poll
772 // the new padder at least once.
773 self.waker.wake_by_ref();
774 }
775
776 /// Transform a [`PerHopPaddingEvent`] for a single hop with index `idx` into a [`PaddingEvent`],
777 /// updating our state as appropriate.
778 fn process_per_hop_event(
779 blocking: &mut BlockingState,
780 hop_idx: usize,
781 event: PerHopPaddingEvent,
782 ) -> PaddingEvent {
783 use PaddingEvent as PE;
784 use PerHopPaddingEvent as PHPE;
785
786 match event {
787 PHPE::SendPadding {
788 machine,
789 replace,
790 bypass,
791 } => PE::SendPadding(SendPadding {
792 machine,
793 hop: hopnum_from_hop_idx(hop_idx),
794 replace,
795 bypass,
796 }),
797 PHPE::StartBlocking { is_bypassable } => {
798 // NOTE that we remember is_bypassable for every hop, but the blocking is only
799 // bypassable if _every_ hop is unblocked, or has bypassable blocking.
800 blocking.set_blocked(hop_idx, is_bypassable);
801 blocking.blocking_update_paddingevent()
802 }
803 PHPE::StopBlocking => {
804 blocking.set_unblocked(hop_idx);
805 blocking.blocking_update_paddingevent()
806 }
807 }
808 }
809
810 /// Extract every PaddingEvent that is ready to be reported to the circuit at time `now`.
811 ///
812 /// May trigger other events, or wake up the stream, in the course of running.
813 fn take_padding_events_at(&mut self, now: Instant) -> PaddingEventQueue {
814 let mut output = PaddingEventQueue::default();
815 for (hop_idx, backend) in self.hops.iter_mut().enumerate() {
816 let Some(backend) = backend else {
817 continue;
818 };
819
820 let hop_events = backend.take_padding_events_at(now, self.next_scheduled_wakeup);
821
822 output.extend(
823 hop_events
824 .into_iter()
825 .map(|ev| Self::process_per_hop_event(&mut self.blocking, hop_idx, ev)),
826 );
827 }
828 output
829 }
830
831 /// Find the next time at which we should wake up the stream, and register it as our
832 /// "next scheduled wakeup".
833 fn schedule_next_wakeup(&mut self, waker: &Waker) -> Option<Instant> {
834 // Find the earliest time at which any hop has a scheduled event.
835 let next_expiration = self
836 .hops
837 .iter_mut()
838 .flatten()
839 .filter_map(|hop| hop.next_wakeup(waker))
840 .min();
841 self.next_scheduled_wakeup = next_expiration;
842 self.waker = waker.clone();
843 next_expiration
844 }
845}
846
847/// A stream of [`PaddingEvent`] to tell a circuit when (if at all) it should send
848/// padding and block traffic.
849//
850// TODO circpad: Optimize this even more for the no-padding case?
851// We could make it smaller or faster.
852pub(crate) struct PaddingEventStream<S = DynTimeProvider>
853where
854 S: SleepProvider,
855{
856 /// An underlying list of PaddingBackend.
857 shared: Arc<Mutex<PaddingShared<S>>>,
858
859 /// A future defining a time at which we must next call `padder.padding_events_at`.
860 ///
861 /// (We also arrange for the backend to wake us up if we need to change this time,
862 /// or call `padder.padding_events_at`.)
863 ///
864 /// Note that this timer is allowed to be _earlier_ than our true wakeup time,
865 /// but not later.
866 sleep_future: S::SleepFuture,
867}
868
869impl futures::Stream for PaddingEventStream {
870 type Item = PaddingEvent;
871
872 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
873 loop {
874 let (now, next_wakeup, runtime) = {
875 // We destructure like this to avoid simultaneous mutable/immutable borrows.
876 let Self { shared, .. } = &mut *self;
877
878 let mut shared = shared.lock().expect("Poisoned lock");
879
880 // Do we have any events that are waiting to be yielded?
881 if let Some(val) = shared.pending_events.pop_front() {
882 return Poll::Ready(Some(val));
883 }
884
885 // Does the padder have any events that have become ready to be yielded?
886 let now = shared.runtime.now();
887 shared.pending_events = shared.take_padding_events_at(now);
888
889 if let Some(val) = shared.pending_events.pop_front() {
890 return Poll::Ready(Some(val));
891 }
892
893 // If we reach this point, there are no events to trigger right now.
894 //
895 // We'll ask all the padders for the time at which they next might need to take
896 // action, and register our Waker with them, to be alerted if we need to take any action
897 // before that.
898 (
899 now,
900 shared.schedule_next_wakeup(cx.waker()),
901 shared.runtime.clone(),
902 )
903 // Here we drop the lock on the shared state.
904 };
905
906 match next_wakeup {
907 None => {
908 return Poll::Pending;
909 }
910 Some(t) => {
911 // TODO circpad: Avoid rebuilding sleep future needlessly. May require new APIs in
912 // tor-rtcompat.
913 self.sleep_future = runtime.sleep(t.saturating_duration_since(now));
914 match self.sleep_future.as_mut().poll(cx) {
915 Poll::Ready(()) => {
916 // Okay, The timer expired already. Continue through the loop.
917 continue;
918 }
919 Poll::Pending => return Poll::Pending,
920 }
921 }
922 }
923 }
924 }
925}
926
927impl futures::stream::FusedStream for PaddingEventStream {
928 fn is_terminated(&self) -> bool {
929 // This stream is _never_ terminated: even if it has no padding machines now,
930 // we might add some in the future.
931 false
932 }
933}
934
935/// Construct a HopNum from an index into the `hops` field of a [`PaddingShared`].
936///
937/// # Panics
938///
939/// Panics if `hop_idx` is greater than u8::MAX, which should be impossible.
940fn hopnum_from_hop_idx(hop_idx: usize) -> HopNum {
941 // (Static assertion: makes sure we can represent every index of hops as a HopNum.)
942 const _: () = assert!(MAX_HOPS < u8::MAX as usize);
943 HopNum::from(u8::try_from(hop_idx).expect("hop_idx out of range!"))
944}
945
946/// Create a new, empty padding instance for a new circuit.
947pub(crate) fn new_padding<S>(runtime: S) -> (PaddingController<S>, PaddingEventStream<S>)
948where
949 S: SleepProvider,
950{
951 // Start with an arbitrary sleep future. We won't actually use this until
952 // the first time that we have an event to schedule, so the timeout doesn't matter.
953 let sleep_future = runtime.sleep(Duration::new(86400, 0));
954
955 let shared = PaddingShared {
956 runtime,
957 hops: Default::default(),
958 stats: Default::default(),
959 blocking: Default::default(),
960 next_scheduled_wakeup: None,
961 pending_events: PaddingEventQueue::default(),
962 waker: Waker::noop().clone(),
963 };
964 let shared = Arc::new(Mutex::new(shared));
965 let controller = PaddingController {
966 shared: shared.clone(),
967 };
968 let stream = PaddingEventStream {
969 shared,
970 sleep_future,
971 };
972
973 (controller, stream)
974}