diff options
Diffstat (limited to 'crates/tor-proto/src')
| -rw-r--r-- | crates/tor-proto/src/channel/reactor.rs | 23 | ||||
| -rw-r--r-- | crates/tor-proto/src/circuit.rs | 78 | ||||
| -rw-r--r-- | crates/tor-proto/src/client/reactor/conflux.rs | 2 | ||||
| -rw-r--r-- | crates/tor-proto/src/congestion.rs | 4 | ||||
| -rw-r--r-- | crates/tor-proto/src/congestion/params.rs | 10 | ||||
| -rw-r--r-- | crates/tor-proto/src/relay/channel/create_handler.rs | 273 |
6 files changed, 334 insertions, 56 deletions
diff --git a/crates/tor-proto/src/channel/reactor.rs b/crates/tor-proto/src/channel/reactor.rs index f5fb07418..6f38bdc62 100644 --- a/crates/tor-proto/src/channel/reactor.rs +++ b/crates/tor-proto/src/channel/reactor.rs @@ -702,16 +702,19 @@ impl<R: Runtime> Reactor<R> { let circ_uniq_id = self.circ_unique_id_ctx.next(self.unique_id); // Build the relay circuit. - let create_result = create_request_handler.handler.handle_create( - &self.runtime, - &chan, - &create_request_handler.our_ed25519_id, - &create_request_handler.our_rsa_id, - circid, - &msg, - &self.details.memquota, - circ_uniq_id, - ); + let create_result = create_request_handler + .handler + .handle_create( + &self.runtime, + &chan, + &create_request_handler.our_ed25519_id, + &create_request_handler.our_rsa_id, + circid, + &msg, + &self.details.memquota, + circ_uniq_id, + ) + .await; // Add the circuit to the circuit map. let response = match create_result { diff --git a/crates/tor-proto/src/circuit.rs b/crates/tor-proto/src/circuit.rs index 061fc7d9f..a4c3f0a44 100644 --- a/crates/tor-proto/src/circuit.rs +++ b/crates/tor-proto/src/circuit.rs @@ -18,6 +18,9 @@ pub use unique_id::UniqId; use crate::ccparams::CongestionControlParams; use crate::stream::flow_ctrl::params::FlowCtrlParameters; +use tor_cell::relaycell::extend::SubprotocolRequest; +use tor_error::ErrorKind; +use tor_protover::Protocols; pub(crate) use circ_sender::{CircuitRxReceiver, CircuitRxSender}; @@ -98,14 +101,57 @@ tor_protover::subprotocol_restricted_set! { /// /// The allowed subprotocols are defined in: /// <https://spec.torproject.org/tor-spec/create-created-cells.html#subproto-request> - #[derive(Copy, Clone, Debug, Default)] + #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] pub(crate) struct HandshakeSubprotocols { RELAY_CRYPT_CGO, } } +impl HandshakeSubprotocols { + /// Build a [`HandshakeSubprotocols`] from a [`SubprotocolRequest`] + /// provided during a circuit handshake. + /// + /// If the `SubprotocolRequest` contains subprotocols that aren't + /// allowed to be requested through a subprotocol request, + /// this returns an error containing the original `SubprotocolRequest`. + // + // It would be nice to return a list of only the invalid subprotocols, + // but it seems a bit expensive to compute on the error path when we probably + // want to fail quickly. + pub(crate) fn try_from_request( + protos: SubprotocolRequest, + ) -> Result<Self, InvalidHandshakeSubprotocolError> { + use std::sync::LazyLock; + static ALL: LazyLock<Protocols> = + LazyLock::new(|| Protocols::from(HandshakeSubprotocols::ALL)); + + if !protos.contains_only(&ALL) { + return Err(InvalidHandshakeSubprotocolError(protos)); + } + + Ok(Self { + relay_crypt_cgo: protos.contains(tor_protover::named::RELAY_CRYPT_CGO), + }) + } +} + +/// The subprotocol request had subprotocols that are not all supported in circuit handshakes. +/// +/// Contains the requested subprotocols (both valid and invalid). +#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)] +#[error("Request included subprotocols that we do not support in circuit handshakes: {0:?}")] +pub(crate) struct InvalidHandshakeSubprotocolError(SubprotocolRequest); + +impl tor_error::HasKind for InvalidHandshakeSubprotocolError { + fn kind(&self) -> ErrorKind { + ErrorKind::TorProtocolViolation + } +} + #[cfg(test)] pub(crate) mod test { + use super::*; + #[cfg(feature = "relay")] use crate::relay::{CircNetParameters, CongestionControlNetParams}; @@ -120,4 +166,34 @@ pub(crate) mod test { cc: CongestionControlNetParams::defaults_for_tests(), } } + + #[test] + fn handshake_subprotocols() { + let empty_iter: [tor_protover::NumberedSubver; 0] = []; + let request = SubprotocolRequest::from_iter(empty_iter); + assert_eq!( + HandshakeSubprotocols::try_from_request(request), + Ok(HandshakeSubprotocols { + relay_crypt_cgo: false, + }), + ); + + let request = SubprotocolRequest::from_iter([tor_protover::named::RELAY_CRYPT_CGO]); + assert_eq!( + HandshakeSubprotocols::try_from_request(request), + Ok(HandshakeSubprotocols { + relay_crypt_cgo: true, + }), + ); + + let request = + SubprotocolRequest::from_iter([tor_protover::named::RELAY_NEGOTIATE_SUBPROTO]); + assert!(HandshakeSubprotocols::try_from_request(request).is_err()); + + let request = SubprotocolRequest::from_iter([ + tor_protover::named::RELAY_NEGOTIATE_SUBPROTO, + tor_protover::named::RELAY_CRYPT_CGO, + ]); + assert!(HandshakeSubprotocols::try_from_request(request).is_err()); + } } diff --git a/crates/tor-proto/src/client/reactor/conflux.rs b/crates/tor-proto/src/client/reactor/conflux.rs index 1d6993ff5..5cfa431d8 100644 --- a/crates/tor-proto/src/client/reactor/conflux.rs +++ b/crates/tor-proto/src/client/reactor/conflux.rs @@ -386,7 +386,7 @@ impl ConfluxSet { // If data is in progress on the leg (inflight > cc_sendme_inc), // then all legs must be closed - if inflight >= cwnd.params().sendme_inc() { + if inflight >= u32::from(cwnd.params().sendme_inc()) { return Err(ReactorError::Shutdown); } diff --git a/crates/tor-proto/src/congestion.rs b/crates/tor-proto/src/congestion.rs index 2a20cfb2f..b6ac1ab30 100644 --- a/crates/tor-proto/src/congestion.rs +++ b/crates/tor-proto/src/congestion.rs @@ -248,7 +248,7 @@ impl CongestionWindow { /// Return the SENDME increment value. pub(crate) fn sendme_inc(&self) -> u32 { - self.params.sendme_inc() + self.params.sendme_inc().into() } /// Return the congestion window params. @@ -450,7 +450,7 @@ mod test { assert_eq!(cwnd.min(), cwnd.params().cwnd_min()); assert_eq!(cwnd.increment(), cwnd.params().cwnd_inc()); assert_eq!(cwnd.increment_rate(), cwnd.params().cwnd_inc_rate()); - assert_eq!(cwnd.sendme_inc(), cwnd.params().sendme_inc()); + assert_eq!(cwnd.sendme_inc(), u32::from(cwnd.params().sendme_inc())); assert!(!cwnd.is_full()); // Validate changes. diff --git a/crates/tor-proto/src/congestion/params.rs b/crates/tor-proto/src/congestion/params.rs index 8e12e45be..0ecc9f83c 100644 --- a/crates/tor-proto/src/congestion/params.rs +++ b/crates/tor-proto/src/congestion/params.rs @@ -222,7 +222,7 @@ pub struct CongestionWindowParams { /// The SENDME increment as in the number of cells to ACK with every SENDME. This is coming /// from the consensus and negotiated during circuit setup. #[getter(as_copy)] - sendme_inc: u32, + sendme_inc: u8, } impl_standard_builder! { CongestionWindowParams: !Deserialize + !Default} @@ -233,7 +233,7 @@ impl CongestionWindowParams { /// [`CongestionWindowParamsBuilder`]. /// Typically the default when built should be from the network parameters from the consensus. pub(crate) fn set_sendme_inc(&mut self, inc: u8) { - self.sendme_inc = u32::from(inc); + self.sendme_inc = inc; } #[cfg(test)] @@ -297,8 +297,6 @@ impl CongestionControlParams { /// Return true iff the given sendme increment is valid with regards to the value in the circuit /// parameters that is taken from the consensus. pub(crate) fn is_sendme_inc_valid(inc: u8, params: &CongestionControlParams) -> bool { - // Ease our lives a bit because the consensus value is u32. - let inc_u32 = u32::from(inc); // A consensus value of 1 would allow this sendme increment to be 0 and thus // we have to special case it before evaluating. if inc == 0 { @@ -306,7 +304,7 @@ pub(crate) fn is_sendme_inc_valid(inc: u8, params: &CongestionControlParams) -> } let inc_consensus = params.cwnd_params().sendme_inc(); // See prop324 section 10.3 - if inc_u32 > (inc_consensus.saturating_add(1)) || inc_u32 < (inc_consensus.saturating_sub(1)) { + if inc > inc_consensus.saturating_add(1) || inc < inc_consensus.saturating_sub(1) { return false; } true @@ -321,7 +319,7 @@ mod test { #[test] fn test_sendme_inc_valid() { let params = build_cc_vegas_params(); - let ref_inc = params.cwnd_params().sendme_inc() as u8; + let ref_inc = params.cwnd_params().sendme_inc(); // In range. assert!(is_sendme_inc_valid(ref_inc, ¶ms)); diff --git a/crates/tor-proto/src/relay/channel/create_handler.rs b/crates/tor-proto/src/relay/channel/create_handler.rs index 2bb6c2d06..d4a73a609 100644 --- a/crates/tor-proto/src/relay/channel/create_handler.rs +++ b/crates/tor-proto/src/relay/channel/create_handler.rs @@ -8,15 +8,20 @@ use crate::ccparams::{ use crate::channel::Channel; use crate::circuit::celltypes::{CreateRequest, CreateResponse}; use crate::circuit::circhop::{HandshakeParamsError, HopSettings}; -use crate::circuit::{CircuitRxSender, HandshakeSubprotocols, UniqId}; +use crate::circuit::{ + CircuitRxSender, HandshakeSubprotocols, InvalidHandshakeSubprotocolError, UniqId, +}; use crate::client::circuit::padding::PaddingController; use crate::crypto::binding::CircuitBinding; use crate::crypto::cell::CryptInit as _; -use crate::crypto::cell::{InboundRelayLayer, OutboundRelayLayer, RelayLayer, tor1}; +use crate::crypto::cell::{ + CgoRelayCrypto, InboundRelayLayer, OutboundRelayLayer, RelayLayer, Tor1RelayCrypto, +}; use crate::crypto::handshake::RelayHandshakeError; use crate::crypto::handshake::ServerHandshake as _; use crate::crypto::handshake::fast::CreateFastServer; use crate::crypto::handshake::ntor::{NtorSecretKey, NtorServer}; +use crate::crypto::handshake::ntor_v3::{NtorV3SecretKey, NtorV3Server}; use crate::memquota::SpecificAccount as _; use crate::memquota::{ChannelAccount, CircuitAccount}; use crate::relay::channel_provider::ChannelProvider; @@ -33,10 +38,11 @@ use tor_cell::chancell::msg::{ CreateFast, Created2, CreatedFast, Destroy, DestroyReason, HandshakeType, }; use tor_cell::relaycell::RelayCmd; +use tor_cell::relaycell::extend::{ + CcRequest, CcResponse, CircRequestExt, CircResponseExt, SubprotocolRequest, +}; use tor_error::{ErrorKind, HasKind, debug_report, internal, into_internal, warn_report}; use tor_linkspec::OwnedChanTarget; -use tor_llcrypto::cipher::aes::Aes128Ctr; -use tor_llcrypto::d::Sha1; use tor_llcrypto::pk::ed25519::Ed25519Identity; use tor_llcrypto::pk::rsa::RsaIdentity; use tor_memquota::mq_queue::ChannelSpec as _; @@ -44,7 +50,7 @@ use tor_memquota::mq_queue::MpscSpec; use tor_relay_crypto::pk::{RelayNtorKeypair, RelayNtorKeys}; use tor_rtcompat::SpawnExt as _; use tor_rtcompat::{DynTimeProvider, Runtime}; -use tracing::trace; +use tracing::{debug, trace}; /// Everything needed to handle CREATE* messages on channels. #[derive(derive_more::Debug)] @@ -91,6 +97,9 @@ pub struct CreateRequestHandler { circuit_stream_tx: mpsc::Sender<Box<dyn Stream<Item = IncomingStream> + Send + Sync + Unpin>>, } +// We make the CREATE-handling methods of `CreateRequestHandler` async +// since we expect that in the future we may want to offload the crypto to a worker thread. +#[expect(clippy::unused_async)] impl CreateRequestHandler { /// Build a new [`CreateRequestHandler`], and a [`CircuitIncomingStreamReceiver`] /// for receiving new streams that are opened on any incoming circuits. @@ -150,7 +159,7 @@ impl CreateRequestHandler { /// relay. This is especially important here since we're handling data that is controllable from /// the other end of the circuit. #[allow(clippy::too_many_arguments)] - pub(crate) fn handle_create<R: Runtime>( + pub(crate) async fn handle_create<R: Runtime>( &self, runtime: &R, channel: &Arc<Channel>, @@ -161,16 +170,18 @@ impl CreateRequestHandler { memquota: &ChannelAccount, circ_unique_id: UniqId, ) -> Result<(CreateResponse, RelayCircComponents), Destroy> { - let result = self.handle_create_inner( - runtime, - channel, - our_ed25519_id, - our_rsa_id, - circ_id, - msg, - memquota, - circ_unique_id, - ); + let result = self + .handle_create_inner( + runtime, + channel, + our_ed25519_id, + our_rsa_id, + circ_id, + msg, + memquota, + circ_unique_id, + ) + .await; match result { Ok(x) => Ok(x), @@ -189,7 +200,7 @@ impl CreateRequestHandler { /// See [`Self::handle_create`]. #[allow(clippy::too_many_arguments)] - fn handle_create_inner<R: Runtime>( + async fn handle_create_inner<R: Runtime>( &self, runtime: &R, channel: &Arc<Channel>, @@ -202,10 +213,13 @@ impl CreateRequestHandler { ) -> Result<(CreateResponse, RelayCircComponents), HandleCreateError> { // Perform the handshake crypto and build the response. let handshake_components = match msg { - CreateRequest::CreateFast(msg) => self.handle_create_fast(msg)?, + CreateRequest::CreateFast(msg) => self.handle_create_fast(msg).await?, CreateRequest::Create2(msg) => match msg.handshake_type() { - HandshakeType::NTOR_V3 => self.handle_create2_ntorv3(msg.body(), our_ed25519_id)?, - HandshakeType::NTOR => self.handle_create2_ntor(msg.body(), our_rsa_id)?, + HandshakeType::NTOR_V3 => { + self.handle_create2_ntorv3(msg.body(), our_ed25519_id) + .await? + } + HandshakeType::NTOR => self.handle_create2_ntor(msg.body(), our_rsa_id).await?, x @ HandshakeType::TAP | x => { return Err(HandleCreateError::Create2HandshakeType(x)); } @@ -296,7 +310,7 @@ impl CreateRequestHandler { } /// The handshake code for a CREATE_FAST request. - fn handle_create_fast( + async fn handle_create_fast( &self, msg: &CreateFast, ) -> Result<CompletedHandshakeComponents, HandleCreateError> { @@ -327,7 +341,7 @@ impl CreateRequestHandler { subprotos, )?; - let crypt = tor1::CryptStatePair::<Aes128Ctr, Sha1>::construct(keygen) + let crypt = Tor1RelayCrypto::construct(keygen) .map_err(into_internal!("Circuit crypt state construction failed"))?; let (crypto_out, crypto_in, _binding) = split_relay_layer(crypt); @@ -346,7 +360,7 @@ impl CreateRequestHandler { } /// The handshake code for a CREATE2 ntor (non-v3) request. - fn handle_create2_ntor( + async fn handle_create2_ntor( &self, msg_body: &[u8], our_rsa_id: &RsaIdentity, @@ -381,7 +395,7 @@ impl CreateRequestHandler { subprotos, )?; - let crypt = tor1::CryptStatePair::<Aes128Ctr, Sha1>::construct(keygen) + let crypt = Tor1RelayCrypto::construct(keygen) .map_err(into_internal!("Circuit crypt state construction failed"))?; let (crypto_out, crypto_in, _binding) = split_relay_layer(crypt); @@ -400,14 +414,137 @@ impl CreateRequestHandler { } /// The handshake code for a CREATE2 ntor-v3 request. - fn handle_create2_ntorv3( + async fn handle_create2_ntorv3( &self, - _msg_body: &[u8], - _our_ed25519_id: &Ed25519Identity, + msg_body: &[u8], + our_ed25519_id: &Ed25519Identity, ) -> Result<CompletedHandshakeComponents, HandleCreateError> { - Err(HandleCreateError::Create2HandshakeType( - HandshakeType::NTOR_V3, - )) + let ntor_keys = self.ntor_keys(|k| { + NtorV3SecretKey::new(k.secret().clone(), *k.public().inner(), *our_ed25519_id) + }); + + let circ_net_params = self + .circ_net_params + .read() + .expect("rwlock poisoned") + .clone(); + + // These extensions can be negotiated during the handshake. + let mut cc_algorithm = AlgorithmDiscriminants::FixedWindow; + + // These subprotocols were requested during the handshake. + // They are not validated. + let mut subprotos = SubprotocolRequest::default(); + + // Helper which processes extension requests and returns any responses. + // Returns `None` if the handshake should fail. + let mut ext_reply_fn = |client_exts: &[CircRequestExt]| { + let mut response_exts = Vec::new(); + + // https://spec.torproject.org/tor-spec/create-created-cells.html#additional-data + // + // > Unless otherwise specified in the documentation for an extension type: + // > - [...] + // > - Parties MUST ignore any occurrence of an extension with a given type after the first such occurrence. + // + // TODO: Is there something nicer that we can do here? + // We could use accessors like `ExtList::get_cc_request()` + // which iterate over the extension list for each extension, + // but using an enum match like we do below is kind of nice. + let mut handled_cc_request = false; + let mut handled_subproto_request = false; + + for ext in client_exts { + match ext { + CircRequestExt::CcRequest(CcRequest { .. }) => { + if handled_cc_request { + continue; + } + handled_cc_request = true; + + cc_algorithm = AlgorithmDiscriminants::Vegas; + + let sendme_inc: u8 = circ_net_params.cc.cwnd.sendme_inc(); + let response = CcResponse::new(sendme_inc); + response_exts.push(CircResponseExt::CcResponse(response)); + } + // The given `SubprotocolRequest` stores a list of `NumberedSubver`, + // but a circuit extension request is limited to 255 bytes (127 subprotocols). + // So while a malicious client could send us a lot of invalid subprotocols, + // this limit prevents this list from being excessively large. + CircRequestExt::SubprotocolRequest(subproto_request) => { + if handled_subproto_request { + continue; + } + handled_subproto_request = true; + + // We don't check the requested subprotocols here. + subprotos = subproto_request.clone(); + } + CircRequestExt::Unrecognized(ext) => { + // https://spec.torproject.org/tor-spec/create-created-cells.html#additional-data + // + // > Parties MUST ignore extensions with `EXT_FIELD_TYPE` bodies they do not recognize. + debug!( + ?ext, + "CREATE2 ntor-v3 handshake requested unrecognized extension", + ); + } + ext => { + // https://spec.torproject.org/tor-spec/create-created-cells.html#additional-data + // + // > Parties MUST ignore extensions with `EXT_FIELD_TYPE` bodies they do not recognize. + // + // We recognize this but don't know what to do with it. + // We haven't implemented it, or it doesn't make sense + // (for example `CircRequestExt::ProofOfWork`). + // So we'll just behave as if we don't recognize it. + debug!( + ?ext, + "CREATE2 ntor-v3 handshake requested unsupported extension", + ); + } + } + } + + Some(response_exts) + }; + + // TODO(relay): We might want to offload this to a CPU worker in the future. + let (keygen, handshake_msg) = NtorV3Server::server( + &mut rand::rng(), + &mut ext_reply_fn, + ntor_keys.as_ref(), + msg_body, + )?; + + // Ensure that the client did not request invalid/unsupported subprotocols. + let subprotos = HandshakeSubprotocols::try_from_request(subprotos)?; + + let hop_settings = + HopSettings::from_handshake_params(circ_net_params, cc_algorithm, subprotos)?; + + let (crypto_out, crypto_in, _binding) = if subprotos.relay_crypt_cgo { + let crypt = CgoRelayCrypto::construct(keygen) + .map_err(into_internal!("Circuit crypt state construction failed"))?; + split_relay_layer(crypt) + } else { + let crypt = Tor1RelayCrypto::construct(keygen) + .map_err(into_internal!("Circuit crypt state construction failed"))?; + split_relay_layer(crypt) + }; + + let response = Created2::new(handshake_msg); + let response = CreateResponse::Created2(response); + + trace!(?cc_algorithm, ?subprotos, "Completed ntor-v3 handshake"); + + Ok(CompletedHandshakeComponents { + response, + hop_settings, + crypto_out, + crypto_in, + }) } /// Helper to get the ntor keypairs after some transformation `map`. @@ -494,6 +631,9 @@ enum HandleCreateError { /// Circuit relay handshake failed. #[error("Failed to process the circuit relay handshake parameters")] HandshakeParameters(#[from] HandshakeParamsError), + /// Requested subprotocols which aren't supported. + #[error("Client requested subprotocol(s) which aren't supported")] + HandshakeSubprotocols(#[from] InvalidHandshakeSubprotocolError), /// The requested handshake type is unsupported. #[error("Unsupported handshake type {0}")] Create2HandshakeType(HandshakeType), @@ -516,6 +656,7 @@ impl HasKind for HandleCreateError { match self { Self::Handshake(e) => e.kind(), Self::HandshakeParameters(e) => e.kind(), + Self::HandshakeSubprotocols(e) => e.kind(), Self::Create2HandshakeType(_) => ErrorKind::NotImplemented, Self::Memquota(e) => e.kind(), Self::Spawn(e) => e.kind(), @@ -634,6 +775,7 @@ mod test { #![allow(clippy::string_slice)] // See arti#2571 //! <!-- @@ end test lint list maintained by maint/add_warning @@ --> + use tor_cell::chancell::msg::{AnyChanMsg, HandshakeType}; use tor_cell::chancell::{ChanCmd, ChanMsg as _}; use tor_rtcompat::test_with_one_runtime; @@ -742,14 +884,75 @@ mod test { .await .unwrap(); + let client_cell = conn_inspector.try_client_cell().unwrap().msg().clone(); + let relay_cell = conn_inspector.try_relay_cell().unwrap().msg().clone(); + + // Check that we got CREATE2 and CREATED2. + assert_eq!(client_cell.cmd(), ChanCmd::CREATE2); + assert_eq!(relay_cell.cmd(), ChanCmd::CREATED2); + + // Check that it was an ntor handshake. + let AnyChanMsg::Create2(client_cell) = client_cell else { + unreachable!("CREATE2 checked above"); + }; + assert_eq!(client_cell.handshake_type(), HandshakeType::NTOR); + + drop(tunnel); + assert_eq!( - conn_inspector.try_client_cell().unwrap().msg().cmd(), - ChanCmd::CREATE2, + conn_inspector.client_cell().await.unwrap().msg().cmd(), + ChanCmd::DESTROY, ); + // TODO(relay): I think the relay shouldn't be sending a DESTROY back to the client. + // https://gitlab.torproject.org/tpo/core/arti/-/work_items/2648 assert_eq!( - conn_inspector.try_relay_cell().unwrap().msg().cmd(), - ChanCmd::CREATED2, + conn_inspector.relay_cell().await.unwrap().msg().cmd(), + ChanCmd::DESTROY, ); + } + }); + } + + #[test] + fn ntor_v3() { + test_with_one_runtime!(|rt| async move { + let mut conn_inspector = test_utils::ConnInspector::new(); + + let (client_chan, _relay_chan, _circuit_stream_rx, mut target_builder) = + test_utils::new_channel_pair_with_keys(&rt, &conn_inspector); + + // https://spec.torproject.org/tor-spec/subprotocol-versioning.html + // 4 = RELAY_NTORV3 + // 5 = RELAY_NEGOTIATE_SUBPROTO + // 6 = RELAY_CRYPT_CGO + for relay_version in [4, 5, 6] { + let pending_tunnel = test_utils::new_pending_tunnel(&rt, &client_chan).await; + + let circ_params = CircParameters::default(); + + let protocols = format!("Relay=4-{relay_version}").parse().unwrap(); + let target = target_builder.protocols(protocols).build().unwrap(); + + let tunnel = pending_tunnel + .create_firsthop(&target, circ_params) + .await + .unwrap(); + + let client_cell = conn_inspector.try_client_cell().unwrap().msg().clone(); + let relay_cell = conn_inspector.try_relay_cell().unwrap().msg().clone(); + + // Check that we got CREATE2 and CREATED2. + assert_eq!(client_cell.cmd(), ChanCmd::CREATE2); + assert_eq!(relay_cell.cmd(), ChanCmd::CREATED2); + + // Check that it was an ntor-v3 handshake. + let AnyChanMsg::Create2(client_cell) = client_cell else { + unreachable!("CREATE2 checked above"); + }; + assert_eq!(client_cell.handshake_type(), HandshakeType::NTOR_V3); + + // TODO: It would be nice if we had a way to check that CGO was in use when + // `relay_version` is >=6, but I don't see a nice way to do that. drop(tunnel); @@ -766,6 +969,4 @@ mod test { } }); } - - // TODO(relay): Test ntor-v3 handshake once implemented. } |
