diff --git a/nym-registration-client/src/builder/config.rs b/nym-registration-client/src/builder/config.rs index bf3dff774c..c5f26ae973 100644 --- a/nym-registration-client/src/builder/config.rs +++ b/nym-registration-client/src/builder/config.rs @@ -75,9 +75,7 @@ impl BuilderConfig { // Mixnet mode uses 5-hop configuration RegistrationMode::Mixnet => mixnet_debug_config(&self.mixnet_client_config), // Wireguard and LP both use 2-hop configuration - RegistrationMode::Wireguard | RegistrationMode::Lp => { - two_hop_debug_config(&self.mixnet_client_config) - } + RegistrationMode::Wireguard => two_hop_debug_config(&self.mixnet_client_config), } } @@ -121,7 +119,7 @@ impl BuilderConfig { let debug_config = self.mixnet_client_debug_config(); let remember_me = match self.mode { RegistrationMode::Mixnet => RememberMe::new_mixnet(), - RegistrationMode::Wireguard | RegistrationMode::Lp => RememberMe::new_vpn(), + RegistrationMode::Wireguard => RememberMe::new_vpn(), }; let identity = self.entry_node.node.identity.to_string(); diff --git a/nym-registration-client/src/config.rs b/nym-registration-client/src/config.rs index b5b59435fb..b438134a98 100644 --- a/nym-registration-client/src/config.rs +++ b/nym-registration-client/src/config.rs @@ -8,22 +8,8 @@ use crate::builder::config::NymNodeWithKeys; pub enum RegistrationMode { /// 5-hop mixnet with IPR (IP Packet Router) Mixnet, - /// 2-hop WireGuard with authenticator + /// 2-hop WireGuard Wireguard, - /// 2-hop WireGuard with LP (Lewes Protocol) - Lp, -} - -impl RegistrationMode { - /// Legacy method for backward compatibility - #[deprecated(note = "use explicit enum variant instead")] - pub fn legacy_two_hop(use_two_hop: bool) -> RegistrationMode { - if use_two_hop { - Self::Wireguard - } else { - Self::Mixnet - } - } } pub struct RegistrationClientConfig { diff --git a/nym-registration-client/src/error.rs b/nym-registration-client/src/error.rs index 449d6d4608..822d44dcae 100644 --- a/nym-registration-client/src/error.rs +++ b/nym-registration-client/src/error.rs @@ -74,25 +74,6 @@ pub enum RegistrationClientError { #[source] source: Box, }, - - #[error("LP registration not possible for gateway {node_id}: no LP address available")] - LpRegistrationNotPossible { node_id: String }, - - #[error("failed to register LP with entry gateway {gateway_id} at {lp_address}: {source}")] - EntryGatewayRegisterLp { - gateway_id: String, - lp_address: std::net::SocketAddr, - #[source] - source: Box, - }, - - #[error("failed to register LP with exit gateway {gateway_id} at {lp_address}: {source}")] - ExitGatewayRegisterLp { - gateway_id: String, - lp_address: std::net::SocketAddr, - #[source] - source: Box, - }, } impl RegistrationClientError { diff --git a/nym-registration-client/src/lib.rs b/nym-registration-client/src/lib.rs index 516f7c6245..9b8934fff7 100644 --- a/nym-registration-client/src/lib.rs +++ b/nym-registration-client/src/lib.rs @@ -1,17 +1,15 @@ // Copyright 2025 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use tokio_util::sync::CancellationToken; - -use crate::config::RegistrationClientConfig; +use config::RegistrationClientConfig; +use nym_authenticator_client::AuthClientMixnetListenerHandle; use nym_authenticator_client::{AuthClientMixnetListener, AuthenticatorClient}; use nym_bandwidth_controller::BandwidthTicketProvider; use nym_credentials_interface::TicketType; use nym_ip_packet_client::IprClientConnect; use nym_registration_common::AssignedAddresses; use nym_sdk::mixnet::{EventReceiver, MixnetClient, Recipient}; -use std::sync::Arc; -use tokio::net::TcpStream; +use tokio_util::sync::CancellationToken; mod builder; mod config; @@ -40,22 +38,66 @@ pub struct RegistrationClient { event_rx: EventReceiver, } +enum MixnetClientHandle { + Authenticator(AuthClientMixnetListenerHandle), + Sdk(Box), +} + +impl MixnetClientHandle { + async fn stop(self) { + match self { + Self::Authenticator(handle) => handle.stop().await, + Self::Sdk(handle) => handle.disconnect().await, + } + } +} +// Bundle of an actual error and the underlying mixnet client so it can be shutdown correctly if needed +struct RegistrationError { + mixnet_client_handle: MixnetClientHandle, + source: crate::RegistrationClientError, +} + impl RegistrationClient { - async fn register_mix_exit(self) -> Result { + async fn register_mix_exit(self) -> Result { let entry_mixnet_gateway_ip = self.config.entry.node.ip_address; let exit_mixnet_gateway_ip = self.config.exit.node.ip_address; - let ipr_address = self.config.exit.node.ipr_address.ok_or( - RegistrationClientError::NoIpPacketRouterAddress { - node_id: self.config.exit.node.identity.to_base58_string(), - }, - )?; - let mut ipr_client = IprClientConnect::new(self.mixnet_client, self.cancel_token.clone()); - let interface_addresses = ipr_client - .connect(ipr_address) + let Some(ipr_address) = self.config.exit.node.ipr_address else { + return Err(RegistrationError { + mixnet_client_handle: MixnetClientHandle::Sdk(Box::new(self.mixnet_client)), + source: RegistrationClientError::NoIpPacketRouterAddress { + node_id: self.config.exit.node.identity.to_base58_string(), + }, + }); + }; + + let mut ipr_client = + IprClientConnect::new(self.mixnet_client, self.cancel_token.child_token()); + + let interface_addresses = match self + .cancel_token + .run_until_cancelled(ipr_client.connect(ipr_address)) .await - .map_err(RegistrationClientError::ConnectToIpPacketRouter)?; + { + Some(Ok(addr)) => addr, + Some(Err(e)) => { + return Err(RegistrationError { + mixnet_client_handle: MixnetClientHandle::Sdk(Box::new( + ipr_client.into_mixnet_client(), + )), + source: RegistrationClientError::ConnectToIpPacketRouter(e), + }); + } + None => { + return Err(RegistrationError { + mixnet_client_handle: MixnetClientHandle::Sdk(Box::new( + ipr_client.into_mixnet_client(), + )), + source: RegistrationClientError::Cancelled, + }); + } + }; Ok(RegistrationResult::Mixnet(Box::new( MixnetRegistrationResult { @@ -72,18 +114,24 @@ impl RegistrationClient { ))) } - async fn register_wg(self) -> Result { - let entry_auth_address = self.config.entry.node.authenticator_address.ok_or( - RegistrationClientError::AuthenticationNotPossible { - node_id: self.config.entry.node.identity.to_base58_string(), - }, - )?; + async fn register_wg(self) -> Result { + let Some(entry_auth_address) = self.config.entry.node.authenticator_address else { + return Err(RegistrationError { + mixnet_client_handle: MixnetClientHandle::Sdk(Box::new(self.mixnet_client)), + source: RegistrationClientError::AuthenticationNotPossible { + node_id: self.config.entry.node.identity.to_base58_string(), + }, + }); + }; - let exit_auth_address = self.config.exit.node.authenticator_address.ok_or( - RegistrationClientError::AuthenticationNotPossible { - node_id: self.config.exit.node.identity.to_base58_string(), - }, - )?; + let Some(exit_auth_address) = self.config.exit.node.authenticator_address else { + return Err(RegistrationError { + mixnet_client_handle: MixnetClientHandle::Sdk(Box::new(self.mixnet_client)), + source: RegistrationClientError::AuthenticationNotPossible { + node_id: self.config.exit.node.identity.to_base58_string(), + }, + }); + }; let entry_version = self.config.entry.node.version; tracing::debug!("Entry gateway version: {entry_version}"); @@ -93,7 +141,8 @@ impl RegistrationClient { // Start the auth client mixnet listener, which will listen for incoming messages from the // mixnet and rebroadcast them to the auth clients. let mixnet_listener = - AuthClientMixnetListener::new(self.mixnet_client, self.cancel_token.clone()).start(); + AuthClientMixnetListener::new(self.mixnet_client, self.cancel_token.child_token()) + .start(); let mut entry_auth_client = AuthenticatorClient::new( mixnet_listener.subscribe(), @@ -120,24 +169,50 @@ impl RegistrationClient { let exit_fut = exit_auth_client .register_wireguard(&*self.bandwidth_controller, TicketType::V1WireguardExit); - let (entry, exit) = Box::pin(async { tokio::join!(entry_fut, exit_fut) }).await; + let (entry, exit) = match Box::pin( + self.cancel_token + .run_until_cancelled(async { tokio::join!(entry_fut, exit_fut) }), + ) + .await + { + Some((entry, exit)) => (entry, exit), + None => { + return Err(RegistrationError { + mixnet_client_handle: MixnetClientHandle::Authenticator(mixnet_listener), + source: RegistrationClientError::Cancelled, + }); + } + }; - let entry = entry.map_err(|source| { - RegistrationClientError::from_authenticator_error( - source, - self.config.entry.node.identity.to_base58_string(), - entry_auth_address, - true, // is entry - ) - })?; - let exit = exit.map_err(|source| { - RegistrationClientError::from_authenticator_error( - source, - self.config.exit.node.identity.to_base58_string(), - exit_auth_address, - false, // is exit (not entry) - ) - })?; + let entry = match entry { + Ok(entry) => entry, + Err(source) => { + return Err(RegistrationError { + mixnet_client_handle: MixnetClientHandle::Authenticator(mixnet_listener), + source: RegistrationClientError::from_authenticator_error( + source, + self.config.entry.node.identity.to_base58_string(), + entry_auth_address, + true, + ), + }); + } + }; + + let exit = match exit { + Ok(exit) => exit, + Err(source) => { + return Err(RegistrationError { + mixnet_client_handle: MixnetClientHandle::Authenticator(mixnet_listener), + source: RegistrationClientError::from_authenticator_error( + source, + self.config.exit.node.identity.to_base58_string(), + exit_auth_address, + false, + ), + }); + } + }; Ok(RegistrationResult::Wireguard(Box::new( WireguardRegistrationResult { @@ -151,125 +226,22 @@ impl RegistrationClient { ))) } - async fn register_lp(self) -> Result { - use crate::lp_client::{LpRegistrationClient, NestedLpSession}; - - // Extract and validate LP addresses - let entry_lp_address = self.config.entry.node.lp_address.ok_or( - RegistrationClientError::LpRegistrationNotPossible { - node_id: self.config.entry.node.identity.to_base58_string(), - }, - )?; - - let exit_lp_address = self.config.exit.node.lp_address.ok_or( - RegistrationClientError::LpRegistrationNotPossible { - node_id: self.config.exit.node.identity.to_base58_string(), - }, - )?; - - tracing::debug!("Entry gateway LP address: {entry_lp_address}"); - tracing::debug!("Exit gateway LP address: {exit_lp_address}"); - - // Generate fresh Ed25519 keypairs for LP registration - // These are ephemeral and used only for the LP handshake protocol - use nym_crypto::asymmetric::ed25519; - use rand::rngs::OsRng; - let entry_lp_keypair = Arc::new(ed25519::KeyPair::new(&mut OsRng)); - let exit_lp_keypair = Arc::new(ed25519::KeyPair::new(&mut OsRng)); - - // STEP 1: Establish outer session with entry gateway - // This creates the LP session that will be used to forward packets to exit. - // Uses packet-per-connection model: each handshake packet on new TCP connection. - tracing::info!("Establishing outer session with entry gateway"); - let mut entry_client = LpRegistrationClient::::new_with_default_psk( - entry_lp_keypair.clone(), - self.config.entry.node.identity, - entry_lp_address, - self.config.entry.node.ip_address, - ); - - // Perform handshake with entry gateway (outer session now established) - entry_client.perform_handshake().await.map_err(|source| { - RegistrationClientError::EntryGatewayRegisterLp { - gateway_id: self.config.entry.node.identity.to_base58_string(), - lp_address: entry_lp_address, - source: Box::new(source), - } - })?; - - tracing::info!("Outer session with entry gateway established"); - - // STEP 2: Use nested session to register with exit gateway via forwarding - // This hides the client's IP address from the exit gateway - tracing::info!("Registering with exit gateway via entry forwarding"); - let mut nested_session = NestedLpSession::new( - self.config.exit.node.identity.to_bytes(), - exit_lp_address.to_string(), - exit_lp_keypair, - self.config.exit.node.identity, - ); - - // Perform handshake and registration with exit gateway (all via entry forwarding) - let exit_gateway_data = nested_session - .handshake_and_register::( - &mut entry_client, - &self.config.exit.keys, - &self.config.exit.node.identity, - &*self.bandwidth_controller, - TicketType::V1WireguardExit, - self.config.exit.node.ip_address, - ) - .await - .map_err(|source| RegistrationClientError::ExitGatewayRegisterLp { - gateway_id: self.config.exit.node.identity.to_base58_string(), - lp_address: exit_lp_address, - source: Box::new(source), - })?; - - tracing::info!("Exit gateway registration completed via forwarding"); - - // STEP 3: Register with entry gateway (packet-per-connection) - tracing::info!("Registering with entry gateway"); - let entry_gateway_data = entry_client - .register( - &self.config.entry.keys, - &self.config.entry.node.identity, - &*self.bandwidth_controller, - TicketType::V1WireguardEntry, - ) - .await - .map_err(|source| RegistrationClientError::EntryGatewayRegisterLp { - gateway_id: self.config.entry.node.identity.to_base58_string(), - lp_address: entry_lp_address, - source: Box::new(source), - })?; - - tracing::info!("Entry gateway registration successful"); - - tracing::info!("LP registration successful for both gateways"); - - // LP is registration-only (packet-per-connection model). - // All data flows through WireGuard after this point. - // Each LP packet used its own TCP connection which was closed after the exchange. - // Exit registration was completed via forwarding through entry gateway. - Ok(RegistrationResult::Lp(Box::new(LpRegistrationResult { - entry_gateway_data, - exit_gateway_data, - bw_controller: self.bandwidth_controller, - }))) - } - pub async fn register(self) -> Result { - self.cancel_token - .clone() - .run_until_cancelled(async { - match self.config.mode { - RegistrationMode::Mixnet => self.register_mix_exit().await, - RegistrationMode::Wireguard => self.register_wg().await, - RegistrationMode::Lp => self.register_lp().await, - } - }) - .await - .ok_or(RegistrationClientError::Cancelled)? + let registration_result = match self.config.mode { + RegistrationMode::Mixnet => self.register_mix_exit().await, + RegistrationMode::Wireguard => self.register_wg().await, + }; + + // If we failed to register, shut down the mixnet client and wait for it to exit + match registration_result { + Ok(result) => Ok(result), + Err(error) => { + tracing::debug!("Registration failed"); + tracing::debug!("Shutting down mixnet client"); + error.mixnet_client_handle.stop().await; + tracing::debug!("Mixnet client stopped"); + Err(error.source) + } + } } } diff --git a/nym-registration-client/src/lp_client/mod.rs b/nym-registration-client/src/lp_client/mod.rs index d4085bbbaa..fa662eb94e 100644 --- a/nym-registration-client/src/lp_client/mod.rs +++ b/nym-registration-client/src/lp_client/mod.rs @@ -38,5 +38,4 @@ mod nested_session; pub use client::LpRegistrationClient; pub use config::LpConfig; -pub use error::LpClientError; pub use nested_session::NestedLpSession;