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