Skip to main content

tor_proto/client/reactor/circuit/
extender.rs

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