Skip to main content

tor_proto/circuit/
circ_sender.rs

1//! Implements a sender and a [`Stream`] type for sending messages
2//! from a [channel](crate::channel) to a circuit,
3//! prioritizing the delivery of `DESTROY` messages.
4//!
5//! [`CircuitRxSender`] and [`CircuitRxReceiver`] take any channel message,
6//! because the receiving end can be either a client or a relay circuit reactor.
7//! The reactor itself will convert into its restricted message set.
8
9use std::pin::Pin;
10use std::task::{self, Context, Poll};
11
12use futures::{FutureExt as _, SinkExt as _, Stream, StreamExt as _};
13use oneshot_fused_workaround as oneshot;
14use tor_basic_utils::assert_val_impl_trait;
15use tor_cell::chancell::msg::{AnyChanMsg, Destroy};
16use tor_memquota::mq_queue::{self, ChannelSpec, MpscSpec};
17
18/// The sending end of the SPSC queue for inbound data on its way from channel to circuit
19///
20/// A [`CircuitRxSender`] sender is closed for sending as soon as the first
21/// `DESTROY` message is sent, and will discard any unflushed cells
22/// from its underlying [`mq_queue`], by dropping it.
23///
24/// ## No [`Sink`](futures::Sink) implementation
25///
26/// This type intentionally does not implement [`Sink`](futures::Sink).
27/// Instead it provides a [`send()`](CircuitRxSender::send) function
28/// similar to [`SinkExt::send`](futures::SinkExt::send).
29///
30/// The reason for doing it this way is because we cannot provide
31/// a correct `Sink::poll:ready()` implementation
32/// that wouldn't block DESTROY cells from being sent
33/// when our underlying MPSC sender is full:
34/// `SinkExt::send()` calls `poll_ready()` followed by `start_send()`,
35/// so in order for our `poll_ready()` implementation to not block DESTROY
36/// on the MPSC queue's readiness, it would need to know whether
37/// the cell that will be sent via `start_send()` is a DESTROY or not,
38/// but that's not possible because of the way the `Sink`/`SinkExt` traits
39/// are designed.
40#[derive(Debug)]
41pub(crate) struct CircuitRxSender(Option<CircuitRxSenderInner>);
42
43/// The inner state of a [`CircuitRxSender`].
44#[derive(Debug)]
45struct CircuitRxSenderInner {
46    /// Sender for sending `DESTROY` to [`CircuitRxReceiver`]
47    destroy_tx: oneshot::Sender<Destroy>,
48    /// Sender for sending all other [`AnyChanMsg`]s to [`CircuitRxReceiver`]
49    cell_tx: mq_queue::Sender<AnyChanMsg, MpscSpec>,
50}
51
52/// The receiving end of the SPSC queue for inbound data on its way from channel to circuit
53///
54/// A [`CircuitRxReceiver`] stream ends as soon as the first `DESTROY` message
55/// is received, causing the stream to discard any unflushed cells
56/// from its underlying [`mq_queue`], by dropping it.
57#[derive(Debug)]
58pub(crate) struct CircuitRxReceiver(Option<CircuitRxReceiverInner>);
59
60/// The inner state of a [`CircuitRxReceiver`].
61#[derive(Debug)]
62struct CircuitRxReceiverInner {
63    /// Receiver for receiving `DESTROY` from [`CircuitRxSender`]
64    destroy_rx: oneshot::Receiver<Destroy>,
65    /// Receiver for receiving all other [`AnyChanMsg`]s from [`CircuitRxReceiver`]
66    cell_rx: mq_queue::Receiver<AnyChanMsg, MpscSpec>,
67}
68
69/// Wrap the sender and receiver of an [`mq_queue`] channel
70/// into [`CircuitRxSender`] and [`CircuitRxReceiver`].
71///
72/// The returned channel will ensure any DESTROY messages sent
73/// over the [`CircuitRxSender`] will be delivered
74/// by the [`CircuitRxReceiver`] immediately,
75/// ahead of any other messages that might already be queued,
76/// which will be discarded.
77///
78/// We are fine with the resulting data loss, because inbound DESTROY
79/// can be indicative of malicious activity on the circuit.
80/// We choose to err on the safe side, and free up the resources associated
81/// with such circuits as soon as possible.
82/// DESTROY messages are also sent by relays when they're about to hibernate,
83/// and by clients once they've decided to stop using a circuit.
84/// In the latter case, the lack of an `RELAY_COMMAND_END_ACK`
85/// does mean that this prioritization can cause data loss
86/// (if the client closes the circuit immediately after END-ing a stream).
87/// However, this is a deficiency in the protocol,
88/// and not something we want to fix by implementing custom flushing logic
89/// in the reactor. See torspec#196 and the discussion in #2490.
90///
91/// Note: the underlying buffer of the [`mq_queue`] will only be freed
92/// once both the [`CircuitRxSender`] and [`CircuitRxReceiver`] are dropped;
93/// in other words, after a `DESTROY` cell has been obtained from the [`CircuitRxReceiver`],
94/// via its [`Stream`] implementation
95pub(crate) fn channel(
96    cell_tx: mq_queue::Sender<AnyChanMsg, MpscSpec>,
97    cell_rx: mq_queue::Receiver<AnyChanMsg, MpscSpec>,
98) -> (CircuitRxSender, CircuitRxReceiver) {
99    let (destroy_tx, destroy_rx) = oneshot::channel();
100    let sender = CircuitRxSender(Some(CircuitRxSenderInner {
101        destroy_tx,
102        cell_tx,
103    }));
104
105    let receiver = CircuitRxReceiver(Some(CircuitRxReceiverInner {
106        destroy_rx,
107        cell_rx,
108    }));
109
110    (sender, receiver)
111}
112
113impl Stream for CircuitRxReceiver {
114    type Item = AnyChanMsg;
115
116    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
117        let Some(inner) = self.0.as_mut() else {
118            return Poll::Ready(None);
119        };
120
121        // It's important that destroy_rx is fused,
122        // because we call poll_unpin() unconditionally below.
123        assert_val_impl_trait!(inner.destroy_rx, futures_util::future::FusedFuture);
124
125        // First, check if we have a DESTROY message ready
126        let destroy_cell = match inner.destroy_rx.poll_unpin(cx) {
127            Poll::Ready(destroy) => {
128                // If destroy.is_err(), it means the CircuitRxSender was dropped,
129                // but there may be more data buffered in the underlying mpsc,
130                // so we need to continue polling cell_rx.
131                //
132                // This is important, because we want to preserve the behavior
133                // of the mq_queue, whose Receiver will continue yielding queued
134                // messages even after the Sender is dropped.
135                destroy.ok()
136            }
137            Poll::Pending => {
138                // No DESTROY message yet, so it's time to poll the non-priority
139                // message queue
140                None
141            }
142        };
143
144        if let Some(destroy) = destroy_cell {
145            // Drop the inner state, closing this stream
146            self.0 = None;
147            return Poll::Ready(Some(AnyChanMsg::Destroy(destroy)));
148        }
149
150        let res = task::ready!(inner.cell_rx.poll_next_unpin(cx));
151
152        // Our CircuitRxSender impl will never send DESTROY messages
153        // on the cell_rx queue (they're always sent via the oneshot channel)
154        debug_assert!(!matches!(res, Some(AnyChanMsg::Destroy(_))));
155
156        Poll::Ready(res)
157    }
158}
159
160/// Error returned when trying to write to a [`CircuitRxSender`]
161#[derive(thiserror::Error, Clone, Debug)]
162pub(crate) enum SendError {
163    /// The underlying MPSC channel rejected the message
164    #[error("{0}")]
165    Channel(#[from] mq_queue::SendError<<MpscSpec as ChannelSpec>::SendError>),
166
167    /// The receiver has dropped
168    ///
169    // Note: technically, there are two "Disconnected" variants:
170    // this one, for the oneshot channel, and a second, hidden variant
171    // inside mq_queue:SendError, for the mq_queue one.
172    //
173    // It would be nice if we only had one variant covering both cases,
174    // but this will have to do for now.
175    #[error("the receiver has dropped")]
176    Disconnected,
177
178    /// The sender is closed
179    ///
180    /// Returned if the [`CircuitRxSender`] is used after a DESTROY cell has been written to it.
181    #[error("sender has closed")]
182    Closed,
183}
184
185impl CircuitRxSender {
186    /// Send a cell down this channel
187    ///
188    /// If the sender is already closed (i.e., if we have already sent DESTROY),
189    /// this will return an error.
190    ///
191    // In practice, we never write more than 1 DESTROY cell to this sender,
192    // because the channel reactor removes the circuit (and corresponding CircuitRxSender)
193    // from its circ map after the first DESTROY.
194    pub(crate) async fn send(&mut self, msg: AnyChanMsg) -> Result<(), SendError> {
195        if let AnyChanMsg::Destroy(d) = msg {
196            let inner = self.take_inner()?;
197
198            if inner.destroy_tx.send(d).is_err() {
199                return Err(SendError::Disconnected);
200            }
201
202            Ok(())
203        } else {
204            self.borrow_for_sending()?.cell_tx.send(msg).await?;
205            Ok(())
206        }
207    }
208
209    /// Borrow the [`CircuitRxSenderInner`] state for sending.
210    ///
211    /// Returns an error if the sender is closed.
212    fn borrow_for_sending(&mut self) -> Result<&mut CircuitRxSenderInner, SendError> {
213        self.0.as_mut().ok_or_else(|| SendError::Closed)
214    }
215
216    /// Take the inner [`CircuitRxSenderInner`], closing the sender.
217    ///
218    /// Returns an error if the sender is already closed.
219    fn take_inner(&mut self) -> Result<CircuitRxSenderInner, SendError> {
220        self.0.take().ok_or_else(|| SendError::Closed)
221    }
222}
223
224#[cfg(test)]
225pub(crate) mod test {
226    // @@ begin test lint list maintained by maint/add_warning @@
227    #![allow(clippy::bool_assert_comparison)]
228    #![allow(clippy::clone_on_copy)]
229    #![allow(clippy::dbg_macro)]
230    #![allow(clippy::mixed_attributes_style)]
231    #![allow(clippy::print_stderr)]
232    #![allow(clippy::print_stdout)]
233    #![allow(clippy::single_char_pattern)]
234    #![allow(clippy::unwrap_used)]
235    #![allow(clippy::unchecked_time_subtraction)]
236    #![allow(clippy::useless_vec)]
237    #![allow(clippy::needless_pass_by_value)]
238    #![allow(clippy::string_slice)] // See arti#2571
239    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
240
241    use super::*;
242
243    use tor_cell::chancell::msg::{self, DestroyReason};
244    use tor_rtmock::MockRuntime;
245
246    use std::task::Waker;
247
248    /// Make an MPSC queue, of the type we use to send cells
249    /// from the channel reactor to the circuit reactor,
250    /// but a fake one for testing
251    #[cfg(test)]
252    pub(crate) fn fake_mpsc(buffer: usize) -> (CircuitRxSender, CircuitRxReceiver) {
253        let (tx, rx) = crate::fake_mpsc(buffer);
254
255        crate::circuit::circ_sender::channel(tx, rx)
256    }
257
258    /// A DESTROY message
259    fn destroy_msg(reason: DestroyReason) -> AnyChanMsg {
260        AnyChanMsg::Destroy(msg::Destroy::new(reason))
261    }
262
263    /// A RELAY message
264    fn relay_msg() -> AnyChanMsg {
265        AnyChanMsg::Relay(msg::Relay::new(b"hello"))
266    }
267
268    macro_rules! assert_eos {
269        ($tx:expr, $rx:expr) => {{
270            assert!($rx.next().await.is_none());
271            // Cannot send any more cells once the sender is closed
272            let err = $tx.send(relay_msg()).await.unwrap_err();
273            assert!(matches!(err, SendError::Closed));
274        }};
275    }
276
277    /// The buffer size to use for the fake MPSC queues
278    const BUFFER_SIZE: usize = 16;
279
280    #[test]
281    fn destroy_skips_queue() {
282        MockRuntime::test_with_various(|_rt| async move {
283            let (mut tx, mut rx) = fake_mpsc(BUFFER_SIZE);
284
285            tx.send(relay_msg()).await.unwrap();
286            tx.send(destroy_msg(DestroyReason::HIBERNATING))
287                .await
288                .unwrap();
289
290            // Destroy skips the queue
291            let destroy = rx.next().await.unwrap();
292
293            assert!(matches!(destroy, AnyChanMsg::Destroy(_)));
294            // And we've reached EOS
295            assert_eos!(tx, rx);
296        });
297    }
298
299    #[test]
300    fn destroy_on_empty_queue() {
301        MockRuntime::test_with_various(|_rt| async move {
302            let (mut tx, mut rx) = fake_mpsc(BUFFER_SIZE);
303
304            tx.send(destroy_msg(DestroyReason::HIBERNATING))
305                .await
306                .unwrap();
307            let destroy = rx.next().await.unwrap();
308
309            assert!(matches!(destroy, AnyChanMsg::Destroy(_)));
310            // And we've reached EOS
311            assert_eos!(tx, rx);
312        });
313    }
314
315    #[test]
316    fn destroy_after_data() {
317        MockRuntime::test_with_various(|_rt| async move {
318            let (mut tx, mut rx) = fake_mpsc(BUFFER_SIZE);
319
320            for _ in 0..3 {
321                tx.send(relay_msg()).await.unwrap();
322            }
323
324            for _ in 0..3 {
325                let data = rx.next().await.unwrap();
326                assert!(matches!(data, AnyChanMsg::Relay(_)));
327            }
328
329            let mut noop_cx = Context::from_waker(Waker::noop());
330            // The queue is now empty
331            assert!(rx.poll_next_unpin(&mut noop_cx).is_pending());
332
333            tx.send(destroy_msg(DestroyReason::PROTOCOL)).await.unwrap();
334
335            let destroy = rx.next().await.unwrap();
336            assert!(matches!(destroy, AnyChanMsg::Destroy(_)));
337            // And we've reached EOS
338            assert_eos!(tx, rx);
339        });
340    }
341
342    #[test]
343    fn destroy_full_queue() {
344        MockRuntime::test_with_various(|_rt| async move {
345            let (mut tx, mut rx) = fake_mpsc(BUFFER_SIZE);
346
347            // Fill the queue with data...
348            loop {
349                let fut = Box::pin(tx.send(relay_msg()));
350                match futures::poll!(fut) {
351                    Poll::Pending => {
352                        // Full, time to break
353                        break;
354                    }
355                    Poll::Ready(res) => {
356                        let () = res.unwrap();
357                    }
358                }
359            }
360            // ...followed by a destroy
361            tx.send(destroy_msg(DestroyReason::INTERNAL)).await.unwrap();
362
363            // The destroy cell goes through even though the queue is full,
364            // ahead of all the queued data
365            let destroy = rx.next().await.unwrap();
366
367            assert!(matches!(destroy, AnyChanMsg::Destroy(_)));
368            // And we've reached EOS
369            assert_eos!(tx, rx);
370        });
371    }
372}