Skip to main content

tor_proto/circuit/reactor/
hop_mgr.rs

1//! Channel for sending messages to [`StreamReactor`].
2
3use crate::circuit::UniqId;
4use crate::circuit::circhop::{CircHopOutbound, HopSettings};
5use crate::circuit::reactor::circhop::CircHopList;
6use crate::circuit::reactor::stream::{CtrlMsg, ReadyStreamMsg, StreamHandler, StreamReactor};
7use crate::congestion::CongestionControl;
8use crate::memquota::CircuitAccount;
9use crate::util::err::ReactorError;
10use crate::{Error, HopNum, Result};
11
12#[cfg(any(feature = "hs-service", feature = "relay"))]
13use {
14    crate::stream::CloseStreamBehavior, crate::stream::incoming::IncomingStreamRequestHandler,
15    tor_cell::relaycell::StreamId,
16};
17
18use tor_cell::chancell::CircId;
19use tor_error::internal;
20use tor_rtcompat::Runtime;
21
22use futures::SinkExt;
23use futures::channel::mpsc;
24
25use std::result::Result as StdResult;
26use std::sync::{Arc, Mutex, RwLock};
27
28/// The hop manager of a reactor.
29///
30/// This contains the per-hop state (e.g. congestion control information),
31/// and a handle to the stream reactor of the hop.
32///
33/// The stream reactor of the hop is launched lazily,
34/// when the first [`CtrlMsg`] is sent via [`HopMgr::send`].
35pub(crate) struct HopMgr<R: Runtime> {
36    /// A handle to the runtime.
37    runtime: R,
38    /// Context used when spawning a stream reactor.
39    ctx: StreamReactorContext,
40    /// Sender for sending messages to BWD.
41    ///
42    /// The receiver is in BWD.
43    ///
44    /// A clone of this is passed to each spawned StreamReactor
45    bwd_tx: mpsc::Sender<ReadyStreamMsg>,
46    /// The underlying senders, indexed by [`HopNum`].
47    ///
48    /// Relays have at most one stream reactor per circuit.
49    /// Clients have at most one stream reactor per circuit hop.
50    ///
51    /// This is shared with the backward reactor.
52    /// The backward reactor only ever *reads* from this
53    /// (it never mutates the list).
54    ///
55    // TODO: the backward reactor only ever reads from this.
56    // Conceptually, it is the HopMgr that owns this list,
57    // because only HopMgr can add hops to the list.
58    //
59    // Perhaps we need a specialized abstraction that only allows reading here.
60    // This could be a wrapper over RwLock, providing a read-only API for the BWD.
61    hops: Arc<RwLock<CircHopList>>,
62    /// Memory quota account
63    memquota: CircuitAccount,
64}
65
66/// State needed to build a stream reactor.
67///
68/// Used when spawning the stream reactor of a hop.
69struct StreamReactorContext {
70    /// An identifier for logging about this reactor's circuit.
71    unique_id: UniqId,
72    /// The circuit identifier on the inbound Tor channel.
73    circ_id: CircId,
74    /// The incoming stream handler.
75    ///
76    /// This is shared with every StreamReactor.
77    #[cfg(any(feature = "hs-service", feature = "relay"))]
78    incoming: Arc<Mutex<Option<IncomingStreamRequestHandler>>>,
79    /// A handler for customizing the stream reactor behavior.
80    handler: Arc<dyn StreamHandler>,
81}
82
83impl<R: Runtime> HopMgr<R> {
84    /// Create a new [`HopMgr`] with an empty hop list,
85    /// settings the incoming stream request handler to `incoming_handler`.
86    ///
87    /// Hops are added with [`HopMgr::add_hop`].
88    #[cfg(feature = "relay")]
89    pub(crate) fn new_with_incoming_handler<S: StreamHandler>(
90        runtime: R,
91        unique_id: UniqId,
92        circ_id: CircId,
93        handler: S,
94        bwd_tx: mpsc::Sender<ReadyStreamMsg>,
95        incoming_handler: IncomingStreamRequestHandler,
96        memquota: CircuitAccount,
97    ) -> Self {
98        Self::new_inner(
99            runtime,
100            unique_id,
101            circ_id,
102            handler,
103            bwd_tx,
104            Some(incoming_handler),
105            memquota,
106        )
107    }
108
109    /// Create a new [`HopMgr`] with an empty hop list.
110    ///
111    /// Hops are added with [`HopMgr::add_hop`].
112    #[expect(unused)] // TODO(dedup): clients will use this
113    pub(crate) fn new<S: StreamHandler>(
114        runtime: R,
115        unique_id: UniqId,
116        circ_id: CircId,
117        handler: S,
118        bwd_tx: mpsc::Sender<ReadyStreamMsg>,
119        memquota: CircuitAccount,
120    ) -> Self {
121        Self::new_inner(
122            runtime,
123            unique_id,
124            circ_id,
125            handler,
126            bwd_tx,
127            #[cfg(any(feature = "hs-service", feature = "relay"))]
128            None,
129            memquota,
130        )
131    }
132
133    /// Helper for the new*() functions.
134    fn new_inner<S: StreamHandler>(
135        runtime: R,
136        unique_id: UniqId,
137        circ_id: CircId,
138        handler: S,
139        bwd_tx: mpsc::Sender<ReadyStreamMsg>,
140        #[cfg(any(feature = "hs-service", feature = "relay"))] incoming_handler: Option<
141            IncomingStreamRequestHandler,
142        >,
143        memquota: CircuitAccount,
144    ) -> Self {
145        // We don't spawn any stream reactors ahead of time.
146        // Instead we spawn them lazily, when opening streams.
147        let hops = Arc::new(RwLock::new(Default::default()));
148        let ctx = StreamReactorContext {
149            unique_id,
150            circ_id,
151            #[cfg(any(feature = "hs-service", feature = "relay"))]
152            incoming: Arc::new(Mutex::new(incoming_handler)),
153            handler: Arc::new(handler),
154        };
155
156        Self {
157            runtime,
158            hops,
159            ctx,
160            bwd_tx,
161            memquota,
162        }
163    }
164
165    /// Return a reference to our hop list.
166    pub(crate) fn hops(&self) -> &Arc<RwLock<CircHopList>> {
167        &self.hops
168    }
169
170    /// Set the incoming stream handler for this reactor.
171    ///
172    /// There can only be one incoming stream handler per reactor,
173    /// and each stream handler only pertains to a single hop (see expected_hop())
174    //
175    // TODO: eventually, we might want a different design here,
176    // for example we might want to allow multiple stream handlers per reactor (one per hop).
177    // However, for now, the implementation is intentionally kept similar to that
178    // in the client reactor (to make it easier to migrate it to the new reactor design).
179    //
180    /// Returns an error if the hop manager already has a stream handler.
181    ///
182    /// Since the handler is shared with every hop's stream reactor,
183    /// this function will update the handler for all of them.
184    ///
185    // TODO(DEDUP): almost identical to the client-side
186    // CellHandlers::set_incoming_stream_req_handler()
187    #[cfg(any(feature = "hs-service", feature = "relay"))]
188    pub(crate) fn set_incoming_handler(&self, handler: IncomingStreamRequestHandler) -> Result<()> {
189        let mut lock = self.ctx.incoming.lock().expect("poisoned lock");
190
191        if lock.is_none() {
192            *lock = Some(handler);
193            Ok(())
194        } else {
195            Err(Error::from(internal!(
196                "Tried to install a BEGIN cell handler before the old one was gone."
197            )))
198        }
199    }
200
201    /// Push a new hop to our hop list.
202    ///
203    /// Prepares a cc object for the hop, but does not spawn a stream reactor.
204    ///
205    /// Will return an error if the circuit already has [`u8::MAX`] hops.
206    pub(crate) fn add_hop(&mut self, settings: HopSettings) -> Result<()> {
207        let mut hops = self.hops.write().expect("poisoned lock");
208        hops.add_hop(settings)
209    }
210
211    /// Send a message to the stream reactor of the specified `hop`,
212    /// spawning it if necessary.
213    pub(crate) async fn send(
214        &mut self,
215        hopnum: Option<HopNum>,
216        msg: CtrlMsg,
217    ) -> StdResult<(), ReactorError> {
218        let mut tx = self.get_or_spawn_stream_reactor(hopnum)?;
219
220        tx.send(msg).await.map_err(|_| {
221            // The stream reactor has shut down
222            ReactorError::Shutdown
223        })
224    }
225
226    /// Tell the stream reactor of the specified `hop`
227    /// to close the stream with the specified `stream_id`.
228    #[cfg(any(feature = "hs-service", feature = "relay"))]
229    pub(crate) async fn close_pending(
230        &mut self,
231        hopnum: Option<HopNum>,
232        stream_id: StreamId,
233        behav: CloseStreamBehavior,
234    ) -> StdResult<(), Error> {
235        let mut tx = self.get_or_spawn_stream_reactor(hopnum)?;
236
237        let msg = CtrlMsg::ClosePendingStream { stream_id, behav };
238
239        tx.send(msg).await.map_err(|_| {
240            // The stream reactor has shut down
241            Error::NotConnected
242        })
243    }
244
245    /// Get a handle to the stream reactor, spawning it if necessary
246    fn get_or_spawn_stream_reactor(
247        &self,
248        hopnum: Option<HopNum>,
249    ) -> StdResult<mpsc::Sender<CtrlMsg>, Error> {
250        let mut hops = self.hops.write().expect("poisoned lock");
251        let hop = hops
252            .get_mut(hopnum)
253            .ok_or_else(|| internal!("tried to send cell to nonexistent hop?!"))?;
254
255        let tx = match &hop.tx {
256            Some(tx) => tx.clone(),
257            None => {
258                // If we don't have a handle to the stream reactor,
259                // it means it hasn't been spawned yet, so we have to spawn it now.
260                let tx =
261                    self.spawn_stream_reactor(hopnum, &hop.settings, Arc::clone(&hop.ccontrol))?;
262
263                hop.tx = Some(tx.clone());
264
265                // Return a copy of this sender (can't borrow because the hop
266                // is behind a Mutex, and we can't keep it locked across the send()
267                // await point)
268                tx
269            }
270        };
271
272        Ok(tx)
273    }
274
275    /// Spawn a [`StreamReactor`] for the specified hop.
276    fn spawn_stream_reactor(
277        &self,
278        hopnum: Option<HopNum>,
279        settings: &HopSettings,
280        ccontrol: Arc<Mutex<CongestionControl>>,
281    ) -> StdResult<mpsc::Sender<CtrlMsg>, Error> {
282        use tor_rtcompat::SpawnExt as _;
283
284        // NOTE: not registering this channel with the memquota subsystem is okay,
285        // because it has no buffering (if ever decide to make the size of this buffer
286        // non-zero for whatever reason, we must remember to register it with memquota
287        // so that it counts towards the total memory usage for the circuit.
288        //
289        // TODO(tuning): having zero buffering here is very likely suboptimal.
290        // We should do *some* buffering here, and then figure out if we should it
291        // up to memquota or not.
292        #[allow(clippy::disallowed_methods)]
293        let (fwd_stream_tx, fwd_stream_rx) = mpsc::channel(0);
294
295        let flow_ctrl_params = Arc::new(settings.flow_ctrl_params.clone());
296        let relay_format = settings.relay_crypt_protocol().relay_cell_format();
297        let outbound = CircHopOutbound::new(ccontrol, relay_format, flow_ctrl_params, settings);
298
299        let stream_reactor = StreamReactor::new(
300            self.runtime.clone(),
301            hopnum,
302            outbound,
303            self.ctx.unique_id,
304            self.ctx.circ_id,
305            fwd_stream_rx,
306            self.bwd_tx.clone(),
307            Arc::clone(&self.ctx.handler),
308            #[cfg(any(feature = "hs-service", feature = "relay"))]
309            Arc::clone(&self.ctx.incoming),
310            self.memquota.clone(),
311        );
312
313        self.runtime
314            .spawn(async {
315                let _ = stream_reactor.run().await;
316            })
317            .map_err(|e| Error::Spawn {
318                spawning: "stream reactor",
319                cause: e.into(),
320            })?;
321
322        Ok(fwd_stream_tx)
323    }
324}