Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ecd678a849 | |||
| 6c6e473607 | |||
| d52d728ab8 |
@@ -4,88 +4,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2026.2-oscypek] (2026-01-27)
|
||||
|
||||
- bugfix: downgrade gateway protocol to clients proposed version ([#6377])
|
||||
- bugfix: ack fix ([#6364])
|
||||
- Cherry pick/api urls oscypek ([#6348])
|
||||
- Update nix to v0.30.1 ([#6316])
|
||||
- Deriving Serialize for GatewayData ([#6314])
|
||||
- chore: remove repetitive words in comment ([#6313])
|
||||
- [bugfix] Sqlite transaction escalation was causing errors ([#6299])
|
||||
- DNS static table pre-resolve ([#6297])
|
||||
- Add Copy+Clone to nym_api_provider::Config ([#6296])
|
||||
- [chore] clippy fixes and use fixed rust version from REQUIRED_RUSTC_VERSION ([#6295])
|
||||
- build(deps): bump SonarSource/sonarqube-scan-action from 6 to 7 ([#6294])
|
||||
- build(deps): bump mikefarah/yq from 4.49.2 to 4.50.1 ([#6293])
|
||||
- build(deps): bump actions/upload-artifact from 5 to 6 ([#6292])
|
||||
- build(deps): bump actions/download-artifact from 6 to 7 ([#6291])
|
||||
- build(deps): bump js-yaml from 3.14.1 to 3.14.2 in /documentation/docs ([#6290])
|
||||
- build(deps): bump next from 15.4.9 to 15.4.10 in /nym-node-status-api/nym-node-status-ui ([#6289])
|
||||
- build(deps): bump next from 14.2.33 to 14.2.35 ([#6288])
|
||||
- LP Registration + Telescoping + Gateway Probe Localnet Mode ([#6286])
|
||||
- build(deps): bump next from 15.5.7 to 15.5.9 in /documentation/docs ([#6285])
|
||||
- build(deps): bump next from 15.4.7 to 15.4.9 in /nym-node-status-api/nym-node-status-ui ([#6284])
|
||||
- Minor DNS improvements ([#6283])
|
||||
- HTTP client without default features ([#6281])
|
||||
- DNS: reduce number of attempts ([#6278])
|
||||
- [bugfix] use proper mixing delay instead of poisson delay in cover traffic ([#6269])
|
||||
- build(deps): bump node-forge from 1.3.1 to 1.3.3 in /wasm/zknym-lib/internal-dev ([#6261])
|
||||
- build(deps-dev): bump node-forge from 1.3.1 to 1.3.3 in /wasm/mix-fetch/internal-dev ([#6260])
|
||||
- build(deps-dev): bump node-forge from 1.3.1 to 1.3.2 in /wasm/client/internal-dev ([#6251])
|
||||
- build(deps): bump node-forge from 1.3.1 to 1.3.2 in /nym-credential-proxy/vpn-api-lib-wasm/internal-dev ([#6250])
|
||||
- [Feature] Fallback gateway listener and remove legacy key support ([#6249])
|
||||
- build(deps-dev): bump node-forge from 1.3.0 to 1.3.2 in /clients/native/examples/js-examples/websocket ([#6248])
|
||||
- build(deps): bump node-forge from 1.3.1 to 1.3.2 ([#6246])
|
||||
- build(deps): bump pnpm/action-setup from 4.1.0 to 4.2.0 ([#6245])
|
||||
- build(deps): bump actions/download-artifact from 5 to 6 ([#6244])
|
||||
- build(deps): bump actions/checkout from 4 to 6 ([#6243])
|
||||
- build(deps): bump mikefarah/yq from 4.48.1 to 4.49.2 ([#6242])
|
||||
- build(deps): bump actions/upload-artifact from 4 to 5 ([#6241])
|
||||
- fix: fix assertion ([#6238])
|
||||
- Initial changes to support extra configurable parameters and to print… ([#6237])
|
||||
- Data Observatory ([#6172])
|
||||
|
||||
[#6377]: https://github.com/nymtech/nym/pull/6377
|
||||
[#6364]: https://github.com/nymtech/nym/pull/6364
|
||||
[#6348]: https://github.com/nymtech/nym/pull/6348
|
||||
[#6316]: https://github.com/nymtech/nym/pull/6316
|
||||
[#6314]: https://github.com/nymtech/nym/pull/6314
|
||||
[#6313]: https://github.com/nymtech/nym/pull/6313
|
||||
[#6299]: https://github.com/nymtech/nym/pull/6299
|
||||
[#6297]: https://github.com/nymtech/nym/pull/6297
|
||||
[#6296]: https://github.com/nymtech/nym/pull/6296
|
||||
[#6295]: https://github.com/nymtech/nym/pull/6295
|
||||
[#6294]: https://github.com/nymtech/nym/pull/6294
|
||||
[#6293]: https://github.com/nymtech/nym/pull/6293
|
||||
[#6292]: https://github.com/nymtech/nym/pull/6292
|
||||
[#6291]: https://github.com/nymtech/nym/pull/6291
|
||||
[#6290]: https://github.com/nymtech/nym/pull/6290
|
||||
[#6289]: https://github.com/nymtech/nym/pull/6289
|
||||
[#6288]: https://github.com/nymtech/nym/pull/6288
|
||||
[#6286]: https://github.com/nymtech/nym/pull/6286
|
||||
[#6285]: https://github.com/nymtech/nym/pull/6285
|
||||
[#6284]: https://github.com/nymtech/nym/pull/6284
|
||||
[#6283]: https://github.com/nymtech/nym/pull/6283
|
||||
[#6281]: https://github.com/nymtech/nym/pull/6281
|
||||
[#6278]: https://github.com/nymtech/nym/pull/6278
|
||||
[#6269]: https://github.com/nymtech/nym/pull/6269
|
||||
[#6261]: https://github.com/nymtech/nym/pull/6261
|
||||
[#6260]: https://github.com/nymtech/nym/pull/6260
|
||||
[#6251]: https://github.com/nymtech/nym/pull/6251
|
||||
[#6250]: https://github.com/nymtech/nym/pull/6250
|
||||
[#6249]: https://github.com/nymtech/nym/pull/6249
|
||||
[#6248]: https://github.com/nymtech/nym/pull/6248
|
||||
[#6246]: https://github.com/nymtech/nym/pull/6246
|
||||
[#6245]: https://github.com/nymtech/nym/pull/6245
|
||||
[#6244]: https://github.com/nymtech/nym/pull/6244
|
||||
[#6243]: https://github.com/nymtech/nym/pull/6243
|
||||
[#6242]: https://github.com/nymtech/nym/pull/6242
|
||||
[#6241]: https://github.com/nymtech/nym/pull/6241
|
||||
[#6238]: https://github.com/nymtech/nym/pull/6238
|
||||
[#6237]: https://github.com/nymtech/nym/pull/6237
|
||||
[#6172]: https://github.com/nymtech/nym/pull/6172
|
||||
|
||||
## [2026.1-niolo] (2026-01-13)
|
||||
|
||||
- bugfix: mozzarella -> niolo config migration ([#6259])
|
||||
|
||||
@@ -335,9 +335,36 @@ fn process_final_hop(
|
||||
packet_type: PacketType,
|
||||
key_rotation: SphinxKeyRotation,
|
||||
) -> Result<MixProcessingResultData, PacketProcessingError> {
|
||||
let payload_size = payload.len();
|
||||
debug!(
|
||||
"process_final_hop: payload_size={}, packet_size={:?}, packet_type={:?}, destination={}",
|
||||
payload_size, packet_size, packet_type, destination
|
||||
);
|
||||
|
||||
let (forward_ack, message) =
|
||||
split_into_ack_and_message(payload, packet_size, packet_type, key_rotation)?;
|
||||
|
||||
let message_size = message.len();
|
||||
let ack_present = forward_ack.is_some();
|
||||
debug!(
|
||||
"process_final_hop: after split - message_size={}, ack_present={}, payload_size={}",
|
||||
message_size, ack_present, payload_size
|
||||
);
|
||||
|
||||
// Verify expected message size
|
||||
if let PacketSize::RegularPacket = packet_size {
|
||||
use nym_sphinx_addressing::nodes::MAX_NODE_ADDRESS_UNPADDED_LEN;
|
||||
let sphinx_ack_overhead = PacketSize::AckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN;
|
||||
let expected_message_size = packet_size.plaintext_size() - sphinx_ack_overhead;
|
||||
if message_size != expected_message_size {
|
||||
use tracing::warn;
|
||||
warn!(
|
||||
"process_final_hop: MESSAGE SIZE MISMATCH! message_size={}, expected={}, payload_size={}, ack_present={}",
|
||||
message_size, expected_message_size, payload_size, ack_present
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(MixProcessingResultData::FinalHop {
|
||||
final_hop_data: ProcessedFinalHop {
|
||||
destination,
|
||||
|
||||
@@ -278,6 +278,31 @@ impl<R, S> FreshHandler<R, S> {
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
// Log message sizes before sending to client
|
||||
for (idx, packet) in packets.iter().enumerate() {
|
||||
use nym_sphinx::addressing::nodes::MAX_NODE_ADDRESS_UNPADDED_LEN;
|
||||
use nym_sphinx::params::PacketSize;
|
||||
let sphinx_ack_overhead = PacketSize::AckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN;
|
||||
let expected_size = PacketSize::RegularPacket.plaintext_size() - sphinx_ack_overhead;
|
||||
|
||||
debug!(
|
||||
"push_packets_to_client: packet[{}] size={}, expected_regular_size={}, ack_overhead={}",
|
||||
idx,
|
||||
packet.len(),
|
||||
expected_size,
|
||||
sphinx_ack_overhead
|
||||
);
|
||||
|
||||
if packet.len() == PacketSize::RegularPacket.plaintext_size() {
|
||||
warn!(
|
||||
"push_packets_to_client: WARNING - packet[{}] has full plaintext size ({}), expected {} (after ACK removal)",
|
||||
idx,
|
||||
packet.len(),
|
||||
expected_size
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// note: into_ws_message encrypts the requests and adds a MAC on it. Perhaps it should
|
||||
// be more explicit in the naming?
|
||||
let messages: Vec<Result<Message, WsError>> = packets
|
||||
@@ -496,6 +521,7 @@ impl<R, S> FreshHandler<R, S> {
|
||||
error!("{incompatible_err}");
|
||||
Err(incompatible_err)
|
||||
} else {
|
||||
// Return the client's version for backwards compatibility.. temporary solution.
|
||||
debug!("the client is using exactly the same (or older) protocol version as we are. We're good to continue!");
|
||||
Ok(client_protocol_version)
|
||||
}
|
||||
@@ -717,9 +743,9 @@ impl<R, S> FreshHandler<R, S> {
|
||||
}
|
||||
|
||||
pub(crate) fn handle_supported_protocol_request(&self) -> ServerResponse {
|
||||
debug!("returning gateway protocol version");
|
||||
use nym_gateway_requests::EMBEDDED_KEY_ROTATION_INFO_VERSION;
|
||||
ServerResponse::SupportedProtocol {
|
||||
version: CURRENT_PROTOCOL_VERSION,
|
||||
version: EMBEDDED_KEY_ROTATION_INFO_VERSION,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -846,11 +846,23 @@ impl MixnetListener {
|
||||
| AuthenticatorVersion::V1
|
||||
| AuthenticatorVersion::V2
|
||||
| AuthenticatorVersion::V3
|
||||
| AuthenticatorVersion::V4
|
||||
| AuthenticatorVersion::V5 => {
|
||||
// pre v6 this message hasn't existed
|
||||
| AuthenticatorVersion::V4 => {
|
||||
// pre v5 this message hasn't existed
|
||||
return Err(AuthenticatorError::UnknownVersion);
|
||||
}
|
||||
AuthenticatorVersion::V5 => {
|
||||
// V5 clients shouldn't send this message, but for backward compatibility
|
||||
// with V5 clients connecting to V6 gateways, we allow it and return
|
||||
// a V6-style response (V5 clients won't understand it, but at least we don't error)
|
||||
// This enables V5 clients from mainnet (release/2026.1-kiwi) to work with
|
||||
// V6 gateways (release/2026.2-oscypek) during the transition period
|
||||
v6::response::AuthenticatorResponse::new_upgrade_mode_check(
|
||||
request_id,
|
||||
self.upgrade_mode_enabled(),
|
||||
)
|
||||
.to_bytes()
|
||||
.map_err(AuthenticatorError::response_serialisation)?
|
||||
}
|
||||
AuthenticatorVersion::V6 => {
|
||||
v6::response::AuthenticatorResponse::new_upgrade_mode_check(
|
||||
request_id,
|
||||
|
||||
@@ -151,6 +151,29 @@ impl ConnectionHandler {
|
||||
|
||||
let client = final_hop_data.destination;
|
||||
let message = final_hop_data.message;
|
||||
let message_size = message.len();
|
||||
|
||||
// Log message size for debugging
|
||||
use nym_sphinx_addressing::nodes::MAX_NODE_ADDRESS_UNPADDED_LEN;
|
||||
use nym_sphinx_params::PacketSize;
|
||||
let sphinx_ack_overhead = PacketSize::AckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN;
|
||||
let expected_size = PacketSize::RegularPacket.plaintext_size() - sphinx_ack_overhead;
|
||||
|
||||
debug!(
|
||||
"handle_final_hop: client={}, message_size={}, expected_regular_size={}, ack_overhead={}, forward_ack_present={}",
|
||||
client,
|
||||
message_size,
|
||||
expected_size,
|
||||
sphinx_ack_overhead,
|
||||
final_hop_data.forward_ack.is_some()
|
||||
);
|
||||
|
||||
if message_size == PacketSize::RegularPacket.plaintext_size() {
|
||||
warn!(
|
||||
"handle_final_hop: WARNING - message has full plaintext size ({}), expected {} (after ACK removal)",
|
||||
message_size, expected_size
|
||||
);
|
||||
}
|
||||
|
||||
// if possible attempt to push message directly to the client
|
||||
match self.shared.try_push_message_to_client(client, message) {
|
||||
|
||||
@@ -75,7 +75,9 @@ 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 => two_hop_debug_config(&self.mixnet_client_config),
|
||||
RegistrationMode::Wireguard | RegistrationMode::Lp => {
|
||||
two_hop_debug_config(&self.mixnet_client_config)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +121,7 @@ impl BuilderConfig {
|
||||
let debug_config = self.mixnet_client_debug_config();
|
||||
let remember_me = match self.mode {
|
||||
RegistrationMode::Mixnet => RememberMe::new_mixnet(),
|
||||
RegistrationMode::Wireguard => RememberMe::new_vpn(),
|
||||
RegistrationMode::Wireguard | RegistrationMode::Lp => RememberMe::new_vpn(),
|
||||
};
|
||||
|
||||
let identity = self.entry_node.node.identity.to_string();
|
||||
|
||||
@@ -8,8 +8,22 @@ use crate::builder::config::NymNodeWithKeys;
|
||||
pub enum RegistrationMode {
|
||||
/// 5-hop mixnet with IPR (IP Packet Router)
|
||||
Mixnet,
|
||||
/// 2-hop WireGuard
|
||||
/// 2-hop WireGuard with authenticator
|
||||
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 {
|
||||
|
||||
@@ -74,6 +74,25 @@ 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 {
|
||||
|
||||
+162
-134
@@ -1,15 +1,17 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use config::RegistrationClientConfig;
|
||||
use nym_authenticator_client::AuthClientMixnetListenerHandle;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::config::RegistrationClientConfig;
|
||||
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 tokio_util::sync::CancellationToken;
|
||||
use std::sync::Arc;
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
mod builder;
|
||||
mod config;
|
||||
@@ -38,66 +40,22 @@ 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, RegistrationError> {
|
||||
async fn register_mix_exit(self) -> Result<RegistrationResult, RegistrationClientError> {
|
||||
let entry_mixnet_gateway_ip = self.config.entry.node.ip_address;
|
||||
|
||||
let exit_mixnet_gateway_ip = self.config.exit.node.ip_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))
|
||||
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)
|
||||
.await
|
||||
{
|
||||
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,
|
||||
});
|
||||
}
|
||||
};
|
||||
.map_err(RegistrationClientError::ConnectToIpPacketRouter)?;
|
||||
|
||||
Ok(RegistrationResult::Mixnet(Box::new(
|
||||
MixnetRegistrationResult {
|
||||
@@ -114,24 +72,18 @@ impl RegistrationClient {
|
||||
)))
|
||||
}
|
||||
|
||||
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(),
|
||||
},
|
||||
});
|
||||
};
|
||||
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(),
|
||||
},
|
||||
)?;
|
||||
|
||||
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 exit_auth_address = self.config.exit.node.authenticator_address.ok_or(
|
||||
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}");
|
||||
@@ -141,8 +93,7 @@ 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.child_token())
|
||||
.start();
|
||||
AuthClientMixnetListener::new(self.mixnet_client, self.cancel_token.clone()).start();
|
||||
|
||||
let mut entry_auth_client = AuthenticatorClient::new(
|
||||
mixnet_listener.subscribe(),
|
||||
@@ -169,50 +120,24 @@ impl RegistrationClient {
|
||||
let exit_fut = exit_auth_client
|
||||
.register_wireguard(&*self.bandwidth_controller, TicketType::V1WireguardExit);
|
||||
|
||||
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, exit) = Box::pin(async { tokio::join!(entry_fut, exit_fut) }).await;
|
||||
|
||||
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,
|
||||
),
|
||||
});
|
||||
}
|
||||
};
|
||||
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)
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(RegistrationResult::Wireguard(Box::new(
|
||||
WireguardRegistrationResult {
|
||||
@@ -226,22 +151,125 @@ impl RegistrationClient {
|
||||
)))
|
||||
}
|
||||
|
||||
pub async fn register(self) -> Result<RegistrationResult, RegistrationClientError> {
|
||||
let registration_result = match self.config.mode {
|
||||
RegistrationMode::Mixnet => self.register_mix_exit().await,
|
||||
RegistrationMode::Wireguard => self.register_wg().await,
|
||||
};
|
||||
async fn register_lp(self) -> Result<RegistrationResult, RegistrationClientError> {
|
||||
use crate::lp_client::{LpRegistrationClient, NestedLpSession};
|
||||
|
||||
// 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)
|
||||
// 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)?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,4 +38,5 @@ mod nested_session;
|
||||
|
||||
pub use client::LpRegistrationClient;
|
||||
pub use config::LpConfig;
|
||||
pub use error::LpClientError;
|
||||
pub use nested_session::NestedLpSession;
|
||||
|
||||
Reference in New Issue
Block a user