//! Code to handle incoming cells on a circuit. //! //! ## On message validation //! //! There are three steps for validating an incoming message on a stream: //! //! 1. Is the message contextually appropriate? (e.g., no more than one //! `CONNECTED` message per stream.) This is handled by calling //! [`CmdChecker::check_msg`](crate::stream::CmdChecker::check_msg). //! 2. Does the message comply with flow-control rules? (e.g., no more data than //! we've gotten SENDMEs for.) For open streams, the stream itself handles //! this; for half-closed streams, the reactor handles it using the //! `halfstream` module. //! 3. Does the message have an acceptable command type, and is the message //! well-formed? For open streams, the streams themselves handle this check. //! For half-closed streams, the reactor handles it by calling //! `consume_checked_msg()`. pub(super) mod circuit; mod conflux; mod control; pub(super) mod syncview; use crate::crypto::cell::HopNum; use crate::crypto::handshake::ntor_v3::NtorV3PublicKey; use crate::memquota::{CircuitAccount, StreamAccount}; use crate::stream::AnyCmdChecker; #[cfg(feature = "hs-service")] use crate::stream::{IncomingStreamRequest, IncomingStreamRequestFilter}; use crate::tunnel::circuit::celltypes::ClientCircChanMsg; use crate::tunnel::circuit::unique_id::UniqId; use crate::tunnel::circuit::CircuitRxReceiver; use crate::tunnel::circuit::MutableState; use crate::tunnel::{streammap, HopLocation, TargetHop}; use crate::util::err::ReactorError; use crate::util::skew::ClockSkew; use crate::{Error, Result}; use circuit::{Circuit, CircuitCmd}; use conflux::ConfluxSet; use control::ControlHandler; use std::mem::size_of; use tor_cell::relaycell::msg::{AnyRelayMsg, End, Sendme}; use tor_cell::relaycell::{AnyRelayMsgOuter, RelayCellFormat, StreamId, UnparsedRelayMsg}; use tor_error::{bad_api_usage, internal, into_bad_api_usage, Bug}; use futures::channel::mpsc; use futures::StreamExt; use futures::{select_biased, FutureExt as _}; use oneshot_fused_workaround as oneshot; use std::result::Result as StdResult; use std::sync::{Arc, Mutex}; use crate::channel::Channel; use crate::crypto::handshake::ntor::{NtorClient, NtorPublicKey}; use crate::tunnel::circuit::{StreamMpscReceiver, StreamMpscSender}; use derive_deftly::Deftly; use derive_more::From; use tor_cell::chancell::CircId; use tor_llcrypto::pk; use tor_memquota::derive_deftly_template_HasMemoryCost; use tor_memquota::mq_queue::{self, MpscSpec}; use tracing::trace; pub(super) use control::CtrlCmd; pub(super) use control::CtrlMsg; /// The type of a oneshot channel used to inform reactor users of the result of an operation. pub(super) type ReactorResultChannel = oneshot::Sender>; pub(crate) use circuit::{RECV_WINDOW_INIT, STREAM_READER_BUFFER}; /// MPSC queue containing stream requests #[cfg(feature = "hs-service")] type StreamReqSender = mq_queue::Sender; /// A handshake type, to be used when creating circuit hops. #[derive(Clone, Debug)] pub(crate) enum CircuitHandshake { /// Use the CREATE_FAST handshake. CreateFast, /// Use the ntor handshake. Ntor { /// The public key of the relay. public_key: NtorPublicKey, /// The Ed25519 identity of the relay, which is verified against the /// identity held in the circuit's channel. ed_identity: pk::ed25519::Ed25519Identity, }, /// Use the ntor-v3 handshake. NtorV3 { /// The public key of the relay. public_key: NtorV3PublicKey, }, } /// A behavior to perform when closing a stream. /// /// We don't use `Option`` here, since the behavior of `SendNothing` is so surprising /// that we shouldn't let it pass unremarked. #[derive(Clone, Debug)] pub(crate) enum CloseStreamBehavior { /// Send nothing at all, so that the other side will not realize we have /// closed the stream. /// /// We should only do this for incoming onion service streams when we /// want to black-hole the client's requests. SendNothing, /// Send an End cell, if we haven't already sent one. SendEnd(End), } impl Default for CloseStreamBehavior { fn default() -> Self { Self::SendEnd(End::new_misc()) } } // TODO(conflux): the RunOnceCmd/RunOnceCmdInner/CircuitCmd/CircuitAction enum // proliferation is a bit bothersome, but unavoidable with the current design. // // We should consider getting rid of some of these enums (if possible), // and coming up with more intuitive names. /// One or more [`RunOnceCmdInner`] to run inside [`Reactor::run_once`]. #[derive(From, Debug)] enum RunOnceCmd { /// Run a single `RunOnceCmdInner` command. Single(RunOnceCmdInner), /// Run multiple `RunOnceCmdInner` commands. // // Note: this whole enum *could* be replaced with Vec, // but most of the time we're only going to have *one* RunOnceCmdInner // to run per run_once() loop. The enum enables us avoid the extra heap // allocation for the `RunOnceCmd::Single` case. Multiple(Vec), } /// Instructions for running something in the reactor loop. /// /// Run at the end of [`Reactor::run_once`]. // // TODO: many of the variants of this enum have an identical CtrlMsg counterpart. // We should consider making each variant a tuple variant and deduplicating the fields. #[derive(educe::Educe)] #[educe(Debug)] enum RunOnceCmdInner { /// Send a RELAY cell. Send { /// The cell to send. cell: SendRelayCell, /// A channel for sending completion notifications. done: Option>, }, /// Send a given control message on this circuit, and install a control-message handler to /// receive responses. #[cfg(feature = "send-control-msg")] SendMsgAndInstallHandler { /// The message to send, if any msg: Option, /// A message handler to install. /// /// If this is `None`, there must already be a message handler installed #[educe(Debug(ignore))] handler: Option>, /// A sender that we use to tell the caller that the message was sent /// and the handler installed. done: oneshot::Sender>, }, /// Handle a SENDME message. HandleSendMe { /// The hop number. hop: HopNum, /// The SENDME message to handle. sendme: Sendme, }, /// Begin a stream with the provided hop in this circuit. /// /// Uses the provided stream ID, and sends the provided message to that hop. BeginStream { /// The cell to send. cell: Result<(SendRelayCell, StreamId)>, /// The location of the hop on the tunnel. We don't use this (and `Circuit`s shouldn't need /// to worry about legs anyways), but need it so that we can pass it back in `done` to the /// caller. hop: HopLocation, /// Oneshot channel to notify on completion, with the allocated stream ID. done: ReactorResultChannel<(StreamId, HopLocation, RelayCellFormat)>, }, /// Close the specified stream. CloseStream { /// The hop number. hop: HopLocation, /// The ID of the stream to close. sid: StreamId, /// The stream-closing behavior. behav: CloseStreamBehavior, /// The reason for closing the stream. reason: streammap::TerminateReason, /// A channel for sending completion notifications. done: Option>, }, /// Get the clock skew claimed by the first hop of the circuit. FirstHopClockSkew { /// Oneshot channel to return the clock skew. answer: oneshot::Sender>, }, /// Perform a clean shutdown on this circuit. CleanShutdown, } impl RunOnceCmdInner { /// Create a [`RunOnceCmdInner`] out of a [`CircuitCmd`] and [`LegIdKey`]. fn from_circuit_cmd(leg: LegIdKey, cmd: CircuitCmd) -> Self { match cmd { CircuitCmd::Send(cell) => { // TODO(conflux): add leg ID to Send Self::Send { cell, done: None } } CircuitCmd::HandleSendMe { hop, sendme } => { // TODO(conflux): add leg to HandleSendMe Self::HandleSendMe { hop, sendme } } CircuitCmd::CloseStream { hop, sid, behav, reason, } => Self::CloseStream { hop: HopLocation::Hop((LegId(leg), hop)), sid, behav, reason, done: None, }, CircuitCmd::CleanShutdown => Self::CleanShutdown, } } } /// Cmd for sending a relay cell. /// /// The contents of this struct are passed to `send_relay_cell` #[derive(educe::Educe)] #[educe(Debug)] pub(crate) struct SendRelayCell { /// The hop number. pub(crate) hop: HopNum, /// Whether to use a RELAY_EARLY cell. pub(crate) early: bool, /// The cell to send. pub(crate) cell: AnyRelayMsgOuter, } /// A command to execute at the end of [`Reactor::run_once`]. #[derive(From, Debug)] enum CircuitAction { /// Run a single `CircuitCmd` command. RunCmd { /// The unique identifier of the circuit leg to run the command on leg: LegIdKey, /// The command to run. cmd: CircuitCmd, }, /// Handle a control message HandleControl(CtrlMsg), /// Handle an input message. HandleCell { /// The unique identifier of the circuit leg the message was received on. leg: LegIdKey, /// The message to handle. cell: ClientCircChanMsg, }, /// Remove the specified circuit leg. RemoveLeg(LegIdKey), } /// An object that's waiting for a meta cell (one not associated with a stream) in order to make /// progress. /// /// # Background /// /// The `Reactor` can't have async functions that send and receive cells, because its job is to /// send and receive cells: if one of its functions tried to do that, it would just hang forever. /// /// To get around this problem, the reactor can send some cells, and then make one of these /// `MetaCellHandler` objects, which will be run when the reply arrives. pub(crate) trait MetaCellHandler: Send { /// The hop we're expecting the message to come from. This is compared against the hop /// from which we actually receive messages, and an error is thrown if the two don't match. fn expected_hop(&self) -> HopNum; /// Called when the message we were waiting for arrives. /// /// Gets a copy of the `Reactor` in order to do anything it likes there. /// /// If this function returns an error, the reactor will shut down. fn handle_msg( &mut self, msg: UnparsedRelayMsg, reactor: &mut Circuit, ) -> Result; } /// A possible successful outcome of giving a message to a [`MsgHandler`](super::msghandler::MsgHandler). #[derive(Debug, Clone)] #[cfg_attr(feature = "send-control-msg", visibility::make(pub))] #[non_exhaustive] pub(crate) enum MetaCellDisposition { /// The message was consumed; the handler should remain installed. #[cfg(feature = "send-control-msg")] Consumed, /// The message was consumed; the handler should be uninstalled. ConversationFinished, /// The message was consumed; the circuit should be closed. #[cfg(feature = "send-control-msg")] CloseCirc, // TODO: Eventually we might want the ability to have multiple handlers // installed, and to let them say "not for me, maybe for somebody else?". // But right now we don't need that. } /// A unique identifier for a circuit leg. /// /// After the circuit is torn down, its `LegId` becomes invalid. /// The same `LegId` won't be reused for a future circuit. // // TODO(#1857): make this pub #[allow(unused)] #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub(crate) struct LegId(pub(crate) LegIdKey); slotmap_careful::new_key_type! { /// A key type for the circuit leg slotmap /// /// See [`LegId`]. pub(crate) struct LegIdKey; } /// Unwrap the specified [`Option`], returning a [`ReactorError::Shutdown`] if it is `None`. /// /// This is a macro instead of a function to work around borrowck errors /// in the select! from run_once(). macro_rules! unwrap_or_shutdown { ($self:expr, $res:expr, $reason:expr) => {{ match $res { None => { trace!("{}: reactor shutdown due to {}", $self.unique_id, $reason); Err(ReactorError::Shutdown) } Some(v) => Ok(v), } }}; } /// Object to handle incoming cells and background tasks on a circuit /// /// This type is returned when you finish a circuit; you need to spawn a /// new task that calls `run()` on it. #[must_use = "If you don't call run() on a reactor, the circuit won't work."] pub struct Reactor { /// Receiver for control messages for this reactor, sent by `ClientCirc` objects. /// /// This channel is polled in [`Reactor::run_once`], but only if the `chan_sender` sink /// is ready to accept cells. control: mpsc::UnboundedReceiver, /// Receiver for command messages for this reactor, sent by `ClientCirc` objects. /// /// This channel is polled in [`Reactor::run_once`]. /// /// NOTE: this is a separate channel from `control`, because some messages /// have higher priority and need to be handled even if the `chan_sender` is not /// ready (whereas `control` messages are not read until the `chan_sender` sink /// is ready to accept cells). command: mpsc::UnboundedReceiver, /// A oneshot sender that is used to alert other tasks when this reactor is /// finally dropped. /// /// It is a sender for Void because we never actually want to send anything here; /// we only want to generate canceled events. #[allow(dead_code)] // the only purpose of this field is to be dropped. reactor_closed_tx: oneshot::Sender, /// A set of circuits that form a tunnel. /// /// Contains 1 or more circuits. /// /// Circuits may be added to this set throughout the lifetime of the reactor. // // TODO(conflux): add a control command for adding a circuit leg, // and update these docs to explain how legs are added /// /// Sometimes, the reactor will remove circuits from this set, /// for example if the `LINKED` message takes too long to arrive, /// or if congestion control negotiation fails. /// The reactor will continue running with the remaining circuits. /// It will shut down if *all* the circuits are removed. /// // TODO(conflux): document all the reasons why the reactor might // chose to tear down a circuit or tunnel (timeouts, protocol violations, etc.) circuits: ConfluxSet, /// An identifier for logging about this reactor's circuit. unique_id: UniqId, /// Handlers, shared with `Circuit`. cell_handlers: CellHandlers, } /// Cell handlers, shared between the Reactor and its underlying `Circuit`s. struct CellHandlers { /// A handler for a meta cell, together with a result channel to notify on completion. meta_handler: Option>, /// A handler for incoming stream requests. #[cfg(feature = "hs-service")] incoming_stream_req_handler: Option, } /// Information about an incoming stream request. #[cfg(feature = "hs-service")] #[derive(Debug, Deftly)] #[derive_deftly(HasMemoryCost)] pub(crate) struct StreamReqInfo { /// The [`IncomingStreamRequest`]. pub(crate) req: IncomingStreamRequest, /// The ID of the stream being requested. pub(crate) stream_id: StreamId, /// The [`HopNum`]. // // TODO: When we add support for exit relays, we need to turn this into an Option. // (For outbound messages (towards relays), there is only one hop that can send them: the client.) // // TODO: For onion services, we might be able to enforce the HopNum earlier: we would never accept an // incoming stream request from two separate hops. (There is only one that's valid.) pub(crate) hop_num: HopNum, /// The format which must be used with this stream to encode messages. #[deftly(has_memory_cost(indirect_size = "0"))] pub(crate) relay_cell_format: RelayCellFormat, /// A channel for receiving messages from this stream. #[deftly(has_memory_cost(indirect_size = "0"))] // estimate pub(crate) receiver: StreamMpscReceiver, /// A channel for sending messages to be sent on this stream. #[deftly(has_memory_cost(indirect_size = "size_of::()"))] // estimate pub(crate) msg_tx: StreamMpscSender, /// The memory quota account to be used for this stream #[deftly(has_memory_cost(indirect_size = "0"))] // estimate (it contains an Arc) pub(crate) memquota: StreamAccount, } /// Data required for handling an incoming stream request. #[cfg(feature = "hs-service")] #[derive(educe::Educe)] #[educe(Debug)] struct IncomingStreamRequestHandler { /// A sender for sharing information about an incoming stream request. incoming_sender: StreamReqSender, /// A [`AnyCmdChecker`] for validating incoming stream requests. cmd_checker: AnyCmdChecker, /// The hop to expect incoming stream requests from. hop_num: HopNum, /// An [`IncomingStreamRequestFilter`] for checking whether the user wants /// this request, or wants to reject it immediately. #[educe(Debug(ignore))] filter: Box, } impl Reactor { /// Create a new circuit reactor. /// /// The reactor will send outbound messages on `channel`, receive incoming /// messages on `input`, and identify this circuit by the channel-local /// [`CircId`] provided. /// /// The internal unique identifier for this circuit will be `unique_id`. #[allow(clippy::type_complexity)] // TODO pub(super) fn new( channel: Arc, channel_id: CircId, unique_id: UniqId, input: CircuitRxReceiver, memquota: CircuitAccount, ) -> ( Self, mpsc::UnboundedSender, mpsc::UnboundedSender, oneshot::Receiver, Arc>, ) { let (control_tx, control_rx) = mpsc::unbounded(); let (command_tx, command_rx) = mpsc::unbounded(); let mutable = Arc::new(Mutex::new(MutableState::default())); let (reactor_closed_tx, reactor_closed_rx) = oneshot::channel(); let cell_handlers = CellHandlers { meta_handler: None, #[cfg(feature = "hs-service")] incoming_stream_req_handler: None, }; let circuit_leg = Circuit::new( channel, channel_id, unique_id, input, memquota, Arc::clone(&mutable), ); let reactor = Reactor { circuits: ConfluxSet::new(circuit_leg), control: control_rx, command: command_rx, reactor_closed_tx, unique_id, cell_handlers, }; (reactor, control_tx, command_tx, reactor_closed_rx, mutable) } /// Launch the reactor, and run until the circuit closes or we /// encounter an error. /// /// Once this method returns, the circuit is dead and cannot be /// used again. pub async fn run(mut self) -> Result<()> { trace!("{}: Running circuit reactor", self.unique_id); let result: Result<()> = loop { match self.run_once().await { Ok(()) => (), Err(ReactorError::Shutdown) => break Ok(()), Err(ReactorError::Err(e)) => break Err(e), } }; trace!("{}: Circuit reactor stopped: {:?}", self.unique_id, result); result } /// Helper for run: doesn't mark the circuit closed on finish. Only /// processes one cell or control message. async fn run_once(&mut self) -> StdResult<(), ReactorError> { // If all the circuits are closed, shut down the reactor // // TODO(conflux): we might need to rethink this behavior if self.circuits.is_empty() { trace!( "{}: Circuit reactor shutting down: all circuits have closed", self.unique_id ); return Err(ReactorError::Shutdown); } // If this is a single path circuit, we need to wait until the first hop // is created before doing anything else let single_path_with_hops = self .circuits .single_leg_mut() .is_ok_and(|(_id, leg)| !leg.has_hops()); if single_path_with_hops { self.wait_for_create().await?; return Ok(()); } // TODO(conflux): support adding and linking circuits // TODO(conflux): support switching the primary leg let action = select_biased! { res = self.command.next() => { let cmd = unwrap_or_shutdown!(self, res, "command channel drop")?; return ControlHandler::new(self).handle_cmd(cmd); }, // Check whether we've got a control message pending. // // Note: unfortunately, reading from control here means we might start // handling control messages before our chan_senders are ready. // With the current design, this is inevitable: we can't know which circuit leg // a control message is meant for without first reading the control message from // the channel, and at that point, we can't know for sure whether that particular // circuit is ready for sending. ret = self.control.next() => { let msg = unwrap_or_shutdown!(self, ret, "control drop")?; CircuitAction::HandleControl(msg) }, res = self.circuits.next_circ_action().fuse() => res?, }; let cmd = match action { CircuitAction::RunCmd { leg, cmd } => Some(RunOnceCmd::Single( RunOnceCmdInner::from_circuit_cmd(leg, cmd), )), CircuitAction::HandleControl(ctrl) => ControlHandler::new(self) .handle_msg(ctrl)? .map(RunOnceCmd::Single), CircuitAction::HandleCell { leg, cell } => { let circ = self .circuits .leg_mut(LegId(leg)) .ok_or_else(|| internal!("the circuit leg we just had disappeared?!"))?; let circ_cmds = circ.handle_cell(&mut self.cell_handlers, cell)?; if circ_cmds.is_empty() { None } else { // TODO(conflux): we return RunOnceCmd::Multiple even if there's a single command. // // See the TODO(conflux) on `Circuit::handle_cell`. let cmd = RunOnceCmd::Multiple( circ_cmds .into_iter() .map(|cmd| RunOnceCmdInner::from_circuit_cmd(leg, cmd)) .collect(), ); Some(cmd) } } CircuitAction::RemoveLeg(leg_id) => { self.circuits.remove(leg_id)?; None } }; if let Some(cmd) = cmd { self.handle_run_once_cmd(cmd).await?; } Ok(()) } /// Handle a [`RunOnceCmd`]. async fn handle_run_once_cmd(&mut self, cmd: RunOnceCmd) -> StdResult<(), ReactorError> { match cmd { RunOnceCmd::Single(cmd) => return self.handle_single_run_once_cmd(cmd).await, RunOnceCmd::Multiple(cmds) => { // While we know `sendable` is ready to accept *one* cell, // we can't be certain it will be able to accept *all* of the cells // that need to be sent here. This means we *may* end up buffering // in its underlying SometimesUnboundedSink! That is OK, because // RunOnceCmd::Multiple is only used for handling packed cells. for cmd in cmds { self.handle_single_run_once_cmd(cmd).await?; } } } Ok(()) } /// Handle a [`RunOnceCmd`]. async fn handle_single_run_once_cmd( &mut self, cmd: RunOnceCmdInner, ) -> StdResult<(), ReactorError> { match cmd { RunOnceCmdInner::Send { cell, done } => { // TODO: check the cc window // TODO(conflux): let the RunOnceCmdInner specify which leg to send the cell on let res = self.circuits.primary_leg_mut()?.send_relay_cell(cell).await; if let Some(done) = done { // Don't care if the receiver goes away let _ = done.send(res.clone()); } res?; } #[cfg(feature = "send-control-msg")] RunOnceCmdInner::SendMsgAndInstallHandler { msg, handler, done } => { let cell: Result> = self.prepare_msg_and_install_handler(msg, handler); match cell { Ok(Some(cell)) => { // TODO(conflux): let the RunOnceCmdInner specify which leg to send the cell on let outcome = self.circuits.primary_leg_mut()?.send_relay_cell(cell).await; // don't care if receiver goes away. let _ = done.send(outcome.clone()); outcome?; } Ok(None) => { // don't care if receiver goes away. let _ = done.send(Ok(())); } Err(e) => { // don't care if receiver goes away. let _ = done.send(Err(e.clone())); return Err(e.into()); } } } // TODO(conflux)/TODO(#1857): should this take a leg_id argument? // Currently, we always begin streams on the primary leg RunOnceCmdInner::BeginStream { cell, hop, done } => { match cell { Ok((cell, stream_id)) => { // TODO(conflux): let the RunOnceCmdInner specify which leg to send the cell on // (currently it is an error to use BeginStream on a multipath tunnel) let (_id, leg) = self.circuits.single_leg_mut()?; let cell_hop = cell.hop; let relay_format = leg .hop_mut(cell_hop) // TODO: Is this the right error type here? Or should there be a "HopDisappeared"? .ok_or(Error::NoSuchHop)? .relay_cell_format(); let outcome = leg.send_relay_cell(cell).await; // don't care if receiver goes away. let _ = done.send(outcome.clone().map(|_| (stream_id, hop, relay_format))); outcome?; } Err(e) => { // don't care if receiver goes away. let _ = done.send(Err(e.clone())); return Err(e.into()); } } } RunOnceCmdInner::CloseStream { hop, sid, behav, reason, done, } => { let result = (move || { // this is needed to force the closure to be FnOnce rather than FnMut :( let self_ = self; let (leg_id, hop_num) = self_ .resolve_hop_location(hop) .map_err(into_bad_api_usage!("Could not resolve {hop:?}"))?; let leg = self_ .circuits .leg_mut(leg_id) .ok_or(bad_api_usage!("No leg for id {:?}", leg_id))?; Ok::<_, Bug>((leg, hop_num)) })(); let (leg, hop_num) = match result { Ok(x) => x, Err(e) => { if let Some(done) = done { // don't care if the sender goes away let e = into_bad_api_usage!("Could not resolve {hop:?}")(e); let _ = done.send(Err(e.into())); } return Ok(()); } }; let res: Result<()> = leg.close_stream(hop_num, sid, behav, reason).await; if let Some(done) = done { // don't care if the sender goes away let _ = done.send(res); } } RunOnceCmdInner::HandleSendMe { hop, sendme } => { // TODO(conflux): this should specify which leg of the circuit the SENDME // came on let (_id, leg) = self.circuits.single_leg_mut()?; // NOTE: it's okay to await. We are only awaiting on the congestion_signals // future which *should* resolve immediately let signals = leg.congestion_signals().await; leg.handle_sendme(hop, sendme, signals)?; } RunOnceCmdInner::FirstHopClockSkew { answer } => { let res = self .circuits .single_leg_mut() .map(|(_id, leg)| leg.clock_skew()); // don't care if the sender goes away let _ = answer.send(res.map_err(Into::into)); } RunOnceCmdInner::CleanShutdown => { trace!("{}: reactor shutdown due to handled cell", self.unique_id); return Err(ReactorError::Shutdown); } } Ok(()) } /// Wait for a [`CtrlMsg::Create`] to come along to set up the circuit. /// /// Returns an error if an unexpected `CtrlMsg` is received. async fn wait_for_create(&mut self) -> StdResult<(), ReactorError> { let msg = select_biased! { res = self.command.next() => { let cmd = unwrap_or_shutdown!(self, res, "shutdown channel drop")?; match cmd { CtrlCmd::Shutdown => return self.handle_shutdown().map(|_| ()), #[cfg(test)] CtrlCmd::AddFakeHop { relay_cell_format: format, fwd_lasthop, rev_lasthop, params, done, } => { let (_id, leg) = self.circuits.single_leg_mut()?; leg.handle_add_fake_hop(format, fwd_lasthop, rev_lasthop, ¶ms, done); return Ok(()) }, _ => { trace!("reactor shutdown due to unexpected command: {:?}", cmd); return Err(Error::CircProto(format!("Unexpected control {cmd:?} on client circuit")).into()); } } }, res = self.control.next() => unwrap_or_shutdown!(self, res, "control drop")?, }; match msg { CtrlMsg::Create { recv_created, handshake, mut params, done, } => { // TODO(conflux): instead of crashing the reactor, it might be better // to send the error via the done channel instead let (_id, leg) = self.circuits.single_leg_mut()?; leg.handle_create(recv_created, handshake, &mut params, done) .await } _ => { trace!("reactor shutdown due to unexpected cell: {:?}", msg); Err(Error::CircProto(format!("Unexpected {msg:?} cell on client circuit")).into()) } } } /// Prepare a `SendRelayCell` request, and install the given meta-cell handler. fn prepare_msg_and_install_handler( &mut self, msg: Option, handler: Option>, ) -> Result> { let msg = msg .map(|msg| { let handlers = &mut self.cell_handlers; let handler = handler .as_ref() .or(handlers.meta_handler.as_ref()) .ok_or_else(|| internal!("tried to use an ended Conversation"))?; Ok::<_, crate::Error>(SendRelayCell { hop: handler.expected_hop(), early: false, cell: msg, }) }) .transpose()?; if let Some(handler) = handler { self.cell_handlers.set_meta_handler(handler)?; } Ok(msg) } /// Handle a shutdown request. fn handle_shutdown(&self) -> StdResult, ReactorError> { trace!( "{}: reactor shutdown due to explicit request", self.unique_id ); Err(ReactorError::Shutdown) } /// Handle a request to shutdown the reactor and return the only [`Circuit`] in this tunnel. /// /// Returns an error over the `answer` channel if the reactor has no circuits, /// or more than one circuit. The reactor will shut down regardless. #[cfg(feature = "conflux")] fn handle_shutdown_and_return_circuit( &mut self, answer: oneshot::Sender>, ) -> StdResult<(), ReactorError> { // Don't care if the receiver goes away let _ = answer.send(self.circuits.take_single_leg().map_err(Into::into)); self.handle_shutdown().map(|_| ()) } /// Resolves a [`TargetHop`] to a [`HopLocation`]. /// /// After resolving a `TargetHop::LastHop`, /// the `HopLocation` can become stale if a single-path circuit is later extended or truncated. /// This means that the `HopLocation` can become stale from one reactor iteration to the next. /// /// It's generally okay to hold on to a (possibly stale) `HopLocation` /// if you need a fixed hop position in the tunnel. /// For example if we open a stream to `TargetHop::LastHop`, /// we would want to store the stream position as a `HopLocation` and not a `TargetHop::LastHop` /// as we don't want the stream position to change as the tunnel is extended or truncated. /// /// Returns [`NoHopsBuiltError`] if trying to resolve `TargetHop::LastHop` /// and the tunnel has no hops /// (either has no legs, or has legs which contain no hops). fn resolve_target_hop(&self, hop: TargetHop) -> StdResult { match hop { TargetHop::Hop(hop) => Ok(hop), TargetHop::LastHop => { if let Ok((leg_id, leg)) = self.circuits.single_leg() { // single-path tunnel let num_hops = leg.num_hops(); if num_hops == 0 { // asked for the last hop, but there are no hops return Err(NoHopsBuiltError); } let hop = HopNum::from(num_hops - 1); Ok(HopLocation::Hop((leg_id, hop))) } else if !self.circuits.is_empty() { // multi-path tunnel return Ok(HopLocation::JoinPoint); } else { // no legs Err(NoHopsBuiltError) } } } } /// Resolves a [`HopLocation`] to a [`LegId`] and [`HopNum`]. /// /// After resolving a `HopLocation::JoinPoint`, /// the [`LegId`] and [`HopNum`] can become stale if the primary leg changes. /// /// You should try to only resolve to a specific [`LegId`] and [`HopNum`] immediately before you /// need them, /// and you should not hold on to the resolved [`LegId`] and [`HopNum`] between reactor /// iterations as the primary leg may change from one iteration to the next. /// /// Returns [`NoJoinPointError`] if trying to resolve `HopLocation::JoinPoint` /// but it does not have a join point. fn resolve_hop_location( &self, hop: HopLocation, ) -> StdResult<(LegId, HopNum), NoJoinPointError> { match hop { HopLocation::Hop((leg_id, hop_num)) => Ok((leg_id, hop_num)), HopLocation::JoinPoint => { if let Some((leg_id, hop_num)) = self.circuits.primary_join_point() { Ok((leg_id, hop_num)) } else { // Attempted to get the join point of a non-multipath tunnel. Err(NoJoinPointError) } } } } /// Does congestion control use stream SENDMEs for the given hop? /// /// Returns `None` if either the `leg` or `hop` don't exist. fn uses_stream_sendme(&self, leg: LegId, hop: HopNum) -> Option { self.circuits.uses_stream_sendme(leg, hop) } } /// The tunnel does not have any hops. #[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)] #[non_exhaustive] #[error("no hops have been built for this tunnel")] pub(crate) struct NoHopsBuiltError; /// The tunnel does not have a join point. #[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)] #[non_exhaustive] #[error("the tunnel does not have a join point")] pub(crate) struct NoJoinPointError; impl CellHandlers { /// Try to install a given meta-cell handler to receive any unusual cells on /// this circuit, along with a result channel to notify on completion. fn set_meta_handler(&mut self, handler: Box) -> Result<()> { if self.meta_handler.is_none() { self.meta_handler = Some(handler); Ok(()) } else { Err(Error::from(internal!( "Tried to install a meta-cell handler before the old one was gone." ))) } } /// Try to install a given cell handler on this circuit. #[cfg(feature = "hs-service")] fn set_incoming_stream_req_handler( &mut self, handler: IncomingStreamRequestHandler, ) -> Result<()> { if self.incoming_stream_req_handler.is_none() { self.incoming_stream_req_handler = Some(handler); Ok(()) } else { Err(Error::from(internal!( "Tried to install a BEGIN cell handler before the old one was gone." ))) } } } #[cfg(test)] mod test { // Tested in [`crate::tunnel::circuit::test`]. }