tor_proto/circuit/circhop.rs
1//! Module exposing structures relating to a reactor's view of a circuit hop.
2
3// TODO(relay): don't import from the client module
4use crate::client::circuit::handshake::RelayCryptLayerProtocol;
5
6use crate::ccparams::CongestionControlParams;
7use crate::circuit::CircParameters;
8use crate::congestion::{CongestionControl, sendme};
9use crate::memquota::{SpecificAccount, StreamAccount};
10use crate::stream::CloseStreamBehavior;
11use crate::stream::SEND_WINDOW_INIT;
12use crate::stream::StreamMpscSender;
13use crate::stream::cmdcheck::{AnyCmdChecker, StreamStatus};
14use crate::stream::flow_ctrl::params::FlowCtrlParameters;
15use crate::stream::flow_ctrl::state::{FlowCtrlHooks, StreamFlowCtrl, StreamRateLimit};
16use crate::stream::flow_ctrl::xon_xoff::reader::DrainRateRequest;
17use crate::stream::queue::{StreamQueueReceiver, stream_queue};
18use crate::streammap::{
19 self, EndSentStreamEnt, OpenStreamEnt, ShouldSendEnd, StreamEntMut, StreamMap,
20};
21use crate::util::notify::{NotifyReceiver, NotifySender};
22use crate::{Error, HopNum, Result};
23
24use derive_deftly::Deftly;
25use postage::watch;
26use safelog::sensitive as sv;
27use tracing::{debug, trace};
28
29use tor_cell::chancell::{BoxedCellBody, CircId};
30use tor_cell::relaycell::extend::{CcRequest, CircRequestExt};
31use tor_cell::relaycell::flow_ctrl::{Xoff, Xon, XonKBpsEwma};
32use tor_cell::relaycell::msg::AnyRelayMsg;
33use tor_cell::relaycell::{
34 AnyRelayMsgOuter, RelayCellDecoder, RelayCellDecoderResult, RelayCellFormat, RelayCmd,
35 StreamId, UnparsedRelayMsg,
36};
37use tor_error::{Bug, ErrorKind, HasKind, internal, into_internal};
38use tor_memquota::derive_deftly_template_HasMemoryCost;
39use tor_memquota::mq_queue::{ChannelSpec as _, MpscSpec};
40use tor_protover::named;
41use tor_rtcompat::DynTimeProvider;
42
43use std::num::NonZeroU32;
44use std::pin::Pin;
45use std::result::Result as StdResult;
46use std::sync::{Arc, Mutex};
47use web_time_compat::Instant;
48
49#[cfg(test)]
50use tor_cell::relaycell::msg::SendmeTag;
51
52#[cfg(feature = "relay")]
53use {
54 crate::ccparams::{Algorithm, AlgorithmDiscriminants},
55 crate::circuit::HandshakeSubprotocols,
56 crate::relay::{CircNetParameters, CongestionControlNetParams},
57};
58
59use cfg_if::cfg_if;
60
61/// The size of the stream's outbound RELAY message queue.
62// TODO(tuning): figure out if this is a good size for this buffer
63const CIRCUIT_BUFFER_SIZE: usize = 128;
64
65/// Type of negotiation that we'll be performing as we establish a hop.
66///
67/// Determines what flavor of extensions we can send and receive, which in turn
68/// limits the hop settings we can negotiate.
69///
70// TODO-CGO: This is likely to be refactored when we finally add support for
71// HsV3+CGO, which will require refactoring
72#[derive(Debug, Clone, Copy, Eq, PartialEq)]
73pub(crate) enum HopNegotiationType {
74 /// We're using a handshake in which extension-based negotiation cannot occur.
75 None,
76 /// We're using the HsV3-ntor handshake, in which the client can send extensions,
77 /// but the server cannot.
78 ///
79 /// As a special case, the default relay encryption protocol is the hsv3
80 /// variant of Tor1.
81 //
82 // We would call this "HalfDuplex" or something, but we do not expect to add
83 // any more handshakes of this type.
84 HsV3,
85 /// We're using a handshake in which both client and relay can send extensions.
86 Full,
87}
88
89/// The settings we use for single hop of a circuit.
90///
91/// Unlike [`CircParameters`], this type is crate-internal.
92/// We construct it based on our settings from the circuit,
93/// and from the hop's actual capabilities.
94/// Then, we negotiate with the hop as part of circuit
95/// creation/extension to determine the actual settings that will be in use.
96/// Finally, we use those settings to construct the negotiated circuit hop.
97//
98// TODO: Relays should probably derive an instance of this type too, as
99// part of the circuit creation handshake.
100#[derive(Clone, Debug)]
101pub(crate) struct HopSettings {
102 /// The negotiated congestion control settings for this hop .
103 pub(crate) ccontrol: CongestionControlParams,
104
105 /// Flow control parameters that will be used for streams on this hop.
106 pub(crate) flow_ctrl_params: FlowCtrlParameters,
107
108 /// Maximum number of permitted incoming relay cells for this hop.
109 pub(crate) n_incoming_cells_permitted: Option<u32>,
110
111 /// Maximum number of permitted outgoing relay cells for this hop.
112 pub(crate) n_outgoing_cells_permitted: Option<u32>,
113
114 /// The relay cell encryption algorithm and cell format for this hop.
115 relay_crypt_protocol: RelayCryptLayerProtocol,
116}
117
118impl HopSettings {
119 /// Construct a new `HopSettings` based on `params` (a set of circuit parameters)
120 /// and `caps` (a set of protocol capabilities for a circuit target).
121 ///
122 /// The resulting settings will represent what the client would prefer to negotiate
123 /// (determined by `params`),
124 /// as modified by what the target relay is believed to support (represented by `caps`).
125 ///
126 /// This represents the `HopSettings` in a pre-negotiation state:
127 /// the circuit negotiation process will modify it.
128 #[allow(clippy::unnecessary_wraps)] // likely to become fallible in the future.
129 pub(crate) fn from_params_and_caps(
130 hoptype: HopNegotiationType,
131 params: &CircParameters,
132 caps: &tor_protover::Protocols,
133 ) -> Result<Self> {
134 let mut ccontrol = params.ccontrol.clone();
135 match ccontrol.alg() {
136 crate::ccparams::Algorithm::FixedWindow(_) => {}
137 crate::ccparams::Algorithm::Vegas(_) => {
138 // If the target doesn't support FLOWCTRL_CC, we can't use Vegas.
139 if !caps.supports_named_subver(named::FLOWCTRL_CC) {
140 ccontrol.use_fallback_alg();
141 }
142 }
143 };
144 if hoptype == HopNegotiationType::None {
145 ccontrol.use_fallback_alg();
146 }
147 let ccontrol = ccontrol; // drop mut
148
149 // Negotiate CGO if it is supported, if CC is also supported,
150 // and if CGO is available on this relay.
151 let relay_crypt_protocol = match hoptype {
152 HopNegotiationType::None => RelayCryptLayerProtocol::Tor1(RelayCellFormat::V0),
153 HopNegotiationType::HsV3 => {
154 // TODO-CGO: Support CGO when available.
155 cfg_if! {
156 if #[cfg(all(feature = "hs-common", feature = "flowctl-cc", feature = "counter-galois-onion"))] {
157 if ccontrol.alg().compatible_with_cgo() && caps.supports_named_subver(named::RELAY_CRYPT_CGO) {
158 RelayCryptLayerProtocol::Cgo
159 } else {
160 RelayCryptLayerProtocol::HsV3(RelayCellFormat::V0)
161 }
162 } else if #[cfg(feature = "hs-common")] {
163 RelayCryptLayerProtocol::HsV3(RelayCellFormat::V0)
164 } else {
165 return Err(
166 tor_error::internal!("Unexpectedly tried to negotiate HsV3 without support!").into(),
167 );
168 }
169 }
170 }
171 HopNegotiationType::Full => {
172 cfg_if! {
173 if #[cfg(all(feature = "flowctl-cc", feature = "counter-galois-onion"))] {
174 #[allow(clippy::overly_complex_bool_expr)]
175 if ccontrol.alg().compatible_with_cgo()
176 && caps.supports_named_subver(named::RELAY_NEGOTIATE_SUBPROTO)
177 && caps.supports_named_subver(named::RELAY_CRYPT_CGO)
178 {
179 RelayCryptLayerProtocol::Cgo
180 } else {
181 RelayCryptLayerProtocol::Tor1(RelayCellFormat::V0)
182 }
183 } else {
184 RelayCryptLayerProtocol::Tor1(RelayCellFormat::V0)
185 }
186 }
187 }
188 };
189
190 Ok(Self {
191 ccontrol,
192 flow_ctrl_params: params.flow_ctrl.clone(),
193 relay_crypt_protocol,
194 n_incoming_cells_permitted: params.n_incoming_cells_permitted,
195 n_outgoing_cells_permitted: params.n_outgoing_cells_permitted,
196 })
197 }
198
199 /// Build a [`HopSettings`] from the parameters requested during a circuit handshake.
200 //
201 // We disable `unused` warnings at the root of tor-proto,
202 // but it's nice to have here so we re-enable it.
203 #[warn(unused)]
204 #[cfg(feature = "relay")]
205 pub(crate) fn from_handshake_params(
206 circ_net_params: CircNetParameters,
207 cc_algorithm: AlgorithmDiscriminants,
208 subprotos_requested: HandshakeSubprotocols,
209 ) -> StdResult<Self, HandshakeParamsError> {
210 // Unpack everything to make sure that we aren't missing anything
211 // (otherwise clippy would warn).
212 let CircNetParameters {
213 cc:
214 CongestionControlNetParams {
215 fixed_window,
216 vegas_exit,
217 cwnd,
218 rtt,
219 flow_ctrl,
220 },
221 } = circ_net_params;
222
223 let HandshakeSubprotocols { relay_crypt_cgo } = subprotos_requested;
224
225 // TODO: We have similar logic in and around `HopSettings` that deals with determining the
226 // crypt protocol and cc algorithm to use. We might want to try to dedup some of this, or
227 // make it more self-contained. This is a bit tricky though since the code is used in
228 // different situations and the inputs are not the same.
229 let (cc_algorithm, relay_crypt_protocol) = match (cc_algorithm, relay_crypt_cgo) {
230 (AlgorithmDiscriminants::FixedWindow, false) => (
231 Algorithm::FixedWindow(fixed_window),
232 RelayCryptLayerProtocol::Tor1(RelayCellFormat::V0),
233 ),
234 (AlgorithmDiscriminants::FixedWindow, true) => {
235 return Err(HandshakeParamsError::IncompatibleParams(
236 "requested CGO but not congestion control",
237 ));
238 }
239 (AlgorithmDiscriminants::Vegas, false) => (
240 Algorithm::Vegas(vegas_exit),
241 RelayCryptLayerProtocol::Tor1(RelayCellFormat::V0),
242 ),
243 (AlgorithmDiscriminants::Vegas, true) => {
244 (Algorithm::Vegas(vegas_exit), RelayCryptLayerProtocol::Cgo)
245 }
246 };
247
248 // TODO(arti#2442): The builder pattern here seems like a footgun.
249 let ccontrol = CongestionControlParams::builder()
250 .alg(cc_algorithm)
251 .fixed_window_params(fixed_window)
252 .cwnd_params(cwnd)
253 .rtt_params(rtt)
254 .build()
255 .map_err(into_internal!("Could not build `CongestionControlParams`"))?;
256
257 Ok(Self {
258 ccontrol,
259 flow_ctrl_params: flow_ctrl,
260 relay_crypt_protocol,
261 n_incoming_cells_permitted: None,
262 n_outgoing_cells_permitted: None,
263 })
264 }
265
266 /// Return the negotiated relay crypto protocol.
267 pub(crate) fn relay_crypt_protocol(&self) -> RelayCryptLayerProtocol {
268 self.relay_crypt_protocol
269 }
270
271 /// Return the client circuit-creation extensions that we should use in order to negotiate
272 /// these circuit hop parameters.
273 #[allow(clippy::unnecessary_wraps)]
274 pub(crate) fn circuit_request_extensions(&self) -> Result<Vec<CircRequestExt>> {
275 // allow 'unused_mut' because of the combinations of `cfg` conditions below
276 #[allow(unused_mut)]
277 let mut client_extensions = Vec::new();
278
279 #[allow(unused, unused_mut)]
280 let mut cc_extension_set = false;
281
282 if self.ccontrol.is_enabled() {
283 cfg_if::cfg_if! {
284 if #[cfg(feature = "flowctl-cc")] {
285 client_extensions.push(CircRequestExt::CcRequest(CcRequest::default()));
286 cc_extension_set = true;
287 } else {
288 return Err(
289 tor_error::internal!(
290 "Congestion control is enabled on this circuit, but 'flowctl-cc' feature is not enabled"
291 )
292 .into()
293 );
294 }
295 }
296 }
297
298 // See whether we need to send a list of required protocol capabilities.
299 // These aren't "negotiated" per se; they're simply demanded.
300 // The relay will refuse the circuit if it doesn't support all of them,
301 // and if any of them isn't supported in the SubprotocolRequest extension.
302 //
303 // (In other words, don't add capabilities here just because you want the
304 // relay to have them! They must be explicitly listed as supported for use
305 // with this extension. For the current list, see
306 // https://spec.torproject.org/tor-spec/create-created-cells.html#subproto-request)
307 //
308 // TODO: Should this use `HandshakeSubprotocols` so that the above comment has some
309 // compile-time checks?
310 #[allow(unused_mut)]
311 let mut required_protocol_capabilities: Vec<tor_protover::NamedSubver> = Vec::new();
312
313 #[cfg(feature = "counter-galois-onion")]
314 if matches!(self.relay_crypt_protocol(), RelayCryptLayerProtocol::Cgo) {
315 if !cc_extension_set {
316 return Err(tor_error::internal!("Tried to negotiate CGO without CC.").into());
317 }
318 required_protocol_capabilities.push(tor_protover::named::RELAY_CRYPT_CGO);
319 }
320
321 if !required_protocol_capabilities.is_empty() {
322 client_extensions.push(CircRequestExt::SubprotocolRequest(
323 required_protocol_capabilities.into_iter().collect(),
324 ));
325 }
326
327 Ok(client_extensions)
328 }
329}
330
331#[cfg(test)]
332impl std::default::Default for CircParameters {
333 fn default() -> Self {
334 Self {
335 extend_by_ed25519_id: true,
336 ccontrol: crate::congestion::test_utils::params::build_cc_fixed_params(),
337 flow_ctrl: FlowCtrlParameters::defaults_for_tests(),
338 n_incoming_cells_permitted: None,
339 n_outgoing_cells_permitted: None,
340 }
341 }
342}
343
344/// An error that can occur when building a [`HopSettings`] using parameters requested during a
345/// circuit handshake.
346#[derive(Clone, Debug, thiserror::Error)]
347pub(crate) enum HandshakeParamsError {
348 /// The provided parameters are incompatible with each other.
349 #[error("The provided handshake parameters are incompatible with each other: {0}")]
350 IncompatibleParams(&'static str),
351 /// An internal error.
352 #[error("Internal error")]
353 Internal(#[from] tor_error::Bug),
354}
355
356impl HasKind for HandshakeParamsError {
357 fn kind(&self) -> ErrorKind {
358 match self {
359 Self::IncompatibleParams(_) => ErrorKind::TorProtocolViolation,
360 Self::Internal(_) => ErrorKind::Internal,
361 }
362 }
363}
364
365impl CircParameters {
366 /// Constructor
367 pub fn new(
368 extend_by_ed25519_id: bool,
369 ccontrol: CongestionControlParams,
370 flow_ctrl: FlowCtrlParameters,
371 ) -> Self {
372 Self {
373 extend_by_ed25519_id,
374 ccontrol,
375 flow_ctrl,
376 n_incoming_cells_permitted: None,
377 n_outgoing_cells_permitted: None,
378 }
379 }
380}
381
382/// Instructions for sending a RELAY cell.
383///
384/// This instructs a circuit reactor to send a RELAY cell to a given target
385/// (a hop, if we are a client, or the client, if we are a relay).
386#[derive(educe::Educe)]
387#[educe(Debug)]
388pub(crate) struct SendRelayCell {
389 /// The hop number, or `None` if we are a relay.
390 pub(crate) hop: Option<HopNum>,
391 /// Whether to use a RELAY_EARLY cell.
392 pub(crate) early: bool,
393 /// The cell to send.
394 pub(crate) cell: AnyRelayMsgOuter,
395}
396
397/// The inbound state of a hop.
398pub(crate) struct CircHopInbound {
399 /// Decodes relay cells received from this hop.
400 decoder: RelayCellDecoder,
401 /// Remaining permitted incoming relay cells from this hop, plus 1.
402 ///
403 /// (In other words, `None` represents no limit,
404 /// `Some(1)` represents an exhausted limit,
405 /// and `Some(n)` means that n-1 more cells may be received.)
406 ///
407 /// If this ever decrements from Some(1), then the circuit must be torn down with an error.
408 n_incoming_cells_permitted: Option<NonZeroU32>,
409}
410
411/// The outbound state of a hop.
412pub(crate) struct CircHopOutbound {
413 /// Congestion control object.
414 ///
415 /// This object is also in charge of handling circuit level SENDME logic for this hop.
416 ccontrol: Arc<Mutex<CongestionControl>>,
417 /// Map from stream IDs to streams.
418 ///
419 /// We store this with the reactor instead of the circuit, since the
420 /// reactor needs it for every incoming cell on a stream, whereas
421 /// the circuit only needs it when allocating new streams.
422 ///
423 /// NOTE: this is behind a mutex because the client reactor polls the `StreamMap`s
424 /// of all hops concurrently, in a `FuturesUnordered`. Without the mutex,
425 /// this wouldn't be possible, because it would mean holding multiple
426 /// mutable references to `self` (the reactor). Note, however,
427 /// that there should never be any contention on this mutex:
428 /// we never create more than one
429 /// `CircHopList::ready_streams_iterator()` stream
430 /// at a time, and we never clone/lock the hop's `StreamMap` outside of it.
431 ///
432 /// Additionally, the stream map of the last hop (join point) of a conflux tunnel
433 /// is shared with all the circuits in the tunnel.
434 map: Arc<Mutex<StreamMap>>,
435 /// Format to use for relay cells.
436 //
437 // When we have packed/fragmented cells, this may be replaced by a RelayCellEncoder.
438 relay_format: RelayCellFormat,
439 /// Flow control parameters for new streams.
440 flow_ctrl_params: Arc<FlowCtrlParameters>,
441 /// Remaining permitted outgoing relay cells from this hop, plus 1.
442 ///
443 /// If this ever decrements from Some(1), then the circuit must be torn down with an error.
444 n_outgoing_cells_permitted: Option<NonZeroU32>,
445}
446
447impl CircHopInbound {
448 /// Create a new [`CircHopInbound`].
449 pub(crate) fn new(decoder: RelayCellDecoder, settings: &HopSettings) -> Self {
450 Self {
451 decoder,
452 n_incoming_cells_permitted: settings.n_incoming_cells_permitted.map(cvt),
453 }
454 }
455
456 /// Parse a RELAY or RELAY_EARLY cell body.
457 ///
458 /// Requires that the cryptographic checks on the message have already been
459 /// performed
460 pub(crate) fn decode(&mut self, cell: BoxedCellBody) -> Result<RelayCellDecoderResult> {
461 self.decoder
462 .decode(cell)
463 .map_err(|e| Error::from_bytes_err(e, "relay cell"))
464 }
465
466 /// Decrement the limit of inbound cells that may be received from this hop; give
467 /// an error if it would reach zero.
468 pub(crate) fn decrement_cell_limit(&mut self) -> Result<()> {
469 try_decrement_cell_limit(&mut self.n_incoming_cells_permitted)
470 .map_err(|_| Error::ExcessInboundCells)
471 }
472}
473
474impl CircHopOutbound {
475 /// Create a new [`CircHopOutbound`].
476 pub(crate) fn new(
477 ccontrol: Arc<Mutex<CongestionControl>>,
478 relay_format: RelayCellFormat,
479 flow_ctrl_params: Arc<FlowCtrlParameters>,
480 settings: &HopSettings,
481 ) -> Self {
482 Self {
483 ccontrol,
484 map: Arc::new(Mutex::new(StreamMap::new())),
485 relay_format,
486 flow_ctrl_params,
487 n_outgoing_cells_permitted: settings.n_outgoing_cells_permitted.map(cvt),
488 }
489 }
490
491 /// Start a stream. Creates an entry in the stream map with the given channels, and sends the
492 /// `message` to the provided hop.
493 pub(crate) fn begin_stream(
494 &mut self,
495 hop: Option<HopNum>,
496 message: AnyRelayMsg,
497 time_prov: &DynTimeProvider,
498 cmd_checker: AnyCmdChecker,
499 memquota: &StreamAccount,
500 ) -> Result<(SendRelayCell, StreamId, ReactorStreamComponents)> {
501 // TODO: This has a lot of duplicated code with `Self::add_ent_with_id()`.
502
503 // A channel for the reactor to inform the writer of a new rate limit.
504 let (rate_limit_tx, rate_limit_rx) = watch::channel_with(StreamRateLimit::MAX);
505
506 // A channel for the reactor to request a new drain rate from the reader.
507 // Typically this notification will be sent after an XOFF is sent so that the reader can
508 // send us a new drain rate when the stream data queue becomes empty.
509 let mut drain_rate_request_tx = NotifySender::new_typed();
510 let drain_rate_request_rx = drain_rate_request_tx.subscribe();
511
512 let flow_ctrl = self.build_flow_ctrl(rate_limit_tx, drain_rate_request_tx)?;
513
514 let stream_queue_max_len = flow_ctrl.inbound_queue_max_len();
515
516 // A queue for inbound RELAY messages.
517 let (sender, receiver) = stream_queue(stream_queue_max_len, memquota, time_prov)?;
518
519 // A queue for outbound RELAY messages.
520 let (msg_tx, msg_rx) = MpscSpec::new(CIRCUIT_BUFFER_SIZE)
521 .new_mq(time_prov.clone(), memquota.as_raw_account())?;
522
523 let r = self.map.lock().expect("lock poisoned").add_ent(
524 sender,
525 msg_rx,
526 flow_ctrl,
527 cmd_checker,
528 )?;
529 let cell = AnyRelayMsgOuter::new(Some(r), message);
530
531 let stream_components = ReactorStreamComponents {
532 stream_inbound_rx: receiver,
533 stream_outbound_tx: msg_tx,
534 rate_limit_rx,
535 drain_rate_request_rx,
536 };
537
538 Ok((
539 SendRelayCell {
540 hop,
541 early: false,
542 cell,
543 },
544 r,
545 stream_components,
546 ))
547 }
548
549 /// Close the stream associated with `id` because the stream was dropped.
550 ///
551 /// If we have not already received an END cell on this stream, send one.
552 /// If no END cell is specified, an END cell with the reason byte set to
553 /// REASON_MISC will be sent.
554 ///
555 // Note(relay): `circ_uniq_id` is an opaque displayable type
556 // because relays use a different circuit ID type
557 // than clients. Eventually, we should probably make
558 // them both use the same ID type, or have a nicer approach here
559 #[allow(clippy::too_many_arguments)]
560 pub(crate) fn close_stream(
561 &mut self,
562 circ_uniq_id: impl std::fmt::Display,
563 circ_id: CircId,
564 id: StreamId,
565 hop: Option<HopNum>,
566 message: CloseStreamBehavior,
567 why: streammap::TerminateReason,
568 expiry: Instant,
569 ) -> Result<Option<SendRelayCell>> {
570 let should_send_end = self
571 .map
572 .lock()
573 .expect("lock poisoned")
574 .terminate(id, why, expiry)?;
575 trace!(
576 circ_uniq_id = %circ_uniq_id,
577 circ_id = %circ_id,
578 stream_id = %id,
579 should_send_end = ?should_send_end,
580 "Ending stream",
581 );
582 // TODO: I am about 80% sure that we only send an END cell if
583 // we didn't already get an END cell. But I should double-check!
584 if let (ShouldSendEnd::Send, CloseStreamBehavior::SendEnd(end_message)) =
585 (should_send_end, message)
586 {
587 let end_cell = AnyRelayMsgOuter::new(Some(id), end_message.into());
588 let cell = SendRelayCell {
589 hop,
590 early: false,
591 cell: end_cell,
592 };
593
594 return Ok(Some(cell));
595 }
596 Ok(None)
597 }
598
599 /// Check if we should send an XON message.
600 ///
601 /// If we should, then returns the XON message that should be sent.
602 pub(crate) fn maybe_send_xon(
603 &mut self,
604 rate: XonKBpsEwma,
605 id: StreamId,
606 ) -> Result<Option<Xon>> {
607 // the call below will return an error if XON/XOFF aren't supported,
608 // so we check for support here
609 if !self
610 .ccontrol()
611 .lock()
612 .expect("poisoned lock")
613 .uses_xon_xoff()
614 {
615 return Ok(None);
616 }
617
618 let mut map = self.map.lock().expect("lock poisoned");
619 let Some(StreamEntMut::Open(ent)) = map.get_mut(id) else {
620 // stream went away
621 return Ok(None);
622 };
623
624 ent.maybe_send_xon(rate)
625 }
626
627 /// Check if we should send an XOFF message.
628 ///
629 /// If we should, then returns the XOFF message that should be sent.
630 pub(crate) fn maybe_send_xoff(&mut self, id: StreamId) -> Result<Option<Xoff>> {
631 // the call below will return an error if XON/XOFF aren't supported,
632 // so we check for support here
633 if !self
634 .ccontrol()
635 .lock()
636 .expect("poisoned lock")
637 .uses_xon_xoff()
638 {
639 return Ok(None);
640 }
641
642 let mut map = self.map.lock().expect("lock poisoned");
643 let Some(StreamEntMut::Open(ent)) = map.get_mut(id) else {
644 // stream went away
645 return Ok(None);
646 };
647
648 ent.maybe_send_xoff()
649 }
650
651 /// Return the format that is used for relay cells sent to this hop.
652 ///
653 /// For the most part, this format isn't necessary to interact with a CircHop;
654 /// it becomes relevant when we are deciding _what_ we can encode for the hop.
655 pub(crate) fn relay_cell_format(&self) -> RelayCellFormat {
656 self.relay_format
657 }
658
659 /// Delegate to CongestionControl, for testing purposes
660 #[cfg(test)]
661 pub(crate) fn send_window_and_expected_tags(&self) -> (u32, Vec<SendmeTag>) {
662 self.ccontrol()
663 .lock()
664 .expect("poisoned lock")
665 .send_window_and_expected_tags()
666 }
667
668 /// Return the number of open streams on this hop.
669 ///
670 /// WARNING: because this locks the stream map mutex,
671 /// it should never be called from a context where that mutex is already locked.
672 pub(crate) fn n_open_streams(&self) -> usize {
673 self.map.lock().expect("lock poisoned").n_open_streams()
674 }
675
676 /// Return a reference to our CongestionControl object.
677 pub(crate) fn ccontrol(&self) -> &Arc<Mutex<CongestionControl>> {
678 &self.ccontrol
679 }
680
681 /// We're about to send `msg`.
682 ///
683 /// See [`OpenStreamEnt::about_to_send`](crate::streammap::OpenStreamEnt::about_to_send).
684 //
685 // TODO prop340: This should take a cell or similar, not a message.
686 //
687 // Note(relay): `circ_uniq_id` is an opaque displayable type
688 // because relays use a different circuit ID type
689 // than clients. Eventually, we should probably make
690 // them both use the same ID type, or have a nicer approach here
691 pub(crate) fn about_to_send(
692 &mut self,
693 circ_uniq_id: impl std::fmt::Display,
694 circ_id: CircId,
695 stream_id: StreamId,
696 msg: &AnyRelayMsg,
697 ) -> Result<()> {
698 let mut hop_map = self.map.lock().expect("lock poisoned");
699 let Some(StreamEntMut::Open(ent)) = hop_map.get_mut(stream_id) else {
700 // This can happen when we have outgoing data queued when we received an END.
701 // We shouldn't return an error here since it would close the circuit along with all
702 // other streams, and instead we just let the caller send this message anyways.
703 // Also the caller only calls `about_to_send()` for DATA cells,
704 // which means that other non-DATA cells don't hit this code path and are always sent,
705 // and so we should handle all cell types consistently.
706 // TODO: We should drop the message and not send it,
707 // but the caller of `about_to_send()` isn't designed to handle fallible sends
708 // so it would need some refactoring to handle this.
709 debug!(
710 circ_uniq_id = %circ_uniq_id,
711 circ_id = %circ_id,
712 stream_id = %stream_id,
713 "sending a relay cell for non-existent or non-open stream!",
714 );
715 return Ok(());
716 };
717
718 ent.about_to_send(msg)
719 }
720
721 /// Add an entry to this map using the specified StreamId.
722 #[cfg(any(feature = "hs-service", feature = "relay"))]
723 pub(crate) fn add_ent_with_id(
724 &self,
725 time_prov: &DynTimeProvider,
726 stream_id: StreamId,
727 cmd_checker: AnyCmdChecker,
728 memquota: &StreamAccount,
729 ) -> Result<ReactorStreamComponents> {
730 // TODO: This has a lot of duplicated code with `Self::begin_stream()`.
731
732 // A channel for the reactor to inform the writer of a new rate limit.
733 let (rate_limit_tx, rate_limit_rx) = watch::channel_with(StreamRateLimit::MAX);
734
735 // A channel for the reactor to request a new drain rate from the reader.
736 // Typically this notification will be sent after an XOFF is sent so that the reader can
737 // send us a new drain rate when the stream data queue becomes empty.
738 let mut drain_rate_request_tx = NotifySender::new_typed();
739 let drain_rate_request_rx = drain_rate_request_tx.subscribe();
740
741 let flow_ctrl = self.build_flow_ctrl(rate_limit_tx, drain_rate_request_tx)?;
742
743 let stream_queue_max_len = flow_ctrl.inbound_queue_max_len();
744
745 // A queue for inbound RELAY messages.
746 let (sender, receiver) = stream_queue(stream_queue_max_len, memquota, time_prov)?;
747
748 // A queue for outbound RELAY messages.
749 let (msg_tx, msg_rx) = MpscSpec::new(CIRCUIT_BUFFER_SIZE)
750 .new_mq(time_prov.clone(), memquota.as_raw_account())?;
751
752 let mut hop_map = self.map.lock().expect("lock poisoned");
753 hop_map.add_ent_with_id(sender, msg_rx, flow_ctrl, stream_id, cmd_checker)?;
754
755 Ok(ReactorStreamComponents {
756 stream_inbound_rx: receiver,
757 stream_outbound_tx: msg_tx,
758 rate_limit_rx,
759 drain_rate_request_rx,
760 })
761 }
762
763 /// Builds the reactor's flow control handler for a new stream.
764 // TODO: remove the `Result` once we remove the "flowctl-cc" feature
765 #[cfg_attr(feature = "flowctl-cc", expect(clippy::unnecessary_wraps))]
766 fn build_flow_ctrl(
767 &self,
768 rate_limit_updater: watch::Sender<StreamRateLimit>,
769 drain_rate_requester: NotifySender<DrainRateRequest>,
770 ) -> Result<StreamFlowCtrl> {
771 let params = Arc::clone(&self.flow_ctrl_params);
772
773 if self
774 .ccontrol()
775 .lock()
776 .expect("poisoned lock")
777 .uses_stream_sendme()
778 {
779 let window = sendme::StreamSendWindow::new(SEND_WINDOW_INIT);
780 Ok(StreamFlowCtrl::new_window(window))
781 } else {
782 cfg_if::cfg_if! {
783 if #[cfg(feature = "flowctl-cc")] {
784 // TODO: Currently arti only supports clients, and we don't support connecting
785 // to onion services while using congestion control, so we hardcode this. In the
786 // future we will need to somehow tell the `CircHop` this so that we can set it
787 // correctly, since we don't want to enable this at exits.
788 let use_sidechannel_mitigations = true;
789
790 Ok(StreamFlowCtrl::new_xon_xoff(
791 params,
792 use_sidechannel_mitigations,
793 rate_limit_updater,
794 drain_rate_requester,
795 ))
796 } else {
797 drop(params);
798 drop(rate_limit_updater);
799 drop(drain_rate_requester);
800 Err(internal!(
801 "`CongestionControl` doesn't use sendmes, but 'flowctl-cc' feature not enabled",
802 ).into())
803 }
804 }
805 }
806 }
807
808 /// Deliver `msg` to the specified open stream entry `ent`.
809 fn deliver_msg_to_stream(
810 streamid: StreamId,
811 ent: &mut OpenStreamEnt,
812 cell_counts_toward_windows: bool,
813 msg: UnparsedRelayMsg,
814 ) -> Result<bool> {
815 use tor_async_utils::SinkTrySend as _;
816 use tor_async_utils::SinkTrySendError as _;
817
818 // The stream for this message exists, and is open.
819
820 // We need to handle SENDME/XON/XOFF messages here, not in the stream's recv() method, or
821 // else we'd never notice them if the stream isn't reading.
822 match msg.cmd() {
823 RelayCmd::SENDME => {
824 ent.put_for_incoming_sendme(msg)?;
825 return Ok(false);
826 }
827 RelayCmd::XON => {
828 ent.handle_incoming_xon(msg)?;
829 return Ok(false);
830 }
831 RelayCmd::XOFF => {
832 ent.handle_incoming_xoff(msg)?;
833 return Ok(false);
834 }
835 _ => {}
836 }
837
838 let message_closes_stream = ent.cmd_checker.check_msg(&msg)? == StreamStatus::Closed;
839
840 if let Err(e) = Pin::new(&mut ent.sink).try_send(msg) {
841 if e.is_full() {
842 cfg_if::cfg_if! {
843 if #[cfg(not(feature = "flowctl-cc"))] {
844 // If we get here, we either have a logic bug (!), or an attacker
845 // is sending us more cells than we asked for via congestion control.
846 return Err(Error::CircProto(format!(
847 "Stream sink would block; received too many cells on stream ID {}",
848 sv(streamid),
849 )));
850 } else {
851 return Err(internal!(
852 "Stream (ID {}) uses an unbounded queue, but apparently it's full?",
853 sv(streamid),
854 )
855 .into());
856 }
857 }
858 }
859 if e.is_disconnected() && cell_counts_toward_windows {
860 // the other side of the stream has gone away; remember
861 // that we received a cell that we couldn't queue for it.
862 //
863 // Later this value will be recorded in a half-stream.
864 ent.dropped += 1;
865 }
866 }
867
868 Ok(message_closes_stream)
869 }
870
871 /// Note that we received an END message (or other message indicating the end of
872 /// the stream) on the stream with `id`.
873 ///
874 /// See [`StreamMap::ending_msg_received`](crate::streammap::StreamMap::ending_msg_received).
875 #[cfg(feature = "hs-service")]
876 pub(crate) fn ending_msg_received(&self, stream_id: StreamId) -> Result<()> {
877 let mut hop_map = self.map.lock().expect("lock poisoned");
878
879 hop_map.ending_msg_received(stream_id)?;
880
881 Ok(())
882 }
883
884 /// Handle `msg`, delivering it to the stream with the specified `streamid` if appropriate.
885 ///
886 /// Returns back the provided `msg`, if the message is an incoming stream request
887 /// that needs to be handled by the calling code.
888 ///
889 // TODO: the above is a bit of a code smell -- we should try to avoid passing the msg
890 // back and forth like this.
891 pub(crate) fn handle_msg<F>(
892 &self,
893 possible_proto_violation_err: F,
894 cell_counts_toward_windows: bool,
895 streamid: StreamId,
896 msg: UnparsedRelayMsg,
897 now: Instant,
898 ) -> Result<Option<UnparsedRelayMsg>>
899 where
900 F: FnOnce(StreamId) -> Error,
901 {
902 let mut hop_map = self.map.lock().expect("lock poisoned");
903
904 match hop_map.get_mut(streamid) {
905 Some(StreamEntMut::Open(ent)) => {
906 // Can't have a stream level SENDME when congestion control is enabled.
907 let message_closes_stream =
908 Self::deliver_msg_to_stream(streamid, ent, cell_counts_toward_windows, msg)?;
909
910 if message_closes_stream {
911 hop_map.ending_msg_received(streamid)?;
912 }
913 }
914 Some(StreamEntMut::EndSent(EndSentStreamEnt { expiry, .. })) if now >= *expiry => {
915 return Err(possible_proto_violation_err(streamid));
916 }
917 Some(StreamEntMut::EndSent(_))
918 if matches!(
919 msg.cmd(),
920 RelayCmd::BEGIN | RelayCmd::BEGIN_DIR | RelayCmd::RESOLVE
921 ) =>
922 {
923 // If the other side is sending us a BEGIN but hasn't yet acknowledged our END
924 // message, just remove the old stream from the map and stop waiting for a
925 // response
926 hop_map.ending_msg_received(streamid)?;
927 return Ok(Some(msg));
928 }
929 Some(StreamEntMut::EndSent(EndSentStreamEnt { half_stream, .. })) => {
930 // We sent an end but maybe the other side hasn't heard.
931
932 match half_stream.handle_msg(msg)? {
933 StreamStatus::Open => {}
934 StreamStatus::Closed => {
935 hop_map.ending_msg_received(streamid)?;
936 }
937 }
938 }
939 None if matches!(
940 msg.cmd(),
941 RelayCmd::BEGIN | RelayCmd::BEGIN_DIR | RelayCmd::RESOLVE
942 ) =>
943 {
944 return Ok(Some(msg));
945 }
946 _ => {
947 // No stream wants this message, or ever did.
948 return Err(possible_proto_violation_err(streamid));
949 }
950 }
951
952 Ok(None)
953 }
954
955 /// Get the stream map of this hop.
956 pub(crate) fn stream_map(&self) -> &Arc<Mutex<StreamMap>> {
957 &self.map
958 }
959
960 /// Set the stream map of this hop to `map`.
961 ///
962 /// Returns an error if the existing stream map of the hop has any open stream.
963 pub(crate) fn set_stream_map(&mut self, map: Arc<Mutex<StreamMap>>) -> StdResult<(), Bug> {
964 if self.n_open_streams() != 0 {
965 return Err(internal!("Tried to discard existing open streams?!"));
966 }
967
968 self.map = map;
969
970 Ok(())
971 }
972
973 /// Decrement the limit of outbound cells that may be sent to this hop; give
974 /// an error if it would reach zero.
975 pub(crate) fn decrement_cell_limit(&mut self) -> Result<()> {
976 try_decrement_cell_limit(&mut self.n_outgoing_cells_permitted)
977 .map_err(|_| Error::ExcessOutboundCells)
978 }
979}
980
981/// If `val` is `Some(1)`, return Err(());
982/// otherwise decrement it (if it is Some) and return Ok(()).
983#[inline]
984fn try_decrement_cell_limit(val: &mut Option<NonZeroU32>) -> StdResult<(), ()> {
985 // This is a bit verbose, but I've confirmed that it optimizes nicely.
986 match val {
987 Some(x) => {
988 let z = u32::from(*x);
989 if z == 1 {
990 Err(())
991 } else {
992 *x = (z - 1).try_into().expect("NonZeroU32 was zero?!");
993 Ok(())
994 }
995 }
996 None => Ok(()),
997 }
998}
999
1000/// Convert a limit from the form used in a HopSettings to that used here.
1001/// (The format we use here is more compact.)
1002fn cvt(limit: u32) -> NonZeroU32 {
1003 // See "known limitations" comment on n_incoming_cells_permitted.
1004 limit
1005 .saturating_add(1)
1006 .try_into()
1007 .expect("Adding one left it as zero?")
1008}
1009
1010/// A collection of components that can be used to interact with the reactor's view of a Tor stream.
1011//
1012// TODO: We also have a `StreamComponents` type that is used and built outside of the reactor.
1013// It's maybe confusing to have these similar type names, so a better name would be nice.
1014//
1015// TODO(arti#2068): The components we return should maybe depend on what type of flow control is
1016// used, so in the future we might want to make some of these fields optional.
1017#[derive(Debug, Deftly)]
1018#[derive_deftly(HasMemoryCost)]
1019pub(crate) struct ReactorStreamComponents {
1020 /// An MPSC receiver for inbound messages that arrive on the stream.
1021 #[deftly(has_memory_cost(indirect_size = "0"))] // estimate
1022 pub(crate) stream_inbound_rx: StreamQueueReceiver,
1023
1024 /// An MPSC sender for outbound messages to be sent on the stream.
1025 #[deftly(has_memory_cost(indirect_size = "size_of::<AnyRelayMsg>()"))] // estimate
1026 pub(crate) stream_outbound_tx: StreamMpscSender<AnyRelayMsg>,
1027
1028 /// A mechanism to allow the stream's writer to receive rate limit updates from the reactor.
1029 // The `watch::Sender` owns the indirect data.
1030 #[deftly(has_memory_cost(indirect_size = "0"))]
1031 pub(crate) rate_limit_rx: watch::Receiver<StreamRateLimit>,
1032
1033 /// A mechanism to allow the stream's reader to receive drain rate update requests from the
1034 /// reactor.
1035 #[deftly(has_memory_cost(indirect_size = "0"))]
1036 pub(crate) drain_rate_request_rx: NotifyReceiver<DrainRateRequest>,
1037}