Skip to main content

tor_proto/client/reactor/circuit/
circhop.rs

1//! Module exposing structures relating to the reactor's view of a circuit's hops.
2
3use super::{CircuitCmd, CloseStreamBehavior};
4use crate::circuit::circhop::{
5    CircHopInbound, CircHopOutbound, HopSettings, ReactorStreamComponents, SendRelayCell,
6};
7use crate::client::reactor::circuit::path::PathEntry;
8use crate::congestion::CongestionControl;
9use crate::crypto::cell::HopNum;
10use crate::memquota::StreamAccount;
11use crate::stream::cmdcheck::AnyCmdChecker;
12use crate::stream::flow_ctrl::state::WithSidechannelMitigations;
13use crate::streammap::{self, StreamEntMut, StreamMap};
14use crate::tunnel::TunnelScopedCircId;
15use crate::util::tunnel_activity::TunnelActivity;
16use crate::{Error, Result};
17
18use futures::Stream;
19use futures::stream::FuturesUnordered;
20use smallvec::SmallVec;
21use tor_cell::chancell::{BoxedCellBody, CircId};
22use tor_cell::relaycell::flow_ctrl::{Xoff, Xon, XonKBpsEwma};
23use tor_cell::relaycell::msg::AnyRelayMsg;
24use tor_cell::relaycell::{
25    AnyRelayMsgOuter, RelayCellDecoder, RelayCellDecoderResult, RelayCellFormat, StreamId,
26    UnparsedRelayMsg,
27};
28use tor_rtcompat::DynTimeProvider;
29use web_time_compat::Instant;
30
31use safelog::sensitive as sv;
32use tor_error::Bug;
33use tracing::instrument;
34
35use std::result::Result as StdResult;
36use std::sync::{Arc, Mutex, MutexGuard};
37use std::task::Poll;
38
39#[cfg(test)]
40use tor_cell::relaycell::msg::SendmeTag;
41
42/// The "usual" number of hops in a [`CircHopList`].
43///
44/// This saves us a heap allocation when the number of hops is less than or equal to this value.
45const NUM_HOPS: usize = 3;
46
47/// Represents the reactor's view of a circuit's hop.
48#[derive(Default)]
49pub(crate) struct CircHopList {
50    /// The list of hops.
51    hops: SmallVec<[CircHop; NUM_HOPS]>,
52}
53
54impl CircHopList {
55    /// Return a reference to the hop corresponding to `hopnum`, if there is one.
56    pub(super) fn hop(&self, hopnum: HopNum) -> Option<&CircHop> {
57        self.hops.get(Into::<usize>::into(hopnum))
58    }
59
60    /// Return a mutable reference to the hop corresponding to `hopnum`, if there is one.
61    pub(super) fn get_mut(&mut self, hopnum: HopNum) -> Option<&mut CircHop> {
62        self.hops.get_mut(Into::<usize>::into(hopnum))
63    }
64
65    /// Append the specified hop.
66    pub(crate) fn push(&mut self, hop: CircHop) {
67        self.hops.push(hop);
68    }
69
70    /// Returns `true` if the list contains no [`CircHop`]s.
71    pub(crate) fn is_empty(&self) -> bool {
72        self.hops.is_empty()
73    }
74
75    /// Returns the number of hops in the list.
76    pub(crate) fn len(&self) -> usize {
77        self.hops.len()
78    }
79
80    /// Returns a [`Stream`] of [`CircuitCmd`] to poll from the main loop.
81    ///
82    /// The iterator contains at most one [`CircuitCmd`] for each hop,
83    /// representing the instructions for handling the ready-item, if any,
84    /// of its highest priority stream.
85    ///
86    /// IMPORTANT: this stream locks the stream map mutexes of each `CircHop`!
87    /// To avoid contention, never create more than one
88    /// [`ready_streams_iterator`](Self::ready_streams_iterator)
89    /// stream at a time!
90    ///
91    /// This is cancellation-safe.
92    pub(in crate::client::reactor) fn ready_streams_iterator(
93        &self,
94        exclude: Option<HopNum>,
95    ) -> impl Stream<Item = CircuitCmd> + use<> {
96        self.hops
97            .iter()
98            .enumerate()
99            .filter_map(|(i, hop)| {
100                let hop_num = HopNum::from(i as u8);
101
102                if exclude == Some(hop_num) {
103                    // We must skip polling this hop
104                    return None;
105                }
106
107                if !hop.ccontrol().can_send() {
108                    // We can't send anything on this hop that counts towards SENDME windows.
109                    //
110                    // In theory we could send messages that don't count towards
111                    // windows (like `RESOLVE`), and process end-of-stream
112                    // events (to send an `END`), but it's probably not worth
113                    // doing an O(N) iteration over flow-control-ready streams
114                    // to see if that's the case.
115                    //
116                    // This *doesn't* block outgoing flow-control messages (e.g.
117                    // SENDME), which are initiated via the control-message
118                    // channel, handled above.
119                    //
120                    // TODO: Consider revisiting. OTOH some extra throttling when circuit-level
121                    // congestion control has "bottomed out" might not be so bad, and the
122                    // alternatives have complexity and/or performance costs.
123                    return None;
124                }
125
126                let hop_map = Arc::clone(self.hops[i].stream_map());
127                Some(futures::future::poll_fn(move |cx| {
128                    // Process an outbound message from the first ready stream on
129                    // this hop. The stream map implements round robin scheduling to
130                    // ensure fairness across streams.
131                    // TODO: Consider looping here to process multiple ready
132                    // streams. Need to be careful though to balance that with
133                    // continuing to service incoming and control messages.
134                    let mut hop_map = hop_map.lock().expect("lock poisoned");
135                    let Some((sid, msg)) = hop_map.poll_ready_streams_iter(cx).next() else {
136                        // No ready streams for this hop.
137                        return Poll::Pending;
138                    };
139
140                    if msg.is_none() {
141                        return Poll::Ready(CircuitCmd::CloseStream {
142                            hop: hop_num,
143                            sid,
144                            behav: CloseStreamBehavior::default(),
145                            reason: streammap::TerminateReason::StreamTargetClosed,
146                        });
147                    };
148                    let msg = hop_map.take_ready_msg(sid).expect("msg disappeared");
149
150                    #[allow(unused)] // unused in non-debug builds
151                    let Some(StreamEntMut::Open(s)) = hop_map.get_mut(sid) else {
152                        panic!("Stream {sid} disappeared");
153                    };
154
155                    debug_assert!(
156                        s.can_send(&msg),
157                        "Stream {sid} produced a message it can't send: {msg:?}"
158                    );
159
160                    let cell = SendRelayCell {
161                        hop: Some(hop_num),
162                        early: false,
163                        cell: AnyRelayMsgOuter::new(Some(sid), msg),
164                    };
165                    Poll::Ready(CircuitCmd::Send(cell))
166                }))
167            })
168            .collect::<FuturesUnordered<_>>()
169    }
170
171    /// Remove all halfstreams that are expired at `now`.
172    pub(super) fn remove_expired_halfstreams(&mut self, now: Instant) {
173        for hop in self.hops.iter_mut() {
174            hop.stream_map()
175                .lock()
176                .expect("lock poisoned")
177                .remove_expired_halfstreams(now);
178        }
179    }
180
181    /// Returns true if there are any streams on this circuit
182    ///
183    /// Important: this function locks the stream map of its each of the [`CircHop`]s
184    /// in this circuit, so it must **not** be called from any function where the
185    /// stream map lock is held (such as [`ready_streams_iterator`](Self::ready_streams_iterator).
186    pub(super) fn has_streams(&self) -> bool {
187        self.hops.iter().any(|hop| {
188            hop.stream_map()
189                .lock()
190                .expect("lock poisoned")
191                .n_open_streams()
192                > 0
193        })
194    }
195
196    /// Return the most active [`TunnelActivity`] for any hop on this `CircHopList`.
197    pub(crate) fn tunnel_activity(&self) -> TunnelActivity {
198        self.hops
199            .iter()
200            .map(|hop| {
201                hop.stream_map()
202                    .lock()
203                    .expect("Poisoned lock")
204                    .tunnel_activity()
205            })
206            .max()
207            .unwrap_or_else(TunnelActivity::never_used)
208    }
209}
210
211/// Represents the reactor's view of a single hop.
212pub(crate) struct CircHop {
213    /// The unique ID of the circuit. Used for logging.
214    unique_id: TunnelScopedCircId,
215    /// The Tor circuit identifier. Used for logging.
216    circ_id: CircId,
217    /// Hop number in the path.
218    hop_num: HopNum,
219    /// The inbound state of the hop.
220    ///
221    /// Used for processing cells received from this hop.
222    inbound: CircHopInbound,
223    /// The outbound state of the hop.
224    ///
225    /// Used for preparing cells to send to this hop.
226    outbound: CircHopOutbound,
227}
228
229impl CircHop {
230    /// Create a new hop.
231    pub(crate) fn new(
232        unique_id: TunnelScopedCircId,
233        circ_id: CircId,
234        hop_num: HopNum,
235        settings: &HopSettings,
236    ) -> Self {
237        let relay_format = settings.relay_crypt_protocol().relay_cell_format();
238
239        let ccontrol = Arc::new(Mutex::new(CongestionControl::new(&settings.ccontrol)));
240        let inbound = CircHopInbound::new(RelayCellDecoder::new(relay_format), settings);
241
242        let outbound = CircHopOutbound::new(
243            ccontrol,
244            relay_format,
245            Arc::new(settings.flow_ctrl_params.clone()),
246            settings,
247        );
248
249        CircHop {
250            unique_id,
251            circ_id,
252            hop_num,
253            inbound,
254            outbound,
255        }
256    }
257
258    /// Start a stream. Creates an entry in the stream map with the given channels, and sends the
259    /// `message` to the provided hop.
260    pub(crate) fn begin_stream(
261        &mut self,
262        message: AnyRelayMsg,
263        time_prov: &DynTimeProvider,
264        cmd_checker: AnyCmdChecker,
265        memquota: &StreamAccount,
266    ) -> Result<(SendRelayCell, StreamId, ReactorStreamComponents)> {
267        self.outbound.begin_stream(
268            Some(self.hop_num),
269            message,
270            time_prov,
271            cmd_checker,
272            memquota,
273        )
274    }
275
276    /// Close the stream associated with `id` because the stream was
277    /// dropped.
278    ///
279    /// See [`CircHopOutbound::close_stream`].
280    pub(crate) fn close_stream(
281        &mut self,
282        id: StreamId,
283        message: CloseStreamBehavior,
284        why: streammap::TerminateReason,
285        expiry: Instant,
286    ) -> Result<Option<SendRelayCell>> {
287        self.outbound.close_stream(
288            self.unique_id,
289            self.circ_id,
290            id,
291            Some(self.hop_num),
292            message,
293            why,
294            expiry,
295        )
296    }
297
298    /// Check if we should send an XON message.
299    ///
300    /// If we should, then returns the XON message that should be sent.
301    #[instrument(level = "trace", skip_all)]
302    pub(crate) fn maybe_send_xon(
303        &mut self,
304        rate: XonKBpsEwma,
305        id: StreamId,
306    ) -> Result<Option<Xon>> {
307        self.outbound.maybe_send_xon(rate, id)
308    }
309
310    /// Check if we should send an XOFF message.
311    ///
312    /// If we should, then returns the XOFF message that should be sent.
313    pub(crate) fn maybe_send_xoff(&mut self, id: StreamId) -> Result<Option<Xoff>> {
314        self.outbound.maybe_send_xoff(id)
315    }
316
317    /// Return the format that is used for relay cells sent to this hop.
318    ///
319    /// For the most part, this format isn't necessary to interact with a CircHop;
320    /// it becomes relevant when we are deciding _what_ we can encode for the hop.
321    pub(crate) fn relay_cell_format(&self) -> RelayCellFormat {
322        self.outbound.relay_cell_format()
323    }
324
325    /// Delegate to CongestionControl, for testing purposes
326    #[cfg(test)]
327    pub(crate) fn send_window_and_expected_tags(&self) -> (u32, Vec<SendmeTag>) {
328        self.outbound.send_window_and_expected_tags()
329    }
330
331    /// Return a mutable reference to our CongestionControl object.
332    pub(crate) fn ccontrol(&self) -> MutexGuard<'_, CongestionControl> {
333        self.outbound.ccontrol().lock().expect("poisoned lock")
334    }
335
336    /// Return a reference to our CircHopOutbound object.
337    pub(crate) fn outbound(&self) -> &CircHopOutbound {
338        &self.outbound
339    }
340
341    /// We're about to send `msg`.
342    ///
343    /// See [`OpenStreamEnt::about_to_send`](crate::streammap::OpenStreamEnt::about_to_send).
344    //
345    // TODO prop340: This should take a cell or similar, not a message.
346    pub(crate) fn about_to_send(&mut self, stream_id: StreamId, msg: &AnyRelayMsg) -> Result<()> {
347        self.outbound
348            .about_to_send(self.unique_id, self.circ_id, stream_id, msg)
349    }
350
351    /// Add an entry to this map using the specified StreamId.
352    #[cfg(feature = "hs-service")]
353    pub(crate) fn add_ent_with_id(
354        &self,
355        time_prov: &DynTimeProvider,
356        stream_id: StreamId,
357        cmd_checker: AnyCmdChecker,
358        with_sidechannel_mitigations: WithSidechannelMitigations,
359        memquota: &StreamAccount,
360    ) -> Result<ReactorStreamComponents> {
361        self.outbound.add_ent_with_id(
362            time_prov,
363            stream_id,
364            cmd_checker,
365            with_sidechannel_mitigations,
366            memquota,
367        )
368    }
369
370    /// Note that we received an END message (or other message indicating the end of
371    /// the stream) on the stream with `id`.
372    ///
373    /// See [`StreamMap::ending_msg_received`](crate::streammap::StreamMap::ending_msg_received).
374    #[cfg(feature = "hs-service")]
375    pub(crate) fn ending_msg_received(&self, stream_id: StreamId) -> Result<()> {
376        self.outbound.ending_msg_received(stream_id)
377    }
378
379    /// Parse a RELAY or RELAY_EARLY cell body.
380    ///
381    /// Requires that the cryptographic checks on the message have already been
382    /// performed
383    pub(crate) fn decode(&mut self, cell: BoxedCellBody) -> Result<RelayCellDecoderResult> {
384        self.inbound.decode(cell)
385    }
386
387    /// Handle `msg`, delivering it to the stream with the specified `streamid` if appropriate.
388    ///
389    /// Returns back the provided `msg`, if the message is an incoming stream request
390    /// that needs to be handled by the calling code.
391    ///
392    // TODO: the above is a bit of a code smell -- we should try to avoid passing the msg
393    // back and forth like this.
394    pub(super) fn handle_msg(
395        &self,
396        hop_detail: &PathEntry,
397        cell_counts_toward_windows: bool,
398        streamid: StreamId,
399        msg: UnparsedRelayMsg,
400        now: Instant,
401    ) -> Result<Option<UnparsedRelayMsg>> {
402        let possible_proto_violation_err = |streamid: StreamId| Error::UnknownStream {
403            src: sv(hop_detail.clone()),
404            streamid,
405        };
406
407        self.outbound.handle_msg(
408            possible_proto_violation_err,
409            cell_counts_toward_windows,
410            streamid,
411            msg,
412            now,
413        )
414    }
415
416    /// Get the stream map of this hop.
417    pub(crate) fn stream_map(&self) -> &Arc<Mutex<StreamMap>> {
418        self.outbound.stream_map()
419    }
420
421    /// Set the stream map of this hop to `map`.
422    ///
423    /// Returns an error if the existing stream map of the hop has any open stream.
424    pub(crate) fn set_stream_map(&mut self, map: Arc<Mutex<StreamMap>>) -> StdResult<(), Bug> {
425        self.outbound.set_stream_map(map)
426    }
427
428    /// Decrement the limit of outbound cells that may be sent to this hop; give
429    /// an error if it would reach zero.
430    pub(crate) fn decrement_outbound_cell_limit(&mut self) -> Result<()> {
431        self.outbound.decrement_cell_limit()
432    }
433
434    /// Decrement the limit of inbound cells that may be received from this hop; give
435    /// an error if it would reach zero.
436    pub(crate) fn decrement_inbound_cell_limit(&mut self) -> Result<()> {
437        self.inbound.decrement_cell_limit()
438    }
439}