Registration Client (#6059)

* removing wg-gateway-client

* bandwidth_provider trait

* authenticator client

* adapt ip-packet-client

* nit

* registration_client

* accomodate new shutdown and bugfix

* sdk changes

* cleanup and shutdown management

* remove credential mode

* error cleanup

* better error handling

* removing useless cover traffic delay

* wasm client stuff

* cfg unix

* more wasm stuff

* change authenticator client to not be blocked by mixnet client
This commit is contained in:
Simon Wicky
2025-09-30 15:50:04 +02:00
committed by GitHub
parent 5cc650e901
commit 51779c06a4
42 changed files with 2693 additions and 2033 deletions
Generated
+39 -20
View File
@@ -4923,11 +4923,16 @@ dependencies = [
"bincode",
"futures",
"nym-authenticator-requests",
"nym-bandwidth-controller",
"nym-credentials-interface",
"nym-crypto",
"nym-pemstore",
"nym-registration-common",
"nym-sdk",
"nym-service-provider-requests-common",
"nym-validator-client",
"nym-wireguard-types",
"rand 0.8.5",
"semver 1.0.26",
"thiserror 2.0.12",
"tokio",
@@ -4949,8 +4954,10 @@ dependencies = [
"nym-sphinx",
"nym-wireguard-types",
"rand 0.8.5",
"semver 1.0.26",
"serde",
"sha2 0.10.9",
"strum_macros",
"thiserror 2.0.12",
"x25519-dalek",
]
@@ -4959,6 +4966,7 @@ dependencies = [
name = "nym-bandwidth-controller"
version = "0.1.0"
dependencies = [
"async-trait",
"bip39",
"log",
"nym-credential-storage",
@@ -6007,6 +6015,7 @@ dependencies = [
name = "nym-ip-packet-client"
version = "0.1.0"
dependencies = [
"bincode",
"bytes",
"futures",
"nym-ip-packet-requests",
@@ -6659,6 +6668,36 @@ dependencies = [
"time",
]
[[package]]
name = "nym-registration-client"
version = "0.1.0"
dependencies = [
"nym-authenticator-client",
"nym-bandwidth-controller",
"nym-credential-storage",
"nym-credentials-interface",
"nym-ip-packet-client",
"nym-registration-common",
"nym-sdk",
"nym-validator-client",
"thiserror 2.0.12",
"tokio",
"tokio-util",
"tracing",
"url",
]
[[package]]
name = "nym-registration-common"
version = "0.1.0"
dependencies = [
"nym-authenticator-requests",
"nym-crypto",
"nym-ip-packet-requests",
"nym-sphinx",
"tokio-util",
]
[[package]]
name = "nym-sdk"
version = "0.1.0"
@@ -7404,26 +7443,6 @@ dependencies = [
"ts-rs",
]
[[package]]
name = "nym-wg-gateway-client"
version = "0.1.0"
dependencies = [
"nym-authenticator-client",
"nym-authenticator-requests",
"nym-bandwidth-controller",
"nym-credentials-interface",
"nym-crypto",
"nym-node-requests",
"nym-pemstore",
"nym-sdk",
"nym-statistics-common",
"nym-validator-client",
"rand 0.8.5",
"thiserror 2.0.12",
"tracing",
"url",
]
[[package]]
name = "nym-wireguard"
version = "0.1.0"
+6 -4
View File
@@ -85,6 +85,7 @@ members = [
"common/nymsphinx/types",
"common/nyxd-scraper",
"common/pemstore",
"common/registration",
"common/serde-helpers",
"common/service-provider-requests-common",
"common/socks5-client-core",
@@ -92,7 +93,8 @@ members = [
"common/socks5/requests",
"common/statistics",
"common/store-cipher",
"common/task", "common/test-utils",
"common/task",
"common/test-utils",
"common/ticketbooks-merkle",
"common/topology",
"common/tun",
@@ -125,10 +127,11 @@ members = [
"nym-node-status-api/nym-node-status-client",
"nym-node/nym-node-metrics",
"nym-node/nym-node-requests",
"nym-outfox", "nym-signers-monitor",
"nym-outfox",
"nym-registration-client",
"nym-signers-monitor",
"nym-statistics-api",
"nym-validator-rewarder",
"nym-wg-gateway-client",
"nyx-chain-watcher",
"sdk/ffi/cpp",
"sdk/ffi/go",
@@ -145,7 +148,6 @@ members = [
# "tools/internal/sdk-version-bump",
"tools/internal/ssl-inject",
"tools/internal/testnet-manager",
"tools/internal/testnet-manager",
"tools/internal/testnet-manager/dkg-bypass-contract",
"tools/internal/validator-status-check",
"tools/nym-cli",
+2
View File
@@ -13,6 +13,8 @@ base64 = { workspace = true }
bincode = { workspace = true }
rand = { workspace = true }
serde = { workspace = true, features = ["derive"] }
semver = { workspace = true }
strum_macros = { workspace = true }
thiserror = { workspace = true }
nym-credentials-interface = { path = "../credentials-interface" }
@@ -0,0 +1,272 @@
// Copyright 2025 Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use nym_sphinx::addressing::Recipient;
use nym_wireguard_types::PeerPublicKey;
use crate::{
latest::registration::IpPair,
traits::{FinalMessage, InitMessage, QueryBandwidthMessage, TopUpMessage, Versionable},
v2, v3, v4, v5, AuthenticatorVersion, Error,
};
// This is very redundant with AuthenticatorRequest and I reckon they could be smooshed.
// It is a bit out of scope for me at the moment though
#[derive(Debug)]
pub enum ClientMessage {
Initial(Box<dyn InitMessage + Send + Sync + 'static>),
Final(Box<dyn FinalMessage + Send + Sync + 'static>),
Query(Box<dyn QueryBandwidthMessage + Send + Sync + 'static>),
TopUp(Box<dyn TopUpMessage + Send + Sync + 'static>),
}
impl ClientMessage {
// check if message is wasteful e.g. contains a credential
pub fn is_wasteful(&self) -> bool {
match self {
Self::Final(msg) => msg.credential().is_some(),
Self::TopUp(_) => true,
Self::Initial(_) | Self::Query(_) => false,
}
}
fn version(&self) -> AuthenticatorVersion {
match self {
ClientMessage::Initial(msg) => msg.version(),
ClientMessage::Final(msg) => msg.version(),
ClientMessage::Query(msg) => msg.version(),
ClientMessage::TopUp(msg) => msg.version(),
}
}
pub fn bytes(&self, reply_to: Recipient) -> Result<(Vec<u8>, u64), Error> {
match self.version() {
AuthenticatorVersion::V1 => Err(Error::UnsupportedVersion),
AuthenticatorVersion::V2 => {
use v2::{
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage},
request::AuthenticatorRequest,
};
match self {
ClientMessage::Initial(init_message) => {
let (req, id) = AuthenticatorRequest::new_initial_request(
InitMessage {
pub_key: init_message.pub_key(),
},
reply_to,
);
Ok((req.to_bytes()?, id))
}
ClientMessage::Final(final_message) => {
let (req, id) = AuthenticatorRequest::new_final_request(
FinalMessage {
gateway_client: GatewayClient {
pub_key: final_message.gateway_client_pub_key(),
private_ip: final_message
.gateway_client_ipv4()
.ok_or(Error::UnsupportedMessage)?
.into(),
mac: ClientMac::new(final_message.gateway_client_mac()),
},
credential: final_message.credential(),
},
reply_to,
);
Ok((req.to_bytes()?, id))
}
ClientMessage::Query(query_message) => {
let (req, id) = AuthenticatorRequest::new_query_request(
query_message.pub_key(),
reply_to,
);
Ok((req.to_bytes()?, id))
}
_ => Err(Error::UnsupportedMessage),
}
}
AuthenticatorVersion::V3 => {
use v3::{
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage},
request::AuthenticatorRequest,
topup::TopUpMessage,
};
match self {
ClientMessage::Initial(init_message) => {
let (req, id) = AuthenticatorRequest::new_initial_request(
InitMessage {
pub_key: init_message.pub_key(),
},
reply_to,
);
Ok((req.to_bytes()?, id))
}
ClientMessage::Final(final_message) => {
let (req, id) = AuthenticatorRequest::new_final_request(
FinalMessage {
gateway_client: GatewayClient {
pub_key: final_message.gateway_client_pub_key(),
private_ip: final_message
.gateway_client_ipv4()
.ok_or(Error::UnsupportedMessage)?
.into(),
mac: ClientMac::new(final_message.gateway_client_mac()),
},
credential: final_message.credential(),
},
reply_to,
);
Ok((req.to_bytes()?, id))
}
ClientMessage::Query(query_message) => {
let (req, id) = AuthenticatorRequest::new_query_request(
query_message.pub_key(),
reply_to,
);
Ok((req.to_bytes()?, id))
}
ClientMessage::TopUp(top_up_message) => {
let (req, id) = AuthenticatorRequest::new_topup_request(
TopUpMessage {
pub_key: top_up_message.pub_key(),
credential: top_up_message.credential(),
},
reply_to,
);
Ok((req.to_bytes()?, id))
}
}
}
AuthenticatorVersion::V4 => {
use v4::{
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage},
request::AuthenticatorRequest,
topup::TopUpMessage,
};
match self {
ClientMessage::Initial(init_message) => {
let (req, id) = AuthenticatorRequest::new_initial_request(
InitMessage {
pub_key: init_message.pub_key(),
},
reply_to,
);
Ok((req.to_bytes()?, id))
}
ClientMessage::Final(final_message) => {
let (req, id) = AuthenticatorRequest::new_final_request(
FinalMessage {
gateway_client: GatewayClient {
pub_key: final_message.gateway_client_pub_key(),
private_ips: IpPair {
ipv4: final_message
.gateway_client_ipv4()
.ok_or(Error::UnsupportedMessage)?,
ipv6: final_message
.gateway_client_ipv6()
.ok_or(Error::UnsupportedMessage)?,
}
.into(),
mac: ClientMac::new(final_message.gateway_client_mac()),
},
credential: final_message.credential(),
},
reply_to,
);
Ok((req.to_bytes()?, id))
}
ClientMessage::Query(query_message) => {
let (req, id) = AuthenticatorRequest::new_query_request(
query_message.pub_key(),
reply_to,
);
Ok((req.to_bytes()?, id))
}
ClientMessage::TopUp(top_up_message) => {
let (req, id) = AuthenticatorRequest::new_topup_request(
TopUpMessage {
pub_key: top_up_message.pub_key(),
credential: top_up_message.credential(),
},
reply_to,
);
Ok((req.to_bytes()?, id))
}
}
}
AuthenticatorVersion::V5 => {
use v5::{
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage},
request::AuthenticatorRequest,
topup::TopUpMessage,
};
match self {
ClientMessage::Initial(init_message) => {
let (req, id) = AuthenticatorRequest::new_initial_request(InitMessage {
pub_key: init_message.pub_key(),
});
Ok((req.to_bytes()?, id))
}
ClientMessage::Final(final_message) => {
let (req, id) = AuthenticatorRequest::new_final_request(FinalMessage {
gateway_client: GatewayClient {
pub_key: final_message.gateway_client_pub_key(),
private_ips: IpPair {
ipv4: final_message
.gateway_client_ipv4()
.ok_or(Error::UnsupportedMessage)?,
ipv6: final_message
.gateway_client_ipv6()
.ok_or(Error::UnsupportedMessage)?,
},
mac: ClientMac::new(final_message.gateway_client_mac()),
},
credential: final_message.credential(),
});
Ok((req.to_bytes()?, id))
}
ClientMessage::Query(query_message) => {
let (req, id) =
AuthenticatorRequest::new_query_request(query_message.pub_key());
Ok((req.to_bytes()?, id))
}
ClientMessage::TopUp(top_up_message) => {
let (req, id) = AuthenticatorRequest::new_topup_request(TopUpMessage {
pub_key: top_up_message.pub_key(),
credential: top_up_message.credential(),
});
Ok((req.to_bytes()?, id))
}
}
}
AuthenticatorVersion::UNKNOWN => Err(Error::UnknownVersion),
}
}
pub fn use_surbs(&self) -> bool {
use AuthenticatorVersion::*;
match self.version() {
V1 | V2 | V3 | V4 => false,
V5 => true,
UNKNOWN => true,
}
}
}
// Same comment as above struct
#[derive(Debug)]
pub struct QueryMessageImpl {
pub pub_key: PeerPublicKey,
pub version: AuthenticatorVersion,
}
impl Versionable for QueryMessageImpl {
fn version(&self) -> AuthenticatorVersion {
self.version
}
}
impl QueryBandwidthMessage for QueryMessageImpl {
fn pub_key(&self) -> PeerPublicKey {
self.pub_key
}
}
+13 -2
View File
@@ -23,6 +23,17 @@ pub enum Error {
#[error("conversion: {0}")]
Conversion(String),
#[error("failed to serialize response packet: {source}")]
FailedToSerializeResponsePacket { source: Box<bincode::ErrorKind> },
// TODO add version number for debugging
#[error("unknown version number")]
UnknownVersion,
// TODO add version number for debugging
#[error("unsupported request version")]
UnsupportedVersion,
#[error("gateway doesn't support this type of message")]
UnsupportedMessage,
#[error(transparent)]
Bincode(#[from] bincode::Error),
}
+6 -1
View File
@@ -1,6 +1,9 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod client_message;
pub mod request;
pub mod response;
pub mod traits;
pub mod v1;
pub mod v2;
@@ -10,11 +13,13 @@ pub mod v5;
mod error;
mod util;
mod version;
pub use error::Error;
pub use v5 as latest;
pub use version::AuthenticatorVersion;
pub const CURRENT_VERSION: u8 = 5;
pub const CURRENT_VERSION: u8 = latest::VERSION;
fn make_bincode_serializer() -> impl bincode::Options {
use bincode::Options;
@@ -0,0 +1,204 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
use nym_sphinx::addressing::Recipient;
use crate::traits::{FinalMessage, InitMessage, QueryBandwidthMessage, TopUpMessage};
use crate::{v1, v2, v3, v4, v5};
#[derive(Debug)]
pub enum AuthenticatorRequest {
Initial {
msg: Box<dyn InitMessage + Send + Sync + 'static>,
protocol: Protocol,
reply_to: Option<Recipient>,
request_id: u64,
},
Final {
msg: Box<dyn FinalMessage + Send + Sync + 'static>,
protocol: Protocol,
reply_to: Option<Recipient>,
request_id: u64,
},
QueryBandwidth {
msg: Box<dyn QueryBandwidthMessage + Send + Sync + 'static>,
protocol: Protocol,
reply_to: Option<Recipient>,
request_id: u64,
},
TopUpBandwidth {
msg: Box<dyn TopUpMessage + Send + Sync + 'static>,
protocol: Protocol,
reply_to: Option<Recipient>,
request_id: u64,
},
}
impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
fn from(value: v1::request::AuthenticatorRequest) -> Self {
match value.data {
v1::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: Protocol {
version: value.version,
service_provider_type: ServiceProviderType::Authenticator,
},
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v1::request::AuthenticatorRequestData::Final(gateway_client) => Self::Final {
msg: Box::new(gateway_client),
protocol: Protocol {
version: value.version,
service_provider_type: ServiceProviderType::Authenticator,
},
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v1::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: Protocol {
version: value.version,
service_provider_type: ServiceProviderType::Authenticator,
},
reply_to: Some(value.reply_to),
request_id: value.request_id,
}
}
}
}
}
impl From<v2::request::AuthenticatorRequest> for AuthenticatorRequest {
fn from(value: v2::request::AuthenticatorRequest) -> Self {
match value.data {
v2::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: value.protocol,
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v2::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message,
protocol: value.protocol,
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v2::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: value.protocol,
reply_to: Some(value.reply_to),
request_id: value.request_id,
}
}
}
}
}
impl From<v3::request::AuthenticatorRequest> for AuthenticatorRequest {
fn from(value: v3::request::AuthenticatorRequest) -> Self {
match value.data {
v3::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: value.protocol,
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v3::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message,
protocol: value.protocol,
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v3::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: value.protocol,
reply_to: Some(value.reply_to),
request_id: value.request_id,
}
}
v3::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
Self::TopUpBandwidth {
msg: top_up_message,
protocol: value.protocol,
reply_to: Some(value.reply_to),
request_id: value.request_id,
}
}
}
}
}
impl From<v4::request::AuthenticatorRequest> for AuthenticatorRequest {
fn from(value: v4::request::AuthenticatorRequest) -> Self {
match value.data {
v4::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: value.protocol,
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v4::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message,
protocol: value.protocol,
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v4::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: value.protocol,
reply_to: Some(value.reply_to),
request_id: value.request_id,
}
}
v4::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
Self::TopUpBandwidth {
msg: top_up_message,
protocol: value.protocol,
reply_to: Some(value.reply_to),
request_id: value.request_id,
}
}
}
}
}
impl From<v5::request::AuthenticatorRequest> for AuthenticatorRequest {
fn from(value: v5::request::AuthenticatorRequest) -> Self {
match value.data {
v5::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: value.protocol,
reply_to: None,
request_id: value.request_id,
},
v5::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message,
protocol: value.protocol,
reply_to: None,
request_id: value.request_id,
},
v5::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: value.protocol,
reply_to: None,
request_id: value.request_id,
}
}
v5::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
Self::TopUpBandwidth {
msg: top_up_message,
protocol: value.protocol,
reply_to: None,
request_id: value.request_id,
}
}
}
}
}
@@ -0,0 +1,106 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::traits::{
Id, PendingRegistrationResponse, RegisteredResponse, RemainingBandwidthResponse,
TopUpBandwidthResponse,
};
use crate::{v2, v3, v4, v5};
#[derive(Debug)]
pub enum AuthenticatorResponse {
PendingRegistration(Box<dyn PendingRegistrationResponse + Send + Sync + 'static>),
Registered(Box<dyn RegisteredResponse + Send + Sync + 'static>),
RemainingBandwidth(Box<dyn RemainingBandwidthResponse + Send + Sync + 'static>),
TopUpBandwidth(Box<dyn TopUpBandwidthResponse + Send + Sync + 'static>),
}
impl Id for AuthenticatorResponse {
fn id(&self) -> u64 {
match self {
AuthenticatorResponse::PendingRegistration(pending_registration_response) => {
pending_registration_response.id()
}
AuthenticatorResponse::Registered(registered_response) => registered_response.id(),
AuthenticatorResponse::RemainingBandwidth(remaining_bandwidth_response) => {
remaining_bandwidth_response.id()
}
AuthenticatorResponse::TopUpBandwidth(top_up_bandwidth_response) => {
top_up_bandwidth_response.id()
}
}
}
}
impl From<v2::response::AuthenticatorResponse> for AuthenticatorResponse {
fn from(value: v2::response::AuthenticatorResponse) -> Self {
match value.data {
v2::response::AuthenticatorResponseData::PendingRegistration(
pending_registration_response,
) => Self::PendingRegistration(Box::new(pending_registration_response)),
v2::response::AuthenticatorResponseData::Registered(registered_response) => {
Self::Registered(Box::new(registered_response))
}
v2::response::AuthenticatorResponseData::RemainingBandwidth(
remaining_bandwidth_response,
) => Self::RemainingBandwidth(Box::new(remaining_bandwidth_response)),
}
}
}
impl From<v3::response::AuthenticatorResponse> for AuthenticatorResponse {
fn from(value: v3::response::AuthenticatorResponse) -> Self {
match value.data {
v3::response::AuthenticatorResponseData::PendingRegistration(
pending_registration_response,
) => Self::PendingRegistration(Box::new(pending_registration_response)),
v3::response::AuthenticatorResponseData::Registered(registered_response) => {
Self::Registered(Box::new(registered_response))
}
v3::response::AuthenticatorResponseData::RemainingBandwidth(
remaining_bandwidth_response,
) => Self::RemainingBandwidth(Box::new(remaining_bandwidth_response)),
v3::response::AuthenticatorResponseData::TopUpBandwidth(top_up_bandwidth_response) => {
Self::TopUpBandwidth(Box::new(top_up_bandwidth_response))
}
}
}
}
impl From<v4::response::AuthenticatorResponse> for AuthenticatorResponse {
fn from(value: v4::response::AuthenticatorResponse) -> Self {
match value.data {
v4::response::AuthenticatorResponseData::PendingRegistration(
pending_registration_response,
) => Self::PendingRegistration(Box::new(pending_registration_response)),
v4::response::AuthenticatorResponseData::Registered(registered_response) => {
Self::Registered(Box::new(registered_response))
}
v4::response::AuthenticatorResponseData::RemainingBandwidth(
remaining_bandwidth_response,
) => Self::RemainingBandwidth(Box::new(remaining_bandwidth_response)),
v4::response::AuthenticatorResponseData::TopUpBandwidth(top_up_bandwidth_response) => {
Self::TopUpBandwidth(Box::new(top_up_bandwidth_response))
}
}
}
}
impl From<v5::response::AuthenticatorResponse> for AuthenticatorResponse {
fn from(value: v5::response::AuthenticatorResponse) -> Self {
match value.data {
v5::response::AuthenticatorResponseData::PendingRegistration(
pending_registration_response,
) => Self::PendingRegistration(Box::new(pending_registration_response)),
v5::response::AuthenticatorResponseData::Registered(registered_response) => {
Self::Registered(Box::new(registered_response))
}
v5::response::AuthenticatorResponseData::RemainingBandwidth(
remaining_bandwidth_response,
) => Self::RemainingBandwidth(Box::new(remaining_bandwidth_response)),
v5::response::AuthenticatorResponseData::TopUpBandwidth(top_up_bandwidth_response) => {
Self::TopUpBandwidth(Box::new(top_up_bandwidth_response))
}
}
}
}
+437 -220
View File
@@ -1,49 +1,105 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::fmt;
use std::net::{Ipv4Addr, Ipv6Addr};
use nym_credentials_interface::CredentialSpendingData;
use nym_crypto::asymmetric::x25519::PrivateKey;
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
use nym_sphinx::addressing::clients::Recipient;
use nym_wireguard_types::PeerPublicKey;
use crate::{
v1, v2, v3, v4,
v5::{self, registration::IpPair},
Error,
};
use crate::latest::registration::IpPair;
use crate::{v1, v2, v3, v4, v5, AuthenticatorVersion, Error};
#[derive(Copy, Clone, Debug)]
pub enum AuthenticatorVersion {
V1,
V2,
V3,
V4,
V5,
UNKNOWN,
pub trait Versionable {
fn version(&self) -> AuthenticatorVersion;
}
impl From<Protocol> for AuthenticatorVersion {
fn from(value: Protocol) -> Self {
if value.service_provider_type != ServiceProviderType::Authenticator {
AuthenticatorVersion::UNKNOWN
} else if value.version == v1::VERSION {
AuthenticatorVersion::V1
} else if value.version == v2::VERSION {
AuthenticatorVersion::V2
} else if value.version == v3::VERSION {
AuthenticatorVersion::V3
} else if value.version == v4::VERSION {
AuthenticatorVersion::V4
} else if value.version == v5::VERSION {
AuthenticatorVersion::V5
} else {
AuthenticatorVersion::UNKNOWN
}
impl Versionable for v1::GatewayClient {
fn version(&self) -> AuthenticatorVersion {
AuthenticatorVersion::V1
}
}
pub trait InitMessage {
impl Versionable for v1::registration::InitMessage {
fn version(&self) -> AuthenticatorVersion {
AuthenticatorVersion::V1
}
}
impl Versionable for v2::registration::InitMessage {
fn version(&self) -> AuthenticatorVersion {
AuthenticatorVersion::V2
}
}
impl Versionable for v3::registration::InitMessage {
fn version(&self) -> AuthenticatorVersion {
AuthenticatorVersion::V3
}
}
impl Versionable for v4::registration::InitMessage {
fn version(&self) -> AuthenticatorVersion {
AuthenticatorVersion::V4
}
}
impl Versionable for v5::registration::InitMessage {
fn version(&self) -> AuthenticatorVersion {
AuthenticatorVersion::V5
}
}
impl Versionable for v2::registration::FinalMessage {
fn version(&self) -> AuthenticatorVersion {
AuthenticatorVersion::V2
}
}
impl Versionable for v3::registration::FinalMessage {
fn version(&self) -> AuthenticatorVersion {
AuthenticatorVersion::V3
}
}
impl Versionable for v4::registration::FinalMessage {
fn version(&self) -> AuthenticatorVersion {
AuthenticatorVersion::V4
}
}
impl Versionable for v5::registration::FinalMessage {
fn version(&self) -> AuthenticatorVersion {
AuthenticatorVersion::V5
}
}
impl Versionable for PeerPublicKey {
fn version(&self) -> AuthenticatorVersion {
AuthenticatorVersion::V3
}
}
impl Versionable for v3::topup::TopUpMessage {
fn version(&self) -> AuthenticatorVersion {
AuthenticatorVersion::V3
}
}
impl Versionable for v4::topup::TopUpMessage {
fn version(&self) -> AuthenticatorVersion {
AuthenticatorVersion::V4
}
}
impl Versionable for v5::topup::TopUpMessage {
fn version(&self) -> AuthenticatorVersion {
AuthenticatorVersion::V5
}
}
pub trait InitMessage: Versionable + fmt::Debug {
fn pub_key(&self) -> PeerPublicKey;
}
@@ -77,15 +133,18 @@ impl InitMessage for v5::registration::InitMessage {
}
}
pub trait FinalMessage {
fn pub_key(&self) -> PeerPublicKey;
pub trait FinalMessage: Versionable + fmt::Debug {
fn gateway_client_pub_key(&self) -> PeerPublicKey;
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error>;
fn private_ips(&self) -> IpPair;
fn gateway_client_ipv4(&self) -> Option<Ipv4Addr>;
fn gateway_client_ipv6(&self) -> Option<Ipv6Addr>;
fn gateway_client_mac(&self) -> Vec<u8>;
fn credential(&self) -> Option<CredentialSpendingData>;
}
impl FinalMessage for v1::GatewayClient {
fn pub_key(&self) -> PeerPublicKey {
fn gateway_client_pub_key(&self) -> PeerPublicKey {
self.pub_key
}
@@ -97,13 +156,28 @@ impl FinalMessage for v1::GatewayClient {
self.private_ip.into()
}
fn gateway_client_ipv4(&self) -> Option<Ipv4Addr> {
match self.private_ip {
std::net::IpAddr::V4(ipv4_addr) => Some(ipv4_addr),
std::net::IpAddr::V6(_) => None,
}
}
fn gateway_client_ipv6(&self) -> Option<Ipv6Addr> {
None
}
fn gateway_client_mac(&self) -> Vec<u8> {
self.mac.to_vec()
}
fn credential(&self) -> Option<CredentialSpendingData> {
None
}
}
impl FinalMessage for v2::registration::FinalMessage {
fn pub_key(&self) -> PeerPublicKey {
fn gateway_client_pub_key(&self) -> PeerPublicKey {
self.gateway_client.pub_key
}
@@ -115,13 +189,28 @@ impl FinalMessage for v2::registration::FinalMessage {
self.gateway_client.private_ip.into()
}
fn gateway_client_ipv4(&self) -> Option<Ipv4Addr> {
match self.gateway_client.private_ip {
std::net::IpAddr::V4(ipv4_addr) => Some(ipv4_addr),
std::net::IpAddr::V6(_) => None,
}
}
fn gateway_client_ipv6(&self) -> Option<Ipv6Addr> {
None
}
fn gateway_client_mac(&self) -> Vec<u8> {
self.gateway_client.mac.to_vec()
}
fn credential(&self) -> Option<CredentialSpendingData> {
self.credential.clone()
}
}
impl FinalMessage for v3::registration::FinalMessage {
fn pub_key(&self) -> PeerPublicKey {
fn gateway_client_pub_key(&self) -> PeerPublicKey {
self.gateway_client.pub_key
}
@@ -133,13 +222,28 @@ impl FinalMessage for v3::registration::FinalMessage {
self.gateway_client.private_ip.into()
}
fn gateway_client_ipv4(&self) -> Option<Ipv4Addr> {
match self.gateway_client.private_ip {
std::net::IpAddr::V4(ipv4_addr) => Some(ipv4_addr),
std::net::IpAddr::V6(_) => None,
}
}
fn gateway_client_ipv6(&self) -> Option<Ipv6Addr> {
None
}
fn gateway_client_mac(&self) -> Vec<u8> {
self.gateway_client.mac.to_vec()
}
fn credential(&self) -> Option<CredentialSpendingData> {
self.credential.clone()
}
}
impl FinalMessage for v4::registration::FinalMessage {
fn pub_key(&self) -> PeerPublicKey {
fn gateway_client_pub_key(&self) -> PeerPublicKey {
self.gateway_client.pub_key
}
@@ -151,13 +255,25 @@ impl FinalMessage for v4::registration::FinalMessage {
self.gateway_client.private_ips.into()
}
fn gateway_client_ipv4(&self) -> Option<Ipv4Addr> {
Some(self.gateway_client.private_ips.ipv4)
}
fn gateway_client_ipv6(&self) -> Option<Ipv6Addr> {
Some(self.gateway_client.private_ips.ipv6)
}
fn gateway_client_mac(&self) -> Vec<u8> {
self.gateway_client.mac.to_vec()
}
fn credential(&self) -> Option<CredentialSpendingData> {
self.credential.clone()
}
}
impl FinalMessage for v5::registration::FinalMessage {
fn pub_key(&self) -> PeerPublicKey {
fn gateway_client_pub_key(&self) -> PeerPublicKey {
self.gateway_client.pub_key
}
@@ -169,12 +285,24 @@ impl FinalMessage for v5::registration::FinalMessage {
self.gateway_client.private_ips
}
fn gateway_client_ipv4(&self) -> Option<Ipv4Addr> {
Some(self.gateway_client.private_ips.ipv4)
}
fn gateway_client_ipv6(&self) -> Option<Ipv6Addr> {
Some(self.gateway_client.private_ips.ipv6)
}
fn gateway_client_mac(&self) -> Vec<u8> {
self.gateway_client.mac.to_vec()
}
fn credential(&self) -> Option<CredentialSpendingData> {
self.credential.clone()
}
}
pub trait QueryBandwidthMessage {
pub trait QueryBandwidthMessage: Versionable + fmt::Debug {
fn pub_key(&self) -> PeerPublicKey;
}
@@ -184,7 +312,7 @@ impl QueryBandwidthMessage for PeerPublicKey {
}
}
pub trait TopUpMessage {
pub trait TopUpMessage: Versionable + fmt::Debug {
fn pub_key(&self) -> PeerPublicKey;
fn credential(&self) -> CredentialSpendingData;
}
@@ -219,197 +347,286 @@ impl TopUpMessage for v5::topup::TopUpMessage {
}
}
pub enum AuthenticatorRequest {
Initial {
msg: Box<dyn InitMessage + Send + Sync + 'static>,
protocol: Protocol,
reply_to: Option<Recipient>,
request_id: u64,
},
Final {
msg: Box<dyn FinalMessage + Send + Sync + 'static>,
protocol: Protocol,
reply_to: Option<Recipient>,
request_id: u64,
},
QueryBandwidth {
msg: Box<dyn QueryBandwidthMessage + Send + Sync + 'static>,
protocol: Protocol,
reply_to: Option<Recipient>,
request_id: u64,
},
TopUpBandwidth {
msg: Box<dyn TopUpMessage + Send + Sync + 'static>,
protocol: Protocol,
reply_to: Option<Recipient>,
request_id: u64,
},
pub trait Id {
fn id(&self) -> u64;
}
impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
fn from(value: v1::request::AuthenticatorRequest) -> Self {
match value.data {
v1::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: Protocol {
version: value.version,
service_provider_type: ServiceProviderType::Authenticator,
},
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v1::request::AuthenticatorRequestData::Final(gateway_client) => Self::Final {
msg: Box::new(gateway_client),
protocol: Protocol {
version: value.version,
service_provider_type: ServiceProviderType::Authenticator,
},
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v1::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: Protocol {
version: value.version,
service_provider_type: ServiceProviderType::Authenticator,
},
reply_to: Some(value.reply_to),
request_id: value.request_id,
}
}
}
impl Id for v2::response::PendingRegistrationResponse {
fn id(&self) -> u64 {
self.request_id
}
}
impl From<v2::request::AuthenticatorRequest> for AuthenticatorRequest {
fn from(value: v2::request::AuthenticatorRequest) -> Self {
match value.data {
v2::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: value.protocol,
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v2::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message,
protocol: value.protocol,
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v2::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: value.protocol,
reply_to: Some(value.reply_to),
request_id: value.request_id,
}
}
}
impl Id for v3::response::PendingRegistrationResponse {
fn id(&self) -> u64 {
self.request_id
}
}
impl From<v3::request::AuthenticatorRequest> for AuthenticatorRequest {
fn from(value: v3::request::AuthenticatorRequest) -> Self {
match value.data {
v3::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: value.protocol,
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v3::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message,
protocol: value.protocol,
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v3::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: value.protocol,
reply_to: Some(value.reply_to),
request_id: value.request_id,
}
}
v3::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
Self::TopUpBandwidth {
msg: top_up_message,
protocol: value.protocol,
reply_to: Some(value.reply_to),
request_id: value.request_id,
}
}
}
impl Id for v4::response::PendingRegistrationResponse {
fn id(&self) -> u64 {
self.request_id
}
}
impl From<v4::request::AuthenticatorRequest> for AuthenticatorRequest {
fn from(value: v4::request::AuthenticatorRequest) -> Self {
match value.data {
v4::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: value.protocol,
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v4::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message,
protocol: value.protocol,
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v4::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: value.protocol,
reply_to: Some(value.reply_to),
request_id: value.request_id,
}
}
v4::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
Self::TopUpBandwidth {
msg: top_up_message,
protocol: value.protocol,
reply_to: Some(value.reply_to),
request_id: value.request_id,
}
}
}
impl Id for v5::response::PendingRegistrationResponse {
fn id(&self) -> u64 {
self.request_id
}
}
impl From<v5::request::AuthenticatorRequest> for AuthenticatorRequest {
fn from(value: v5::request::AuthenticatorRequest) -> Self {
match value.data {
v5::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: value.protocol,
reply_to: None,
request_id: value.request_id,
},
v5::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message,
protocol: value.protocol,
reply_to: None,
request_id: value.request_id,
},
v5::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: value.protocol,
reply_to: None,
request_id: value.request_id,
}
}
v5::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
Self::TopUpBandwidth {
msg: top_up_message,
protocol: value.protocol,
reply_to: None,
request_id: value.request_id,
}
}
}
impl Id for v2::response::RegisteredResponse {
fn id(&self) -> u64 {
self.request_id
}
}
impl Id for v3::response::RegisteredResponse {
fn id(&self) -> u64 {
self.request_id
}
}
impl Id for v4::response::RegisteredResponse {
fn id(&self) -> u64 {
self.request_id
}
}
impl Id for v5::response::RegisteredResponse {
fn id(&self) -> u64 {
self.request_id
}
}
impl Id for v2::response::RemainingBandwidthResponse {
fn id(&self) -> u64 {
self.request_id
}
}
impl Id for v3::response::RemainingBandwidthResponse {
fn id(&self) -> u64 {
self.request_id
}
}
impl Id for v4::response::RemainingBandwidthResponse {
fn id(&self) -> u64 {
self.request_id
}
}
impl Id for v5::response::RemainingBandwidthResponse {
fn id(&self) -> u64 {
self.request_id
}
}
impl Id for v3::response::TopUpBandwidthResponse {
fn id(&self) -> u64 {
self.request_id
}
}
impl Id for v4::response::TopUpBandwidthResponse {
fn id(&self) -> u64 {
self.request_id
}
}
impl Id for v5::response::TopUpBandwidthResponse {
fn id(&self) -> u64 {
self.request_id
}
}
pub trait PendingRegistrationResponse: Id + fmt::Debug {
fn nonce(&self) -> u64;
fn verify(&self, gateway_key: &PrivateKey) -> std::result::Result<(), Error>;
fn pub_key(&self) -> PeerPublicKey;
fn private_ips(&self) -> IpPair;
}
impl PendingRegistrationResponse for v2::response::PendingRegistrationResponse {
fn nonce(&self) -> u64 {
self.reply.nonce
}
fn verify(&self, gateway_key: &PrivateKey) -> std::result::Result<(), Error> {
self.reply.gateway_data.verify(gateway_key, self.nonce())
}
fn pub_key(&self) -> PeerPublicKey {
self.reply.gateway_data.pub_key
}
fn private_ips(&self) -> IpPair {
self.reply.gateway_data.private_ip.into()
}
}
impl PendingRegistrationResponse for v3::response::PendingRegistrationResponse {
fn nonce(&self) -> u64 {
self.reply.nonce
}
fn verify(&self, gateway_key: &PrivateKey) -> std::result::Result<(), Error> {
self.reply.gateway_data.verify(gateway_key, self.nonce())
}
fn pub_key(&self) -> PeerPublicKey {
self.reply.gateway_data.pub_key
}
fn private_ips(&self) -> IpPair {
self.reply.gateway_data.private_ip.into()
}
}
impl PendingRegistrationResponse for v4::response::PendingRegistrationResponse {
fn nonce(&self) -> u64 {
self.reply.nonce
}
fn verify(&self, gateway_key: &PrivateKey) -> std::result::Result<(), Error> {
self.reply.gateway_data.verify(gateway_key, self.nonce())
}
fn pub_key(&self) -> PeerPublicKey {
self.reply.gateway_data.pub_key
}
fn private_ips(&self) -> IpPair {
self.reply.gateway_data.private_ips.into()
}
}
impl PendingRegistrationResponse for v5::response::PendingRegistrationResponse {
fn nonce(&self) -> u64 {
self.reply.nonce
}
fn verify(&self, gateway_key: &PrivateKey) -> std::result::Result<(), Error> {
self.reply.gateway_data.verify(gateway_key, self.nonce())
}
fn pub_key(&self) -> PeerPublicKey {
self.reply.gateway_data.pub_key
}
fn private_ips(&self) -> IpPair {
self.reply.gateway_data.private_ips
}
}
pub trait RegisteredResponse: Id + fmt::Debug {
fn private_ips(&self) -> IpPair;
fn pub_key(&self) -> PeerPublicKey;
fn wg_port(&self) -> u16;
}
impl RegisteredResponse for v2::response::RegisteredResponse {
fn private_ips(&self) -> IpPair {
self.reply.private_ip.into()
}
fn pub_key(&self) -> PeerPublicKey {
self.reply.pub_key
}
fn wg_port(&self) -> u16 {
self.reply.wg_port
}
}
impl RegisteredResponse for v3::response::RegisteredResponse {
fn private_ips(&self) -> IpPair {
self.reply.private_ip.into()
}
fn pub_key(&self) -> PeerPublicKey {
self.reply.pub_key
}
fn wg_port(&self) -> u16 {
self.reply.wg_port
}
}
impl RegisteredResponse for v4::response::RegisteredResponse {
fn private_ips(&self) -> IpPair {
self.reply.private_ips.into()
}
fn pub_key(&self) -> PeerPublicKey {
self.reply.pub_key
}
fn wg_port(&self) -> u16 {
self.reply.wg_port
}
}
impl RegisteredResponse for v5::response::RegisteredResponse {
fn private_ips(&self) -> IpPair {
self.reply.private_ips
}
fn pub_key(&self) -> PeerPublicKey {
self.reply.pub_key
}
fn wg_port(&self) -> u16 {
self.reply.wg_port
}
}
pub trait RemainingBandwidthResponse: Id + fmt::Debug {
fn available_bandwidth(&self) -> Option<i64>;
}
impl RemainingBandwidthResponse for v2::response::RemainingBandwidthResponse {
fn available_bandwidth(&self) -> Option<i64> {
self.reply.as_ref().map(|r| r.available_bandwidth)
}
}
impl RemainingBandwidthResponse for v3::response::RemainingBandwidthResponse {
fn available_bandwidth(&self) -> Option<i64> {
self.reply.as_ref().map(|r| r.available_bandwidth)
}
}
impl RemainingBandwidthResponse for v4::response::RemainingBandwidthResponse {
fn available_bandwidth(&self) -> Option<i64> {
self.reply.as_ref().map(|r| r.available_bandwidth)
}
}
impl RemainingBandwidthResponse for v5::response::RemainingBandwidthResponse {
fn available_bandwidth(&self) -> Option<i64> {
self.reply.as_ref().map(|r| r.available_bandwidth)
}
}
pub trait TopUpBandwidthResponse: Id + fmt::Debug {
fn available_bandwidth(&self) -> i64;
}
impl TopUpBandwidthResponse for v3::response::TopUpBandwidthResponse {
fn available_bandwidth(&self) -> i64 {
self.reply.available_bandwidth
}
}
impl TopUpBandwidthResponse for v4::response::TopUpBandwidthResponse {
fn available_bandwidth(&self) -> i64 {
self.reply.available_bandwidth
}
}
impl TopUpBandwidthResponse for v5::response::TopUpBandwidthResponse {
fn available_bandwidth(&self) -> i64 {
self.reply.available_bandwidth
}
}
@@ -0,0 +1,191 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::{v1, v2, v3, v4, v5};
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
#[derive(Copy, Clone, Debug, PartialEq, strum_macros::Display)]
#[strum(serialize_all = "snake_case")]
pub enum AuthenticatorVersion {
/// introduced in wispa release (1.1.5)
V1,
/// introduced in aero release (1.1.9)
V2,
/// introduced in magura release (1.1.10)
V3,
/// introduced in crunch release (1.2.0)
V4,
/// introduced in dorina-patched release (1.6.1)
V5,
UNKNOWN,
}
impl AuthenticatorVersion {
pub const LATEST: Self = Self::V5;
pub const fn release_version(&self) -> semver::Version {
match self {
AuthenticatorVersion::V1 => semver::Version::new(1, 1, 5),
AuthenticatorVersion::V2 => semver::Version::new(1, 1, 9),
AuthenticatorVersion::V3 => semver::Version::new(1, 1, 10),
AuthenticatorVersion::V4 => semver::Version::new(1, 2, 0),
AuthenticatorVersion::V5 => semver::Version::new(1, 6, 1),
AuthenticatorVersion::UNKNOWN => semver::Version::new(0, 0, 0),
}
}
}
impl From<Protocol> for AuthenticatorVersion {
fn from(value: Protocol) -> Self {
if value.service_provider_type != ServiceProviderType::Authenticator {
AuthenticatorVersion::UNKNOWN
} else if value.version == v1::VERSION {
AuthenticatorVersion::V1
} else if value.version == v2::VERSION {
AuthenticatorVersion::V2
} else if value.version == v3::VERSION {
AuthenticatorVersion::V3
} else if value.version == v4::VERSION {
AuthenticatorVersion::V4
} else if value.version == v5::VERSION {
AuthenticatorVersion::V5
} else {
AuthenticatorVersion::UNKNOWN
}
}
}
impl From<u8> for AuthenticatorVersion {
fn from(value: u8) -> Self {
if value == v1::VERSION {
AuthenticatorVersion::V1
} else if value == v2::VERSION {
AuthenticatorVersion::V2
} else if value == v3::VERSION {
AuthenticatorVersion::V3
} else if value == v4::VERSION {
AuthenticatorVersion::V4
} else if value == v5::VERSION {
AuthenticatorVersion::V5
} else {
AuthenticatorVersion::UNKNOWN
}
}
}
impl From<&str> for AuthenticatorVersion {
fn from(value: &str) -> Self {
let Ok(semver) = semver::Version::parse(value) else {
return Self::UNKNOWN;
};
semver.into()
}
}
impl From<Option<&String>> for AuthenticatorVersion {
fn from(value: Option<&String>) -> Self {
match value {
None => Self::UNKNOWN,
Some(value) => value.as_str().into(),
}
}
}
impl From<String> for AuthenticatorVersion {
fn from(value: String) -> Self {
Self::from(value.as_str())
}
}
impl From<Option<String>> for AuthenticatorVersion {
fn from(value: Option<String>) -> Self {
value.as_ref().into()
}
}
impl From<semver::Version> for AuthenticatorVersion {
fn from(semver: semver::Version) -> Self {
if semver < AuthenticatorVersion::V1.release_version() {
return Self::UNKNOWN;
}
if semver < AuthenticatorVersion::V2.release_version() {
return Self::V1;
}
if semver < AuthenticatorVersion::V3.release_version() {
return Self::V2;
}
if semver < AuthenticatorVersion::V4.release_version() {
return Self::V3;
}
if semver < AuthenticatorVersion::V5.release_version() {
return Self::V4;
}
// if provided version is higher (or equal) to release version of V5,
// we return the latest (i.e. v5)
debug_assert_eq!(Self::V5, Self::LATEST, "a new AuthenticatorVersion variant has been introduced without adjusting the `From<semver::Version>` trait");
Self::LATEST
}
}
#[cfg(test)]
mod tests {
use super::super::latest;
use super::*;
#[test]
fn strum_display() {
// sanity check on formatting and casing
assert_eq!("v1", AuthenticatorVersion::V1.to_string());
assert_eq!("v2", AuthenticatorVersion::V2.to_string());
assert_eq!("unknown", AuthenticatorVersion::UNKNOWN.to_string());
}
#[test]
fn u8_conversion() {
assert_eq!(AuthenticatorVersion::V1, AuthenticatorVersion::from(1u8));
assert_eq!(AuthenticatorVersion::V2, AuthenticatorVersion::from(2u8));
assert_eq!(
AuthenticatorVersion::UNKNOWN,
AuthenticatorVersion::from(latest::VERSION + 1)
);
assert_eq!(
AuthenticatorVersion::UNKNOWN,
AuthenticatorVersion::from(0u8)
);
assert_eq!(
AuthenticatorVersion::UNKNOWN,
AuthenticatorVersion::from(255u8)
);
}
#[test]
fn semver_checks() {
assert_eq!(AuthenticatorVersion::UNKNOWN, "1.1.4".into());
assert_eq!(AuthenticatorVersion::UNKNOWN, "0.1.0".into());
assert_eq!(AuthenticatorVersion::UNKNOWN, "1.0.4".into());
assert_eq!(AuthenticatorVersion::V1, "1.1.5".into());
assert_eq!(AuthenticatorVersion::V1, "1.1.6".into());
assert_eq!(AuthenticatorVersion::V1, "1.1.8".into());
assert_eq!(AuthenticatorVersion::V2, "1.1.9".into());
assert_eq!(AuthenticatorVersion::V3, "1.1.10".into());
assert_eq!(AuthenticatorVersion::V3, "1.1.11".into());
assert_eq!(AuthenticatorVersion::V3, "1.1.60".into());
assert_eq!(AuthenticatorVersion::V4, "1.2.0".into());
assert_eq!(AuthenticatorVersion::V4, "1.2.1".into());
assert_eq!(AuthenticatorVersion::V4, "1.5.1".into());
assert_eq!(AuthenticatorVersion::V4, "1.6.0".into());
assert_eq!(AuthenticatorVersion::V5, "1.6.1".into());
assert_eq!(AuthenticatorVersion::V5, "1.6.11".into());
assert_eq!(AuthenticatorVersion::V5, "1.7.0".into());
assert_eq!(AuthenticatorVersion::V5, "1.16.11".into());
assert_eq!(AuthenticatorVersion::V5, "1.17.0".into());
}
}
+1
View File
@@ -7,6 +7,7 @@ license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-trait = { workspace = true }
bip39 = { workspace = true }
log = { workspace = true }
rand = { workspace = true }
+2
View File
@@ -23,10 +23,12 @@ use nym_validator_client::nym_api::EpochId;
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
pub use event::BandwidthStatusMessage;
pub use traits::{BandwidthTicketProvider, DEFAULT_TICKETS_TO_SPEND};
pub mod acquire;
pub mod error;
mod event;
mod traits;
mod utils;
#[derive(Debug)]
+42
View File
@@ -0,0 +1,42 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use async_trait::async_trait;
use nym_credential_storage::storage::Storage;
use nym_credentials_interface::TicketType;
use nym_crypto::asymmetric::ed25519;
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
use crate::{error::BandwidthControllerError, BandwidthController, PreparedCredential};
pub const DEFAULT_TICKETS_TO_SPEND: u32 = 1;
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait BandwidthTicketProvider: Send + Sync {
async fn get_ecash_ticket(
&self,
ticket_type: TicketType,
gateway_id: ed25519::PublicKey,
tickets_to_spend: u32,
) -> Result<PreparedCredential, BandwidthControllerError>;
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<C, St> BandwidthTicketProvider for BandwidthController<C, St>
where
C: DkgQueryClient + Sync + Send,
St: nym_credential_storage::storage::Storage,
<St as Storage>::StorageError: Send + Sync + 'static,
{
async fn get_ecash_ticket(
&self,
ticket_type: TicketType,
gateway_id: ed25519::PublicKey,
tickets_to_spend: u32,
) -> Result<PreparedCredential, BandwidthControllerError> {
self.prepare_ecash_ticket(ticket_type, gateway_id.to_bytes(), tickets_to_spend)
.await
}
}
@@ -1050,7 +1050,7 @@ where
gateway_connection: GatewayConnection { gateway_ws_fd },
},
stats_reporter,
shutdown_handle: Some(shutdown_tracker), // The primary tracker for this client
shutdown_handle: shutdown_tracker, // The primary tracker for this client
client_request_sender,
forget_me: self.config.debug.forget_me,
remember_me: self.config.debug.remember_me,
@@ -1066,7 +1066,7 @@ pub struct BaseClient {
pub client_state: ClientState,
pub stats_reporter: ClientStatsSender,
pub client_request_sender: ClientRequestSender,
pub shutdown_handle: Option<ShutdownTracker>,
pub shutdown_handle: ShutdownTracker,
pub forget_me: ForgetMe,
pub remember_me: RememberMe,
}
+20
View File
@@ -0,0 +1,20 @@
[package]
name = "nym-registration-common"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
[lints]
workspace = true
[dependencies]
tokio-util.workspace = true
nym-authenticator-requests = { path = "../authenticator-requests" }
nym-crypto = { path = "../crypto" }
nym-ip-packet-requests = { path = "../ip-packet-requests" }
nym-sphinx = { path = "../nymsphinx" }
+40
View File
@@ -0,0 +1,40 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use nym_authenticator_requests::AuthenticatorVersion;
use nym_crypto::asymmetric::x25519::PublicKey;
use nym_ip_packet_requests::IpPair;
use nym_sphinx::addressing::{NodeIdentity, Recipient};
pub const DEFAULT_PRIVATE_ENTRY_WIREGUARD_KEY_FILENAME: &str = "free_private_entry_wireguard.pem";
pub const DEFAULT_PUBLIC_ENTRY_WIREGUARD_KEY_FILENAME: &str = "free_public_entry_wireguard.pem";
pub const DEFAULT_PRIVATE_EXIT_WIREGUARD_KEY_FILENAME: &str = "free_private_exit_wireguard.pem";
pub const DEFAULT_PUBLIC_EXIT_WIREGUARD_KEY_FILENAME: &str = "free_public_exit_wireguard.pem";
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct NymNode {
pub identity: NodeIdentity,
pub ip_address: IpAddr,
pub ipr_address: Option<Recipient>,
pub authenticator_address: Option<Recipient>,
pub version: AuthenticatorVersion,
}
#[derive(Clone, Debug)]
pub struct GatewayData {
pub public_key: PublicKey,
pub endpoint: SocketAddr,
pub private_ipv4: Ipv4Addr,
pub private_ipv6: Ipv6Addr,
}
#[derive(Clone, Copy, Debug)]
pub struct AssignedAddresses {
pub entry_mixnet_gateway_ip: IpAddr,
pub exit_mixnet_gateway_ip: IpAddr,
pub mixnet_client_address: Recipient,
pub exit_mix_address: Recipient,
pub interface_addresses: IpPair,
}
@@ -42,6 +42,40 @@ impl TryFrom<u8> for ServiceProviderType {
}
}
pub trait ServiceProviderTypeExt {
fn is_network_requester(&self) -> bool;
fn is_ip_packet_router(&self) -> bool;
fn is_authenticator(&self) -> bool;
}
impl ServiceProviderTypeExt for ServiceProviderType {
fn is_network_requester(&self) -> bool {
matches!(self, Self::NetworkRequester)
}
fn is_ip_packet_router(&self) -> bool {
matches!(self, Self::IpPacketRouter)
}
fn is_authenticator(&self) -> bool {
matches!(self, Self::Authenticator)
}
}
impl ServiceProviderTypeExt for u8 {
fn is_network_requester(&self) -> bool {
ServiceProviderType::NetworkRequester as u8 == *self
}
fn is_ip_packet_router(&self) -> bool {
ServiceProviderType::IpPacketRouter as u8 == *self
}
fn is_authenticator(&self) -> bool {
ServiceProviderType::Authenticator as u8 == *self
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct Protocol {
pub version: u8,
@@ -19,11 +19,9 @@ use nym_authenticator_requests::{
};
use nym_authenticator_requests::{
latest::registration::{GatewayClient, PendingRegistrations, PrivateIPs},
traits::{
AuthenticatorRequest, AuthenticatorVersion, FinalMessage, InitMessage,
QueryBandwidthMessage, TopUpMessage,
},
v1, v2, v3, v4, v5, CURRENT_VERSION,
request::AuthenticatorRequest,
traits::{FinalMessage, InitMessage, QueryBandwidthMessage, TopUpMessage},
v1, v2, v3, v4, v5, AuthenticatorVersion, CURRENT_VERSION,
};
use nym_credential_verification::ecash::traits::EcashManager;
use nym_credential_verification::{
@@ -480,7 +478,7 @@ impl MixnetListener {
let mut registred_and_free = self.registred_and_free.write().await;
let registration_data = registred_and_free
.registration_in_progres
.get(&final_message.pub_key())
.get(&final_message.gateway_client_pub_key())
.ok_or(AuthenticatorError::RegistrationNotInProgress)?
.clone();
@@ -491,7 +489,7 @@ impl MixnetListener {
return Err(AuthenticatorError::MacVerificationFailure);
}
let mut peer = Peer::new(Key::new(final_message.pub_key().to_bytes()));
let mut peer = Peer::new(Key::new(final_message.gateway_client_pub_key().to_bytes()));
peer.allowed_ips
.push(IpAddrMask::new(final_message.private_ips().ipv4.into(), 32));
peer.allowed_ips.push(IpAddrMask::new(
@@ -532,7 +530,7 @@ impl MixnetListener {
registred_and_free
.registration_in_progres
.remove(&final_message.pub_key());
.remove(&final_message.gateway_client_pub_key());
let bytes = match AuthenticatorVersion::from(protocol) {
AuthenticatorVersion::V1 => v1::response::AuthenticatorResponse::new_registered(
+5
View File
@@ -14,6 +14,7 @@ workspace = true
[dependencies]
bincode.workspace = true
futures.workspace = true
rand.workspace = true
semver.workspace = true
thiserror.workspace = true
tokio-util.workspace = true
@@ -21,8 +22,12 @@ tokio.workspace = true
tracing.workspace = true
nym-authenticator-requests = { path = "../common/authenticator-requests" }
nym-bandwidth-controller = { path = "../common/bandwidth-controller" }
nym-credentials-interface = { path = "../common/credentials-interface" }
nym-crypto = { path = "../common/crypto" }
nym-pemstore = { path = "../common/pemstore" }
nym-registration-common = { path = "../common/registration" }
nym-sdk = { path = "../sdk/rust/nym-sdk" }
nym-service-provider-requests-common = { path = "../common/service-provider-requests-common" }
nym-validator-client = { path = "../common/client-libs/validator-client" }
nym-wireguard-types = { path = "../common/wireguard-types" }
+29 -20
View File
@@ -1,41 +1,50 @@
use nym_credentials_interface::TicketType;
use nym_sdk::mixnet::InputMessage;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("mixnet client stopped returning responses")]
NoMixnetMessagesReceived,
#[error("failed to get version from message")]
NoVersionInMessage,
#[error(
"received response with version v{received}, the client is too new and can only understand v{expected}"
)]
ReceivedResponseWithOldVersion { expected: u8, received: u8 },
#[error(
"received response with version v{received}, the client is too old and can only understand v{expected}"
)]
ReceivedResponseWithNewVersion { expected: u8, received: u8 },
#[error("failed to send mixnet message")]
SendMixnetMessage(#[source] Box<nym_sdk::Error>),
SendMixnetMessage(#[source] Box<tokio::sync::mpsc::error::SendError<InputMessage>>),
#[error("timeout waiting for connect response from exit gateway (authenticator)")]
TimeoutWaitingForConnectResponse,
#[error("unable to get mixnet handle when sending authenticator message")]
UnableToGetMixnetHandle,
#[error("unknown version number")]
UnknownVersion,
#[error("unsupported request version")]
UnsupportedVersion,
#[error(transparent)]
Bincode(#[from] bincode::Error),
#[error("gateway doesn't support this type of message")]
UnsupportedMessage,
#[error(transparent)]
AuthenticatorRequests(#[from] nym_authenticator_requests::Error),
#[error("verification failure")]
VerificationFailed(#[source] nym_authenticator_requests::Error),
#[error("failed to parse entry gateway socket addr")]
FailedToParseEntryGatewaySocketAddr(#[source] std::net::AddrParseError),
#[error("received invalid response from gateway authenticator")]
InvalidGatewayAuthResponse,
#[error("failed to get {ticketbook_type} ticket")]
GetTicket {
ticketbook_type: TicketType,
#[source]
source: nym_bandwidth_controller::error::BandwidthControllerError,
},
#[error("unknown authenticator version number")]
UnsupportedAuthenticatorVersion,
#[error("failed to wait on AuthenticatorClientListener")]
FailedToJoinOnTask(#[from] tokio::task::JoinError),
}
// Result type based on our error type
+49
View File
@@ -0,0 +1,49 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_crypto::asymmetric::x25519::KeyPair;
use nym_pemstore::KeyPairPath;
use nym_sdk::mixnet::{IncludedSurbs, Recipient, TransmissionLane};
use rand::{CryptoRng, RngCore};
pub(crate) fn create_input_message(
recipient: Recipient,
data: Vec<u8>,
surbs: IncludedSurbs,
) -> nym_sdk::mixnet::InputMessage {
match surbs {
IncludedSurbs::Amount(surbs) => nym_sdk::mixnet::InputMessage::new_anonymous(
recipient,
data,
surbs,
TransmissionLane::General,
None,
),
IncludedSurbs::ExposeSelfAddress => nym_sdk::mixnet::InputMessage::new_regular(
recipient,
data,
TransmissionLane::General,
None,
),
}
}
pub(crate) fn load_or_generate_keypair<R: RngCore + CryptoRng>(
rng: &mut R,
paths: KeyPairPath,
) -> KeyPair {
match nym_pemstore::load_keypair(&paths) {
Ok(keypair) => keypair,
Err(_) => {
let keypair = KeyPair::new(rng);
if let Err(e) = nym_pemstore::store_keypair(&keypair, &paths) {
tracing::error!(
"could not store generated keypair at {:?} - {:?}; will use ephemeral keys",
paths,
e
);
}
keypair
}
}
}
+224
View File
@@ -0,0 +1,224 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::time::Duration;
use tracing::{debug, error};
use crate::mixnet_listener::{MixnetMessageBroadcastReceiver, MixnetMessageInputSender};
use crate::{helpers, ClientMessage, Error, Result};
use nym_authenticator_requests::{
client_message::QueryMessageImpl, response::AuthenticatorResponse, traits::Id, v2, v3, v4, v5,
AuthenticatorVersion,
};
use nym_credentials_interface::CredentialSpendingData;
use nym_crypto::asymmetric::x25519::{KeyPair, PublicKey};
use nym_sdk::mixnet::{IncludedSurbs, Recipient};
use nym_service_provider_requests_common::{Protocol, ServiceProviderTypeExt};
use nym_wireguard_types::PeerPublicKey;
impl crate::AuthenticatorClient {
pub fn into_legacy_and_keypair(self) -> (LegacyAuthenticatorClient, KeyPair) {
(
LegacyAuthenticatorClient {
public_key: *self.keypair.public_key(),
mixnet_listener: self.mixnet_listener,
mixnet_sender: self.mixnet_sender,
our_nym_address: self.our_nym_address,
auth_recipient: self.auth_recipient,
auth_version: self.auth_version,
},
self.keypair,
)
}
}
// This is the legacy Authenticator that has to be used to handle bandwidth top up for legacy gateaways
pub struct LegacyAuthenticatorClient {
public_key: PublicKey,
mixnet_listener: MixnetMessageBroadcastReceiver,
mixnet_sender: MixnetMessageInputSender,
our_nym_address: Recipient,
pub auth_recipient: Recipient,
auth_version: AuthenticatorVersion,
}
impl LegacyAuthenticatorClient {
pub async fn send_and_wait_for_response(
&mut self,
message: &ClientMessage,
) -> Result<AuthenticatorResponse> {
let request_id = self.send_request(message).await?;
debug!("Waiting for reply...");
self.listen_for_response(request_id).await
}
async fn send_request(&self, message: &ClientMessage) -> Result<u64> {
let (data, request_id) = message.bytes(self.our_nym_address)?;
// We use 20 surbs for the connect request because typically the
// authenticator mixnet client on the nym-node is configured to have a min
// threshold of 10 surbs that it reserves for itself to request additional
// surbs.
let surbs = if message.use_surbs() {
match &message {
ClientMessage::Initial(_) => IncludedSurbs::new(20),
_ => IncludedSurbs::new(1),
}
} else {
IncludedSurbs::ExposeSelfAddress
};
let input_message = helpers::create_input_message(self.auth_recipient, data, surbs);
self.mixnet_sender
.send(input_message)
.await
.map_err(|e| Error::SendMixnetMessage(Box::new(e)))?;
Ok(request_id)
}
async fn listen_for_response(&mut self, request_id: u64) -> Result<AuthenticatorResponse> {
let timeout = tokio::time::sleep(Duration::from_secs(10));
tokio::pin!(timeout);
loop {
tokio::select! {
_ = &mut timeout => {
error!("Timed out waiting for reply to connect request");
return Err(Error::TimeoutWaitingForConnectResponse);
}
msg = self.mixnet_listener.recv() => match msg {
Err(_) => {
return Err(Error::NoMixnetMessagesReceived);
}
Ok(msg) => {
let Some(header) = msg.message.first_chunk::<2>() else {
debug!("received too short message that couldn't have been from the authenticator while waiting for connect response");
continue;
};
let Ok(protocol) = Protocol::try_from(header) else {
debug!("received a message not meant to any service provider while waiting for connect response");
continue;
};
if !protocol.service_provider_type.is_authenticator() {
debug!("Received non-authenticator message while waiting for connect response");
continue;
}
// Confirm that the version is correct
let version = AuthenticatorVersion::from(protocol.version);
// Then we deserialize the message
debug!("AuthClient: got message while waiting for connect response with version {version:?}");
let ret: Result<AuthenticatorResponse> = match version {
AuthenticatorVersion::V1 => Err(Error::UnsupportedVersion),
AuthenticatorVersion::V2 => v2::response::AuthenticatorResponse::from_reconstructed_message(&msg).map(Into::into).map_err(Into::into),
AuthenticatorVersion::V3 => v3::response::AuthenticatorResponse::from_reconstructed_message(&msg).map(Into::into).map_err(Into::into),
AuthenticatorVersion::V4 => v4::response::AuthenticatorResponse::from_reconstructed_message(&msg).map(Into::into).map_err(Into::into),
AuthenticatorVersion::V5 => v5::response::AuthenticatorResponse::from_reconstructed_message(&msg).map(Into::into).map_err(Into::into),
AuthenticatorVersion::UNKNOWN => Err(Error::UnknownVersion),
};
let Ok(response) = ret else {
// This is ok, it's likely just one of our self-pings
debug!("Failed to deserialize reconstructed message");
continue;
};
if response.id() == request_id {
debug!("Got response with matching id");
return Ok(response);
}
}
}
}
}
}
pub async fn query_bandwidth(&mut self) -> Result<Option<i64>> {
let query_message = match self.auth_version {
AuthenticatorVersion::V1 => return Err(Error::UnsupportedAuthenticatorVersion),
AuthenticatorVersion::V2 => ClientMessage::Query(Box::new(QueryMessageImpl {
pub_key: PeerPublicKey::new(self.public_key.to_bytes().into()),
version: AuthenticatorVersion::V2,
})),
AuthenticatorVersion::V3 => ClientMessage::Query(Box::new(QueryMessageImpl {
pub_key: PeerPublicKey::new(self.public_key.to_bytes().into()),
version: AuthenticatorVersion::V3,
})),
AuthenticatorVersion::V4 => ClientMessage::Query(Box::new(QueryMessageImpl {
pub_key: PeerPublicKey::new(self.public_key.to_bytes().into()),
version: AuthenticatorVersion::V4,
})),
AuthenticatorVersion::V5 => ClientMessage::Query(Box::new(QueryMessageImpl {
pub_key: PeerPublicKey::new(self.public_key.to_bytes().into()),
version: AuthenticatorVersion::V5,
})),
AuthenticatorVersion::UNKNOWN => return Err(Error::UnsupportedAuthenticatorVersion),
};
let response = self.send_and_wait_for_response(&query_message).await?;
let available_bandwidth = match response {
AuthenticatorResponse::RemainingBandwidth(remaining_bandwidth_response) => {
if let Some(available_bandwidth) =
remaining_bandwidth_response.available_bandwidth()
{
available_bandwidth
} else {
return Ok(None);
}
}
_ => return Err(Error::InvalidGatewayAuthResponse),
};
let remaining_pretty = if available_bandwidth > 1024 * 1024 {
format!("{:.2} MB", available_bandwidth as f64 / 1024.0 / 1024.0)
} else {
format!("{} KB", available_bandwidth / 1024)
};
tracing::debug!(
"Remaining wireguard bandwidth with gateway {} for today: {}",
self.auth_recipient.gateway(),
remaining_pretty
);
if available_bandwidth < 1024 * 1024 {
tracing::warn!(
"Remaining bandwidth is under 1 MB. The wireguard mode will get suspended after that until tomorrow, UTC time. The client might shutdown with timeout soon"
);
}
Ok(Some(available_bandwidth))
}
pub async fn top_up(&mut self, credential: CredentialSpendingData) -> Result<i64> {
let top_up_message = match self.auth_version {
AuthenticatorVersion::V3 => ClientMessage::TopUp(Box::new(v3::topup::TopUpMessage {
pub_key: PeerPublicKey::new(self.public_key.to_bytes().into()),
credential,
})),
// NOTE: looks like a bug here using v3. But we're leaving it as is since it's working
// and V4 is deprecated in favour of V5
AuthenticatorVersion::V4 => ClientMessage::TopUp(Box::new(v4::topup::TopUpMessage {
pub_key: PeerPublicKey::new(self.public_key.to_bytes().into()),
credential,
})),
AuthenticatorVersion::V5 => ClientMessage::TopUp(Box::new(v5::topup::TopUpMessage {
pub_key: PeerPublicKey::new(self.public_key.to_bytes().into()),
credential,
})),
AuthenticatorVersion::V1 | AuthenticatorVersion::V2 | AuthenticatorVersion::UNKNOWN => {
return Err(Error::UnsupportedAuthenticatorVersion);
}
};
let response = self.send_and_wait_for_response(&top_up_message).await?;
let remaining_bandwidth = match response {
AuthenticatorResponse::TopUpBandwidth(top_up_bandwidth_response) => {
top_up_bandwidth_response.available_bandwidth()
}
_ => return Err(Error::InvalidGatewayAuthResponse),
};
Ok(remaining_bandwidth)
}
}
File diff suppressed because it is too large Load Diff
+96 -49
View File
@@ -1,113 +1,160 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
// To remove with the Registration Client PR
#![allow(clippy::unwrap_used)]
use std::sync::Arc;
use futures::StreamExt;
use nym_sdk::mixnet::{MixnetClient, ReconstructedMessage};
use tokio::{sync::broadcast, task::JoinHandle};
use nym_sdk::mixnet::{InputMessage, MixnetClient, MixnetMessageSender, ReconstructedMessage};
use tokio::{
sync::{broadcast, mpsc},
task::JoinHandle,
};
use tokio_util::sync::CancellationToken;
use crate::AuthenticatorMixnetClient;
pub type SharedMixnetClient = Arc<tokio::sync::Mutex<Option<MixnetClient>>>;
pub type MixnetMessageBroadcastSender = broadcast::Sender<Arc<ReconstructedMessage>>;
pub type MixnetMessageBroadcastReceiver = broadcast::Receiver<Arc<ReconstructedMessage>>;
pub type MixnetMessageInputSender = mpsc::Sender<InputMessage>;
pub type MixnetMessageInputReceiver = mpsc::Receiver<InputMessage>; // This could be another type, to abstract the mixnet message creation to here
// The AuthClientsMixnetListener listens to mixnet messages and rebroadcasts them to the
// AuthClients, or whoever else is interested.
// While it is running, it has a lock on the shared mixnet client. This is the reason it's
// designed to be able to start and stop, so that the lock can be released when it's not needed.
// It also manages the message input for the mixnet so it can keep the sole ownership of the MixnetClient
//
// NOTE: this is potentially bit wasteful. Ideally we should have proper channels where the
// recipient only gets messages they're interested in.
pub struct AuthClientMixnetListener {
// The shared mixnet client that we're listening to
mixnet_client: SharedMixnetClient,
// The mixnet client that we're listening to
mixnet_client: MixnetClient,
// Broadcast channel for the messages that we re-broadcast to the AuthClients
message_broadcast: MixnetMessageBroadcastSender,
// Channel for message to send to the mixnet
input_message_tx: MixnetMessageInputSender, // we keep on to make sure it's open
input_message_rx: MixnetMessageInputReceiver,
// Listen to cancel from the outside world
shutdown_token: CancellationToken,
}
impl AuthClientMixnetListener {
pub fn new(mixnet_client: SharedMixnetClient, shutdown_token: CancellationToken) -> Self {
pub fn new(mixnet_client: MixnetClient, shutdown_token: CancellationToken) -> Self {
let (message_broadcast, _) = broadcast::channel(100);
let (input_message_tx, input_message_rx) = mpsc::channel(100);
Self {
mixnet_client,
message_broadcast,
input_message_tx,
input_message_rx,
shutdown_token,
}
}
pub fn subscribe(&self) -> MixnetMessageBroadcastReceiver {
self.message_broadcast.subscribe()
}
async fn run(mut self) -> Self {
let mixnet_cancel_token = self.mixnet_client.cancellation_token();
self.shutdown_token.run_until_cancelled(async {
loop {
tokio::select! {
biased;
_ = mixnet_cancel_token.cancelled() => {
tracing::debug!("AuthClientMixnetListener: mixnet client was shutdown");
break;
}
async fn run(self) {
let mut mixnet_client = self.mixnet_client.lock().await.take().unwrap();
self.shutdown_token
.run_until_cancelled(async {
while let Some(event) = mixnet_client.next().await {
if let Err(err) = self.message_broadcast.send(Arc::new(event)) {
tracing::error!("Failed to broadcast mixnet message: {err}");
// Sending loop
input_msg = self.input_message_rx.recv() => {
match input_msg {
None => {
tracing::error!("All senders were dropped. It shouldn't happen as we're holding one");
break;
},
Some(mix_msg) => {
if let Err(err) = self.mixnet_client.send(mix_msg).await {
tracing::error!("Failed to send mixnet message: {err}");
}
},
}
}
// Receiving loop
msg = self.mixnet_client.next() => {
match msg {
None => {
tracing::error!("Mixnet client stream ended unexpectedly");
break;
},
Some(event) => {
if let Err(err) = self.message_broadcast.send(Arc::new(event)) {
tracing::error!("Failed to broadcast mixnet message: {err}");
}
},
}
}
}
tracing::error!("Mixnet client stream ended unexpectedly");
})
.await;
self.mixnet_client.lock().await.replace(mixnet_client);
}
tracing::debug!("AuthClientMixnetListener is shutting down");
}).await;
self
}
// Disconnects the mixnet client and effectively drop itself, since it doesn't work without one, and reconnecting isn't supported
pub async fn disconnect_mixnet_client(self) {
if !self.mixnet_client.cancellation_token().is_cancelled() {
self.mixnet_client.disconnect().await;
}
}
pub fn start(self) -> AuthClientMixnetListenerHandle {
let mixnet_client = self.mixnet_client.clone();
let message_broadcast = self.message_broadcast.clone();
let message_sender = self.input_message_tx.clone();
// Allows stopping only this, e.g. if we don't need it in the new bandwidth controller
let cancellation_token = self.shutdown_token.clone();
let mixnet_cancellation_token = self.mixnet_client.cancellation_token();
let handle = tokio::spawn(self.run());
AuthClientMixnetListenerHandle {
mixnet_client,
message_broadcast,
message_sender,
cancellation_token,
mixnet_cancellation_token,
handle,
}
}
}
pub struct AuthClientMixnetListenerHandle {
mixnet_client: SharedMixnetClient,
message_broadcast: MixnetMessageBroadcastSender,
handle: JoinHandle<()>,
message_sender: MixnetMessageInputSender,
cancellation_token: CancellationToken,
mixnet_cancellation_token: CancellationToken,
handle: JoinHandle<AuthClientMixnetListener>,
}
impl AuthClientMixnetListenerHandle {
/// Returns new `AuthClient` or `None` if `MixnetClient` is already moved from shared reference.
pub async fn new_auth_client(&self) -> Option<AuthenticatorMixnetClient> {
let mixnet_client_guard = self.mixnet_client.lock().await;
let mixnet_client_ref = mixnet_client_guard.as_ref()?;
let mixnet_sender = mixnet_client_ref.split_sender();
let nym_address = *mixnet_client_ref.nym_address();
Some(
AuthenticatorMixnetClient::new(
mixnet_sender,
self.message_broadcast.subscribe(),
nym_address,
)
.await,
)
pub fn mixnet_sender(&self) -> MixnetMessageInputSender {
self.message_sender.clone()
}
pub fn subscribe(&self) -> MixnetMessageBroadcastReceiver {
self.message_broadcast.subscribe()
}
pub async fn wait(self) {
if let Err(err) = self.handle.await {
tracing::error!("Error waiting for auth clients mixnet listener to stop: {err}");
pub fn mixnet_cancel_token(&self) -> CancellationToken {
self.mixnet_cancellation_token.clone()
}
pub async fn stop(self) {
// If shutdown was externally called, that call is a no-op
// If we're only stopping this, it is very much needed
self.cancellation_token.cancel();
match self.handle.await {
Ok(auth_client_mixnet_listener) => {
auth_client_mixnet_listener.disconnect_mixnet_client().await;
}
Err(e) => {
tracing::error!("Error waiting for auth clients mixnet listener to stop: {e}");
}
}
}
}
+1
View File
@@ -12,6 +12,7 @@ license.workspace = true
workspace = true
[dependencies]
bincode.workspace = true
bytes.workspace = true
futures.workspace = true
thiserror.workspace = true
+23 -24
View File
@@ -1,15 +1,11 @@
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
// To remove with the Registration Client PR
#![allow(clippy::unwrap_used)]
use std::{sync::Arc, time::Duration};
use std::time::Duration;
use nym_ip_packet_requests::IpPair;
use nym_sdk::mixnet::{
InputMessage, MixnetClient, MixnetClientSender, MixnetMessageSender, Recipient,
TransmissionLane,
InputMessage, MixnetClient, MixnetMessageSender, Recipient, TransmissionLane,
};
use tokio::time::sleep;
use tokio_util::sync::CancellationToken;
@@ -27,8 +23,6 @@ use crate::{
helpers::check_ipr_message_version,
};
pub type SharedMixnetClient = Arc<tokio::sync::Mutex<Option<MixnetClient>>>;
const IPR_CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -43,24 +37,24 @@ enum ConnectionState {
pub struct IprClientConnect {
// During connection we need the mixnet client, but once connected we expect to setup a channel
// from the main mixnet listener at the top-level.
// As such, we drop the shared mixnet client once we're connected.
mixnet_client: SharedMixnetClient,
mixnet_sender: MixnetClientSender,
mixnet_client: MixnetClient,
connected: ConnectionState,
cancel_token: CancellationToken,
}
impl IprClientConnect {
pub async fn new(mixnet_client: SharedMixnetClient, cancel_token: CancellationToken) -> Self {
let mixnet_sender = mixnet_client.lock().await.as_ref().unwrap().split_sender();
pub async fn new(mixnet_client: MixnetClient, cancel_token: CancellationToken) -> Self {
Self {
mixnet_client,
mixnet_sender,
connected: ConnectionState::Disconnected,
cancel_token,
}
}
pub fn into_mixnet_client(self) -> MixnetClient {
self.mixnet_client
}
pub async fn connect(&mut self, ip_packet_router_address: Recipient) -> Result<IpPair> {
if self.connected != ConnectionState::Disconnected {
return Err(Error::AlreadyConnected);
@@ -95,12 +89,12 @@ impl IprClientConnect {
// We use 20 surbs for the connect request because typically the IPR is configured to have
// a min threshold of 10 surbs that it reserves for itself to request additional surbs.
let surbs = 20;
self.mixnet_sender
self.mixnet_client
.send(create_input_message(
ip_packet_router_address,
request,
surbs,
))
)?)
.await
.map_err(|err| Error::SdkError(Box::new(err)))?;
@@ -133,26 +127,31 @@ impl IprClientConnect {
}
}
async fn listen_for_connect_response(&self, request_id: u64) -> Result<IpPair> {
async fn listen_for_connect_response(&mut self, request_id: u64) -> Result<IpPair> {
// Connecting is basically synchronous from the perspective of the mixnet client, so it's safe
// to just grab ahold of the mutex and keep it until we get the response.
let mut mixnet_client_handle = self.mixnet_client.lock().await;
let mixnet_client = mixnet_client_handle.as_mut().unwrap();
let timeout = sleep(IPR_CONNECT_TIMEOUT);
tokio::pin!(timeout);
let mixnet_cancel_token = self.mixnet_client.cancellation_token();
loop {
tokio::select! {
_ = self.cancel_token.cancelled() => {
error!("Cancelled while waiting for reply to connect request");
return Err(Error::Cancelled);
},
_ = mixnet_cancel_token.cancelled() => {
error!("Mixnet client stopped while waiting for reply to connect request");
return Err(Error::Cancelled);
},
_ = &mut timeout => {
error!("Timed out waiting for reply to connect request");
return Err(Error::TimeoutWaitingForConnectResponse);
},
msgs = mixnet_client.wait_for_messages() => match msgs {
msgs = self.mixnet_client.wait_for_messages() => match msgs {
None => {
return Err(Error::NoMixnetMessagesReceived);
}
@@ -188,12 +187,12 @@ fn create_input_message(
recipient: Recipient,
request: IpPacketRequest,
surbs: u32,
) -> InputMessage {
InputMessage::new_anonymous(
) -> Result<InputMessage> {
Ok(InputMessage::new_anonymous(
recipient,
request.to_bytes().unwrap(),
request.to_bytes()?,
surbs,
TransmissionLane::General,
None,
)
))
}
+2 -7
View File
@@ -18,9 +18,6 @@ pub enum Error {
)]
ReceivedResponseWithNewVersion { expected: u8, received: u8 },
#[error("got reply for connect request, but it appears intended for the wrong address?")]
GotReplyIntendedForWrongAddress,
#[error("unexpected connect response")]
UnexpectedConnectResponse,
@@ -42,10 +39,8 @@ pub enum Error {
#[error("already connected to the mixnet")]
AlreadyConnected,
#[error("failed to create connect request")]
FailedToCreateConnectRequest {
source: nym_ip_packet_requests::sign::SignatureError,
},
#[error(transparent)]
Bincode(#[from] bincode::Error),
}
// Result type based on our error type
@@ -1,5 +1,5 @@
[package]
name = "nym-wg-gateway-client"
name = "nym-registration-client"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
@@ -12,17 +12,17 @@ license.workspace = true
workspace = true
[dependencies]
nym-authenticator-client = { path = "../nym-authenticator-client" }
nym-authenticator-requests = { path = "../common/authenticator-requests" }
nym-bandwidth-controller = { path = "../common/bandwidth-controller" }
nym-credentials-interface = { path = "../common/credentials-interface" }
nym-crypto = { path = "../common/crypto" }
nym-node-requests = { path = "../nym-node/nym-node-requests" }
nym-pemstore = { path = "../common/pemstore" }
nym-sdk = { path = "../sdk/rust/nym-sdk" }
nym-statistics-common = { path = "../common/statistics" }
nym-validator-client = { path = "../common/client-libs/validator-client" }
rand.workspace = true
thiserror.workspace = true
tokio.workspace = true
tokio-util.workspace = true
tracing.workspace = true
url.workspace = true
nym-authenticator-client = { path = "../nym-authenticator-client" }
nym-bandwidth-controller = { path = "../common/bandwidth-controller" }
nym-credential-storage = { path = "../common/credential-storage" }
nym-credentials-interface = { path = "../common/credentials-interface" }
nym-ip-packet-client = { path = "../nym-ip-packet-client" }
nym-registration-common = { path = "../common/registration" }
nym-sdk = { path = "../sdk/rust/nym-sdk" }
nym-validator-client = { path = "../common/client-libs/validator-client" }
@@ -0,0 +1,206 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use nym_credential_storage::persistent_storage::PersistentStorage;
use nym_registration_common::NymNode;
use nym_sdk::{
mixnet::{
CredentialStorage, GatewaysDetailsStore, KeyStore, MixnetClient, MixnetClientBuilder,
MixnetClientStorage, OnDiskPersistent, ReplyStorageBackend, StoragePaths,
},
DebugConfig, NymNetworkDetails, RememberMe, TopologyProvider, UserAgent,
};
#[cfg(unix)]
use std::{os::fd::RawFd, sync::Arc};
use std::{path::PathBuf, time::Duration};
use tokio_util::sync::CancellationToken;
use crate::error::RegistrationClientError;
const VPN_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(15);
pub struct BuilderConfig {
pub entry_node: NymNode,
pub exit_node: NymNode,
pub data_path: Option<PathBuf>,
pub mixnet_client_config: MixnetClientConfig,
pub two_hops: bool,
pub user_agent: UserAgent,
pub custom_topology_provider: Box<dyn TopologyProvider + Send + Sync>,
pub network_env: NymNetworkDetails,
pub cancel_token: CancellationToken,
#[cfg(unix)]
pub connection_fd_callback: Arc<dyn Fn(RawFd) + Send + Sync>,
}
#[derive(Clone, Default, Debug, Eq, PartialEq)]
pub struct MixnetClientConfig {
/// Disable Poission process rate limiting of outbound traffic.
pub disable_poisson_rate: bool,
/// Disable constant rate background loop cover traffic
pub disable_background_cover_traffic: bool,
/// The minimum performance of mixnodes to use.
pub min_mixnode_performance: Option<u8>,
/// The minimum performance of gateways to use.
pub min_gateway_performance: Option<u8>,
}
impl BuilderConfig {
pub fn mixnet_client_debug_config(&self) -> DebugConfig {
if self.two_hops {
two_hop_debug_config(&self.mixnet_client_config)
} else {
mixnet_debug_config(&self.mixnet_client_config)
}
}
pub async fn setup_storage(
&self,
) -> Result<Option<(OnDiskPersistent, PersistentStorage)>, RegistrationClientError> {
if let Some(path) = &self.data_path {
tracing::debug!("Using custom key storage path: {}", path.display());
let storage_paths = StoragePaths::new_from_dir(path)
.map_err(|err| RegistrationClientError::BuildMixnetClient(Box::new(err)))?;
let mixnet_client_storage = storage_paths
.initialise_persistent_storage(&self.mixnet_client_debug_config())
.await
.map_err(|err| RegistrationClientError::BuildMixnetClient(Box::new(err)))?;
let credential_storage = storage_paths
.persistent_credential_storage()
.await
.map_err(|err| RegistrationClientError::BuildMixnetClient(Box::new(err)))?;
Ok(Some((mixnet_client_storage, credential_storage)))
} else {
Ok(None)
}
}
pub async fn build_and_connect_mixnet_client<S>(
self,
builder: MixnetClientBuilder<S>,
) -> Result<MixnetClient, RegistrationClientError>
where
S: MixnetClientStorage + Clone + 'static,
S::ReplyStore: Send + Sync,
S::GatewaysDetailsStore: Sync,
<S::ReplyStore as ReplyStorageBackend>::StorageError: Sync + Send,
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync,
<S::KeyStore as KeyStore>::StorageError: Send + Sync,
<S::GatewaysDetailsStore as GatewaysDetailsStore>::StorageError: Send + Sync,
{
let debug_config = self.mixnet_client_debug_config();
let remember_me = if self.two_hops {
RememberMe::new_vpn()
} else {
RememberMe::new_mixnet()
};
let builder = builder
.with_user_agent(self.user_agent)
.request_gateway(self.entry_node.identity.to_string())
.network_details(self.network_env)
.debug_config(debug_config)
.credentials_mode(true)
.with_remember_me(remember_me)
.custom_topology_provider(self.custom_topology_provider);
#[cfg(unix)]
let builder = builder.with_connection_fd_callback(self.connection_fd_callback);
builder
.build()
.map_err(|err| RegistrationClientError::BuildMixnetClient(Box::new(err)))?
.connect_to_mixnet()
.await
.map_err(|err| RegistrationClientError::ConnectToMixnet(Box::new(err)))
}
}
fn two_hop_debug_config(mixnet_client_config: &MixnetClientConfig) -> DebugConfig {
let mut debug_config = DebugConfig::default();
debug_config.traffic.average_packet_delay = VPN_AVERAGE_PACKET_DELAY;
// We disable mix hops for the mixnet connection.
debug_config.traffic.disable_mix_hops = true;
// Always disable poisson process for outbound traffic in wireguard.
debug_config
.traffic
.disable_main_poisson_packet_distribution = true;
// Always disable background cover traffic in wireguard.
debug_config.cover_traffic.disable_loop_cover_traffic_stream = true;
if let Some(min_mixnode_performance) = mixnet_client_config.min_mixnode_performance {
debug_config.topology.minimum_mixnode_performance = min_mixnode_performance;
}
if let Some(min_gateway_performance) = mixnet_client_config.min_gateway_performance {
debug_config.topology.minimum_gateway_performance = min_gateway_performance;
}
log_mixnet_client_config(&debug_config);
debug_config
}
fn mixnet_debug_config(mixnet_client_config: &MixnetClientConfig) -> DebugConfig {
let mut debug_config = DebugConfig::default();
debug_config.traffic.average_packet_delay = VPN_AVERAGE_PACKET_DELAY;
debug_config
.traffic
.disable_main_poisson_packet_distribution = mixnet_client_config.disable_poisson_rate;
debug_config.cover_traffic.disable_loop_cover_traffic_stream =
mixnet_client_config.disable_background_cover_traffic;
if let Some(min_mixnode_performance) = mixnet_client_config.min_mixnode_performance {
debug_config.topology.minimum_mixnode_performance = min_mixnode_performance;
}
if let Some(min_gateway_performance) = mixnet_client_config.min_gateway_performance {
debug_config.topology.minimum_gateway_performance = min_gateway_performance;
}
log_mixnet_client_config(&debug_config);
debug_config
}
fn log_mixnet_client_config(debug_config: &DebugConfig) {
tracing::info!(
"mixnet client poisson rate limiting: {}",
true_to_disabled(
debug_config
.traffic
.disable_main_poisson_packet_distribution
)
);
tracing::info!(
"mixnet client background loop cover traffic stream: {}",
true_to_disabled(debug_config.cover_traffic.disable_loop_cover_traffic_stream)
);
tracing::info!(
"mixnet client minimum mixnode performance: {}",
debug_config.topology.minimum_mixnode_performance,
);
tracing::info!(
"mixnet client minimum gateway performance: {}",
debug_config.topology.minimum_gateway_performance,
);
}
fn true_to_disabled(val: bool) -> &'static str {
if val {
"disabled"
} else {
"enabled"
}
}
@@ -0,0 +1,96 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_bandwidth_controller::{BandwidthController, BandwidthTicketProvider};
use nym_credential_storage::ephemeral_storage::EphemeralCredentialStorage;
use nym_sdk::{
mixnet::{MixnetClient, MixnetClientBuilder},
NymNetworkDetails,
};
use nym_validator_client::{
nyxd::{Config as NyxdClientConfig, NyxdClient},
QueryHttpRpcNyxdClient,
};
use std::time::Duration;
use crate::{config::RegistrationClientConfig, error::RegistrationClientError, RegistrationClient};
use config::BuilderConfig;
pub(crate) mod config;
pub(crate) const MIXNET_CLIENT_STARTUP_TIMEOUT: Duration = Duration::from_secs(30);
pub struct RegistrationClientBuilder {
pub config: BuilderConfig,
}
impl RegistrationClientBuilder {
pub fn new(config: BuilderConfig) -> Self {
Self { config }
}
pub async fn build(self) -> Result<RegistrationClient, RegistrationClientError> {
let storage = self.config.setup_storage().await?;
let config = RegistrationClientConfig {
entry: self.config.entry_node,
exit: self.config.exit_node,
two_hops: self.config.two_hops,
data_path: self.config.data_path.clone(),
};
let cancel_token = self.config.cancel_token.clone();
let nyxd_client = get_nyxd_client(&self.config.network_env)?;
let (mixnet_client, bandwidth_controller): (
MixnetClient,
Box<dyn BandwidthTicketProvider>,
) = if let Some((mixnet_client_storage, credential_storage)) = storage {
let builder = MixnetClientBuilder::new_with_storage(mixnet_client_storage);
let mixnet_client = tokio::time::timeout(
MIXNET_CLIENT_STARTUP_TIMEOUT,
self.config.build_and_connect_mixnet_client(builder),
)
.await??;
let bandwidth_controller =
Box::new(BandwidthController::new(credential_storage, nyxd_client));
(mixnet_client, bandwidth_controller)
} else {
let builder = MixnetClientBuilder::new_ephemeral();
let mixnet_client = tokio::time::timeout(
MIXNET_CLIENT_STARTUP_TIMEOUT,
self.config.build_and_connect_mixnet_client(builder),
)
.await??;
let bandwidth_controller = Box::new(BandwidthController::new(
EphemeralCredentialStorage::default(),
nyxd_client,
));
(mixnet_client, bandwidth_controller)
};
let mixnet_client_address = *mixnet_client.nym_address();
Ok(RegistrationClient {
mixnet_client,
config,
cancel_token,
mixnet_client_address,
bandwidth_controller,
})
}
}
// temporary while we use the legacy bandwidth-controller
fn get_nyxd_client(
network: &NymNetworkDetails,
) -> Result<QueryHttpRpcNyxdClient, RegistrationClientError> {
let config = NyxdClientConfig::try_from_nym_network_details(network)
.map_err(RegistrationClientError::FailedToCreateNyxdClientConfig)?;
let nyxd_url = network
.endpoints
.first()
.map(|ep| ep.nyxd_url())
.ok_or(RegistrationClientError::InvalidNyxdUrl)?;
NyxdClient::connect(config, nyxd_url.as_str())
.map_err(RegistrationClientError::FailedToConnectUsingNyxdClient)
}
+12
View File
@@ -0,0 +1,12 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use nym_registration_common::NymNode;
use std::path::PathBuf;
pub struct RegistrationClientConfig {
pub(crate) entry: NymNode,
pub(crate) exit: NymNode,
pub(crate) two_hops: bool,
pub(crate) data_path: Option<PathBuf>,
}
+53
View File
@@ -0,0 +1,53 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
#[derive(thiserror::Error, Debug)]
pub enum RegistrationClientError {
#[error("failed to build mixnet client")]
BuildMixnetClient(#[source] Box<nym_sdk::Error>),
#[error("failed to connect to mixnet")]
ConnectToMixnet(#[source] Box<nym_sdk::Error>),
#[error("failed to connect to ip packet router")]
ConnectToIpPacketRouter(#[source] nym_ip_packet_client::Error),
#[error("the selected node does not have an IP packet router : {node_id}")]
NoIpPacketRouterAddress { node_id: String },
#[error(
"wireguard authentication is not possible due to one of the gateways not running the authenticator process: {node_id} "
)]
AuthenticationNotPossible { node_id: String },
#[error("Failed to create nyxd client config")]
FailedToCreateNyxdClientConfig(nym_validator_client::nyxd::error::NyxdError),
#[error("failed to parse nyxd_url")]
InvalidNyxdUrl,
#[error("Failed to connect using nyxd client")]
FailedToConnectUsingNyxdClient(nym_validator_client::nyxd::error::NyxdError),
#[error("connection cancelled")]
Cancelled,
#[error("timeout connecting the mixnet client")]
Timeout(#[from] tokio::time::error::Elapsed),
#[error("failed to register wireguard with the gateway for {gateway_id}")]
EntryGatewayRegisterWireguard {
gateway_id: String,
authenticator_address: Box<nym_sdk::mixnet::Recipient>,
#[source]
source: Box<nym_authenticator_client::Error>,
},
#[error("failed to register wireguard with the gateway for {gateway_id}")]
ExitGatewayRegisterWireguard {
gateway_id: String,
authenticator_address: Box<nym_sdk::mixnet::Recipient>,
#[source]
source: Box<nym_authenticator_client::Error>,
},
}
+157
View File
@@ -0,0 +1,157 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use tokio_util::sync::CancellationToken;
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::{MixnetClient, Recipient};
use crate::config::RegistrationClientConfig;
mod builder;
mod config;
mod error;
mod types;
pub use builder::config::{BuilderConfig as RegistrationClientBuilderConfig, MixnetClientConfig};
pub use builder::RegistrationClientBuilder;
pub use error::RegistrationClientError;
pub use types::{MixnetRegistrationResult, RegistrationResult, WireguardRegistrationResult};
pub struct RegistrationClient {
mixnet_client: MixnetClient,
config: RegistrationClientConfig,
mixnet_client_address: Recipient,
bandwidth_controller: Box<dyn BandwidthTicketProvider>,
cancel_token: CancellationToken,
}
impl RegistrationClient {
async fn register_mix_exit(self) -> Result<RegistrationResult, RegistrationClientError> {
let entry_mixnet_gateway_ip = self.config.entry.ip_address;
let exit_mixnet_gateway_ip = self.config.exit.ip_address;
let ipr_address = self.config.exit.ipr_address.ok_or(
RegistrationClientError::NoIpPacketRouterAddress {
node_id: self.config.exit.identity.to_base58_string(),
},
)?;
let mut ipr_client =
IprClientConnect::new(self.mixnet_client, self.cancel_token.clone()).await;
let interface_addresses = ipr_client
.connect(ipr_address)
.await
.map_err(RegistrationClientError::ConnectToIpPacketRouter)?;
Ok(RegistrationResult::Mixnet(Box::new(
MixnetRegistrationResult {
mixnet_client: ipr_client.into_mixnet_client(),
assigned_addresses: AssignedAddresses {
interface_addresses,
exit_mix_address: ipr_address,
mixnet_client_address: self.mixnet_client_address,
entry_mixnet_gateway_ip,
exit_mixnet_gateway_ip,
},
},
)))
}
async fn register_wg(self) -> Result<RegistrationResult, RegistrationClientError> {
let entry_auth_address = self.config.entry.authenticator_address.ok_or(
RegistrationClientError::AuthenticationNotPossible {
node_id: self.config.entry.identity.to_base58_string(),
},
)?;
let exit_auth_address = self.config.exit.authenticator_address.ok_or(
RegistrationClientError::AuthenticationNotPossible {
node_id: self.config.exit.identity.to_base58_string(),
},
)?;
let entry_version = self.config.entry.version;
tracing::debug!("Entry gateway version: {entry_version}");
let exit_version = self.config.exit.version;
tracing::debug!("Exit gateway version: {exit_version}");
// 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();
let mut entry_auth_client = AuthenticatorClient::new_entry(
&self.config.data_path,
mixnet_listener.subscribe(),
mixnet_listener.mixnet_sender(),
self.mixnet_client_address,
entry_auth_address,
entry_version,
self.config.entry.ip_address,
);
let mut exit_auth_client = AuthenticatorClient::new_exit(
&self.config.data_path,
mixnet_listener.subscribe(),
mixnet_listener.mixnet_sender(),
self.mixnet_client_address,
exit_auth_address,
exit_version,
self.config.exit.ip_address,
);
let entry_fut = entry_auth_client
.register_wireguard(&*self.bandwidth_controller, TicketType::V1WireguardEntry);
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 =
entry.map_err(
|source| RegistrationClientError::EntryGatewayRegisterWireguard {
gateway_id: self.config.entry.identity.to_base58_string(),
authenticator_address: Box::new(entry_auth_address),
source: Box::new(source),
},
)?;
let exit =
exit.map_err(
|source| RegistrationClientError::ExitGatewayRegisterWireguard {
gateway_id: self.config.exit.identity.to_base58_string(),
authenticator_address: Box::new(exit_auth_address),
source: Box::new(source),
},
)?;
Ok(RegistrationResult::Wireguard(Box::new(
WireguardRegistrationResult {
entry_gateway_client: entry_auth_client,
exit_gateway_client: exit_auth_client,
entry_gateway_data: entry,
exit_gateway_data: exit,
authenticator_listener_handle: mixnet_listener,
bw_controller: self.bandwidth_controller,
},
)))
}
pub async fn register(self) -> Result<RegistrationResult, RegistrationClientError> {
self.cancel_token
.clone()
.run_until_cancelled(async {
if self.config.two_hops {
self.register_wg().await
} else {
self.register_mix_exit().await
}
})
.await
.ok_or(RegistrationClientError::Cancelled)?
}
}
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_authenticator_client::{AuthClientMixnetListenerHandle, AuthenticatorClient};
use nym_bandwidth_controller::BandwidthTicketProvider;
use nym_registration_common::{AssignedAddresses, GatewayData};
use nym_sdk::mixnet::MixnetClient;
pub enum RegistrationResult {
Mixnet(Box<MixnetRegistrationResult>),
Wireguard(Box<WireguardRegistrationResult>),
}
pub struct MixnetRegistrationResult {
pub assigned_addresses: AssignedAddresses,
pub mixnet_client: MixnetClient,
}
pub struct WireguardRegistrationResult {
pub entry_gateway_client: AuthenticatorClient,
pub exit_gateway_client: AuthenticatorClient,
pub entry_gateway_data: GatewayData,
pub exit_gateway_data: GatewayData,
pub authenticator_listener_handle: AuthClientMixnetListenerHandle,
pub bw_controller: Box<dyn BandwidthTicketProvider>,
}
-154
View File
@@ -1,154 +0,0 @@
use std::time::Duration;
use nym_authenticator_client::{
AuthenticatorClient, AuthenticatorResponse, AuthenticatorVersion, ClientMessage,
QueryMessageImpl,
};
use nym_authenticator_requests::{v3, v4, v5};
use nym_credentials_interface::CredentialSpendingData;
use nym_crypto::asymmetric::encryption;
use nym_node_requests::api::v1::gateway::client_interfaces::wireguard::models::PeerPublicKey;
use nym_sdk::mixnet::Recipient;
use crate::error::{Error, Result};
const RETRY_PERIOD: Duration = Duration::from_secs(30);
impl crate::WgGatewayClient {
pub fn light_client(&self) -> WgGatewayLightClient {
WgGatewayLightClient {
public_key: *self.keypair.public_key(),
auth_client: self.auth_client.clone(),
}
}
}
#[derive(Clone)]
pub struct WgGatewayLightClient {
public_key: encryption::PublicKey,
auth_client: AuthenticatorClient,
}
impl WgGatewayLightClient {
pub fn auth_recipient(&self) -> Recipient {
self.auth_client.auth_recipient()
}
pub fn auth_client(&self) -> &AuthenticatorClient {
&self.auth_client
}
pub fn set_auth_client(&mut self, auth_client: AuthenticatorClient) {
self.auth_client = auth_client;
}
pub async fn query_bandwidth(&mut self) -> Result<Option<i64>> {
let query_message = match self.auth_client.auth_version() {
AuthenticatorVersion::V2 => ClientMessage::Query(Box::new(QueryMessageImpl {
pub_key: PeerPublicKey::new(self.public_key.to_bytes().into()),
version: AuthenticatorVersion::V2,
})),
AuthenticatorVersion::V3 => ClientMessage::Query(Box::new(QueryMessageImpl {
pub_key: PeerPublicKey::new(self.public_key.to_bytes().into()),
version: AuthenticatorVersion::V3,
})),
AuthenticatorVersion::V4 => ClientMessage::Query(Box::new(QueryMessageImpl {
pub_key: PeerPublicKey::new(self.public_key.to_bytes().into()),
version: AuthenticatorVersion::V4,
})),
AuthenticatorVersion::V5 => ClientMessage::Query(Box::new(QueryMessageImpl {
pub_key: PeerPublicKey::new(self.public_key.to_bytes().into()),
version: AuthenticatorVersion::V5,
})),
AuthenticatorVersion::UNKNOWN => return Err(Error::UnsupportedAuthenticatorVersion),
};
let response = self.auth_client.send(&query_message).await?;
let available_bandwidth = match response {
nym_authenticator_client::AuthenticatorResponse::RemainingBandwidth(
remaining_bandwidth_response,
) => {
if let Some(available_bandwidth) =
remaining_bandwidth_response.available_bandwidth()
{
available_bandwidth
} else {
return Ok(None);
}
}
_ => return Err(Error::InvalidGatewayAuthResponse),
};
let remaining_pretty = if available_bandwidth > 1024 * 1024 {
format!("{:.2} MB", available_bandwidth as f64 / 1024.0 / 1024.0)
} else {
format!("{} KB", available_bandwidth / 1024)
};
tracing::debug!(
"Remaining wireguard bandwidth with gateway {} for today: {}",
self.auth_client.auth_recipient().gateway(),
remaining_pretty
);
if available_bandwidth < 1024 * 1024 {
tracing::warn!(
"Remaining bandwidth is under 1 MB. The wireguard mode will get suspended after that until tomorrow, UTC time. The client might shutdown with timeout soon"
);
}
Ok(Some(available_bandwidth))
}
async fn send(&mut self, msg: ClientMessage) -> Result<AuthenticatorResponse> {
let now = std::time::Instant::now();
while now.elapsed() < RETRY_PERIOD {
match self.auth_client.send(&msg).await {
Ok(response) => return Ok(response),
Err(nym_authenticator_client::Error::TimeoutWaitingForConnectResponse) => continue,
Err(source) => {
if msg.is_wasteful() {
return Err(Error::NoRetry { source });
} else {
return Err(Error::AuthenticatorClientError(source));
}
}
}
}
if msg.is_wasteful() {
Err(Error::NoRetry {
source: nym_authenticator_client::Error::TimeoutWaitingForConnectResponse,
})
} else {
Err(Error::AuthenticatorClientError(
nym_authenticator_client::Error::TimeoutWaitingForConnectResponse,
))
}
}
pub async fn top_up(&mut self, credential: CredentialSpendingData) -> Result<i64> {
let top_up_message = match self.auth_client.auth_version() {
AuthenticatorVersion::V3 => ClientMessage::TopUp(Box::new(v3::topup::TopUpMessage {
pub_key: PeerPublicKey::new(self.public_key.to_bytes().into()),
credential,
})),
// NOTE: looks like a bug here using v3. But we're leaving it as is since it's working
// and V4 is deprecated in favour of V5
AuthenticatorVersion::V4 => ClientMessage::TopUp(Box::new(v4::topup::TopUpMessage {
pub_key: PeerPublicKey::new(self.public_key.to_bytes().into()),
credential,
})),
AuthenticatorVersion::V5 => ClientMessage::TopUp(Box::new(v5::topup::TopUpMessage {
pub_key: PeerPublicKey::new(self.public_key.to_bytes().into()),
credential,
})),
AuthenticatorVersion::V2 | AuthenticatorVersion::UNKNOWN => {
return Err(Error::UnsupportedAuthenticatorVersion);
}
};
let response = self.send(top_up_message).await?;
let remaining_bandwidth = match response {
AuthenticatorResponse::TopUpBandwidth(top_up_bandwidth_response) => {
top_up_bandwidth_response.available_bandwidth()
}
_ => return Err(Error::InvalidGatewayAuthResponse),
};
Ok(remaining_bandwidth)
}
}
-48
View File
@@ -1,48 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use nym_credentials_interface::TicketType;
use nym_sdk::mixnet::NodeIdentity;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("received invalid response from gateway authenticator")]
InvalidGatewayAuthResponse,
#[error("unknown authenticator version number")]
UnsupportedAuthenticatorVersion,
#[error(transparent)]
AuthenticatorClientError(#[from] nym_authenticator_client::Error),
#[error("error that should stop auto retrying")]
NoRetry {
#[source]
source: nym_authenticator_client::Error,
},
#[error("verification failure")]
VerificationFailed(#[source] nym_authenticator_requests::Error),
#[error("failed to parse entry gateway socket addr")]
FailedToParseEntryGatewaySocketAddr(#[source] std::net::AddrParseError),
#[error("failed to get {ticketbook_type} ticket")]
GetTicket {
ticketbook_type: TicketType,
#[source]
source: nym_bandwidth_controller::error::BandwidthControllerError,
},
}
#[derive(Debug, thiserror::Error)]
pub enum ErrorMessage {
#[error("out of bandwidth for gateway: {gateway_id}")]
OutOfBandwidth { gateway_id: Box<NodeIdentity> },
#[error("gateway {gateway_id} is erroring out")]
ErrorsFromGateway { gateway_id: Box<NodeIdentity> },
}
// Result type based on our error type
pub type Result<T> = std::result::Result<T, Error>;
-302
View File
@@ -1,302 +0,0 @@
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
pub mod deprecated;
mod error;
use std::{
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
path::PathBuf,
str::FromStr,
};
pub use error::{Error, ErrorMessage};
use nym_authenticator_client::{
AuthenticatorClient, AuthenticatorMixnetClient, AuthenticatorResponse, AuthenticatorVersion,
ClientMessage,
};
use nym_authenticator_requests::{v2, v3, v4, v5};
use nym_bandwidth_controller::PreparedCredential;
use nym_credentials_interface::TicketType;
use nym_crypto::asymmetric::{encryption, x25519::KeyPair, x25519::PublicKey};
use nym_node_requests::api::v1::gateway::client_interfaces::wireguard::models::PeerPublicKey;
use nym_pemstore::KeyPairPath;
use nym_sdk::mixnet::{CredentialStorage, NodeIdentity, Recipient};
use nym_validator_client::QueryHttpRpcNyxdClient;
use rand::{rngs::OsRng, CryptoRng, RngCore};
use tracing::{debug, error, trace};
use crate::error::Result;
pub const DEFAULT_PRIVATE_ENTRY_WIREGUARD_KEY_FILENAME: &str = "free_private_entry_wireguard.pem";
pub const DEFAULT_PUBLIC_ENTRY_WIREGUARD_KEY_FILENAME: &str = "free_public_entry_wireguard.pem";
pub const DEFAULT_PRIVATE_EXIT_WIREGUARD_KEY_FILENAME: &str = "free_private_exit_wireguard.pem";
pub const DEFAULT_PUBLIC_EXIT_WIREGUARD_KEY_FILENAME: &str = "free_public_exit_wireguard.pem";
pub const TICKETS_TO_SPEND: u32 = 1;
#[derive(Clone, Debug)]
pub struct GatewayData {
pub public_key: PublicKey,
pub endpoint: SocketAddr,
pub private_ipv4: Ipv4Addr,
pub private_ipv6: Ipv6Addr,
}
pub struct WgGatewayClient {
keypair: encryption::KeyPair,
auth_client: AuthenticatorClient,
}
impl WgGatewayClient {
fn new_type(
data_path: &Option<PathBuf>,
auth_mix_client: AuthenticatorMixnetClient,
auth_recipient: Recipient,
auth_version: AuthenticatorVersion,
private_file_name: &str,
public_file_name: &str,
) -> Self {
let mut rng = OsRng;
let auth_client = AuthenticatorClient::new(auth_mix_client, auth_recipient, auth_version);
if let Some(data_path) = data_path {
let paths = KeyPairPath::new(
data_path.join(private_file_name),
data_path.join(public_file_name),
);
let keypair = load_or_generate_keypair(&mut rng, paths);
WgGatewayClient {
keypair,
auth_client,
}
} else {
WgGatewayClient {
keypair: KeyPair::new(&mut rng),
auth_client,
}
}
}
pub fn new_entry(
data_path: &Option<PathBuf>,
auth_mix_client: AuthenticatorMixnetClient,
auth_recipient: Recipient,
auth_version: AuthenticatorVersion,
) -> Self {
Self::new_type(
data_path,
auth_mix_client,
auth_recipient,
auth_version,
DEFAULT_PRIVATE_ENTRY_WIREGUARD_KEY_FILENAME,
DEFAULT_PUBLIC_ENTRY_WIREGUARD_KEY_FILENAME,
)
}
pub fn new_exit(
data_path: &Option<PathBuf>,
auth_mix_client: AuthenticatorMixnetClient,
auth_recipient: Recipient,
auth_version: AuthenticatorVersion,
) -> Self {
Self::new_type(
data_path,
auth_mix_client,
auth_recipient,
auth_version,
DEFAULT_PRIVATE_EXIT_WIREGUARD_KEY_FILENAME,
DEFAULT_PUBLIC_EXIT_WIREGUARD_KEY_FILENAME,
)
}
pub fn keypair(&self) -> &encryption::KeyPair {
&self.keypair
}
pub fn auth_recipient(&self) -> Recipient {
self.auth_client.auth_recipient()
}
pub fn auth_version(&self) -> AuthenticatorVersion {
self.auth_client.auth_version()
}
pub async fn request_bandwidth<St: CredentialStorage>(
gateway_id: NodeIdentity,
controller: &nym_bandwidth_controller::BandwidthController<QueryHttpRpcNyxdClient, St>,
ticketbook_type: TicketType,
) -> Result<PreparedCredential>
where
<St as CredentialStorage>::StorageError: Send + Sync + 'static,
{
let credential = controller
.prepare_ecash_ticket(ticketbook_type, gateway_id.to_bytes(), TICKETS_TO_SPEND)
.await
.map_err(|source| Error::GetTicket {
ticketbook_type,
source,
})?;
Ok(credential)
}
pub async fn register_wireguard<St: CredentialStorage>(
&mut self,
gateway_host: IpAddr,
controller: &nym_bandwidth_controller::BandwidthController<QueryHttpRpcNyxdClient, St>,
ticketbook_type: TicketType,
) -> Result<GatewayData>
where
<St as CredentialStorage>::StorageError: Send + Sync + 'static,
{
debug!("Registering with the wg gateway...");
let init_message = match self.auth_version() {
AuthenticatorVersion::V2 => {
ClientMessage::Initial(Box::new(v2::registration::InitMessage {
pub_key: PeerPublicKey::new(self.keypair.public_key().to_bytes().into()),
}))
}
AuthenticatorVersion::V3 => {
ClientMessage::Initial(Box::new(v3::registration::InitMessage {
pub_key: PeerPublicKey::new(self.keypair.public_key().to_bytes().into()),
}))
}
AuthenticatorVersion::V4 => {
ClientMessage::Initial(Box::new(v4::registration::InitMessage {
pub_key: PeerPublicKey::new(self.keypair.public_key().to_bytes().into()),
}))
}
AuthenticatorVersion::V5 => {
ClientMessage::Initial(Box::new(v5::registration::InitMessage {
pub_key: PeerPublicKey::new(self.keypair.public_key().to_bytes().into()),
}))
}
AuthenticatorVersion::UNKNOWN => return Err(Error::UnsupportedAuthenticatorVersion),
};
trace!("sending init msg to {}: {:?}", &gateway_host, &init_message);
let response = self.auth_client.send(&init_message).await?;
let registered_data = match response {
AuthenticatorResponse::PendingRegistration(pending_registration_response) => {
// Unwrap since we have already checked that we have the keypair.
debug!("Verifying data");
if let Err(e) = pending_registration_response.verify(self.keypair.private_key()) {
return Err(Error::VerificationFailed(e));
}
trace!(
"received \"pending-registration\" msg from {}: {:?}",
&gateway_host,
&pending_registration_response
);
let credential = Some(
Self::request_bandwidth(
self.auth_recipient().gateway(),
controller,
ticketbook_type,
)
.await?
.data,
);
let finalized_message = match self.auth_version() {
AuthenticatorVersion::V2 => {
ClientMessage::Final(Box::new(v2::registration::FinalMessage {
gateway_client: v2::registration::GatewayClient::new(
self.keypair.private_key(),
pending_registration_response.pub_key().inner(),
pending_registration_response.private_ips().ipv4.into(),
pending_registration_response.nonce(),
),
credential,
}))
}
AuthenticatorVersion::V3 => {
ClientMessage::Final(Box::new(v3::registration::FinalMessage {
gateway_client: v3::registration::GatewayClient::new(
self.keypair.private_key(),
pending_registration_response.pub_key().inner(),
pending_registration_response.private_ips().ipv4.into(),
pending_registration_response.nonce(),
),
credential,
}))
}
AuthenticatorVersion::V4 => {
ClientMessage::Final(Box::new(v4::registration::FinalMessage {
gateway_client: v4::registration::GatewayClient::new(
self.keypair.private_key(),
pending_registration_response.pub_key().inner(),
pending_registration_response.private_ips().into(),
pending_registration_response.nonce(),
),
credential,
}))
}
AuthenticatorVersion::V5 => {
ClientMessage::Final(Box::new(v5::registration::FinalMessage {
gateway_client: v5::registration::GatewayClient::new(
self.keypair.private_key(),
pending_registration_response.pub_key().inner(),
pending_registration_response.private_ips(),
pending_registration_response.nonce(),
),
credential,
}))
}
AuthenticatorVersion::UNKNOWN => {
return Err(Error::UnsupportedAuthenticatorVersion);
}
};
trace!(
"sending final msg to {}: {:?}",
&gateway_host,
&finalized_message
);
let response = self.auth_client.send(&finalized_message).await?;
let AuthenticatorResponse::Registered(registered_response) = response else {
return Err(Error::InvalidGatewayAuthResponse);
};
registered_response
}
AuthenticatorResponse::Registered(registered_response) => registered_response,
_ => return Err(Error::InvalidGatewayAuthResponse),
};
trace!(
"received \"registered\" msg from {}: {:?}",
&gateway_host,
&registered_data
);
let gateway_data = GatewayData {
public_key: registered_data.pub_key().inner().into(),
endpoint: SocketAddr::from_str(&format!(
"{}:{}",
gateway_host,
registered_data.wg_port()
))
.map_err(Error::FailedToParseEntryGatewaySocketAddr)?,
private_ipv4: registered_data.private_ips().ipv4,
private_ipv6: registered_data.private_ips().ipv6,
};
Ok(gateway_data)
}
}
fn load_or_generate_keypair<R: RngCore + CryptoRng>(rng: &mut R, paths: KeyPairPath) -> KeyPair {
match nym_pemstore::load_keypair(&paths) {
Ok(keypair) => keypair,
Err(_) => {
let keypair = KeyPair::new(rng);
if let Err(e) = nym_pemstore::store_keypair(&keypair, &paths) {
error!(
"could not store generated keypair at {:?} - {:?}; will use ephemeral keys",
paths, e
);
}
keypair
}
}
}
+1 -1
View File
@@ -20,7 +20,7 @@ pub use nym_client_core::{
NymApiTopologyProvider, NymApiTopologyProviderConfig, TopologyProvider,
},
},
config::DebugConfig,
config::{DebugConfig, RememberMe},
};
pub use nym_network_defaults::{
ChainDetails, DenomDetails, DenomDetailsOwned, NymContracts, NymNetworkDetails,
+2 -15
View File
@@ -37,7 +37,6 @@ use std::path::Path;
use std::path::PathBuf;
#[cfg(unix)]
use std::sync::Arc;
use tokio_util::sync::CancellationToken;
use url::Url;
use zeroize::Zeroizing;
@@ -119,11 +118,6 @@ where
<S::KeyStore as KeyStore>::StorageError: Send + Sync,
<S::GatewaysDetailsStore as GatewaysDetailsStore>::StorageError: Send + Sync,
{
pub fn with_shutdown_token(self, token: CancellationToken) -> Self {
let shutdown_tracker = ShutdownTracker::new_from_external_shutdown_token(token.into());
self.custom_shutdown(shutdown_tracker)
}
/// Creates a client builder with the provided client storage implementation.
#[must_use]
pub fn new_with_storage(storage: S) -> MixnetClientBuilder<S> {
@@ -755,13 +749,6 @@ where
let packet_type = self.config.debug_config.traffic.packet_type;
let (mut started_client, nym_address) = self.connect_to_mixnet_common().await?;
// TODO: more graceful handling here, surely both variants should work... I think?
let Some(tracker) = started_client.shutdown_handle else {
return Err(Error::new_unsupported(
"connecting with socks5 is currently unsupported with custom shutdown",
));
};
let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer();
let client_state = started_client.client_state;
@@ -773,14 +760,14 @@ where
client_output,
client_state.clone(),
nym_address,
tracker.child_tracker(),
started_client.shutdown_handle.child_tracker(),
packet_type,
);
Ok(Socks5MixnetClient {
nym_address,
client_state,
task_handle: tracker,
task_handle: started_client.shutdown_handle,
socks5_config,
})
}
+9 -5
View File
@@ -24,6 +24,7 @@ use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use tokio::sync::RwLockReadGuard;
use tokio_util::sync::CancellationToken;
/// Client connected to the Nym mixnet.
pub struct MixnetClient {
@@ -52,7 +53,7 @@ pub struct MixnetClient {
pub(crate) stats_events_reporter: ClientStatsSender,
/// The task manager that controls all the spawned tasks that the clients uses to do it's job.
pub(crate) shutdown_handle: Option<ShutdownTracker>,
pub(crate) shutdown_handle: ShutdownTracker,
pub(crate) packet_type: Option<PacketType>,
// internal state used for the `Stream` implementation
@@ -72,7 +73,7 @@ impl MixnetClient {
client_state: ClientState,
reconstructed_receiver: ReconstructedMessagesReceiver,
stats_events_reporter: ClientStatsSender,
task_handle: Option<ShutdownTracker>,
task_handle: ShutdownTracker,
packet_type: Option<PacketType>,
client_request_sender: ClientRequestSender,
forget_me: ForgetMe,
@@ -122,6 +123,11 @@ impl MixnetClient {
&self.nym_address
}
/// Get a child token of the root, to monitor unexpected shutdown, without causing one
pub fn cancellation_token(&self) -> CancellationToken {
self.shutdown_handle.child_shutdown_token().inner().clone()
}
pub fn client_request_sender(&self) -> ClientRequestSender {
self.client_request_sender.clone()
}
@@ -238,9 +244,7 @@ impl MixnetClient {
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
}
if let Some(tracker) = self.shutdown_handle {
tracker.shutdown().await;
}
self.shutdown_handle.shutdown().await;
}
pub async fn send_forget_me(&self) -> Result<()> {
+1 -4
View File
@@ -249,10 +249,7 @@ impl NymClientBuilder {
client_input: Arc::new(client_input),
client_state: Arc::new(started_client.client_state),
_full_topology: None,
// this cannot fail as we haven't passed an external task manager
_task_manager: started_client
.shutdown_handle
.expect("shutdown manager missing"),
_task_manager: started_client.shutdown_handle,
packet_type,
})
}
+1 -6
View File
@@ -187,12 +187,7 @@ impl MixFetchClientBuilder {
self_address,
client_input,
requests: active_requests,
// this cannot fail as we haven't passed an external task manager
_shutdown_manager: Mutex::new(
started_client
.shutdown_handle
.expect("shutdown manager missing"),
),
_shutdown_manager: Mutex::new(started_client.shutdown_handle),
})
}
}