tor_proto/tunnel/reactor/circuit/
extender.rs

1//! Module providing [`CircuitExtender`].
2
3use super::{Circuit, ReactorResultChannel};
4use crate::circuit::HopSettings;
5use crate::crypto::cell::{
6    ClientLayer, CryptInit, HopNum, InboundClientLayer, OutboundClientLayer,
7};
8use crate::crypto::handshake::fast::CreateFastClient;
9use crate::crypto::handshake::ntor_v3::NtorV3Client;
10use crate::tunnel::reactor::MetaCellDisposition;
11use crate::tunnel::TunnelScopedCircId;
12use crate::{congestion, HopLocation};
13use crate::{Error, Result};
14use oneshot_fused_workaround as oneshot;
15use std::borrow::Borrow;
16use std::marker::PhantomData;
17use tor_cell::chancell::msg::HandshakeType;
18use tor_cell::relaycell::msg::{Extend2, Extended2};
19use tor_cell::relaycell::{AnyRelayMsgOuter, UnparsedRelayMsg};
20use tor_error::internal;
21
22use crate::crypto::handshake::ntor::NtorClient;
23use crate::crypto::handshake::{ClientHandshake, KeyGenerator};
24use crate::tunnel::circuit::path;
25use crate::tunnel::reactor::{MetaCellHandler, SendRelayCell};
26use tor_cell::relaycell::extend::CircResponseExt;
27use tor_linkspec::{EncodedLinkSpec, OwnedChanTarget};
28use tracing::trace;
29
30/// An object that can extend a circuit by one hop, using the `MetaCellHandler` trait.
31///
32/// Yes, I know having trait bounds on structs is bad, but in this case it's necessary
33/// since we want to be able to use `H::KeyType`.
34pub(crate) struct CircuitExtender<H, L, FWD, REV>
35where
36    H: ClientHandshake,
37{
38    /// The peer that we're extending to.
39    ///
40    /// Used to extend our record of the circuit's path.
41    peer_id: OwnedChanTarget,
42    /// Handshake state.
43    state: Option<H::StateType>,
44    /// In-progress settings that we're negotiating for this hop.
45    settings: HopSettings,
46    /// An identifier for logging about this reactor's circuit.
47    unique_id: TunnelScopedCircId,
48    /// The hop we're expecting the EXTENDED2 cell to come back from.
49    expected_hop: HopNum,
50    /// A oneshot channel that we should inform when we are done with this extend operation.
51    operation_finished: Option<oneshot::Sender<Result<()>>>,
52    /// `PhantomData` used to make the other type parameters required for a circuit extension
53    /// part of the `struct`, instead of having them be provided during a function call.
54    ///
55    /// This is done this way so we can implement `MetaCellHandler` for this type, which
56    /// doesn't include any generic type parameters; we need them to be part of the type
57    /// so we know what they are for that `impl` block.
58    phantom: PhantomData<(L, FWD, REV)>,
59}
60impl<H, L, FWD, REV> CircuitExtender<H, L, FWD, REV>
61where
62    H: ClientHandshake + HandshakeAuxDataHandler,
63    H::KeyGen: KeyGenerator,
64    L: CryptInit + ClientLayer<FWD, REV>,
65    FWD: OutboundClientLayer + 'static + Send,
66    REV: InboundClientLayer + 'static + Send,
67{
68    /// Start extending a circuit, sending the necessary EXTEND cell and returning a
69    /// new `CircuitExtender` to be called when the reply arrives.
70    ///
71    /// The `handshake_id` is the numeric identifier for what kind of
72    /// handshake we're doing.  The `key` is the relay's onion key that
73    /// goes along with the handshake, and the `linkspecs` are the
74    /// link specifiers to include in the EXTEND cell to tell the
75    /// current last hop which relay to connect to.
76    #[allow(clippy::too_many_arguments)]
77    #[allow(clippy::blocks_in_conditions)]
78    pub(crate) fn begin(
79        peer_id: OwnedChanTarget,
80        handshake_id: HandshakeType,
81        key: &H::KeyType,
82        linkspecs: Vec<EncodedLinkSpec>,
83        settings: HopSettings,
84        client_aux_data: &impl Borrow<H::ClientAuxData>,
85        circ: &mut Circuit,
86        done: ReactorResultChannel<()>,
87    ) -> Result<(Self, SendRelayCell)> {
88        match (|| {
89            let mut rng = rand::rng();
90            let unique_id = circ.unique_id;
91
92            let (state, msg) = H::client1(&mut rng, key, client_aux_data)?;
93            let n_hops = circ.crypto_out.n_layers();
94            let hop = ((n_hops - 1) as u8).into();
95            trace!(
96                circ_id = %unique_id,
97                target_hop = n_hops + 1,
98                linkspecs = ?linkspecs,
99                "Extending circuit",
100            );
101            let extend_msg = Extend2::new(linkspecs, handshake_id, msg);
102            let cell = AnyRelayMsgOuter::new(None, extend_msg.into());
103            // Prepare a message to send message to the last hop...
104            let cell = SendRelayCell {
105                hop,
106                early: true, // use a RELAY_EARLY cel
107                cell,
108            };
109
110            trace!(circ_id = %unique_id, "waiting for EXTENDED2 cell");
111            // ... and now we wait for a response.
112            let extender = Self {
113                peer_id,
114                state: Some(state),
115                settings,
116                unique_id,
117                expected_hop: hop,
118                operation_finished: None,
119                phantom: Default::default(),
120            };
121
122            Ok::<(CircuitExtender<_, _, _, _>, SendRelayCell), Error>((extender, cell))
123        })() {
124            Ok(mut result) => {
125                result.0.operation_finished = Some(done);
126                Ok(result)
127            }
128            Err(e) => {
129                // It's okay if the receiver went away.
130                let _ = done.send(Err(e.clone()));
131                Err(e)
132            }
133        }
134    }
135
136    /// Perform the work of extending the circuit another hop.
137    ///
138    /// This is a separate function to simplify the error-handling work of handle_msg().
139    fn extend_circuit(
140        &mut self,
141        msg: UnparsedRelayMsg,
142        circ: &mut Circuit,
143    ) -> Result<MetaCellDisposition> {
144        let msg = msg
145            .decode::<Extended2>()
146            .map_err(|e| Error::from_bytes_err(e, "extended2 message"))?
147            .into_msg();
148
149        let relay_handshake = msg.into_body();
150
151        trace!(
152            circ_id = %self.unique_id,
153            "Received EXTENDED2 cell; completing handshake.",
154        );
155        // Now perform the second part of the handshake, and see if it
156        // succeeded.
157        let (server_aux_data, keygen) = H::client2(
158            self.state
159                .take()
160                .expect("CircuitExtender::finish() called twice"),
161            relay_handshake,
162        )?;
163
164        // Handle auxiliary data returned from the server, e.g. validating that
165        // requested extensions have been acknowledged.
166        H::handle_server_aux_data(&mut self.settings, &server_aux_data)?;
167
168        let layer = L::construct(keygen)?;
169
170        trace!(circ_id = %self.unique_id, "Handshake complete; circuit extended.");
171
172        // If we get here, it succeeded.  Add a new hop to the circuit.
173        let (layer_fwd, layer_back, binding) = layer.split_client_layer();
174        circ.add_hop(
175            path::HopDetail::Relay(self.peer_id.clone()),
176            Box::new(layer_fwd),
177            Box::new(layer_back),
178            Some(binding),
179            &self.settings,
180        )?;
181        Ok(MetaCellDisposition::ConversationFinished)
182    }
183}
184
185impl<H, L, FWD, REV> MetaCellHandler for CircuitExtender<H, L, FWD, REV>
186where
187    H: ClientHandshake + HandshakeAuxDataHandler,
188    H::StateType: Send,
189    H::KeyGen: KeyGenerator,
190    L: CryptInit + ClientLayer<FWD, REV> + Send,
191    FWD: OutboundClientLayer + 'static + Send,
192    REV: InboundClientLayer + 'static + Send,
193{
194    fn expected_hop(&self) -> HopLocation {
195        (self.unique_id.unique_id(), self.expected_hop).into()
196    }
197    fn handle_msg(
198        &mut self,
199        msg: UnparsedRelayMsg,
200        circ: &mut Circuit,
201    ) -> Result<MetaCellDisposition> {
202        let status = self.extend_circuit(msg, circ);
203
204        if let Some(done) = self.operation_finished.take() {
205            // ignore it if the receiving channel went away.
206            let _ = done.send(status.as_ref().map(|_| ()).map_err(Clone::clone));
207            status
208        } else {
209            Err(Error::from(internal!(
210                "Passed two messages to an CircuitExtender!"
211            )))
212        }
213    }
214}
215
216/// Specifies handling of auxiliary handshake data for a given `ClientHandshake`.
217//
218// For simplicity we implement this as a trait of the handshake object itself.
219// This is currently sufficient because
220//
221// 1. We only need or want one handler implementation for a given handshake type.
222// 2. We currently don't need to keep extra state; i.e. its method doesn't take
223//    &self.
224//
225// If we end up wanting to instantiate objects for one or both of the
226// `ClientHandshake` object or the `HandshakeAuxDataHandler` object, we could
227// decouple them by making this something like:
228//
229// ```
230// trait HandshakeAuxDataHandler<H> where H: ClientHandshake
231// ```
232pub(crate) trait HandshakeAuxDataHandler: ClientHandshake {
233    /// Handle auxiliary handshake data returned when creating or extending a
234    /// circuit.
235    fn handle_server_aux_data(
236        settings: &mut HopSettings,
237        data: &<Self as ClientHandshake>::ServerAuxData,
238    ) -> Result<()>;
239}
240
241impl HandshakeAuxDataHandler for NtorV3Client {
242    fn handle_server_aux_data(
243        settings: &mut HopSettings,
244        data: &Vec<CircResponseExt>,
245    ) -> Result<()> {
246        // Process all extensions.
247        // If "flowctl-cc" is not enabled, this loop will always return an error, so tell clippy
248        // that it's okay.
249        #[cfg_attr(not(feature = "flowctl-cc"), allow(clippy::never_loop))]
250        for ext in data {
251            match ext {
252                CircResponseExt::CcResponse(ack_ext) => {
253                    cfg_if::cfg_if! {
254                        if #[cfg(feature = "flowctl-cc")] {
255                            // Unexpected ACK extension as in if CC is disabled on our side, we would never have
256                            // requested it. Reject and circuit must be closed.
257                            if !settings.ccontrol.is_enabled() {
258                                return Err(Error::HandshakeProto(
259                                    "Received unexpected ntorv3 CC ack extension".into(),
260                                ));
261                            }
262                            let sendme_inc = ack_ext.sendme_inc();
263                            // Invalid increment, reject and circuit must be closed.
264                            if !congestion::params::is_sendme_inc_valid(sendme_inc, &settings.ccontrol) {
265                                return Err(Error::HandshakeProto(
266                                    "Received invalid sendme increment in CC ntorv3 extension".into(),
267                                ));
268                            }
269                            // Excellent, we have a negotiated sendme increment. Set it for this circuit.
270                            settings
271                                .ccontrol
272                                .cwnd_params_mut()
273                                .set_sendme_inc(sendme_inc);
274                        } else {
275                            let _ = ack_ext;
276                            return Err(Error::HandshakeProto(
277                                "Received unexpected `AckCongestionControl` ntorv3 extension".into(),
278                            ));
279                        }
280                    }
281                }
282                // Any other extensions is not expected. Reject and circuit must be closed.
283                _ => {
284                    return Err(Error::HandshakeProto(
285                        "Received unexpected ntorv3 extension".into(),
286                    ));
287                }
288            }
289        }
290        Ok(())
291    }
292}
293
294impl HandshakeAuxDataHandler for NtorClient {
295    fn handle_server_aux_data(_settings: &mut HopSettings, _data: &()) -> Result<()> {
296        // This handshake doesn't have any auxiliary data; nothing to do.
297        Ok(())
298    }
299}
300
301impl HandshakeAuxDataHandler for CreateFastClient {
302    fn handle_server_aux_data(_settings: &mut HopSettings, _data: &()) -> Result<()> {
303        // This handshake doesn't have any auxiliary data; nothing to do.
304        Ok(())
305    }
306}