summaryrefslogtreecommitdiff
path: root/crates/tor-proto/src/circuit/cell_sender.rs
blob: 2ffdfada5fd0b9af1fcfe7870fae15c1a7396e42 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
//! Implements an outbound Sink type for cells being sent from a circuit onto a
//! [channel](crate::channel).

use std::{
    pin::{Pin, pin},
    task::{Context, Poll},
};

use cfg_if::cfg_if;
use futures::Sink;
use pin_project::pin_project;
use tor_rtcompat::DynTimeProvider;
use tracing::instrument;

use crate::{
    HopNum,
    channel::{ChanCellQueueEntry, ChannelSender},
    congestion::CongestionSignals,
    util::{SinkExt, sometimes_unbounded_sink::SometimesUnboundedSink},
};

cfg_if! {
    if #[cfg(feature="circ-padding")] {
        use crate::util::sink_blocker::{BooleanPolicy, SinkBlocker};
        /// Inner type used to implement a [`CircuitCellSender`].
        ///
        /// When `circ-padding` feature is enabled, this is a multi-level wrapper around
        /// a ChanSender:
        /// - On the outermost layer, there is a [`SinkBlocker`] that we use
        ///   to make this sink behave as if it were full
        ///   when our [circuit padding](crate::client::circuit::padding) code
        ///   tells us to block outbound traffic.
        /// - Then there is a [`SometimesUnboundedSink`] that we use to queue control messages
        ///   when the target `ChanSender` is full,
        ///   or when we traffic is blocked.
        /// - Finally, there is the [`ChannelSender`] itself.
        ///
        /// NOTE: We once had a second `SinkBlocker` to keep messages from the
        /// SometimesUnboundedSink from reaching the ChanSender
        /// when we were blocked on padding.
        /// We no longer use this SinkBlocker, since we decided in
        /// our [padding design] that non-data messages
        /// would never wait for a padding-based block.
        /// We can reinstate it if we change our mind.
        ///
        /// TODO: Ideally, this type would participate in the memory quota system.
        ///
        /// TODO: At some point in the future, we might want to add
        /// an additional _bounded_ [`futures::sink::Buffer`]
        /// to queue cells before they are put onto the channel,
        /// or to queue data from loud streams.
        ///
        /// [padding design]: https://gitlab.torproject.org/tpo/core/arti/-/blob/main/doc/dev/notes/circuit-padding.md
        type InnerSink = SinkBlocker<
            SometimesUnbounded, BooleanPolicy,
        >;
        /// The type of our `SometimesUnboundedSink`, as instantiated.
        ///
        /// We use this to queue control cells.
        type SometimesUnbounded = SometimesUnboundedSink<
            ChanCellQueueEntry,
            // This is what we would reinstate
            // in order to have control messages blocked by padding frameworks:
            //      SinkBlocker<ChannelSender, CountingPolicy>
            ChannelSender
        >;
    } else {
        /// Inner type used to implement a [`CircuitCellSender`].
        ///
        /// When the `circ-padding` is disabled, this only adds a [`SometimesUnboundedSink`].
        ///
        /// TODO: Ideally, this type would participate in the memory quota system.
        /// TODO: At some point, we might want to add
        /// an additional _bounded_ [`futures::sink::Buffer`]
        /// to queue cells before they are put onto the channel.)
        type InnerSink = SometimesUnboundedSink<ChanCellQueueEntry, ChannelSender>;
        /// The type of our `SometimesUnboundedSink`, as instantiated.
        ///
        /// We use this to queue control cells.
        type SometimesUnbounded = InnerSink;
    }
}

/// A sink that a circuit uses to send cells onto a Channel.
///
/// (This is a separate type so we can more easily control access to its internals.)
///
/// ### You must poll this type
///
/// This type is based on [`SometimesUnboundedSink`].
/// For queued items to be delivered,
/// [`SometimesUnboundedSink`] must be polled,
/// even if you don't have an item to send.
/// The same rule applies here.
///
/// Currently [`Sink::poll_flush`], [`Sink::poll_close`], and [`Sink::poll_ready`]
/// will all work for this purpose.
#[pin_project]
pub(crate) struct CircuitCellSender {
    /// The actual inner sink on which we'll be sending cells.
    ///
    /// See type alias documentation for full details.
    #[pin]
    sink: InnerSink,
}

impl CircuitCellSender {
    /// Construct a new `CircuitCellSender` to deliver cells onto `inner`.
    pub(crate) fn from_channel_sender(inner: ChannelSender) -> Self {
        cfg_if! {
            if #[cfg(feature="circ-padding")] {
                let sink = SinkBlocker::new(
                    SometimesUnboundedSink::new(
                        inner
                    ),
                    BooleanPolicy::Unblocked
                );
            } else {
                let sink = SometimesUnboundedSink::new(inner);
            }
        }

