1
//! Module exposing structures relating to the reactor's view of a circuit's hops.
2

            
3
use super::{CircuitCmd, CloseStreamBehavior};
4
use crate::circuit::circhop::{
5
    CircHopInbound, CircHopOutbound, HopSettings, ReactorStreamComponents, SendRelayCell,
6
};
7
use crate::client::reactor::circuit::path::PathEntry;
8
use crate::congestion::CongestionControl;
9
use crate::crypto::cell::HopNum;
10
use crate::memquota::StreamAccount;
11
use crate::stream::cmdcheck::AnyCmdChecker;
12
use crate::stream::flow_ctrl::state::WithSidechannelMitigations;
13
use crate::streammap::{self, StreamEntMut, StreamMap};
14
use crate::tunnel::TunnelScopedCircId;
15
use crate::util::tunnel_activity::TunnelActivity;
16
use crate::{Error, Result};
17

            
18
use futures::Stream;
19
use futures::stream::FuturesUnordered;
20
use smallvec::SmallVec;
21
use tor_cell::chancell::{BoxedCellBody, CircId};
22
use tor_cell::relaycell::flow_ctrl::{Xoff, Xon, XonKBpsEwma};
23
use tor_cell::relaycell::msg::AnyRelayMsg;
24
use tor_cell::relaycell::{
25
    AnyRelayMsgOuter, RelayCellDecoder, RelayCellDecoderResult, RelayCellFormat, StreamId,
26
    UnparsedRelayMsg,
27
};
28
use tor_rtcompat::DynTimeProvider;
29
use web_time_compat::Instant;
30

            
31
use safelog::sensitive as sv;
32
use tor_error::Bug;
33
use tracing::instrument;
34

            
35
use std::result::Result as StdResult;
36
use std::sync::{Arc, Mutex, MutexGuard};
37
use std::task::Poll;
38

            
39
#[cfg(test)]
40
use 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.
45
const NUM_HOPS: usize = 3;
46

            
47
/// Represents the reactor's view of a circuit's hop.
48
#[derive(Default)]
49
pub(crate) struct CircHopList {
50
    /// The list of hops.
51
    hops: SmallVec<[CircHop; NUM_HOPS]>,
52
}
53

            
54
impl CircHopList {
55
    /// Return a reference to the hop corresponding to `hopnum`, if there is one.
56
4108
    pub(super) fn hop(&self, hopnum: HopNum) -> Option<&CircHop> {
57
4108
        self.hops.get(Into::<usize>::into(hopnum))
58
4108
    }
59

            
60
    /// Return a mutable reference to the hop corresponding to `hopnum`, if there is one.
61
6454
    pub(super) fn get_mut(&mut self, hopnum: HopNum) -> Option<&mut CircHop> {
62
6454
        self.hops.get_mut(Into::<usize>::into(hopnum))
63
6454
    }
64

            
65
    /// Append the specified hop.
66
1034
    pub(crate) fn push(&mut self, hop: CircHop) {
67
1034
        self.hops.push(hop);
68
1034
    }
69

            
70
    /// Returns `true` if the list contains no [`CircHop`]s.
71
4918
    pub(crate) fn is_empty(&self) -> bool {
72
4918
        self.hops.is_empty()
73
4918
    }
74

            
75
    /// Returns the number of hops in the list.
76
2320
    pub(crate) fn len(&self) -> usize {
77
2320
        self.hops.len()
78
2320
    }
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
7132
    pub(in crate::client::reactor) fn ready_streams_iterator(
93
7132
        &self,
94
7132
        exclude: Option<HopNum>,
95
7132
    ) -> impl Stream<Item = CircuitCmd> + use<> {
96
7132
        self.hops
97
7132
            .iter()
98
7132
            .enumerate()
99
24186
            .filter_map(|(i, hop)| {
100
20620
                let hop_num = HopNum::from(i as u8);
101

            
102
20620
                if exclude == Some(hop_num) {
103
                    // We must skip polling this hop
104
1478
                    return None;
105
19142
                }
106

            
107
19142
                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
19142
                }
125

            
126
19142
                let hop_map = Arc::clone(self.hops[i].stream_map());
127
19204
                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
19204
                    let mut hop_map = hop_map.lock().expect("lock poisoned");
135
19204
                    let Some((sid, msg)) = hop_map.poll_ready_streams_iter(cx).next() else {
136
                        // No ready streams for this hop.
137
14962
                        return Poll::Pending;
138
                    };
139

            
140
4242
                    if msg.is_none() {
141
60
                        return Poll::Ready(CircuitCmd::CloseStream {
142
60
                            hop: hop_num,
143
60
                            sid,
144
60
                            behav: CloseStreamBehavior::default(),
145
60
                            reason: streammap::TerminateReason::StreamTargetClosed,
146
60
                        });
147
4182
                    };
148
4182
                    let msg = hop_map.take_ready_msg(sid).expect("msg disappeared");
149

            
150
                    #[allow(unused)] // unused in non-debug builds
151
4182
                    let Some(StreamEntMut::Open(s)) = hop_map.get_mut(sid) else {
152
                        panic!("Stream {sid} disappeared");
153
                    };
154

            
155
4182
                    debug_assert!(
156
4182
                        s.can_send(&msg),
157
                        "Stream {sid} produced a message it can't send: {msg:?}"
158
                    );
159

            
160
4182
                    let cell = SendRelayCell {
161
4182
                        hop: Some(hop_num),
162
4182
                        early: false,
163
4182
                        cell: AnyRelayMsgOuter::new(Some(sid), msg),
164
4182
                    };
165
4182
                    Poll::Ready(CircuitCmd::Send(cell))
166
19204
                }))
