|
|
|
@@ -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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|