        Self { sink }
    }

    /// Return the number of cells queued in this Sender
    /// that have not yet been flushed onto the channel.
    pub(crate) fn n_queued(&self) -> usize {
        self.sometimes_unbounded().n_queued()
    }

    /// Return true if we have a queued cell for the specified hop or later.
    #[cfg(feature = "circ-padding")]
    pub(crate) fn have_queued_cell_for_hop_or_later(&self, hop: HopNum) -> bool {
        if hop.is_first_hop() && self.chan_sender().approx_count() > 0 {
            // There's a cell on the outbound channel queue:
            // That will function perfectly well as padding to the first hop of this circuit,
            // whether it is actually for this circuit or not.
            return true;
        }

        // Now look at our own sometimes_unbounded queue.
        //
        // TODO circpad: in theory we could also look at the members of the per-channel queue to find this out!
        // But that's nontrivial, since the per-channel queue is implemented with an futures mpsc
        // channel, which doesn't have any functionality to let you inspect its queue.
        self.sometimes_unbounded()
            .iter_queue()
            .any(|(_, info)| info.is_some_and(|inf| inf.target_hop >= hop))
    }

    /// Send a cell on this sender,
    /// even if the  underlying channel queues are all full.
    ///
    /// You must `.await` this, but it will never block.
    /// (Its future is always `Ready`.)
    ///
    /// See note on [`CircuitCellSender`] type about polling:
    /// If you don't poll this sink, then queued items might never flush.
    #[instrument(level = "trace", skip_all)]
    pub(crate) async fn send_unbounded(&mut self, entry: ChanCellQueueEntry) -> crate::Result<()> {
        Pin::new(self.sometimes_unbounded_mut())
            .send_unbounded(entry)
            .await?;
        self.chan_sender().note_cell_queued();
        Ok(())
    }

    /// Return the time provider used by the underlying channel sender
    /// for memory quota purposes.
    pub(crate) fn time_provider(&self) -> &DynTimeProvider {
        self.chan_sender().time_provider()
    }

    /// Circpadding only: Put this sink into a blocked state.
    ///
    /// When we are blocked, attempts to `send()` to this sink will fail.
    /// You can still queue items with `send_unbounded()`,
    /// and they will be sent immediately.
    //
    // (Previously we would block those items too,
    // and only allow them to be flushed one by one,
    // but we changed that behavior so that non-DATA cells can _always_ be sent.)
    #[cfg(feature = "circ-padding")]
    pub(crate) fn start_blocking(&mut self) {
        self.pre_queue_blocker_mut().set_blocked();
    }

    /// Circpadding only: Put this sink into an unblocked state.
    #[cfg(feature = "circ-padding")]
    pub(crate) fn stop_blocking(&mut self) {
        self.pre_queue_blocker_mut().set_unblocked();
    }

    /// Note: This is only async because we need a Context to check the underlying sink for readiness.
    /// This will register a new waker (or overwrite any existing waker).
    #[instrument(level = "trace", skip_all)]
    pub(crate) async fn congestion_signals(&mut self) -> CongestionSignals {
        futures::future::poll_fn(|cx| -> Poll<CongestionSignals> {
            // We're looking at the ChanSender's in order to deliberately ignore the blocked/unblocked
            // status of this sink.
            //
            // See https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/3225#note_3252061
            // for a deeper discussion.
            let channel_ready = self
                .chan_sender_mut()
                .poll_ready_unpin_bool(cx)
                .unwrap_or(false);
            Poll::Ready(CongestionSignals::new(
                /* channel_blocked= */ !channel_ready,
                self.n_queued(),
            ))
        })
        .await
    }

    /// Helper: return a reference to the internal [`SometimesUnboundedSink`]
    /// that this `CircuitCellSender` is based on.
    fn sometimes_unbounded(&self) -> &SometimesUnbounded {
        cfg_if! {
            if #[cfg(feature="circ-padding")] {
                self.sink.as_inner()
            } else {
                &self.sink
            }
        }
    }

    /// Helper: return a mutable reference to the internal [`SometimesUnboundedSink`]
    /// that this `CircuitCellSender` is based on.
    fn sometimes_unbounded_mut(&mut self) -> &mut SometimesUnbounded {
        cfg_if! {
            if #[cfg(feature="circ-padding")] {
                self.sink.as_inner_mut()
            } else {
                &mut self.sink
            }
        }
    }

    /// Helper: Return a reference to the internal [`ChannelSender`]
    /// that this `CircuitCellSender` is based on.
    fn chan_sender(&self) -> &ChannelSender {
        cfg_if! {
            if #[cfg(feature="circ-padding")] {
                self.sink.as_inner().as_inner()
            } else {
                self.sink.as_inner()
            }
        }
    }

    /// Helper: Return a mutable reference to the internal [`ChannelSender`]
    /// that this `CircuitCellSender` is based on.
    fn chan_sender_mut(&mut self) -> &mut ChannelSender {
        cfg_if! {
            if #[cfg(feature="circ-padding")] {
                self.sink.as_inner_mut().as_inner_mut()
            } else {
                self.sink.as_inner_mut()
            }
        }
    }

    /// Helper: Return a mutable reference to our outer [`SinkBlocker`]
    #[cfg(feature = "circ-padding")]
    fn pre_queue_blocker_mut(&mut self) -> &mut InnerSink {
        &mut self.sink
    }
}

impl Sink<ChanCellQueueEntry> for CircuitCellSender {
    type Error = <ChannelSender as Sink<ChanCellQueueEntry>>::Error;

    fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        cfg_if! {
            if #[cfg(feature = "circ-padding")] {
                // In this case, our sink is _not_ the same as our SometimesUnboundedSink.
                // But we need to ensure that SometimesUnboundedMut gets polled
                // unconditionally, so that it can actually flush its members.
                //
                // We don't actually _care_ if it's ready;
                // we just need to make sure that it gets polled.
                // See the "You must poll this type" comment on SometimesUnboundedSink.
                let _ignore = pin!(self.sometimes_unbounded_mut()).poll_ready(cx);
            }
        }
        self.project().sink.poll_ready(cx)
    }

    fn start_send(mut self: Pin<&mut Self>, item: ChanCellQueueEntry) -> Result<(), Self::Error> {
        self.as_mut().project().sink.start_send(item)?;
        self.chan_sender().note_cell_queued();
        Ok(())
    }

    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.project().sink.poll_flush(cx)
    }

    fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.project().sink.poll_close(cx)
    }
}