167
20620
            })
168
7132
            .collect::<FuturesUnordered<_>>()
169
7132
    }
170

            
171
    /// Remove all halfstreams that are expired at `now`.
172
7132
    pub(super) fn remove_expired_halfstreams(&mut self, now: Instant) {
173
20620
        for hop in self.hops.iter_mut() {
174
20620
            hop.stream_map()
175
20620
                .lock()
176
20620
                .expect("lock poisoned")
177
20620
                .remove_expired_halfstreams(now);
178
20620
        }
179
7132
    }
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
52
    pub(super) fn has_streams(&self) -> bool {
187
182
        self.hops.iter().any(|hop| {
188
156
            hop.stream_map()
189
156
                .lock()
190
156
                .expect("lock poisoned")
191
156
                .n_open_streams()
192
156
                > 0
193
156
        })
194
52
    }
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.
212
pub(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

            
229
impl CircHop {
230
    /// Create a new hop.
231
1034
    pub(crate) fn new(
232
1034
        unique_id: TunnelScopedCircId,
233
1034
        circ_id: CircId,
234
1034
        hop_num: HopNum,
235
1034
        settings: &HopSettings,
236
1034
    ) -> Self {
237
1034
        let relay_format = settings.relay_crypt_protocol().relay_cell_format();
238

            
239
1034
        let ccontrol = Arc::new(Mutex::new(CongestionControl::new(&settings.ccontrol)));
240
1034
        let inbound = CircHopInbound::new(RelayCellDecoder::new(relay_format), settings);
241

            
242
1034
        let outbound = CircHopOutbound::new(
243
1034
            ccontrol,
244
1034
            relay_format,
245
1034
            Arc::new(settings.flow_ctrl_params.clone()),
246
1034
            settings,
247
        );
248

            
249
1034
        CircHop {
250
1034
            unique_id,
251
1034
            circ_id,
252
1034
            hop_num,
253
1034
            inbound,
254
1034
            outbound,
255
1034
        }
256
1034
    }
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
96
    pub(crate) fn begin_stream(
261
96
        &mut self,
262
96
        message: AnyRelayMsg,
263
96
        time_prov: &DynTimeProvider,
264
96
        cmd_checker: AnyCmdChecker,
265
96
        memquota: &StreamAccount,
266
96
    ) -> Result<(SendRelayCell, StreamId, ReactorStreamComponents)> {
267
96
        self.outbound.begin_stream(
268
96
            Some(self.hop_num),
269
96
            message,
270
96
            time_prov,
271
96
            cmd_checker,
272
96
            memquota,
273
        )
274
96
    }
275

            
276
    /// Close the stream associated with `id` because the stream was
277
    /// dropped.
278
    ///
279
    /// See [`CircHopOutbound::close_stream`].
280
66
    pub(crate) fn close_stream(
281
66
        &mut self,
282
66
        id: StreamId,
283
66
        message: CloseStreamBehavior,
284
66
        why: streammap::TerminateReason,
285
66
        expiry: Instant,
286
66
    ) -> Result<Option<SendRelayCell>> {
287
66
        self.outbound.close_stream(
288
66
            self.unique_id,
289
66
            self.circ_id,
290
66
            id,
291
66
            Some(self.hop_num),
292
66
            message,
293
66
            why,
294
66
            expiry,
295
        )
296
66
    }
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
216
    pub(crate) fn maybe_send_xoff(&mut self, id: StreamId) -> Result<Option<Xoff>> {
314
216
        self.outbound.maybe_send_xoff(id)
315
216
    }
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
4724
    pub(crate) fn relay_cell_format(&self) -> RelayCellFormat {
322
4724
        self.outbound.relay_cell_format()
323
4724
    }
324

            
325
    /// Delegate to CongestionControl, for testing purposes
326
    #[cfg(test)]
327
20
    pub(crate) fn send_window_and_expected_tags(&self) -> (u32, Vec<SendmeTag>) {
328
20
        self.outbound.send_window_and_expected_tags()
329
20
    }
330

            
331
    /// Return a mutable reference to our CongestionControl object.
332
27488
    pub(crate) fn ccontrol(&self) -> MutexGuard<'_, CongestionControl> {
333
27488
        self.outbound.ccontrol().lock().expect("poisoned lock")
334
27488
    }
335

            
336
    /// Return a reference to our CircHopOutbound object.
337
36
    pub(crate) fn outbound(&self) -> &CircHopOutbound {
338
36
        &self.outbound
339
36
    }
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
4158
    pub(crate) fn about_to_send(&mut self, stream_id: StreamId, msg: &AnyRelayMsg) -> Result<()> {
347
4158
        self.outbound
348
4158
            .about_to_send(self.unique_id, self.circ_id, stream_id, msg)
349
4158
    }
350

            
351
    /// Add an entry to this map using the specified StreamId.
352
    #[cfg(feature = "hs-service")]
353
36
    pub(crate) fn add_ent_with_id(
354
36
        &self,
355
36
        time_prov: &DynTimeProvider,
356
36
        stream_id: StreamId,
357
36
        cmd_checker: AnyCmdChecker,
358
36
        with_sidechannel_mitigations: WithSidechannelMitigations,
359
36
        memquota: &StreamAccount,
360
36
    ) -> Result<ReactorStreamComponents> {
361
36
        self.outbound.add_ent_with_id(
362
36
            time_prov,
363
36
            stream_id,
364
36
            cmd_checker,
365
36
            with_sidechannel_mitigations,
366
36
            memquota,
367
        )
368
36
    }
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
500
    pub(crate) fn decode(&mut self, cell: BoxedCellBody) -> Result<RelayCellDecoderResult> {
384
500
        self.inbound.decode(cell)
385
500
    }
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
268
    pub(super) fn handle_msg(
395
268
        &self,
396
268
        hop_detail: &PathEntry,
397
268
        cell_counts_toward_windows: bool,
398
268
        streamid: StreamId,
399
268
        msg: UnparsedRelayMsg,
400
268
        now: Instant,
401
268
    ) -> Result<Option<UnparsedRelayMsg>> {
402
268
        let possible_proto_violation_err = |streamid: StreamId| Error::UnknownStream {
403
4
            src: sv(hop_detail.clone()),
404
4
            streamid,
405
4
        };
406

            
407
268
        self.outbound.handle_msg(
408
268
            possible_proto_violation_err,
409
268
            cell_counts_toward_windows,
410
268
            streamid,
411
268
            msg,
412
268
            now,
413
        )
414
268
    }
415

            
416
    /// Get the stream map of this hop.
417
39978
    pub(crate) fn stream_map(&self) -> &Arc<Mutex<StreamMap>> {
418
39978
        self.outbound.stream_map()
419
39978
    }
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
104
    pub(crate) fn set_stream_map(&mut self, map: Arc<Mutex<StreamMap>>) -> StdResult<(), Bug> {
425
104
        self.outbound.set_stream_map(map)
426
104
    }
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
4592
    pub(crate) fn decrement_outbound_cell_limit(&mut self) -> Result<()> {
431
4592
        self.outbound.decrement_cell_limit()
432
4592
    }
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
500
    pub(crate) fn decrement_inbound_cell_limit(&mut self) -> Result<()> {
437
500
        self.inbound.decrement_cell_limit()
438
500
    }
439
}