moved LP to nym-node crate

This commit is contained in:
Jędrzej Stuczyński
2026-02-26 15:40:49 +00:00
parent 9c271d7344
commit 0f8598ad52
30 changed files with 435 additions and 387 deletions
Generated
+7 -4
View File
@@ -4121,10 +4121,11 @@ dependencies = [
"nym-credential-verification",
"nym-credentials-interface",
"nym-crypto",
"nym-gateway",
"nym-kkt",
"nym-kkt-ciphersuite",
"nym-lp",
"nym-lp-transport",
"nym-node",
"nym-registration-client",
"nym-test-utils",
"nym-wireguard",
@@ -6392,7 +6393,6 @@ dependencies = [
"bincode",
"bip39",
"bs58",
"bytes",
"dashmap",
"defguard_wireguard_rs",
"fastrand",
@@ -6412,9 +6412,7 @@ dependencies = [
"nym-gateway-storage",
"nym-id",
"nym-ip-packet-router",
"nym-kcp",
"nym-lp",
"nym-lp-transport",
"nym-metrics",
"nym-mixnet-client",
"nym-network-defaults",
@@ -7166,6 +7164,7 @@ dependencies = [
"arrayref",
"async-trait",
"axum 0.7.9",
"bincode",
"bip39",
"blake2 0.8.1",
"bloomfilter",
@@ -7180,6 +7179,7 @@ dependencies = [
"criterion",
"csv",
"cupid",
"dashmap",
"futures",
"hex",
"hkdf",
@@ -7199,6 +7199,8 @@ dependencies = [
"nym-http-api-common",
"nym-ip-packet-router",
"nym-kkt",
"nym-lp",
"nym-lp-transport",
"nym-metrics",
"nym-mixnet-client",
"nym-network-requester",
@@ -7208,6 +7210,7 @@ dependencies = [
"nym-noise-keys",
"nym-nonexhaustive-delayqueue",
"nym-pemstore",
"nym-registration-common",
"nym-sphinx-acknowledgements",
"nym-sphinx-addressing",
"nym-sphinx-forwarding",
+2 -1
View File
@@ -119,7 +119,8 @@ sdk-typescript-build:
yarn --cwd sdk/typescript/codegen/contract-clients build
# NOTE: These targets are part of the main workspace (but not as wasm32-unknown-unknown)
WASM_CRATES = extension-storage nym-client-wasm nym-node-tester-wasm zknym-lib
# WASM_CRATES = extension-storage nym-client-wasm nym-node-tester-wasm zknym-lib
WASM_CRATES = nym-client-wasm nym-node-tester-wasm
sdk-wasm-test:
#cargo test $(addprefix -p , $(WASM_CRATES)) --target wasm32-unknown-unknown -- -Dwarnings
-6
View File
@@ -78,13 +78,7 @@ nym-authenticator-requests = { workspace = true }
nym-client-core = { workspace = true, features = ["cli"] }
nym-id = { workspace = true }
nym-service-provider-requests-common = { workspace = true }
# LP dependencies
nym-lp = { path = "../common/nym-lp" }
nym-lp-transport = { path = "../common/nym-lp-transport" }
nym-kcp = { path = "../common/nym-kcp" }
nym-registration-common = { path = "../common/registration" }
bytes = { workspace = true }
defguard_wireguard_rs = { workspace = true }
-4
View File
@@ -15,8 +15,6 @@ pub struct Config {
pub upgrade_mode_watcher: UpgradeModeWatcher,
pub lp: crate::node::lp_listener::LpConfig,
pub debug: Debug,
}
@@ -26,7 +24,6 @@ impl Config {
network_requester: impl Into<NetworkRequester>,
ip_packet_router: impl Into<IpPacketRouter>,
upgrade_mode_watcher: impl Into<UpgradeModeWatcher>,
lp: impl Into<crate::node::lp_listener::LpConfig>,
debug: impl Into<Debug>,
) -> Self {
Config {
@@ -34,7 +31,6 @@ impl Config {
network_requester: network_requester.into(),
ip_packet_router: ip_packet_router.into(),
upgrade_mode_watcher: upgrade_mode_watcher.into(),
lp: lp.into(),
debug: debug.into(),
}
}
-10
View File
@@ -3,7 +3,6 @@
pub use crate::node::client_handling::websocket::connection_handler::authenticated::RequestHandlingError;
use crate::node::internal_service_providers::authenticator::error::AuthenticatorError;
use crate::node::lp_listener::error::LpHandlerError;
use crate::node::wireguard::GatewayWireguardError;
use nym_credential_verification::upgrade_mode::UpgradeModeEnableError;
use nym_gateway_stats_storage::error::StatsStorageError;
@@ -134,12 +133,6 @@ pub enum GatewayError {
#[error("Internal error: {0}")]
InternalError(String),
#[error("Failed to bind listener to {address}: {source}")]
ListenerBindFailure {
address: String,
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("Failed to parse ip address: {source}")]
IpAddrParseError {
#[from]
@@ -154,9 +147,6 @@ pub enum GatewayError {
#[error(transparent)]
WireguardFailure(#[from] GatewayWireguardError),
#[error(transparent)]
LpFailure(#[from] LpHandlerError),
}
impl From<ClientCoreError> for GatewayError {
+5 -46
View File
@@ -18,7 +18,6 @@ use nym_credential_verification::upgrade_mode::{
};
use nym_crypto::asymmetric::ed25519;
use nym_ip_packet_router::IpPacketRouter;
use nym_lp::peer::{DHKeyPair, KEMKeys, LpLocalPeer};
use nym_mixnet_client::forwarder::MixForwardingSender;
use nym_network_defaults::NymNetworkDetails;
use nym_network_requester::NRServiceProviderBuilder;
@@ -33,14 +32,12 @@ use rand::thread_rng;
use std::net::IpAddr;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::Semaphore;
use tracing::*;
use zeroize::Zeroizing;
pub use crate::node::upgrade_mode::watcher::UpgradeModeWatcher;
use crate::node::wireguard::{PeerManager, PeerRegistrator};
pub use client_handling::active_clients::ActiveClientsStore;
pub use lp_listener::LpConfig;
pub use nym_credential_verification::upgrade_mode::UpgradeModeCheckRequestSender;
pub use nym_gateway_stats_storage::PersistentStatsStorage;
pub use nym_gateway_storage::{
@@ -48,12 +45,10 @@ pub use nym_gateway_storage::{
traits::{BandwidthGatewayStorage, InboxGatewayStorage},
GatewayStorage,
};
use nym_lp::LpSession;
pub use nym_sdk::{NymApiTopologyProvider, NymApiTopologyProviderConfig, UserAgent};
pub(crate) mod client_handling;
pub(crate) mod internal_service_providers;
pub mod lp_listener;
mod stale_data_cleaner;
pub mod upgrade_mode;
pub mod wireguard;
@@ -96,12 +91,6 @@ pub struct GatewayTasksBuilder {
/// ed25519 keypair used to assert one's identity.
identity_keypair: Arc<ed25519::KeyPair>,
/// x25519 keypair used within KTT exchange
x25519_keypair: Arc<DHKeyPair>,
/// x25519 (for now, to be changed into MlKem) keypair used for the PSQ derivation
kem_psq_keys: KEMKeys,
storage: GatewayStorage,
mix_packet_sender: MixForwardingSender,
@@ -117,6 +106,8 @@ pub struct GatewayTasksBuilder {
shutdown_tracker: ShutdownTracker,
// populated and cached as necessary
use_mock_ecash: bool,
ecash_manager:
Option<Arc<dyn nym_credential_verification::ecash::traits::EcashManager + Send + Sync>>,
@@ -130,8 +121,6 @@ impl GatewayTasksBuilder {
pub fn new(
config: Config,
identity: Arc<ed25519::KeyPair>,
x25519: Arc<DHKeyPair>,
kem_psq_keys: KEMKeys,
storage: GatewayStorage,
mix_packet_sender: MixForwardingSender,
metrics_sender: MetricEventsSender,
@@ -139,6 +128,7 @@ impl GatewayTasksBuilder {
mnemonic: Arc<Zeroizing<bip39::Mnemonic>>,
user_agent: UserAgent,
upgrade_mode_state: UpgradeModeState,
use_mock_ecash: bool,
shutdown_tracker: ShutdownTracker,
) -> GatewayTasksBuilder {
GatewayTasksBuilder {
@@ -149,8 +139,6 @@ impl GatewayTasksBuilder {
wireguard_data: None,
user_agent,
identity_keypair: identity,
x25519_keypair: x25519,
kem_psq_keys,
storage,
mix_packet_sender,
metrics_sender,
@@ -158,6 +146,7 @@ impl GatewayTasksBuilder {
upgrade_mode_state,
mnemonic,
shutdown_tracker,
use_mock_ecash,
ecash_manager: None,
wireguard_peers: None,
wireguard_networks: None,
@@ -236,7 +225,7 @@ impl GatewayTasksBuilder {
GatewayError,
> {
// Check if we should use mock ecash for testing
if self.config.lp.debug.use_mock_ecash {
if self.use_mock_ecash {
warn!("Using MockEcashManager for LP testing (credentials NOT verified)");
let mock_manager = MockEcashManager::new(Box::new(self.storage.clone()));
return Ok(Arc::new(mock_manager)
@@ -345,36 +334,6 @@ impl GatewayTasksBuilder {
))
}
pub async fn build_lp_listener(
&mut self,
peer_registrator: Option<PeerRegistrator>,
active_clients_store: ActiveClientsStore,
) -> Result<lp_listener::LpListener, GatewayError> {
let handler_state = lp_listener::LpHandlerState {
ecash_verifier: self.ecash_manager().await?,
storage: self.storage.clone(),
local_lp_peer: LpLocalPeer::new(
LpSession::default_ciphersuite(),
self.x25519_keypair.clone(),
)
.with_kem_keys(self.kem_psq_keys.clone()),
metrics: self.metrics.clone(),
active_clients_store,
peer_registrator,
lp_config: self.config.lp,
outbound_mix_sender: self.mix_packet_sender.clone(),
session_states: Arc::new(dashmap::DashMap::new()),
forward_semaphore: Arc::new(Semaphore::new(
self.config.lp.debug.max_concurrent_forwards,
)),
};
Ok(lp_listener::LpListener::new(
handler_state,
self.shutdown_tracker.clone(),
))
}
fn build_network_requester(
&mut self,
topology_provider: Box<dyn TopologyProvider + Send + Sync>,
@@ -1,7 +1,6 @@
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::node::lp_listener::ReceiverIndex;
use crate::node::wireguard::new_peer_registration::pending::{
PendingRegistration, PendingRegistrationData,
};
@@ -58,9 +57,10 @@ impl PeerRegistrator {
pub(super) async fn check_pending_lp_registration(
&self,
sender: ReceiverIndex,
receiver_index: u64,
) -> Result<Option<LpRegistrationResponse>, GatewayWireguardError> {
let Some(pending_registration) = self.pending_registrations.check_lp(sender).await else {
let Some(pending_registration) = self.pending_registrations.check_lp(receiver_index).await
else {
return Ok(None);
};
@@ -104,7 +104,7 @@ impl PeerRegistrator {
pub(super) async fn process_fresh_initial_lp_registration(
&self,
sender: ReceiverIndex,
receiver_index: u64,
remote_public: PeerPublicKey,
psk: Key,
) -> Result<LpRegistrationResponse, GatewayWireguardError> {
@@ -121,7 +121,7 @@ impl PeerRegistrator {
.lp
.write()
.await
.insert(sender, pending);
.insert(receiver_index, pending);
Ok(response)
}
@@ -11,7 +11,6 @@
//! 2. Finalisation request message is received, where credential has to be attached is verified.
//! Upon successful completion, pending registration is transformed into a properly inserted peer.
use crate::node::lp_listener::ReceiverIndex;
use crate::node::wireguard::new_peer_registration::pending::{
PendingRegistration, PendingRegistrations,
};
@@ -225,7 +224,7 @@ impl PeerRegistrator {
Ok(())
}
pub(crate) async fn on_initial_authenticator_request(
pub async fn on_initial_authenticator_request(
&mut self,
init_message: Box<dyn InitMessage + Send + Sync + 'static>,
protocol: Protocol,
@@ -262,7 +261,7 @@ impl PeerRegistrator {
.await
}
pub(crate) async fn on_final_authenticator_request(
pub async fn on_final_authenticator_request(
&mut self,
final_message: Box<dyn FinalMessage + Send + Sync + 'static>,
protocol: Protocol,
@@ -307,10 +306,10 @@ impl PeerRegistrator {
)
}
pub(crate) async fn on_initial_lp_request(
pub async fn on_initial_lp_request(
&self,
init_msg: LpDvpnRegistrationInitialRequest,
sender: ReceiverIndex,
receiver_index: u64,
) -> Result<LpRegistrationResponse, GatewayWireguardError> {
let remote_public = init_msg.wg_public_key;
let psk = Key::new(init_msg.psk);
@@ -318,7 +317,9 @@ impl PeerRegistrator {
// 1. check if there's any pending registration already in progress,
// if so, return the same data again without additional processing,
// but update stored PSK
if let Some(pending_registration) = self.check_pending_lp_registration(sender).await? {
if let Some(pending_registration) =
self.check_pending_lp_registration(receiver_index).await?
{
self.update_peer_psk(remote_public, psk).await?;
return Ok(pending_registration);
}
@@ -332,19 +333,19 @@ impl PeerRegistrator {
}
// 3. process fresh registration request
self.process_fresh_initial_lp_registration(sender, remote_public, psk)
self.process_fresh_initial_lp_registration(receiver_index, remote_public, psk)
.await
}
pub(crate) async fn on_final_lp_request(
pub async fn on_final_lp_request(
&self,
final_msg: LpDvpnRegistrationFinalisation,
sender: ReceiverIndex,
receiver_index: u64,
) -> Result<LpRegistrationResponse, GatewayWireguardError> {
// 1. check if there's any pending registration associated with this peer
let pending_data = self
.pending_registrations
.check_lp(sender)
.check_lp(receiver_index)
.await
.ok_or(GatewayWireguardError::RegistrationNotInProgress)?
.clone();
@@ -356,7 +357,7 @@ impl PeerRegistrator {
.await?;
// 3 remove pending registration
self.pending_registrations.remove_lp(sender).await;
self.pending_registrations.remove_lp(receiver_index).await;
// 4. construct and return the response
Ok(pending_data.to_registered_lp_response(self.upgrade_mode_enabled()))
@@ -1,7 +1,6 @@
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::node::lp_listener::ReceiverIndex;
use crate::node::wireguard::new_peer_registration::helpers::{
build_final_authenticator_response, build_pending_authenticator_response,
};
@@ -116,9 +115,8 @@ pub(crate) struct PendingRegistrations {
pub(crate) authenticator: Arc<RwLock<HashMap<PeerPublicKey, PendingRegistration>>>,
/// Registrations in progress received from the LP Listener via the
/// [`crate::node::lp_listener::handler::LpConnectionHandler`] and handle through
/// [`crate::node::lp_listener::registration::LpHandlerState`]
pub(crate) lp: Arc<RwLock<HashMap<ReceiverIndex, PendingRegistration>>>,
/// `LpConnectionHandler` and handle through `LpHandlerState`
pub(crate) lp: Arc<RwLock<HashMap<u64, PendingRegistration>>>,
}
impl PendingRegistrations {
@@ -133,14 +131,11 @@ impl PendingRegistrations {
self.authenticator.write().await.remove(peer);
}
pub(crate) async fn remove_lp(&self, receiver_index: ReceiverIndex) {
pub(crate) async fn remove_lp(&self, receiver_index: u64) {
self.lp.write().await.remove(&receiver_index);
}
pub(crate) async fn check_lp(
&self,
receiver_index: ReceiverIndex,
) -> Option<PendingRegistration> {
pub(crate) async fn check_lp(&self, receiver_index: u64) -> Option<PendingRegistration> {
self.lp.read().await.get(&receiver_index).cloned()
}
+2 -1
View File
@@ -23,7 +23,8 @@ nym-credentials-interface = { path = "../common/credentials-interface" }
nym-test-utils = { path = "../common/test-utils" }
nym-registration-client = { path = "../nym-registration-client" }
nym-lp-transport = { path = "../common/nym-lp-transport", features = ["io-mocks"] }
nym-gateway = { path = "../gateway" }
nym-node = { path = "../nym-node" }
nym-lp = { path = "../common/nym-lp" }
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite"] }
tracing = { workspace = true }
futures = { workspace = true }
+10 -19
View File
@@ -9,18 +9,18 @@ mod tests {
use nym_credential_verification::upgrade_mode::testing::mock_dummy_upgrade_mode_details;
use nym_credentials_interface::TicketType;
use nym_crypto::asymmetric::{ed25519, x25519};
use nym_gateway::node::lp_listener::error::LpHandlerError;
use nym_gateway::node::lp_listener::handler::LpConnectionHandler;
use nym_gateway::node::lp_listener::{
KEMKeys, LpDebug, LpHandlerState, LpLocalPeer, MixForwardingReceiver, PeerControlRequest,
WireguardGatewayData, mix_forwarding_channels,
};
use nym_gateway::node::wireguard::{PeerManager, PeerRegistrator};
use nym_gateway::node::{ActiveClientsStore, GatewayStorage, LpConfig};
use nym_kkt::key_utils::{
generate_keypair_mceliece, generate_keypair_mlkem, generate_lp_keypair_x25519,
};
use nym_kkt::keys::KEMKeys;
use nym_kkt_ciphersuite::Ciphersuite;
use nym_lp::peer::LpLocalPeer;
use nym_node::config::{LpConfig, LpDebug};
use nym_node::node::GatewayStorage;
use nym_node::node::lp::error::LpHandlerError;
use nym_node::node::lp::handler::LpConnectionHandler;
use nym_node::node::lp::{LpHandlerState, MixForwardingReceiver, mix_forwarding_channels};
use nym_node::wireguard::{PeerManager, PeerRegistrator};
use nym_registration_client::{LpClientError, LpRegistrationClient};
use nym_test_utils::helpers::{CryptoRng09, seeded_rng};
use nym_test_utils::mocks::async_read_write::MockIOStream;
@@ -30,7 +30,7 @@ mod tests {
Key, KeyWrapper, MockPeerController, MockPeerControllerState, PeerControlRequestType,
RegisteredResponse, mock_peer_controller,
};
use nym_wireguard::{IpPool, WireguardConfig};
use nym_wireguard::{IpPool, PeerControlRequest, WireguardConfig, WireguardGatewayData};
use std::mem;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use std::sync::Arc;
@@ -232,19 +232,10 @@ mod tests {
);
let lp_state = LpHandlerState {
// use mock instance of ecash verifier
ecash_verifier,
// use in-memory database (no need for persistency)
storage,
local_lp_peer: base.peer.clone(),
metrics: Default::default(),
// no clients at the beginning
active_clients_store: ActiveClientsStore::new(),
// use default lp config (with enabled flag)
lp_config,
@@ -570,7 +561,7 @@ mod tests {
#[tokio::test]
async fn test_basic_lp_exit_registration() -> anyhow::Result<()> {
nym_test_utils::helpers::setup_test_logger();
// nym_test_utils::helpers::setup_test_logger();
// TODO: update the test once mceliece works
let kem = KEM::MlKem768;
+9
View File
@@ -13,6 +13,8 @@ license = "GPL-3.0"
publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
path = "src/lib.rs"
[dependencies]
async-trait = { workspace = true }
@@ -27,6 +29,7 @@ colored = { workspace = true }
console-subscriber = { workspace = true, optional = true }
csv = { workspace = true }
clap = { workspace = true, features = ["cargo", "env"] }
dashmap = { workspace = true }
futures = { workspace = true }
hex = { workspace = true }
humantime-serde = { workspace = true }
@@ -113,6 +116,12 @@ nym-gateway = { path = "../gateway" }
nym-network-requester = { path = "../service-providers/network-requester" }
nym-ip-packet-router = { path = "../service-providers/ip-packet-router" }
# LP dependencies
nym-lp = { path = "../common/nym-lp" }
nym-lp-transport = { path = "../common/nym-lp-transport" }
nym-registration-common = { path = "../common/registration" }
bincode = { workspace = true }
# throughput tester to recreate lioness
# we don't care about particular versions - just pull whatever is used by sphinx
+7 -2
View File
@@ -2,8 +2,8 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::cli::helpers::{
ConfigArgs, EntryGatewayArgs, ExitGatewayArgs, HostArgs, HttpArgs, MetricsArgs, MixnetArgs,
VerlocArgs, WireguardArgs,
ConfigArgs, EntryGatewayArgs, ExitGatewayArgs, HostArgs, HttpArgs, LpArgs, MetricsArgs,
MixnetArgs, VerlocArgs, WireguardArgs,
};
use crate::config::persistence::NymNodePaths;
use crate::config::{Config, ConfigBuilder, NodeMode, NodeModes};
@@ -125,6 +125,9 @@ pub(crate) struct Args {
#[clap(flatten)]
exit_gateway: ExitGatewayArgs,
#[clap(flatten)]
lp: LpArgs,
}
impl Args {
@@ -174,6 +177,7 @@ impl Args {
.with_metrics(self.metrics.build_config_section())
.with_gateway_tasks(self.entry_gateway.build_config_section(&data_dir)?)
.with_service_providers(self.exit_gateway.build_config_section(&data_dir))
.with_lp(self.lp.build_config_section())
.build()
}
@@ -193,6 +197,7 @@ impl Args {
config.service_providers = self
.exit_gateway
.override_config_section(config.service_providers);
config.lp = self.lp.override_config_section(config.lp);
config
}
}
+59 -12
View File
@@ -458,15 +458,6 @@ pub(crate) struct EntryGatewayArgs {
)]
#[zeroize(skip)]
pub(crate) upgrade_mode_attester_public_key: Option<ed25519::PublicKey>,
/// Use mock ecash manager for LP testing.
/// WARNING: Only use this for local testing! Never enable in production.
/// When enabled, the LP listener will accept any credential without blockchain verification.
#[clap(
long,
env = NYMNODE_LP_USE_MOCK_ECASH_ARG
)]
pub(crate) lp_use_mock_ecash: Option<bool>,
}
impl EntryGatewayArgs {
@@ -500,9 +491,6 @@ impl EntryGatewayArgs {
if let Some(upgrade_mode_attester_public_key) = self.upgrade_mode_attester_public_key {
section.upgrade_mode.attester_public_key = upgrade_mode_attester_public_key
}
if let Some(use_mock_ecash) = self.lp_use_mock_ecash {
section.lp.debug.use_mock_ecash = use_mock_ecash
}
section
}
@@ -549,3 +537,62 @@ impl ExitGatewayArgs {
section
}
}
#[derive(clap::Args, Debug)]
pub(crate) struct LpArgs {
/// Bind address for the TCP LP control traffic.
/// default: `[::]:41264`
#[clap(
long,
env = NYMNODE_LP_CONTROL_BIND_ADDRESS_ARG
)]
pub(crate) lp_control_bind_address: Option<SocketAddr>,
/// Custom announced port for listening for the TCP LP control traffic.
/// If unspecified, the value from the `lp_control_bind_address` will be used instead
#[clap(
long,
env = NYMNODE_LP_CONTROL_ANNOUNCE_PORT_ARG
)]
pub(crate) lp_control_announce_port: Option<u16>,
/// Bind address for the UDP LP data traffic.
/// default: `[::]:51264`
#[clap(
long,
env = NYMNODE_LP_DATA_BIND_ADDRESS_ARG
)]
pub(crate) lp_data_bind_address: Option<SocketAddr>,
/// Custom announced port for listening for the UDP LP data traffic.
/// If unspecified, the value from the `lp_data_bind_address` will be used instead
#[clap(
long,
env = NYMNODE_LP_DATA_ANNOUNCE_PORT_ARG
)]
pub(crate) lp_data_announce_port: Option<u16>,
/// Use mock ecash manager for LP testing.
/// WARNING: Only use this for local testing! Never enable in production.
/// When enabled, the LP listener will accept any credential without blockchain verification.
#[clap(
long,
env = NYMNODE_LP_USE_MOCK_ECASH_ARG
)]
pub(crate) lp_use_mock_ecash: Option<bool>,
}
impl LpArgs {
// TODO: could we perhaps make a clap error here and call `safe_exit` instead?
pub(crate) fn build_config_section(self) -> config::LpConfig {
self.override_config_section(config::LpConfig::default())
}
pub(crate) fn override_config_section(self, mut section: config::LpConfig) -> config::LpConfig {
if let Some(use_mock_ecash) = self.lp_use_mock_ecash {
section.debug.use_mock_ecash = use_mock_ecash
}
section
}
}
-4
View File
@@ -46,9 +46,6 @@ pub struct GatewayTasksConfig {
pub upgrade_mode: UpgradeModeWatcher,
#[serde(default)]
pub lp: nym_gateway::node::LpConfig,
#[serde(default)]
pub debug: Debug,
}
@@ -228,7 +225,6 @@ impl GatewayTasksConfig {
announce_ws_port: None,
announce_wss_port: None,
upgrade_mode: UpgradeModeWatcher::new()?,
lp: Default::default(),
debug: Default::default(),
})
}
-4
View File
@@ -27,7 +27,6 @@ fn ephemeral_gateway_config(config: &Config) -> nym_gateway::config::Config {
enabled: config.service_providers.network_requester.debug.enabled,
},
config.gateway_tasks.upgrade_mode.clone(),
config.gateway_tasks.lp,
nym_gateway::config::Debug {
client_bandwidth_max_flushing_rate: config
.gateway_tasks
@@ -92,8 +91,6 @@ pub struct GatewayTasksConfig {
pub auth_opts: Option<LocalAuthenticatorOpts>,
#[allow(dead_code)]
pub wg_opts: LocalWireguardOpts,
#[allow(dead_code)]
pub lp: nym_gateway::node::LpConfig,
}
// that function is rather disgusting, but I hope it's not going to live for too long
@@ -227,7 +224,6 @@ pub fn gateway_tasks_config(config: &Config) -> GatewayTasksConfig {
ipr_opts: Some(ipr_opts),
auth_opts: Some(auth_opts),
wg_opts,
lp: config.gateway_tasks.lp,
}
}
+153
View File
@@ -0,0 +1,153 @@
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use nym_config::serde_helpers::de_maybe_port;
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
use std::time::Duration;
/// Configuration for LP listener
#[derive(Debug, Clone, Copy, serde::Deserialize, serde::Serialize)]
#[serde(default)]
pub struct LpConfig {
/// Bind address for the TCP LP control traffic.
/// default: `[::]:41264`
pub control_bind_address: SocketAddr,
/// Bind address for the UDP LP data traffic.
/// default: `[::]:51264`
pub data_bind_address: SocketAddr,
/// Custom announced port for listening for the TCP LP control traffic.
/// If unspecified, the value from the `control_bind_address` will be used instead
/// (default: None)
#[serde(deserialize_with = "de_maybe_port")]
pub announce_control_port: Option<u16>,
/// Custom announced port for listening for the UDP LP data traffic.
/// If unspecified, the value from the `data_bind_address` will be used instead
/// (default: None)
#[serde(deserialize_with = "de_maybe_port")]
pub announce_data_port: Option<u16>,
/// Auxiliary configuration
#[serde(default)]
pub debug: LpDebug,
}
#[derive(Debug, Clone, Copy, serde::Deserialize, serde::Serialize)]
#[serde(default)]
pub struct LpDebug {
/// Maximum concurrent connections
pub max_connections: usize,
/// Use mock ecash manager for testing (default: false)
///
/// When enabled, the LP listener will use a mock ecash verifier that
/// accepts any credential without blockchain verification. This is
/// useful for testing the LP protocol implementation without requiring
/// a full blockchain/contract setup.
///
/// WARNING: Only use this for local testing! Never enable in production.
pub use_mock_ecash: bool,
/// Maximum age of in-progress handshakes before cleanup (default: 90s)
///
/// Handshakes should complete quickly (3-5 packets). This TTL accounts for:
/// - Network latency and retransmits
/// - Slow clients
/// - Clock skew tolerance
///
/// Stale handshakes are removed by the cleanup task to prevent memory leaks.
#[serde(with = "humantime_serde")]
pub handshake_ttl: Duration,
/// Maximum age of established sessions before cleanup (default: 24h)
///
/// Sessions can be long-lived for dVPN tunnels. This TTL should be set
/// high enough to accommodate expected usage patterns:
/// - dVPN sessions: hours to days
/// - Registration: minutes
///
/// Sessions with no activity for this duration are removed by the cleanup task.
#[serde(with = "humantime_serde")]
pub session_ttl: Duration,
/// How often to run the state cleanup task (default: 5 minutes)
///
/// The cleanup task scans for and removes stale handshakes and sessions.
/// Lower values = more frequent cleanup but higher overhead.
/// Higher values = less overhead but slower memory reclamation.
#[serde(with = "humantime_serde")]
pub state_cleanup_interval: Duration,
/// Maximum concurrent forward connections (default: 1000)
///
/// Limits simultaneous outbound connections when forwarding LP packets to other gateways
/// during telescope setup. This prevents file descriptor exhaustion under high load.
///
/// When at capacity, new forward requests return an error, signaling the client
/// to choose a different gateway.
pub max_concurrent_forwards: usize,
}
impl LpConfig {
pub const DEFAULT_CONTROL_PORT: u16 = 41264;
pub const DEFAULT_DATA_PORT: u16 = 51264;
pub fn announced_control_port(&self) -> u16 {
self.announce_control_port
.unwrap_or(self.control_bind_address.port())
}
pub fn announced_data_port(&self) -> u16 {
self.announce_data_port
.unwrap_or(self.data_bind_address.port())
}
}
impl Default for LpConfig {
fn default() -> Self {
LpConfig {
control_bind_address: SocketAddr::new(
IpAddr::V6(Ipv6Addr::UNSPECIFIED),
Self::DEFAULT_CONTROL_PORT,
),
data_bind_address: SocketAddr::new(
IpAddr::V6(Ipv6Addr::UNSPECIFIED),
Self::DEFAULT_DATA_PORT,
),
announce_control_port: None,
announce_data_port: None,
debug: Default::default(),
}
}
}
impl LpDebug {
pub const DEFAULT_MAX_CONNECTIONS: usize = 10000;
// 90 seconds - handshakes should complete quickly
pub const DEFAULT_HANDSHAKE_TTL: Duration = Duration::from_secs(90);
// 24 hours - for long-lived dVPN sessions
pub const DEFAULT_SESSION_TTL: Duration = Duration::from_secs(86400);
// 5 minutes - balances memory reclamation with task overhead
pub const DEFAULT_STATE_CLEANUP_INTERVAL: Duration = Duration::from_secs(300);
// Limits concurrent outbound connections to prevent fd exhaustion
pub const DEFAULT_MAX_CONCURRENT_FORWARDS: usize = 1000;
}
impl Default for LpDebug {
fn default() -> Self {
LpDebug {
max_connections: Self::DEFAULT_MAX_CONNECTIONS,
use_mock_ecash: false,
handshake_ttl: Self::DEFAULT_HANDSHAKE_TTL,
session_ttl: Self::DEFAULT_SESSION_TTL,
state_cleanup_interval: Self::DEFAULT_STATE_CLEANUP_INTERVAL,
max_concurrent_forwards: Self::DEFAULT_MAX_CONCURRENT_FORWARDS,
}
}
}
+15 -1
View File
@@ -4,6 +4,7 @@
use crate::config::persistence::{NymNodePaths, ReplayProtectionPaths};
use crate::config::template::CONFIG_TEMPLATE;
use crate::error::NymNodeError;
use crate::node::replay_protection::{bitmap_size, items_in_bloomfilter};
use celes::Country;
use clap::ValueEnum;
use human_repr::HumanCount;
@@ -35,6 +36,7 @@ use url::Url;
pub mod authenticator;
pub mod gateway_tasks;
pub mod helpers;
pub mod lp;
pub mod metrics;
mod old_configs;
pub mod persistence;
@@ -43,9 +45,9 @@ mod template;
pub mod upgrade_helpers;
pub use crate::config::gateway_tasks::GatewayTasksConfig;
pub use crate::config::lp::{LpConfig, LpDebug};
pub use crate::config::metrics::MetricsConfig;
pub use crate::config::service_providers::ServiceProvidersConfig;
use crate::node::replay_protection::{bitmap_size, items_in_bloomfilter};
const DEFAULT_NYMNODES_DIR: &str = "nym-nodes";
@@ -186,6 +188,8 @@ pub struct ConfigBuilder {
pub metrics: Option<MetricsConfig>,
pub lp: Option<LpConfig>,
pub logging: Option<LoggingSettings>,
}
@@ -205,6 +209,7 @@ impl ConfigBuilder {
gateway_tasks: None,
service_providers: None,
metrics: None,
lp: None,
logging: None,
}
}
@@ -234,6 +239,11 @@ impl ConfigBuilder {
self
}
pub fn with_lp(mut self, section: impl Into<Option<LpConfig>>) -> Self {
self.lp = section.into();
self
}
pub fn with_wireguard(mut self, section: impl Into<Option<Wireguard>>) -> Self {
self.wireguard = section.into();
self
@@ -284,6 +294,7 @@ impl ConfigBuilder {
.storage_paths
.unwrap_or_else(|| NymNodePaths::new(&self.data_dir)),
metrics: self.metrics.unwrap_or_default(),
lp: self.lp.unwrap_or_default(),
gateway_tasks,
service_providers: self
.service_providers
@@ -323,6 +334,9 @@ pub struct Config {
pub wireguard: Wireguard,
#[serde(default)]
pub lp: LpConfig,
#[serde(alias = "entry_gateway")]
pub gateway_tasks: GatewayTasksConfig,
@@ -34,8 +34,6 @@ use nym_config::read_config_from_toml_file;
use nym_config::serde_helpers::de_maybe_port;
use nym_crypto::asymmetric::ed25519;
use nym_crypto::asymmetric::ed25519::serde_helpers::bs58_ed25519_pubkey;
use nym_gateway::node::LpConfig;
use nym_gateway::node::lp_listener::LpDebug;
use nym_kkt::key_utils::{
generate_keypair_mceliece, generate_keypair_mlkem, generate_lp_keypair_x25519,
};
@@ -48,6 +46,7 @@ use std::time::Duration;
use tracing::{debug, info, instrument};
use url::Url;
use crate::config::lp::{LpConfig, LpDebug};
pub use unchanged_v12_types::*;
// (while some of those are technically unused, they might be needed in future migrations,
@@ -720,6 +719,20 @@ pub async fn try_upgrade_config_v12<P: AsRef<Path>>(
.public_diffie_hellman_key_file,
},
},
lp: LpConfig {
control_bind_address: old_cfg.gateway_tasks.lp.control_bind_address,
data_bind_address: old_cfg.gateway_tasks.lp.data_bind_address,
announce_control_port: old_cfg.gateway_tasks.lp.announce_control_port,
announce_data_port: old_cfg.gateway_tasks.lp.announce_data_port,
debug: LpDebug {
max_connections: old_cfg.gateway_tasks.lp.debug.max_connections,
use_mock_ecash: old_cfg.gateway_tasks.lp.debug.use_mock_ecash,
handshake_ttl: old_cfg.gateway_tasks.lp.debug.handshake_ttl,
session_ttl: old_cfg.gateway_tasks.lp.debug.session_ttl,
state_cleanup_interval: old_cfg.gateway_tasks.lp.debug.state_cleanup_interval,
max_concurrent_forwards: old_cfg.gateway_tasks.lp.debug.max_concurrent_forwards,
},
},
gateway_tasks: GatewayTasksConfig {
storage_paths: GatewayTasksPaths {
clients_storage: old_cfg.gateway_tasks.storage_paths.clients_storage,
@@ -748,20 +761,6 @@ pub async fn try_upgrade_config_v12<P: AsRef<Path>>(
.expedited_poll_interval,
},
},
lp: LpConfig {
control_bind_address: old_cfg.gateway_tasks.lp.control_bind_address,
data_bind_address: old_cfg.gateway_tasks.lp.data_bind_address,
announce_control_port: old_cfg.gateway_tasks.lp.announce_control_port,
announce_data_port: old_cfg.gateway_tasks.lp.announce_data_port,
debug: LpDebug {
max_connections: old_cfg.gateway_tasks.lp.debug.max_connections,
use_mock_ecash: old_cfg.gateway_tasks.lp.debug.use_mock_ecash,
handshake_ttl: old_cfg.gateway_tasks.lp.debug.handshake_ttl,
session_ttl: old_cfg.gateway_tasks.lp.debug.session_ttl,
state_cleanup_interval: old_cfg.gateway_tasks.lp.debug.state_cleanup_interval,
max_concurrent_forwards: old_cfg.gateway_tasks.lp.debug.max_concurrent_forwards,
},
},
debug: gateway_tasks::Debug {
message_retrieval_limit: old_cfg.gateway_tasks.debug.message_retrieval_limit,
maximum_open_connections: old_cfg.gateway_tasks.debug.maximum_open_connections,
+7 -1
View File
@@ -66,9 +66,15 @@ pub mod vars {
"NYMNODE_UPGRADE_MODE_ATTESTATION_URL";
pub const NYMNODE_UPGRADE_MODE_ATTESTER_PUBKEY_ARG: &str =
"NYMNODE_UPGRADE_MODE_ATTESTER_PUBKEY";
pub const NYMNODE_LP_USE_MOCK_ECASH_ARG: &str = "NYMNODE_LP_USE_MOCK_ECASH";
// exit gateway:
pub const NYMNODE_UPSTREAM_EXIT_POLICY_ARG: &str = "NYMNODE_UPSTREAM_EXIT_POLICY";
pub const NYMNODE_OPEN_PROXY_ARG: &str = "NYMNODE_OPEN_PROXY";
// LP:
pub const NYMNODE_LP_CONTROL_BIND_ADDRESS_ARG: &str = "NYMNODE_LP_CONTROL_BIND_ADDRESS";
pub const NYMNODE_LP_CONTROL_ANNOUNCE_PORT_ARG: &str = "NYMNODE_LP_CONTROL_ANNOUNCE_PORT";
pub const NYMNODE_LP_DATA_BIND_ADDRESS_ARG: &str = "NYMNODE_LP_DATA_BIND_ADDRESS";
pub const NYMNODE_LP_DATA_ANNOUNCE_PORT_ARG: &str = "NYMNODE_LP_DATA_ANNOUNCE_PORT";
pub const NYMNODE_LP_USE_MOCK_ECASH_ARG: &str = "NYMNODE_LP_USE_MOCK_ECASH";
}
+11 -1
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::node::http::error::NymNodeHttpError;
use crate::node::lp::error::LpHandlerError;
use crate::wireguard::error::WireguardError;
use nym_http_api_client::HttpClientError;
use nym_ip_packet_router::error::ClientCoreError;
@@ -9,7 +10,7 @@ use nym_kkt::keys::storage_wrappers::MalformedStoredKeyError;
use nym_validator_client::ValidatorClientError;
use nym_validator_client::nyxd::error::NyxdError;
use std::io;
use std::net::IpAddr;
use std::net::{IpAddr, SocketAddr};
use std::path::PathBuf;
use thiserror::Error;
@@ -235,6 +236,15 @@ pub enum NymNodeError {
#[error(transparent)]
MalformedStoredKey(#[from] MalformedStoredKeyError),
#[error("failed to bind LP to {address}: {source}")]
LpBindFailure {
address: SocketAddr,
source: io::Error,
},
#[error(transparent)]
LpFailure(#[from] LpHandlerError),
}
impl From<EntryGatewayError> for NymNodeError {
+10
View File
@@ -0,0 +1,10 @@
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// allow dead code in the lib imports
#![allow(dead_code)]
pub mod config;
pub mod error;
pub mod node;
pub mod wireguard;
+4 -4
View File
@@ -10,13 +10,13 @@ use nym_bin_common::logging::maybe_print_banner;
use nym_config::defaults::setup_env;
mod cli;
pub(crate) mod config;
pub mod config;
mod env;
pub(crate) mod error;
pub mod error;
mod logging;
pub(crate) mod node;
pub mod node;
pub(crate) mod throughput_tester;
pub(crate) mod wireguard;
pub mod wireguard;
fn main() -> anyhow::Result<()> {
// std::env::set_var(
@@ -16,8 +16,8 @@
//!
use super::LpHandlerState;
use crate::node::lp_listener::error::LpHandlerError;
use crate::GatewayError;
use crate::error::NymNodeError;
use crate::node::lp::error::LpHandlerError;
use nym_lp::OuterHeader;
use nym_metrics::inc;
use std::net::SocketAddr;
@@ -48,12 +48,12 @@ impl LpDataHandler {
bind_addr: SocketAddr,
state: LpHandlerState,
shutdown: nym_task::ShutdownToken,
) -> Result<Self, GatewayError> {
let socket = UdpSocket::bind(bind_addr).await.map_err(|e| {
error!("Failed to bind LP data socket to {bind_addr}: {e}");
GatewayError::ListenerBindFailure {
address: bind_addr.to_string(),
source: Box::new(e),
) -> Result<Self, NymNodeError> {
let socket = UdpSocket::bind(bind_addr).await.map_err(|source| {
error!("Failed to bind LP data socket to {bind_addr}: {source}");
NymNodeError::LpBindFailure {
address: bind_addr,
source,
}
})?;
@@ -1,7 +1,7 @@
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::node::lp_listener::ReceiverIndex;
use crate::node::lp::ReceiverIndex;
use nym_lp::state_machine::{LpAction, LpDataKind};
use nym_lp::{LpError, MalformedLpPacketError};
use nym_lp_transport::LpTransportError;
@@ -2,13 +2,13 @@
// SPDX-License-Identifier: GPL-3.0-only
use super::{LpHandlerState, ReceiverIndex, TimestampedState};
use crate::node::lp_listener::error::LpHandlerError;
use crate::node::lp::error::LpHandlerError;
use nym_lp::state_machine::{LpAction, LpData, LpDataKind, LpInput};
use nym_lp::{
EncryptedLpPacket, ExpectedResponseSize, ForwardPacketData, LpSession, LpStateMachine,
};
use nym_lp_transport::traits::LpTransportChannel;
use nym_lp_transport::LpHandshakeChannel;
use nym_lp_transport::traits::LpTransportChannel;
use nym_metrics::{add_histogram_obs, inc};
use nym_registration_common::{LpRegistrationRequest, RegistrationStatus};
use std::net::SocketAddr;
@@ -305,7 +305,8 @@ where
debug!(
"LP registration request from {remote} (receiver_idx={receiver_idx}): mode={:?}",
request.mode());
request.mode()
);
self.handle_registration_request(receiver_idx, request)
.await
@@ -318,7 +319,10 @@ where
}
typ @ LpDataKind::Opaque => {
// Neither registration nor forwarding - unknown payload type
warn!("Unknown transport payload type from {remote} (receiver_idx={receiver_idx}). dropping {} bytes", bytes.len());
warn!(
"Unknown transport payload type from {remote} (receiver_idx={receiver_idx}). dropping {} bytes",
bytes.len()
);
inc!("lp_errors_unknown_payload_type");
Err(LpHandlerError::UnexpectedLpPayload { typ })
}
@@ -420,6 +424,7 @@ where
}
/// Returns reference to the established forwarding channel to the exit.
#[allow(dead_code)]
pub fn forwarding_channel(&self) -> &Option<(S, SocketAddr)> {
&self.exit_stream
}
@@ -658,10 +663,10 @@ where
#[cfg(test)]
mod tests {
use super::*;
use crate::node::lp_listener::{LpConfig, LpDebug};
use crate::node::ActiveClientsStore;
use nym_lp::peer::{generate_keypair_mceliece, generate_keypair_mlkem, KEMKeys, LpLocalPeer};
use nym_lp::{sessions_for_tests, Ciphersuite, SessionManager};
use crate::config::lp::LpDebug;
use crate::node::lp::LpConfig;
use nym_lp::peer::{KEMKeys, LpLocalPeer, generate_keypair_mceliece, generate_keypair_mlkem};
use nym_lp::{Ciphersuite, SessionManager, sessions_for_tests};
use nym_test_utils::helpers::{deterministic_rng, deterministic_rng_09};
use std::sync::Arc;
// ==================== Test Helpers ====================
@@ -673,15 +678,6 @@ mod tests {
let mut rng = deterministic_rng();
let mut rng09 = deterministic_rng_09();
// Create in-memory storage for testing
let storage = nym_gateway_storage::GatewayStorage::init(":memory:", 100)
.await
.expect("Failed to create test storage");
// Create mock ecash manager for testing
let ecash_verifier =
nym_credential_verification::ecash::MockEcashManager::new(Box::new(storage.clone()));
let lp_config = LpConfig {
debug: LpDebug {
..Default::default()
@@ -706,12 +702,8 @@ mod tests {
LpHandlerState {
lp_config,
ecash_verifier: Arc::new(ecash_verifier)
as Arc<dyn nym_credential_verification::ecash::traits::EcashManager + Send + Sync>,
storage,
local_lp_peer: lp_peer,
metrics: nym_node_metrics::NymNodeMetrics::default(),
active_clients_store: ActiveClientsStore::new(),
outbound_mix_sender: mix_sender,
session_states: Arc::new(dashmap::DashMap::new()),
peer_registrator: None,
@@ -67,28 +67,23 @@
// To view metrics, the nym-metrics registry automatically collects all metrics.
// They can be exported via Prometheus format using the metrics endpoint.
use crate::error::GatewayError;
use crate::node::wireguard::PeerRegistrator;
use crate::node::ActiveClientsStore;
use crate::config::lp::LpConfig;
use crate::error::NymNodeError;
use dashmap::DashMap;
use nym_config::serde_helpers::de_maybe_port;
use nym_credential_verification::ecash::traits::EcashManager;
use nym_gateway_storage::GatewayStorage;
use nym_gateway::node::wireguard::PeerRegistrator;
use nym_lp::peer::LpLocalPeer;
use nym_lp::state_machine::LpStateMachine;
use nym_mixnet_client::forwarder::MixForwardingSender;
use nym_node_metrics::NymNodeMetrics;
use nym_task::ShutdownTracker;
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use tokio::net::TcpListener;
use tokio::sync::Semaphore;
use tracing::*;
pub use nym_lp::peer::{DHKeyPair, DHPublicKey, KEMKeys, LpLocalPeer};
pub use nym_mixnet_client::forwarder::{
mix_forwarding_channels, MixForwardingReceiver, MixForwardingSender,
};
pub use nym_wireguard::{PeerControlRequest, WireguardGatewayData};
pub use nym_mixnet_client::forwarder::{MixForwardingReceiver, mix_forwarding_channels};
mod data_handler;
pub mod error;
@@ -97,153 +92,6 @@ mod registration;
pub type ReceiverIndex = u64;
/// Configuration for LP listener
#[derive(Debug, Clone, Copy, serde::Deserialize, serde::Serialize)]
#[serde(default)]
pub struct LpConfig {
/// Bind address for the TCP LP control traffic.
/// default: `[::]:41264`
pub control_bind_address: SocketAddr,
/// Bind address for the UDP LP data traffic.
/// default: `[::]:51264`
pub data_bind_address: SocketAddr,
/// Custom announced port for listening for the TCP LP control traffic.
/// If unspecified, the value from the `control_bind_address` will be used instead
/// (default: None)
#[serde(deserialize_with = "de_maybe_port")]
pub announce_control_port: Option<u16>,
/// Custom announced port for listening for the UDP LP data traffic.
/// If unspecified, the value from the `data_bind_address` will be used instead
/// (default: None)
#[serde(deserialize_with = "de_maybe_port")]
pub announce_data_port: Option<u16>,
/// Auxiliary configuration
#[serde(default)]
pub debug: LpDebug,
}
#[derive(Debug, Clone, Copy, serde::Deserialize, serde::Serialize)]
#[serde(default)]
pub struct LpDebug {
/// Maximum concurrent connections
pub max_connections: usize,
/// Use mock ecash manager for testing (default: false)
///
/// When enabled, the LP listener will use a mock ecash verifier that
/// accepts any credential without blockchain verification. This is
/// useful for testing the LP protocol implementation without requiring
/// a full blockchain/contract setup.
///
/// WARNING: Only use this for local testing! Never enable in production.
pub use_mock_ecash: bool,
/// Maximum age of in-progress handshakes before cleanup (default: 90s)
///
/// Handshakes should complete quickly (3-5 packets). This TTL accounts for:
/// - Network latency and retransmits
/// - Slow clients
/// - Clock skew tolerance
///
/// Stale handshakes are removed by the cleanup task to prevent memory leaks.
#[serde(with = "humantime_serde")]
pub handshake_ttl: Duration,
/// Maximum age of established sessions before cleanup (default: 24h)
///
/// Sessions can be long-lived for dVPN tunnels. This TTL should be set
/// high enough to accommodate expected usage patterns:
/// - dVPN sessions: hours to days
/// - Registration: minutes
///
/// Sessions with no activity for this duration are removed by the cleanup task.
#[serde(with = "humantime_serde")]
pub session_ttl: Duration,
/// How often to run the state cleanup task (default: 5 minutes)
///
/// The cleanup task scans for and removes stale handshakes and sessions.
/// Lower values = more frequent cleanup but higher overhead.
/// Higher values = less overhead but slower memory reclamation.
#[serde(with = "humantime_serde")]
pub state_cleanup_interval: Duration,
/// Maximum concurrent forward connections (default: 1000)
///
/// Limits simultaneous outbound connections when forwarding LP packets to other gateways
/// during telescope setup. This prevents file descriptor exhaustion under high load.
///
/// When at capacity, new forward requests return an error, signaling the client
/// to choose a different gateway.
pub max_concurrent_forwards: usize,
}
impl LpConfig {
pub const DEFAULT_CONTROL_PORT: u16 = 41264;
pub const DEFAULT_DATA_PORT: u16 = 51264;
pub fn announced_control_port(&self) -> u16 {
self.announce_control_port
.unwrap_or(self.control_bind_address.port())
}
pub fn announced_data_port(&self) -> u16 {
self.announce_data_port
.unwrap_or(self.data_bind_address.port())
}
}
impl Default for LpConfig {
fn default() -> Self {
LpConfig {
control_bind_address: SocketAddr::new(
IpAddr::V6(Ipv6Addr::UNSPECIFIED),
Self::DEFAULT_CONTROL_PORT,
),
data_bind_address: SocketAddr::new(
IpAddr::V6(Ipv6Addr::UNSPECIFIED),
Self::DEFAULT_DATA_PORT,
),
announce_control_port: None,
announce_data_port: None,
debug: Default::default(),
}
}
}
impl LpDebug {
pub const DEFAULT_MAX_CONNECTIONS: usize = 10000;
// 90 seconds - handshakes should complete quickly
pub const DEFAULT_HANDSHAKE_TTL: Duration = Duration::from_secs(90);
// 24 hours - for long-lived dVPN sessions
pub const DEFAULT_SESSION_TTL: Duration = Duration::from_secs(86400);
// 5 minutes - balances memory reclamation with task overhead
pub const DEFAULT_STATE_CLEANUP_INTERVAL: Duration = Duration::from_secs(300);
// Limits concurrent outbound connections to prevent fd exhaustion
pub const DEFAULT_MAX_CONCURRENT_FORWARDS: usize = 1000;
}
impl Default for LpDebug {
fn default() -> Self {
LpDebug {
max_connections: Self::DEFAULT_MAX_CONNECTIONS,
use_mock_ecash: false,
handshake_ttl: Self::DEFAULT_HANDSHAKE_TTL,
session_ttl: Self::DEFAULT_SESSION_TTL,
state_cleanup_interval: Self::DEFAULT_STATE_CLEANUP_INTERVAL,
max_concurrent_forwards: Self::DEFAULT_MAX_CONCURRENT_FORWARDS,
}
}
}
/// Wrapper for state entries with timestamp tracking for cleanup
///
/// This wrapper adds `created_at` and `last_activity` timestamps to state entries,
@@ -289,6 +137,7 @@ impl<T> TimestampedState<T> {
}
/// Get age since creation
#[allow(dead_code)]
pub fn age(&self) -> Duration {
self.created_at.elapsed()
}
@@ -309,21 +158,12 @@ impl<T> TimestampedState<T> {
/// Shared state for LP connection handlers
#[derive(Clone)]
pub struct LpHandlerState {
/// Ecash verifier for bandwidth credentials
pub ecash_verifier: Arc<dyn EcashManager + Send + Sync>,
/// Storage backend for persistence
pub storage: GatewayStorage,
/// Encapsulates all required key information of a local Lewes Protocol Peer.
pub local_lp_peer: LpLocalPeer,
/// Metrics collection
pub metrics: NymNodeMetrics,
/// Active clients tracking
pub active_clients_store: ActiveClientsStore,
/// Handle registering new wireguard peers
pub peer_registrator: Option<PeerRegistrator>,
@@ -334,6 +174,7 @@ pub struct LpHandlerState {
///
/// Used by the LP data handler (UDP:51264) to forward decrypted Sphinx packets
/// from LP clients into the mixnet for routing.
#[allow(dead_code)]
pub outbound_mix_sender: MixForwardingSender,
/// Established sessions keyed by session_id
@@ -397,16 +238,18 @@ impl LpListener {
self.handler_state.lp_config
}
pub async fn run(&mut self) -> Result<(), GatewayError> {
pub async fn run(&mut self) -> Result<(), NymNodeError> {
let control_bind_address = self.lp_config().control_bind_address;
let data_bind_address = self.lp_config().data_bind_address;
let listener = TcpListener::bind(control_bind_address).await.map_err(|e| {
error!("Failed to bind LP listener to {control_bind_address}: {e}",);
GatewayError::ListenerBindFailure {
address: control_bind_address.to_string(),
source: Box::new(e),
}
})?;
let listener = TcpListener::bind(control_bind_address)
.await
.map_err(|source| {
error!("Failed to bind LP listener to {control_bind_address}: {source}",);
NymNodeError::LpBindFailure {
address: control_bind_address,
source,
}
})?;
let shutdown_token = self.shutdown.clone_shutdown_token();
@@ -489,7 +332,7 @@ impl LpListener {
/// The data handler listens on UDP port 51264 and processes LP-wrapped Sphinx packets
/// from registered clients. It decrypts the LP layer and forwards the Sphinx packets
/// into the mixnet.
async fn spawn_data_handler(&self) -> Result<tokio::task::JoinHandle<()>, GatewayError> {
async fn spawn_data_handler(&self) -> Result<tokio::task::JoinHandle<()>, NymNodeError> {
// Create data handler
let data_handler = data_handler::LpDataHandler::new(
self.lp_config().data_bind_address,
@@ -550,7 +393,8 @@ impl LpListener {
}
pub(crate) mod cleanup_task {
use crate::node::lp_listener::{LpDebug, ReceiverIndex, TimestampedState};
use crate::config::lp::LpDebug;
use crate::node::lp::{ReceiverIndex, TimestampedState};
use dashmap::DashMap;
use nym_lp::LpStateMachine;
use nym_metrics::inc_by;
@@ -1,7 +1,7 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node::lp_listener::{LpHandlerState, ReceiverIndex};
use crate::node::lp::{LpHandlerState, ReceiverIndex};
use nym_metrics::{add_histogram_obs, inc};
use nym_registration_common::dvpn::{
LpDvpnRegistrationFinalisation, LpDvpnRegistrationInitialRequest,
+50 -17
View File
@@ -22,6 +22,7 @@ use crate::node::http::{HttpServerConfig, NymNodeHttpServer, NymNodeRouter};
use crate::node::key_rotation::active_keys::ActiveSphinxKeys;
use crate::node::key_rotation::controller::KeyRotationController;
use crate::node::key_rotation::manager::SphinxKeyManager;
use crate::node::lp::{LpHandlerState, LpListener};
use crate::node::metrics::aggregator::MetricsAggregator;
use crate::node::metrics::console_logger::ConsoleLogger;
use crate::node::metrics::handler::client_sessions::GatewaySessionStatsHandler;
@@ -43,12 +44,14 @@ use crate::node::shared_network::{
use nym_bin_common::bin_info;
use nym_credential_verification::UpgradeModeState;
use nym_crypto::asymmetric::{ed25519, x25519};
use nym_gateway::node::lp_listener::DHKeyPair;
use nym_gateway::node::{ActiveClientsStore, GatewayTasksBuilder, UpgradeModeCheckRequestSender};
use nym_gateway::node::wireguard::PeerRegistrator;
use nym_gateway::node::{GatewayTasksBuilder, UpgradeModeCheckRequestSender};
use nym_kkt::key_utils::{
generate_keypair_mceliece, generate_keypair_mlkem, generate_lp_keypair_x25519,
};
use nym_kkt::keys::KEMKeys;
use nym_kkt::keys::{DHKeyPair, KEMKeys};
use nym_lp::LpSession;
use nym_lp::peer::LpLocalPeer;
use nym_mixnet_client::client::ActiveConnections;
use nym_mixnet_client::forwarder::MixForwardingSender;
use nym_network_requester::{
@@ -77,15 +80,19 @@ use std::net::SocketAddr;
use std::ops::Deref;
use std::path::Path;
use std::sync::Arc;
use tokio::sync::mpsc;
use tokio::sync::{Semaphore, mpsc};
use tracing::{debug, info, trace};
use zeroize::Zeroizing;
pub use nym_gateway::node::ActiveClientsStore;
pub use nym_gateway::node::GatewayStorage;
pub mod bonding_information;
pub mod description;
pub mod helpers;
pub(crate) mod http;
pub(crate) mod key_rotation;
pub mod lp;
pub(crate) mod metrics;
pub(crate) mod mixnet;
mod nym_apis_client;
@@ -95,7 +102,7 @@ mod shared_network;
pub struct GatewayTasksData {
mnemonic: Arc<Zeroizing<bip39::Mnemonic>>,
client_storage: nym_gateway::node::GatewayStorage,
client_storage: GatewayStorage,
stats_storage: nym_gateway::node::PersistentStatsStorage,
}
@@ -117,7 +124,7 @@ impl GatewayTasksData {
}
async fn new(config: &GatewayTasksConfig) -> Result<GatewayTasksData, EntryGatewayError> {
let client_storage = nym_gateway::node::GatewayStorage::init(
let client_storage = GatewayStorage::init(
&config.storage_paths.clients_storage,
config.debug.message_retrieval_limit,
)
@@ -378,7 +385,7 @@ impl From<WireguardData> for nym_wireguard::WireguardData {
}
}
pub(crate) struct NymNode {
pub struct NymNode {
config: Config,
accepted_operator_terms_and_conditions: bool,
shutdown_manager: ShutdownManager,
@@ -402,6 +409,7 @@ pub(crate) struct NymNode {
sphinx_key_manager: Option<SphinxKeyManager>,
x25519_noise_keys: Arc<x25519::KeyPair>,
psq_kem_keys: KEMKeys,
x25519_lp_keys: Arc<DHKeyPair>,
}
@@ -478,6 +486,33 @@ impl NymNode {
config.save()
}
pub async fn build_lp_listener(
&mut self,
peer_registrator: Option<PeerRegistrator>,
mix_packet_sender: MixForwardingSender,
) -> Result<LpListener, NymNodeError> {
let handler_state = LpHandlerState {
local_lp_peer: LpLocalPeer::new(
LpSession::default_ciphersuite(),
self.x25519_lp_keys.clone(),
)
.with_kem_keys(self.psq_kem_keys.clone()),
metrics: self.metrics.clone(),
peer_registrator,
lp_config: self.config.lp,
outbound_mix_sender: mix_packet_sender,
session_states: Arc::new(dashmap::DashMap::new()),
forward_semaphore: Arc::new(Semaphore::new(
self.config.lp.debug.max_concurrent_forwards,
)),
};
Ok(LpListener::new(
handler_state,
self.shutdown_manager.shutdown_tracker().clone(),
))
}
pub(crate) async fn new(config: Config) -> Result<Self, NymNodeError> {
let wireguard_data = WireguardData::new(&config.wireguard)?;
let current_rotation_id =
@@ -670,15 +705,14 @@ impl NymNode {
let mut gateway_tasks_builder = GatewayTasksBuilder::new(
config.gateway,
self.ed25519_identity_keys.clone(),
self.x25519_lp_keys.clone(),
self.psq_kem_keys.clone(),
self.entry_gateway.client_storage.clone(),
mix_packet_sender,
mix_packet_sender.clone(),
metrics_sender,
self.metrics.clone(),
self.entry_gateway.mnemonic.clone(),
Self::user_agent(),
self.upgrade_mode_state.clone(),
self.config.lp.debug.use_mock_ecash,
self.shutdown_tracker().clone(),
);
@@ -736,11 +770,10 @@ impl NymNode {
// Start LP listener if enabled
info!(
"starting the LP listener on {} (data handler on: {})",
self.config.gateway_tasks.lp.control_bind_address,
self.config.gateway_tasks.lp.data_bind_address,
self.config.lp.control_bind_address, self.config.lp.data_bind_address,
);
let mut lp_listener = gateway_tasks_builder
.build_lp_listener(wg_peer_registrator.clone(), active_clients_store.clone())
let mut lp_listener = self
.build_lp_listener(wg_peer_registrator.clone(), mix_packet_sender)
.await?;
self.shutdown_tracker()
.try_spawn_named(async move { lp_listener.run().await }, "LpListener");
@@ -913,8 +946,8 @@ impl NymNode {
let lewes_protocol = LewesProtocol {
enabled: self.modes().entry,
control_port: self.config.gateway_tasks.lp.announced_control_port(),
data_port: self.config.gateway_tasks.lp.announced_data_port(),
control_port: self.config.lp.announced_control_port(),
data_port: self.config.lp.announced_data_port(),
x25519: self.x25519_lp_keys.pk,
kem_keys: self.compute_kem_key_hashes(),
};
@@ -1371,7 +1404,7 @@ impl NymNode {
Ok(self.shutdown_manager)
}
pub(crate) async fn run(mut self) -> Result<(), NymNodeError> {
pub async fn run(mut self) -> Result<(), NymNodeError> {
let mut shutdown_signals = self.shutdown_manager.detach_shutdown_signals();
// listen for shutdown signal in case we received it when attempting to spawn all the tasks
+3
View File
@@ -5,3 +5,6 @@
// but let's start putting everything in here
pub mod error;
pub use nym_gateway::node::wireguard::{PeerManager, PeerRegistrator};
pub use nym_wireguard::{PeerControlRequest, WireguardGatewayData};