Skip to main content

tor_proto/relay/channel/
create_handler.rs

1//! Handler for CREATE* cells.
2
3use crate::FlowCtrlParameters;
4use crate::ccparams::{
5    AlgorithmDiscriminants, CongestionWindowParams, FixedWindowParams, RoundTripEstimatorParams,
6    VegasParams,
7};
8use crate::channel::Channel;
9use crate::circuit::celltypes::{CreateRequest, CreateResponse};
10use crate::circuit::circhop::{HandshakeParamsError, HopSettings};
11use crate::circuit::{CircuitRxSender, HandshakeSubprotocols, UniqId};
12use crate::client::circuit::padding::PaddingController;
13use crate::crypto::binding::CircuitBinding;
14use crate::crypto::cell::CryptInit as _;
15use crate::crypto::cell::{InboundRelayLayer, OutboundRelayLayer, RelayLayer, tor1};
16use crate::crypto::handshake::RelayHandshakeError;
17use crate::crypto::handshake::ServerHandshake as _;
18use crate::crypto::handshake::fast::CreateFastServer;
19use crate::crypto::handshake::ntor::{NtorSecretKey, NtorServer};
20use crate::memquota::SpecificAccount as _;
21use crate::memquota::{ChannelAccount, CircuitAccount};
22use crate::relay::channel_provider::ChannelProvider;
23use crate::relay::reactor::Reactor;
24use crate::relay::{IncomingStreamRequestFilter, RelayCirc};
25use crate::stream::IncomingStream;
26use futures::channel::mpsc;
27use futures::{SinkExt, Stream};
28use smallvec::SmallVec;
29use std::sync::{Arc, RwLock, Weak};
30use tor_cell::chancell::ChanMsg as _;
31use tor_cell::chancell::CircId;
32use tor_cell::chancell::msg::{
33    CreateFast, Created2, CreatedFast, Destroy, DestroyReason, HandshakeType,
34};
35use tor_cell::relaycell::RelayCmd;
36use tor_error::{ErrorKind, HasKind, debug_report, internal, into_internal, warn_report};
37use tor_linkspec::OwnedChanTarget;
38use tor_llcrypto::cipher::aes::Aes128Ctr;
39use tor_llcrypto::d::Sha1;
40use tor_llcrypto::pk::ed25519::Ed25519Identity;
41use tor_llcrypto::pk::rsa::RsaIdentity;
42use tor_memquota::mq_queue::ChannelSpec as _;
43use tor_memquota::mq_queue::MpscSpec;
44use tor_relay_crypto::pk::{RelayNtorKeypair, RelayNtorKeys};
45use tor_rtcompat::SpawnExt as _;
46use tor_rtcompat::{DynTimeProvider, Runtime};
47use tracing::trace;
48
49/// Everything needed to handle CREATE* messages on channels.
50#[derive(derive_more::Debug)]
51pub struct CreateRequestHandler {
52    /// Something that can launch channels. Typically the `ChanMgr`.
53    chan_provider: Weak<dyn ChannelProvider<BuildSpec = OwnedChanTarget> + Send + Sync>,
54    /// Circuit-related network parameters.
55    circ_net_params: RwLock<CircNetParameters>,
56    /// The circuit extension keys.
57    #[debug(skip)]
58    ntor_keys: RwLock<RelayNtorKeys>,
59    /// An [`IncomingStreamRequestFilter`] factory for checking whether the user wants
60    /// this request, or wants to reject it immediately.
61    ///
62    /// Used for obtaining a current [`IncomingStreamRequestFilter`]
63    /// for building a circuit reactor.
64    //
65    // TODO(relay): it's likely this will end up changing quite a bit once we start
66    // figuring out exactly how the config/reconfigure() logic and IncomingStreamRequestFilter
67    // should function for relays.
68    #[debug(skip)]
69    incoming_filter_factory: Box<dyn IncomingStreamRequestFilterFactory + Send + Sync>,
70    /// The allowed incoming stream commands.
71    ///
72    /// Used for rejecting BEGIN and RESOLVE if we are not configured to be an exit.
73    ///
74    // TODO(relay): we might use this for rejecting BEGIN_DIR too,
75    // if we decide to allow relays to opt out of being dir mirrors.
76    // See https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/4107/diffs#note_3426447
77    allowed_stream_cmds: SmallVec<[RelayCmd; 3]>,
78    /// A sender for the [`Stream`]s of `IncomingStream` of all circuits.
79    ///
80    /// The receiver will receive one [`Stream`] (of tor streams) per circuit.
81    ///
82    /// This being a bounded MPSC might seem a bit risky, because in theory,
83    /// if the receiver is not reading fast enough, sending will block.
84    /// In practice, however, it should never block (or buffer very much at all,
85    /// for that matter), because the user (arti-relay) is expected to read from
86    /// this in a tight loop, and spawn a task for handling each [`Stream`].
87    ///
88    /// Note: because this MPSC is not associated with any particular circuit or channel,
89    /// it does not participate in the memquota system (see [crate::memquota]).
90    #[debug(skip)]
91    circuit_stream_tx: mpsc::Sender<Box<dyn Stream<Item = IncomingStream> + Send + Sync + Unpin>>,
92}
93
94impl CreateRequestHandler {
95    /// Build a new [`CreateRequestHandler`], and a [`CircuitIncomingStreamReceiver`]
96    /// for receiving new streams that are opened on any incoming circuits.
97    pub fn new(
98        chan_provider: Weak<dyn ChannelProvider<BuildSpec = OwnedChanTarget> + Send + Sync>,
99        circ_net_params: CircNetParameters,
100        ntor_keys: RelayNtorKeys,
101        incoming_filter_factory: Box<dyn IncomingStreamRequestFilterFactory + Send + Sync>,
102        allowed_stream_cmds: &[RelayCmd],
103    ) -> (Self, CircuitIncomingStreamReceiver) {
104        // TODO(relay-tuning): this MPSC can be a bottleneck,
105        // as all the channels on this relay will want to send one item on it
106        // each time a new circuit is created.
107        //
108        // The value set here is a guesstimate.
109        const CIRC_STREAM_BUF_SIZE: usize = 1024;
110
111        // This is not associated with any particular circuit
112        // (it is for *all* circuits), so it doesn't participate in memquota
113        // (see circuit_stream_tx docs)
114        #[allow(clippy::disallowed_methods)]
115        let (stream_tx, stream_rx) = mpsc::channel(CIRC_STREAM_BUF_SIZE);
116
117        let handler = Self {
118            chan_provider,
119            circ_net_params: RwLock::new(circ_net_params),
120            ntor_keys: RwLock::new(ntor_keys),
121            incoming_filter_factory,
122            allowed_stream_cmds: allowed_stream_cmds.into(),
123            circuit_stream_tx: stream_tx,
124        };
125
126        let circuit_stream_rx = CircuitIncomingStreamReceiver {
127            circuit_stream_rx: stream_rx,
128        };
129
130        (handler, circuit_stream_rx)
131    }
132
133    /// Update the circuit parameters from a network consensus.
134    pub fn update_params(&self, circ_net_params: CircNetParameters) {
135        *self.circ_net_params.write().expect("rwlock poisoned") = circ_net_params;
136    }
137
138    /// Update the handler with a new set of circuit extension keys.
139    ///
140    /// This is called periodically by the relay key rotation task.
141    pub fn update_ntor_keys(&self, ntor_keys: RelayNtorKeys) {
142        *self.ntor_keys.write().expect("rwlock poisoned") = ntor_keys;
143    }
144
145    /// Handle a CREATE* cell.
146    ///
147    /// This intentionally does not return a [`crate::Error`] so that we don't accidentally shut
148    /// down the channel reactor when we really should be returning a DESTROY. Shutting down a
149    /// channel may cause us to leak information about paths of circuits travelling through this
150    /// relay. This is especially important here since we're handling data that is controllable from
151    /// the other end of the circuit.
152    #[allow(clippy::too_many_arguments)]
153    pub(crate) fn handle_create<R: Runtime>(
154        &self,
155        runtime: &R,
156        channel: &Arc<Channel>,
157        our_ed25519_id: &Ed25519Identity,
158        our_rsa_id: &RsaIdentity,
159        circ_id: CircId,
160        msg: &CreateRequest,
161        memquota: &ChannelAccount,
162        circ_unique_id: UniqId,
163    ) -> Result<(CreateResponse, RelayCircComponents), Destroy> {
164        let result = self.handle_create_inner(
165            runtime,
166            channel,
167            our_ed25519_id,
168            our_rsa_id,
169            circ_id,
170            msg,
171            memquota,
172            circ_unique_id,
173        );
174
175        match result {
176            Ok(x) => Ok(x),
177            Err(e) => {
178                // TODO(relay): The log messages throughout could be very noisy, so should have rate limiting.
179                let cmd = msg.cmd();
180                debug_report!(&e, %cmd, "Failed to handle circuit create request");
181
182                // `tor-spec/tearing-down-circuits.md`:
183                //
184                // > Implementations SHOULD always use the NONE reason to avoid side channels: [...]
185                Err(Destroy::new(DestroyReason::NONE))
186            }
187        }
188    }
189
190    /// See [`Self::handle_create`].
191    #[allow(clippy::too_many_arguments)]
192    fn handle_create_inner<R: Runtime>(
193        &self,
194        runtime: &R,
195        channel: &Arc<Channel>,
196        our_ed25519_id: &Ed25519Identity,
197        our_rsa_id: &RsaIdentity,
198        circ_id: CircId,
199        msg: &CreateRequest,
200        memquota: &ChannelAccount,
201        circ_unique_id: UniqId,
202    ) -> Result<(CreateResponse, RelayCircComponents), HandleCreateError> {
203        // Perform the handshake crypto and build the response.
204        let handshake_components = match msg {
205            CreateRequest::CreateFast(msg) => self.handle_create_fast(msg)?,
206            CreateRequest::Create2(msg) => match msg.handshake_type() {
207                HandshakeType::NTOR_V3 => self.handle_create2_ntorv3(msg.body(), our_ed25519_id)?,
208                HandshakeType::NTOR => self.handle_create2_ntor(msg.body(), our_rsa_id)?,
209                x @ HandshakeType::TAP | x => {
210                    return Err(HandleCreateError::Create2HandshakeType(x));
211                }
212            },
213        };
214
215        let memquota = CircuitAccount::new(memquota)?;
216
217        // We use a large mpsc queue here since a circuit should never block the channel,
218        // and we hope that memquota will help us if an attacker intentionally fills this buffer.
219        // We use `10_000_000` since `usize::MAX` causes `futures::channel::mpsc` to panic.
220        // TODO(relay): We should switch to an unbounded queue, but the circuit reactor is expecting
221        // a bounded queue.
222        let time_provider = DynTimeProvider::new(runtime.clone());
223        let account = memquota.as_raw_account();
224        let (sender, receiver) =
225            MpscSpec::new(10_000_000).new_mq(time_provider.clone(), account)?;
226        let (sender, receiver) = crate::circuit::circ_sender::channel(sender, receiver);
227
228        // TODO(relay): Do we really want a client padding machine here?
229        let (padding_ctrl, padding_stream) =
230            crate::client::circuit::padding::new_padding(DynTimeProvider::new(runtime.clone()));
231
232        // Upgrade the channel provider, which in practice is the `ChanMgr` so this should not fail.
233        let Some(chan_provider) = self.chan_provider.upgrade() else {
234            return Err(internal!("Unable to upgrade weak `ChannelProvider`").into());
235        };
236
237        // Create an IncomingStreamRequestFilter for this circuit.
238        // This will get applied to every stream request (BEGIN, BEGIN_DIR, RESOLVE)
239        // arriving on the circuit.
240        //
241        // Note: once built, a circuit reactor's IncomingStreamRequestFilter cannot be changed
242        // (it's fixed for the entire duration of the circuit).
243        let incoming_filter = self.incoming_filter_factory.current_filter();
244
245        // Build the relay circuit reactor.
246        let (reactor, circ, incoming_streams) = Reactor::new(
247            runtime.clone(),
248            channel,
249            circ_id,
250            circ_unique_id,
251            receiver,
252            handshake_components.crypto_in,
253            handshake_components.crypto_out,
254            &handshake_components.hop_settings,
255            chan_provider,
256            padding_ctrl.clone(),
257            padding_stream,
258            incoming_filter,
259            &self.allowed_stream_cmds,
260            &memquota,
261        )
262        .map_err(into_internal!("Failed to start circuit reactor"))?;
263
264        let mut circuit_stream_tx = self.circuit_stream_tx.clone();
265        // Start the reactor in a task.
266        let () = runtime.spawn(async move {
267            if let Err(e) = circuit_stream_tx.send(Box::new(incoming_streams)).await {
268                warn_report!(e, "IncomingStream handler disappeared?!");
269                // If we get here, it means the relay stream handler task has gone away,
270                // so there won't be anything handling the incoming streams.
271                //
272                // The reactor is dropped, making the RelayCirc returned below
273                // in the RelayCircComponents unusable
274                // (RelayCirc::is_closing() will return `true`).
275                drop(reactor);
276            } else {
277                // Only spawn the circuit reactor if the incoming stream handler was
278                // able to receive our message
279                match reactor.run().await {
280                    Ok(()) => {}
281                    Err(e) => {
282                        debug_report!(e, "Relay circuit reactor exited with an error");
283                    }
284                }
285            }
286        })?;
287
288        Ok((
289            handshake_components.response,
290            RelayCircComponents {
291                circ,
292                sender,
293                padding_ctrl,
294            },
295        ))
296    }
297
298    /// The handshake code for a CREATE_FAST request.
299    fn handle_create_fast(
300        &self,
301        msg: &CreateFast,
302    ) -> Result<CompletedHandshakeComponents, HandleCreateError> {
303        // TODO(relay): We might want to offload this to a CPU worker in the future.
304        let (keygen, handshake_msg) = CreateFastServer::server(
305            &mut rand::rng(),
306            // The CREATE_FAST handshake doesn't accept or return extensions,
307            // so this `AuxDataReply` is a no-op.
308            &mut |_: &()| Some(()),
309            // The CREATE_FAST handshake doesn't use any keys.
310            &[()],
311            msg.handshake(),
312        )?;
313
314        let circ_net_params = self
315            .circ_net_params
316            .read()
317            .expect("rwlock poisoned")
318            .clone();
319
320        // No subprotocols are requested during a CREATE_FAST handshake.
321        let subprotos = HandshakeSubprotocols::default();
322
323        let hop_settings = HopSettings::from_handshake_params(
324            circ_net_params,
325            // CREATE_FAST always uses fixed-window flow control.
326            AlgorithmDiscriminants::FixedWindow,
327            subprotos,
328        )?;
329
330        let crypt = tor1::CryptStatePair::<Aes128Ctr, Sha1>::construct(keygen)
331            .map_err(into_internal!("Circuit crypt state construction failed"))?;
332
333        let (crypto_out, crypto_in, _binding) = split_relay_layer(crypt);
334
335        let response = CreatedFast::new(handshake_msg);
336        let response = CreateResponse::CreatedFast(response);
337
338        trace!("Completed CREATE_FAST handshake");
339
340        Ok(CompletedHandshakeComponents {
341            response,
342            hop_settings,
343            crypto_out,
344            crypto_in,
345        })
346    }
347
348    /// The handshake code for a CREATE2 ntor (non-v3) request.
349    fn handle_create2_ntor(
350        &self,
351        msg_body: &[u8],
352        our_rsa_id: &RsaIdentity,
353    ) -> Result<CompletedHandshakeComponents, HandleCreateError> {
354        let ntor_keys = self.ntor_keys(|k| {
355            NtorSecretKey::new(k.secret().clone(), *k.public().inner(), *our_rsa_id)
356        });
357
358        // TODO(relay): We might want to offload this to a CPU worker in the future.
359        let (keygen, handshake_msg) = NtorServer::server(
360            &mut rand::rng(),
361            // The ntor (non-v3) handshake doesn't accept or return extensions,
362            // so this `AuxDataReply` is a no-op.
363            &mut |_: &()| Some(()),
364            ntor_keys.as_ref(),
365            msg_body,
366        )?;
367
368        let circ_net_params = self
369            .circ_net_params
370            .read()
371            .expect("rwlock poisoned")
372            .clone();
373
374        // No subprotocols are requested during an ntor (non-v3) handshake.
375        let subprotos = HandshakeSubprotocols::default();
376
377        let hop_settings = HopSettings::from_handshake_params(
378            circ_net_params,
379            // CREATE2 with ntor (non-v3) always uses fixed-window flow control.
380            AlgorithmDiscriminants::FixedWindow,
381            subprotos,
382        )?;
383
384        let crypt = tor1::CryptStatePair::<Aes128Ctr, Sha1>::construct(keygen)
385            .map_err(into_internal!("Circuit crypt state construction failed"))?;
386
387        let (crypto_out, crypto_in, _binding) = split_relay_layer(crypt);
388
389        let response = Created2::new(handshake_msg);
390        let response = CreateResponse::Created2(response);
391
392        trace!("Completed ntor handshake");
393
394        Ok(CompletedHandshakeComponents {
395            response,
396            hop_settings,
397            crypto_out,
398            crypto_in,
399        })
400    }
401
402    /// The handshake code for a CREATE2 ntor-v3 request.
403    fn handle_create2_ntorv3(
404        &self,
405        _msg_body: &[u8],
406        _our_ed25519_id: &Ed25519Identity,
407    ) -> Result<CompletedHandshakeComponents, HandleCreateError> {
408        Err(HandleCreateError::Create2HandshakeType(
409            HandshakeType::NTOR_V3,
410        ))
411    }
412
413    /// Helper to get the ntor keypairs after some transformation `map`.
414    ///
415    /// The `map` transformation must be fast since it blocks a read lock.
416    /// The returned keys are sorted with the most recent key first.
417    ///
418    /// It would be nice if this just returned an iterator,
419    /// but the read lock prevents this.
420    fn ntor_keys<T>(&self, map: impl FnMut(&RelayNtorKeypair) -> T) -> impl AsRef<[T]> {
421        let ntor_keys = self.ntor_keys.read().expect("rwlock poisoned");
422        let ntor_keys = [Some(ntor_keys.latest()), ntor_keys.previous()];
423        ntor_keys
424            .into_iter()
425            .flatten()
426            .map(map)
427            .collect::<SmallVec<[T; 2]>>()
428    }
429}
430
431/// A receiver of [`Stream`]s (one for each incoming circuit),
432/// where each `Stream` produces [`IncomingStream`]s for that circuit.
433///
434// Note: in theory, it would be nice if we could get rid of this type altogether.
435// In an ideal world, I would've instead
436//
437//   * added a `RelayCirc::take_incoming_streams()` method for obtaining
438//     the futures::Stream of IncomingStream of that circuit
439//   * made the CreateRequestHandler send each Arc<RelayCirc> over to arti-relay for handling
440//   * made arti-relay obtain the futures::Stream<Item = IncomingStream> of each RelayCirc
441//     by calling `RelayCirc::take_incoming_streams()`
442//
443// However, that would involve adding some locking/interior mutability within RelayCirc
444// (which is always behind an Arc), or extending mq_queue::Receiver to be Clone,
445// which would be tricky to pull off (see the comment on mq_queue::Receiver about this).
446pub struct CircuitIncomingStreamReceiver {
447    /// The receiver for the [`Stream`]s of `IncomingStream` of all circuits.
448    ///
449    /// Receives one [`Stream`] (of tor streams) per circuit.
450    /// Each of these will be handled in a new task.
451    circuit_stream_rx: mpsc::Receiver<<Self as Stream>::Item>,
452}
453
454impl Stream for CircuitIncomingStreamReceiver {
455    // TODO: it would be nice if we could return a type-erased Stream here
456    // (impl Stream<...>), but impl Trait in associated types is unstable.
457    // See rust issue #63063 <https://github.com/rust-lang/rust/issues/63063>
458    type Item = Box<dyn Stream<Item = IncomingStream> + Send + Sync + Unpin>;
459
460    fn poll_next(
461        mut self: std::pin::Pin<&mut Self>,
462        cx: &mut std::task::Context<'_>,
463    ) -> std::task::Poll<Option<Self::Item>> {
464        use futures::StreamExt as _;
465
466        self.circuit_stream_rx.poll_next_unpin(cx)
467    }
468}
469
470/// Helper function to split a `RelayLayer` into forward and backward type-erased trait objects.
471fn split_relay_layer<F, B>(
472    crypt: impl RelayLayer<F, B>,
473) -> (
474    Box<dyn OutboundRelayLayer + Send>,
475    Box<dyn InboundRelayLayer + Send>,
476    CircuitBinding,
477)
478where
479    F: OutboundRelayLayer + Send + 'static,
480    B: InboundRelayLayer + Send + 'static,
481{
482    let (crypto_out, crypto_in, binding) = crypt.split_relay_layer();
483    let (crypto_out, crypto_in) = (Box::new(crypto_out), Box::new(crypto_in));
484
485    (crypto_out, crypto_in, binding)
486}
487
488/// An error that occurred while handling a CREATE* request.
489#[derive(Debug, thiserror::Error)]
490enum HandleCreateError {
491    /// Circuit relay handshake failed.
492    #[error("Circuit relay handshake failed")]
493    Handshake(#[from] RelayHandshakeError),
494    /// Circuit relay handshake failed.
495    #[error("Failed to process the circuit relay handshake parameters")]
496    HandshakeParameters(#[from] HandshakeParamsError),
497    /// The requested handshake type is unsupported.
498    #[error("Unsupported handshake type {0}")]
499    Create2HandshakeType(HandshakeType),
500    /// A memquota error.
501    #[error("Memquota error")]
502    Memquota(#[from] tor_memquota::Error),
503    /// Error when spawning a task.
504    #[error("Runtime task spawn error")]
505    Spawn(#[from] futures::task::SpawnError),
506    /// An internal error.
507    ///
508    /// Note that other variants (such as `Handshake` containing a [`RelayHandshakeError`])
509    /// may themselves contain internal errors.
510    #[error("Internal error")]
511    Internal(#[from] tor_error::Bug),
512}
513
514impl HasKind for HandleCreateError {
515    fn kind(&self) -> ErrorKind {
516        match self {
517            Self::Handshake(e) => e.kind(),
518            Self::HandshakeParameters(e) => e.kind(),
519            Self::Create2HandshakeType(_) => ErrorKind::NotImplemented,
520            Self::Memquota(e) => e.kind(),
521            Self::Spawn(e) => e.kind(),
522            Self::Internal(_) => ErrorKind::Internal,
523        }
524    }
525}
526
527/// The components of a completed CREATE* handshake.
528struct CompletedHandshakeComponents {
529    /// The message to send in response.
530    response: CreateResponse,
531    /// The negotiated hop settings.
532    hop_settings: HopSettings,
533    /// Outbound onion crypto.
534    crypto_out: Box<dyn OutboundRelayLayer + Send>,
535    /// Inbound onion crypto.
536    crypto_in: Box<dyn InboundRelayLayer + Send>,
537}
538
539/// A collection of objects built for a new relay circuit.
540pub(crate) struct RelayCircComponents {
541    /// The relay circuit handle.
542    pub(crate) circ: Arc<RelayCirc>,
543    /// Used to send data from the channel to the circuit reactor.
544    pub(crate) sender: CircuitRxSender,
545    /// The circuit's padding controller.
546    pub(crate) padding_ctrl: PaddingController,
547}
548
549/// Congestion control network parameters.
550#[derive(Debug, Clone)]
551#[allow(clippy::exhaustive_structs)]
552pub struct CongestionControlNetParams {
553    /// Fixed-window algorithm parameters.
554    pub fixed_window: FixedWindowParams,
555
556    /// Vegas algorithm parameters for exit circuits.
557    // NOTE: In this module we are handling CREATE* cells,
558    // which only happens for non-hs circuits.
559    // So we don't need to store the vegas hs parameters here.
560    pub vegas_exit: VegasParams,
561
562    /// Congestion window parameters.
563    pub cwnd: CongestionWindowParams,
564
565    /// RTT calculation parameters.
566    pub rtt: RoundTripEstimatorParams,
567
568    /// Flow control parameters to use for all streams on this circuit.
569    pub flow_ctrl: FlowCtrlParameters,
570}
571
572impl CongestionControlNetParams {
573    #[cfg(test)]
574    // These have been copied from C-tor.
575    pub(crate) fn defaults_for_tests() -> Self {
576        Self {
577            fixed_window: FixedWindowParams::defaults_for_tests(),
578            vegas_exit: VegasParams::defaults_for_tests(),
579            cwnd: CongestionWindowParams::defaults_for_tests(),
580            rtt: RoundTripEstimatorParams::defaults_for_tests(),
581            flow_ctrl: FlowCtrlParameters::defaults_for_tests(),
582        }
583    }
584}
585
586/// Network consensus parameters for handling incoming circuits.
587///
588/// Unlike `CircParameters`,
589/// this is unopinionated and contains all relevant consensus parameters,
590/// which is needed when handling an incoming CREATE* request where the
591/// circuit origin chooses the type/settings
592/// (for example congestion control type) of the circuit.
593#[derive(Debug, Clone)]
594#[allow(clippy::exhaustive_structs)]
595pub struct CircNetParameters {
596    /// Congestion control network parameters.
597    pub cc: CongestionControlNetParams,
598}
599
600/// An [`IncomingStreamRequestFilter`] factory for building [`IncomingStreamRequestFilter`]s.
601///
602/// Each time a new circuit is opened, the [`CreateRequestHandler`] calls
603/// [`IncomingStreamRequestFilterFactory::current_filter`] to build
604/// an [`IncomingStreamRequestFilter`] for the circuit.
605pub trait IncomingStreamRequestFilterFactory {
606    /// Return the [`IncomingStreamRequestFilter`] to apply to the incoming stream requests
607    /// arriving on a circuit.
608    fn current_filter(&self) -> Box<dyn IncomingStreamRequestFilter>;
609}
610
611impl<F> IncomingStreamRequestFilterFactory for F
612where
613    F: Fn() -> Box<dyn IncomingStreamRequestFilter>,
614{
615    fn current_filter(&self) -> Box<dyn IncomingStreamRequestFilter> {
616        (self)()
617    }
618}
619
620#[cfg(test)]
621mod test {
622    // @@ begin test lint list maintained by maint/add_warning @@
623    #![allow(clippy::bool_assert_comparison)]
624    #![allow(clippy::clone_on_copy)]
625    #![allow(clippy::dbg_macro)]
626    #![allow(clippy::mixed_attributes_style)]
627    #![allow(clippy::print_stderr)]
628    #![allow(clippy::print_stdout)]
629    #![allow(clippy::single_char_pattern)]
630    #![allow(clippy::unwrap_used)]
631    #![allow(clippy::unchecked_time_subtraction)]
632    #![allow(clippy::useless_vec)]
633    #![allow(clippy::needless_pass_by_value)]
634    #![allow(clippy::string_slice)] // See arti#2571
635    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
636
637    use tor_cell::chancell::{ChanCmd, ChanMsg as _};
638    use tor_rtcompat::test_with_one_runtime;
639
640    use crate::channel::test_utils;
641    use crate::circuit::CircParameters;
642
643    #[test]
644    fn create_fast() {
645        test_with_one_runtime!(|rt| async move {
646            let mut conn_inspector = test_utils::ConnInspector::new();
647
648            let (client_chan, _relay_chan, _circuit_stream_rx, _target_builder) =
649                test_utils::new_channel_pair_with_keys(&rt, &conn_inspector);
650
651            let pending_tunnel = test_utils::new_pending_tunnel(&rt, &client_chan).await;
652
653            let circ_params = CircParameters::default();
654
655            let tunnel = pending_tunnel
656                .create_firsthop_fast(circ_params)
657                .await
658                .unwrap();
659
660            assert_eq!(
661                conn_inspector.try_client_cell().unwrap().msg().cmd(),
662                ChanCmd::CREATE_FAST,
663            );
664            assert_eq!(
665                conn_inspector.try_relay_cell().unwrap().msg().cmd(),
666                ChanCmd::CREATED_FAST,
667            );
668
669            drop(tunnel);
670
671            assert_eq!(
672                conn_inspector.client_cell().await.unwrap().msg().cmd(),
673                ChanCmd::DESTROY,
674            );
675            // TODO(relay): I think the relay shouldn't be sending a DESTROY back to the client.
676            // https://gitlab.torproject.org/tpo/core/arti/-/work_items/2648
677            assert_eq!(
678                conn_inspector.relay_cell().await.unwrap().msg().cmd(),
679                ChanCmd::DESTROY,
680            );
681        });
682    }
683
684    #[test]
685    fn tap() {
686        test_with_one_runtime!(|rt| async move {
687            let mut conn_inspector = test_utils::ConnInspector::new();
688
689            let (client_chan, _relay_chan, _circuit_stream_rx, mut target_builder) =
690                test_utils::new_channel_pair_with_keys(&rt, &conn_inspector);
691
692            let pending_tunnel = test_utils::new_pending_tunnel(&rt, &client_chan).await;
693
694            let circ_params = CircParameters::default();
695
696            // https://spec.torproject.org/tor-spec/subprotocol-versioning.html
697            // 1 = RELAY_BASE
698            let protocols = "Relay=1".parse().unwrap();
699            let target = target_builder.protocols(protocols).build().unwrap();
700
701            // TODO: This should fail since we don't support TAP handshakes.
702            // But the channel will do an ntor handshake anyway even though it's not supported.
703            // https://gitlab.torproject.org/tpo/core/arti/-/work_items/2489
704            let _tunnel = pending_tunnel
705                .create_firsthop(&target, circ_params)
706                .await
707                .unwrap();
708
709            // TODO: As above, this is wrong.
710            assert_eq!(
711                conn_inspector.try_client_cell().unwrap().msg().cmd(),
712                ChanCmd::CREATE2,
713            );
714            assert_eq!(
715                conn_inspector.try_relay_cell().unwrap().msg().cmd(),
716                ChanCmd::CREATED2,
717            );
718        });
719    }
720
721    #[test]
722    fn ntor() {
723        test_with_one_runtime!(|rt| async move {
724            let mut conn_inspector = test_utils::ConnInspector::new();
725
726            let (client_chan, _relay_chan, _circuit_stream_rx, mut target_builder) =
727                test_utils::new_channel_pair_with_keys(&rt, &conn_inspector);
728
729            // https://spec.torproject.org/tor-spec/subprotocol-versioning.html
730            // 2 = RELAY_NTOR
731            // 3 = RELAY_EXTEND_IPv6
732            for relay_version in [2, 3] {
733                let pending_tunnel = test_utils::new_pending_tunnel(&rt, &client_chan).await;
734
735                let circ_params = CircParameters::default();
736
737                let protocols = format!("Relay=2-{relay_version}").parse().unwrap();
738                let target = target_builder.protocols(protocols).build().unwrap();
739
740                let tunnel = pending_tunnel
741                    .create_firsthop(&target, circ_params)
742                    .await
743                    .unwrap();
744
745                assert_eq!(
746                    conn_inspector.try_client_cell().unwrap().msg().cmd(),
747                    ChanCmd::CREATE2,
748                );
749                assert_eq!(
750                    conn_inspector.try_relay_cell().unwrap().msg().cmd(),
751                    ChanCmd::CREATED2,
752                );
753
754                drop(tunnel);
755
756                assert_eq!(
757                    conn_inspector.client_cell().await.unwrap().msg().cmd(),
758                    ChanCmd::DESTROY,
759                );
760                // TODO(relay): I think the relay shouldn't be sending a DESTROY back to the client.
761                // https://gitlab.torproject.org/tpo/core/arti/-/work_items/2648
762                assert_eq!(
763                    conn_inspector.relay_cell().await.unwrap().msg().cmd(),
764                    ChanCmd::DESTROY,
765                );
766            }
767        });
768    }
769
770    // TODO(relay): Test ntor-v3 handshake once implemented.
771}