regsitration client fix for oscypek (#6421)

* regsitration client fix

* remove unused import
This commit is contained in:
Simon Wicky
2026-02-06 09:48:19 +01:00
committed by GitHub
parent 83bf9dc7cc
commit cb3d195c69
5 changed files with 138 additions and 202 deletions
@@ -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();
+1 -15
View File
@@ -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 {
-19
View File
@@ -74,25 +74,6 @@ pub enum RegistrationClientError {
#[source]
source: Box<nym_authenticator_client::AuthenticationClientError>,
},
#[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<crate::lp_client::LpClientError>,
},
#[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<crate::lp_client::LpClientError>,
},
}
impl RegistrationClientError {
+135 -163
View File
@@ -1,17 +1,15 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// 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<MixnetClient>),
}
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<RegistrationResult, RegistrationClientError> {
async fn register_mix_exit(self) -> Result<RegistrationResult, RegistrationError> {
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<RegistrationResult, RegistrationClientError> {
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<RegistrationResult, RegistrationError> {
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<RegistrationResult, RegistrationClientError> {
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::<TcpStream>::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::<TcpStream>(
&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<RegistrationResult, RegistrationClientError> {
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)
}
}
}
}
@@ -38,5 +38,4 @@ mod nested_session;
pub use client::LpRegistrationClient;
pub use config::LpConfig;
pub use error::LpClientError;
pub use nested_session::NestedLpSession;