Compare commits

...

17 Commits

Author SHA1 Message Date
mfahampshire f75cd90092 cargo lock after rebase on develop 2025-03-06 17:32:57 +01:00
mfahampshire 1164f66d7a change hardcoded file to tempdir 2025-03-06 17:32:22 +01:00
Jon Häggblad 8ddadf2aa5 Set DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE to 50 2025-03-06 17:32:22 +01:00
benedettadavico b0234d0140 bump api version 2025-03-06 17:32:22 +01:00
benedettadavico 6c2b917706 update changelog for patched-dorina 2025-03-06 17:32:22 +01:00
benedettadavico 0b8a5d5d86 bumping versions dorina patched 2025-03-06 17:32:22 +01:00
Jędrzej Stuczyński 74623fa2b8 bugfix: make sure to correctly decode response content when putting it into error message (#5571) 2025-03-06 17:32:22 +01:00
Jędrzej Stuczyński 454712b520 chore: additional logs when attempting to load ecash keys (#5567) 2025-03-06 17:32:22 +01:00
Jędrzej Stuczyński 5fe5541d02 fix: gateway protocol negotation for v3/v4 2025-03-06 17:32:22 +01:00
Jędrzej Stuczyński c25c4548b2 feature: v2 authentication request (#5537) (#5563)
* introduced v2 authentication request between clients and gateways

* client to send v2 auth when possible

* added persistence to last used authentication timestamp

* added clients identity to signed plaintext
2025-03-06 17:32:22 +01:00
Jon Häggblad d6e996e8e9 Tweak surb management to be more conservative (#5570)
To reduce the risk of the IPR DoS the client:

- Lower the timeout until the IPR will disconnect a client
- Reduce fewer surbs at a time. Large surb requests increases the
  latency until all fragments in the response have been delivered. The
  efficiency gains of having large surb requests dimishes quickly for
  large sizes as well
2025-03-06 17:32:22 +01:00
Jon Häggblad cf88364dda Deserialize v5 authenticator requests (#5568) 2025-03-06 17:32:22 +01:00
Jędrzej Stuczyński 24d32a64e0 hotfix: ensure we bail on merkle leaves insertion upon missing data (#5565)
* hotfix: ensure we bail on merkle leaves insertion upon missing data

* Update Cargo.toml

---------

Co-authored-by: benedetta davico <46782255+benedettadavico@users.noreply.github.com>
2025-03-06 17:32:22 +01:00
Jędrzej Stuczyński 28d1d9d989 add full response body to error message upon decoding failure (#5566) 2025-03-06 17:32:22 +01:00
Jon Häggblad 74a4197b77 Create authenticator v5 request/response types (#5561)
* Create authenticator v5 request/response types

* Support v5 in the authenticator

* Fix tests

* Bump nym-node version
2025-03-06 17:32:22 +01:00
Jon Häggblad 3bc7301d94 Handle disconnect in IPR (#5547)
* Implement disconnect in the IPR

* Remove unused async
2025-03-06 17:32:22 +01:00
Jon Häggblad cc342458f9 Allow IPR reconnect to session (#5562) 2025-03-06 17:32:22 +01:00
45 changed files with 1523 additions and 184 deletions
+20
View File
@@ -4,6 +4,26 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased] ## [Unreleased]
## [2025.4-dorina-patched] (2025-03-06)
- bugfix: make sure to correctly decode response content when putting it into error message ([#5571])
- Tweak surb management to be more conservative ([#5570])
- Deserialize v5 authenticator requests ([#5568])
- chore: additional logs when attempting to load ecash keys ([#5567])
- add full response body to error message upon decoding failure ([#5566])
- hotfix: ensure we bail on merkle leaves insertion upon missing data ([#5565])
- feature: v2 authentication request (#5537) ([#5563])
- Create authenticator v5 request/response types ([#5561])
[#5571]: https://github.com/nymtech/nym/pull/5571
[#5570]: https://github.com/nymtech/nym/pull/5570
[#5568]: https://github.com/nymtech/nym/pull/5568
[#5567]: https://github.com/nymtech/nym/pull/5567
[#5566]: https://github.com/nymtech/nym/pull/5566
[#5565]: https://github.com/nymtech/nym/pull/5565
[#5563]: https://github.com/nymtech/nym/pull/5563
[#5561]: https://github.com/nymtech/nym/pull/5561
## [2025.4-dorina] (2025-03-04) ## [2025.4-dorina] (2025-03-04)
- fixed sphinx version metrics registration ([#5546]) - fixed sphinx version metrics registration ([#5546])
Generated
+13 -8
View File
@@ -2433,7 +2433,7 @@ dependencies = [
[[package]] [[package]]
name = "explorer-api" name = "explorer-api"
version = "1.1.47" version = "1.1.48"
dependencies = [ dependencies = [
"chrono", "chrono",
"clap", "clap",
@@ -4780,7 +4780,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]] [[package]]
name = "nym-api" name = "nym-api"
version = "1.1.51" version = "1.1.53"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@@ -5029,7 +5029,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-cli" name = "nym-cli"
version = "1.1.49" version = "1.1.50"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64 0.22.1", "base64 0.22.1",
@@ -5112,7 +5112,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-client" name = "nym-client"
version = "1.1.49" version = "1.1.50"
dependencies = [ dependencies = [
"bs58", "bs58",
"clap", "clap",
@@ -5874,8 +5874,11 @@ name = "nym-http-api-client"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"bytes",
"encoding_rs",
"hickory-resolver", "hickory-resolver",
"http 1.2.0", "http 1.2.0",
"mime",
"nym-bin-common", "nym-bin-common",
"once_cell", "once_cell",
"reqwest 0.12.4", "reqwest 0.12.4",
@@ -6153,7 +6156,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-network-requester" name = "nym-network-requester"
version = "1.1.50" version = "1.1.51"
dependencies = [ dependencies = [
"addr", "addr",
"anyhow", "anyhow",
@@ -6204,7 +6207,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-node" name = "nym-node"
version = "1.6.0" version = "1.6.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"arc-swap", "arc-swap",
@@ -6489,6 +6492,7 @@ name = "nym-pemstore"
version = "0.3.0" version = "0.3.0"
dependencies = [ dependencies = [
"pem", "pem",
"tracing",
] ]
[[package]] [[package]]
@@ -6536,6 +6540,7 @@ dependencies = [
"reqwest 0.12.4", "reqwest 0.12.4",
"serde", "serde",
"tap", "tap",
"tempfile",
"thiserror 2.0.12", "thiserror 2.0.12",
"tokio", "tokio",
"tokio-stream", "tokio-stream",
@@ -6587,7 +6592,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-socks5-client" name = "nym-socks5-client"
version = "1.1.49" version = "1.1.50"
dependencies = [ dependencies = [
"bs58", "bs58",
"clap", "clap",
@@ -7192,7 +7197,7 @@ dependencies = [
[[package]] [[package]]
name = "nymvisor" name = "nymvisor"
version = "0.1.14" version = "0.1.15"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
+1
View File
@@ -241,6 +241,7 @@ doc-comment = "0.3"
dotenvy = "0.15.6" dotenvy = "0.15.6"
ecdsa = "0.16" ecdsa = "0.16"
ed25519-dalek = "2.1" ed25519-dalek = "2.1"
encoding_rs = "0.8.35"
env_logger = "0.11.6" env_logger = "0.11.6"
envy = "0.4" envy = "0.4"
etherparse = "0.13.0" etherparse = "0.13.0"
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "nym-client" name = "nym-client"
version = "1.1.49" version = "1.1.50"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"] authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client" description = "Implementation of the Nym Client"
edition = "2021" edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "nym-socks5-client" name = "nym-socks5-client"
version = "1.1.49" version = "1.1.50"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"] authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address" description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021" edition = "2021"
+3 -2
View File
@@ -6,14 +6,15 @@ pub mod v1;
pub mod v2; pub mod v2;
pub mod v3; pub mod v3;
pub mod v4; pub mod v4;
pub mod v5;
mod error; mod error;
mod util; mod util;
pub use error::Error; pub use error::Error;
pub use v4 as latest; pub use v5 as latest;
pub const CURRENT_VERSION: u8 = 4; pub const CURRENT_VERSION: u8 = 5;
fn make_bincode_serializer() -> impl bincode::Options { fn make_bincode_serializer() -> impl bincode::Options {
use bincode::Options; use bincode::Options;
+92 -20
View File
@@ -8,8 +8,8 @@ use nym_sphinx::addressing::clients::Recipient;
use nym_wireguard_types::PeerPublicKey; use nym_wireguard_types::PeerPublicKey;
use crate::{ use crate::{
v1, v2, v3, v1, v2, v3, v4,
v4::{self, registration::IpPair}, v5::{self, registration::IpPair},
Error, Error,
}; };
@@ -19,6 +19,7 @@ pub enum AuthenticatorVersion {
V2, V2,
V3, V3,
V4, V4,
V5,
UNKNOWN, UNKNOWN,
} }
@@ -34,6 +35,8 @@ impl From<Protocol> for AuthenticatorVersion {
AuthenticatorVersion::V3 AuthenticatorVersion::V3
} else if value.version == v4::VERSION { } else if value.version == v4::VERSION {
AuthenticatorVersion::V4 AuthenticatorVersion::V4
} else if value.version == v5::VERSION {
AuthenticatorVersion::V5
} else { } else {
AuthenticatorVersion::UNKNOWN AuthenticatorVersion::UNKNOWN
} }
@@ -68,6 +71,12 @@ impl InitMessage for v4::registration::InitMessage {
} }
} }
impl InitMessage for v5::registration::InitMessage {
fn pub_key(&self) -> PeerPublicKey {
self.pub_key
}
}
pub trait FinalMessage { pub trait FinalMessage {
fn pub_key(&self) -> PeerPublicKey; fn pub_key(&self) -> PeerPublicKey;
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error>; fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error>;
@@ -138,6 +147,24 @@ impl FinalMessage for v4::registration::FinalMessage {
self.gateway_client.verify(private_key, nonce) self.gateway_client.verify(private_key, nonce)
} }
fn private_ips(&self) -> IpPair {
self.gateway_client.private_ips.into()
}
fn credential(&self) -> Option<CredentialSpendingData> {
self.credential.clone()
}
}
impl FinalMessage for v5::registration::FinalMessage {
fn pub_key(&self) -> PeerPublicKey {
self.gateway_client.pub_key
}
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
self.gateway_client.verify(private_key, nonce)
}
fn private_ips(&self) -> IpPair { fn private_ips(&self) -> IpPair {
self.gateway_client.private_ips self.gateway_client.private_ips
} }
@@ -182,29 +209,39 @@ impl TopUpMessage for v4::topup::TopUpMessage {
} }
} }
impl TopUpMessage for v5::topup::TopUpMessage {
fn pub_key(&self) -> PeerPublicKey {
self.pub_key
}
fn credential(&self) -> CredentialSpendingData {
self.credential.clone()
}
}
pub enum AuthenticatorRequest { pub enum AuthenticatorRequest {
Initial { Initial {
msg: Box<dyn InitMessage + Send + Sync + 'static>, msg: Box<dyn InitMessage + Send + Sync + 'static>,
protocol: Protocol, protocol: Protocol,
reply_to: Recipient, reply_to: Option<Recipient>,
request_id: u64, request_id: u64,
}, },
Final { Final {
msg: Box<dyn FinalMessage + Send + Sync + 'static>, msg: Box<dyn FinalMessage + Send + Sync + 'static>,
protocol: Protocol, protocol: Protocol,
reply_to: Recipient, reply_to: Option<Recipient>,
request_id: u64, request_id: u64,
}, },
QueryBandwidth { QueryBandwidth {
msg: Box<dyn QueryBandwidthMessage + Send + Sync + 'static>, msg: Box<dyn QueryBandwidthMessage + Send + Sync + 'static>,
protocol: Protocol, protocol: Protocol,
reply_to: Recipient, reply_to: Option<Recipient>,
request_id: u64, request_id: u64,
}, },
TopUpBandwidth { TopUpBandwidth {
msg: Box<dyn TopUpMessage + Send + Sync + 'static>, msg: Box<dyn TopUpMessage + Send + Sync + 'static>,
protocol: Protocol, protocol: Protocol,
reply_to: Recipient, reply_to: Option<Recipient>,
request_id: u64, request_id: u64,
}, },
} }
@@ -218,7 +255,7 @@ impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
version: value.version, version: value.version,
service_provider_type: ServiceProviderType::Authenticator, service_provider_type: ServiceProviderType::Authenticator,
}, },
reply_to: value.reply_to, reply_to: Some(value.reply_to),
request_id: value.request_id, request_id: value.request_id,
}, },
v1::request::AuthenticatorRequestData::Final(gateway_client) => Self::Final { v1::request::AuthenticatorRequestData::Final(gateway_client) => Self::Final {
@@ -227,7 +264,7 @@ impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
version: value.version, version: value.version,
service_provider_type: ServiceProviderType::Authenticator, service_provider_type: ServiceProviderType::Authenticator,
}, },
reply_to: value.reply_to, reply_to: Some(value.reply_to),
request_id: value.request_id, request_id: value.request_id,
}, },
v1::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => { v1::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
@@ -237,7 +274,7 @@ impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
version: value.version, version: value.version,
service_provider_type: ServiceProviderType::Authenticator, service_provider_type: ServiceProviderType::Authenticator,
}, },
reply_to: value.reply_to, reply_to: Some(value.reply_to),
request_id: value.request_id, request_id: value.request_id,
} }
} }
@@ -251,20 +288,20 @@ impl From<v2::request::AuthenticatorRequest> for AuthenticatorRequest {
v2::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial { v2::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message), msg: Box::new(init_message),
protocol: value.protocol, protocol: value.protocol,
reply_to: value.reply_to, reply_to: Some(value.reply_to),
request_id: value.request_id, request_id: value.request_id,
}, },
v2::request::AuthenticatorRequestData::Final(final_message) => Self::Final { v2::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message, msg: final_message,
protocol: value.protocol, protocol: value.protocol,
reply_to: value.reply_to, reply_to: Some(value.reply_to),
request_id: value.request_id, request_id: value.request_id,
}, },
v2::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => { v2::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth { Self::QueryBandwidth {
msg: Box::new(peer_public_key), msg: Box::new(peer_public_key),
protocol: value.protocol, protocol: value.protocol,
reply_to: value.reply_to, reply_to: Some(value.reply_to),
request_id: value.request_id, request_id: value.request_id,
} }
} }
@@ -278,20 +315,20 @@ impl From<v3::request::AuthenticatorRequest> for AuthenticatorRequest {
v3::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial { v3::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message), msg: Box::new(init_message),
protocol: value.protocol, protocol: value.protocol,
reply_to: value.reply_to, reply_to: Some(value.reply_to),
request_id: value.request_id, request_id: value.request_id,
}, },
v3::request::AuthenticatorRequestData::Final(final_message) => Self::Final { v3::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message, msg: final_message,
protocol: value.protocol, protocol: value.protocol,
reply_to: value.reply_to, reply_to: Some(value.reply_to),
request_id: value.request_id, request_id: value.request_id,
}, },
v3::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => { v3::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth { Self::QueryBandwidth {
msg: Box::new(peer_public_key), msg: Box::new(peer_public_key),
protocol: value.protocol, protocol: value.protocol,
reply_to: value.reply_to, reply_to: Some(value.reply_to),
request_id: value.request_id, request_id: value.request_id,
} }
} }
@@ -299,7 +336,7 @@ impl From<v3::request::AuthenticatorRequest> for AuthenticatorRequest {
Self::TopUpBandwidth { Self::TopUpBandwidth {
msg: top_up_message, msg: top_up_message,
protocol: value.protocol, protocol: value.protocol,
reply_to: value.reply_to, reply_to: Some(value.reply_to),
request_id: value.request_id, request_id: value.request_id,
} }
} }
@@ -313,20 +350,20 @@ impl From<v4::request::AuthenticatorRequest> for AuthenticatorRequest {
v4::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial { v4::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message), msg: Box::new(init_message),
protocol: value.protocol, protocol: value.protocol,
reply_to: value.reply_to, reply_to: Some(value.reply_to),
request_id: value.request_id, request_id: value.request_id,
}, },
v4::request::AuthenticatorRequestData::Final(final_message) => Self::Final { v4::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message, msg: final_message,
protocol: value.protocol, protocol: value.protocol,
reply_to: value.reply_to, reply_to: Some(value.reply_to),
request_id: value.request_id, request_id: value.request_id,
}, },
v4::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => { v4::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth { Self::QueryBandwidth {
msg: Box::new(peer_public_key), msg: Box::new(peer_public_key),
protocol: value.protocol, protocol: value.protocol,
reply_to: value.reply_to, reply_to: Some(value.reply_to),
request_id: value.request_id, request_id: value.request_id,
} }
} }
@@ -334,7 +371,42 @@ impl From<v4::request::AuthenticatorRequest> for AuthenticatorRequest {
Self::TopUpBandwidth { Self::TopUpBandwidth {
msg: top_up_message, msg: top_up_message,
protocol: value.protocol, protocol: value.protocol,
reply_to: value.reply_to, 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, request_id: value.request_id,
} }
} }
@@ -0,0 +1,478 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
use crate::{v4, v5};
impl From<v4::request::AuthenticatorRequest> for v5::request::AuthenticatorRequest {
fn from(authenticator_request: v4::request::AuthenticatorRequest) -> Self {
Self {
protocol: Protocol {
version: 5,
service_provider_type: ServiceProviderType::Authenticator,
},
data: authenticator_request.data.into(),
request_id: authenticator_request.request_id,
}
}
}
impl From<v4::request::AuthenticatorRequestData> for v5::request::AuthenticatorRequestData {
fn from(authenticator_request_data: v4::request::AuthenticatorRequestData) -> Self {
match authenticator_request_data {
v4::request::AuthenticatorRequestData::Initial(init_msg) => {
v5::request::AuthenticatorRequestData::Initial(init_msg.into())
}
v4::request::AuthenticatorRequestData::Final(final_msg) => {
v5::request::AuthenticatorRequestData::Final(Box::new((*final_msg).into()))
}
v4::request::AuthenticatorRequestData::QueryBandwidth(pub_key) => {
v5::request::AuthenticatorRequestData::QueryBandwidth(pub_key)
}
v4::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
v5::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message.into())
}
}
}
}
impl From<v4::registration::InitMessage> for v5::registration::InitMessage {
fn from(init_msg: v4::registration::InitMessage) -> Self {
Self {
pub_key: init_msg.pub_key,
}
}
}
impl From<v4::registration::FinalMessage> for v5::registration::FinalMessage {
fn from(final_msg: v4::registration::FinalMessage) -> Self {
Self {
gateway_client: final_msg.gateway_client.into(),
credential: final_msg.credential,
}
}
}
impl From<v4::registration::GatewayClient> for v5::registration::GatewayClient {
fn from(gateway_client: v4::registration::GatewayClient) -> Self {
Self {
pub_key: gateway_client.pub_key,
private_ips: gateway_client.private_ips.into(),
mac: gateway_client.mac.into(),
}
}
}
impl From<v5::registration::GatewayClient> for v4::registration::GatewayClient {
fn from(gateway_client: v5::registration::GatewayClient) -> Self {
Self {
pub_key: gateway_client.pub_key,
private_ips: gateway_client.private_ips.into(),
mac: gateway_client.mac.into(),
}
}
}
impl From<v4::registration::ClientMac> for v5::registration::ClientMac {
fn from(client_mac: v4::registration::ClientMac) -> Self {
Self::new((*client_mac).clone())
}
}
impl From<v5::registration::ClientMac> for v4::registration::ClientMac {
fn from(client_mac: v5::registration::ClientMac) -> Self {
Self::new((*client_mac).clone())
}
}
impl From<Box<v4::topup::TopUpMessage>> for Box<v5::topup::TopUpMessage> {
fn from(top_up_message: Box<v4::topup::TopUpMessage>) -> Self {
Box::new(v5::topup::TopUpMessage {
pub_key: top_up_message.pub_key,
credential: top_up_message.credential,
})
}
}
impl From<v4::response::AuthenticatorResponse> for v5::response::AuthenticatorResponse {
fn from(value: v4::response::AuthenticatorResponse) -> Self {
Self {
protocol: Protocol {
version: 5,
service_provider_type: value.protocol.service_provider_type,
},
data: value.data.into(),
}
}
}
impl From<v4::response::AuthenticatorResponseData> for v5::response::AuthenticatorResponseData {
fn from(authenticator_response_data: v4::response::AuthenticatorResponseData) -> Self {
match authenticator_response_data {
v4::response::AuthenticatorResponseData::PendingRegistration(pending_response) => {
v5::response::AuthenticatorResponseData::PendingRegistration(
pending_response.into(),
)
}
v4::response::AuthenticatorResponseData::Registered(registered_response) => {
v5::response::AuthenticatorResponseData::Registered(registered_response.into())
}
v4::response::AuthenticatorResponseData::RemainingBandwidth(
remaining_bandwidth_response,
) => v5::response::AuthenticatorResponseData::RemainingBandwidth(
remaining_bandwidth_response.into(),
),
v4::response::AuthenticatorResponseData::TopUpBandwidth(top_up_response) => {
v5::response::AuthenticatorResponseData::TopUpBandwidth(top_up_response.into())
}
}
}
}
impl From<v4::response::RegisteredResponse> for v5::response::RegisteredResponse {
fn from(value: v4::response::RegisteredResponse) -> Self {
Self {
request_id: value.request_id,
reply: value.reply.into(),
}
}
}
impl From<v4::response::PendingRegistrationResponse> for v5::response::PendingRegistrationResponse {
fn from(value: v4::response::PendingRegistrationResponse) -> Self {
Self {
request_id: value.request_id,
reply: value.reply.into(),
}
}
}
impl From<v4::registration::RegistrationData> for v5::registration::RegistrationData {
fn from(value: v4::registration::RegistrationData) -> Self {
Self {
nonce: value.nonce,
gateway_data: value.gateway_data.into(),
wg_port: value.wg_port,
}
}
}
impl From<v5::registration::RegistrationData> for v4::registration::RegistrationData {
fn from(value: v5::registration::RegistrationData) -> Self {
Self {
nonce: value.nonce,
gateway_data: value.gateway_data.into(),
wg_port: value.wg_port,
}
}
}
impl From<v4::response::RemainingBandwidthResponse> for v5::response::RemainingBandwidthResponse {
fn from(value: v4::response::RemainingBandwidthResponse) -> Self {
Self {
request_id: value.request_id,
reply: value.reply.map(Into::into),
}
}
}
impl From<v4::response::TopUpBandwidthResponse> for v5::response::TopUpBandwidthResponse {
fn from(value: v4::response::TopUpBandwidthResponse) -> Self {
Self {
request_id: value.request_id,
reply: value.reply.into(),
}
}
}
impl From<v4::registration::RegistredData> for v5::registration::RegistredData {
fn from(value: v4::registration::RegistredData) -> Self {
Self {
pub_key: value.pub_key,
private_ips: value.private_ips.into(),
wg_port: value.wg_port,
}
}
}
impl From<v4::registration::RemainingBandwidthData> for v5::registration::RemainingBandwidthData {
fn from(value: v4::registration::RemainingBandwidthData) -> Self {
Self {
available_bandwidth: value.available_bandwidth,
}
}
}
impl From<v4::registration::IpPair> for v5::registration::IpPair {
fn from(value: v4::registration::IpPair) -> Self {
Self {
ipv4: value.ipv4,
ipv6: value.ipv6,
}
}
}
impl From<v5::registration::IpPair> for v4::registration::IpPair {
fn from(value: v5::registration::IpPair) -> Self {
Self {
ipv4: value.ipv4,
ipv6: value.ipv6,
}
}
}
#[cfg(test)]
mod tests {
use std::{
net::{Ipv4Addr, Ipv6Addr},
str::FromStr,
};
use nym_credentials_interface::CredentialSpendingData;
use nym_crypto::asymmetric::encryption::PrivateKey;
use nym_sphinx::addressing::Recipient;
use nym_wireguard_types::PeerPublicKey;
use x25519_dalek::PublicKey;
use super::*;
use crate::{
util::tests::{CREDENTIAL_BYTES, RECIPIENT},
v4,
};
#[test]
fn upgrade_initial_req() {
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
let (msg, _) = v4::request::AuthenticatorRequest::new_initial_request(
v4::registration::InitMessage::new(pub_key),
reply_to,
);
let upgraded_msg = v5::request::AuthenticatorRequest::from(msg);
assert_eq!(
upgraded_msg.protocol,
Protocol {
version: 5,
service_provider_type: ServiceProviderType::Authenticator
}
);
assert_eq!(
upgraded_msg.data,
v5::request::AuthenticatorRequestData::Initial(v5::registration::InitMessage {
pub_key
})
);
}
#[test]
fn upgrade_final_req() {
let mut rng = rand::thread_rng();
let local_secret = PrivateKey::new(&mut rng);
let remote_secret = x25519_dalek::StaticSecret::random_from_rng(&mut rng);
let ipv4 = Ipv4Addr::from_str("10.10.10.10").unwrap();
let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap();
let ips = v4::registration::IpPair::new(ipv4, ipv6);
let nonce = 42;
let gateway_client = v4::registration::GatewayClient::new(
&local_secret,
(&remote_secret).into(),
ips,
nonce,
);
let credential = Some(CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap());
let final_message = v4::registration::FinalMessage {
gateway_client: gateway_client.clone(),
credential: credential.clone(),
};
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
let (msg, _) =
v4::request::AuthenticatorRequest::new_final_request(final_message, reply_to);
let upgraded_msg = v5::request::AuthenticatorRequest::from(msg);
assert_eq!(
upgraded_msg.protocol,
Protocol {
version: 5,
service_provider_type: ServiceProviderType::Authenticator
}
);
assert_eq!(
upgraded_msg.data,
v5::request::AuthenticatorRequestData::Final(Box::new(
v5::registration::FinalMessage {
gateway_client: v5::registration::GatewayClient::new(
&local_secret,
(&remote_secret).into(),
v5::registration::IpPair::new(ipv4, ipv6),
nonce
),
credential
}
))
);
}
#[test]
fn upgrade_query_req() {
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
let (msg, _) = v4::request::AuthenticatorRequest::new_query_request(pub_key, reply_to);
let upgraded_msg = v5::request::AuthenticatorRequest::from(msg);
assert_eq!(
upgraded_msg.protocol,
Protocol {
version: 5,
service_provider_type: ServiceProviderType::Authenticator
}
);
assert_eq!(
upgraded_msg.data,
v5::request::AuthenticatorRequestData::QueryBandwidth(pub_key)
);
}
#[test]
fn upgrade_pending_reg_resp() {
let mut rng = rand::thread_rng();
let local_secret = PrivateKey::new(&mut rng);
let remote_secret = x25519_dalek::StaticSecret::random_from_rng(&mut rng);
let ipv4 = Ipv4Addr::from_str("10.10.10.10").unwrap();
let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap();
let ips = v4::registration::IpPair::new(ipv4, ipv6);
let nonce = 42;
let wg_port = 51822;
let gateway_data = v4::registration::GatewayClient::new(
&local_secret,
(&remote_secret).into(),
ips,
nonce,
);
let registration_data = v4::registration::RegistrationData {
nonce,
gateway_data,
wg_port,
};
let request_id = 123;
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
let msg = v4::response::AuthenticatorResponse::new_pending_registration_success(
registration_data,
request_id,
reply_to,
);
let upgraded_msg = v5::response::AuthenticatorResponse::from(msg);
assert_eq!(
upgraded_msg.protocol,
Protocol {
version: 5,
service_provider_type: ServiceProviderType::Authenticator
}
);
assert_eq!(
upgraded_msg.data,
v5::response::AuthenticatorResponseData::PendingRegistration(
v5::response::PendingRegistrationResponse {
request_id,
reply: v5::registration::RegistrationData {
nonce,
gateway_data: v5::registration::GatewayClient::new(
&local_secret,
(&remote_secret).into(),
v5::registration::IpPair::new(ipv4, ipv6),
nonce
),
wg_port
}
}
)
);
}
#[test]
fn upgrade_registered_resp() {
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
let ipv4 = Ipv4Addr::from_str("10.1.10.10").unwrap();
let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap();
let private_ips = v4::registration::IpPair::new(ipv4, ipv6);
let wg_port = 51822;
let registred_data = v4::registration::RegistredData {
pub_key,
private_ips,
wg_port,
};
let request_id = 123;
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
let msg = v4::response::AuthenticatorResponse::new_registered(
registred_data,
reply_to,
request_id,
);
let upgraded_msg = v5::response::AuthenticatorResponse::from(msg);
assert_eq!(
upgraded_msg.protocol,
Protocol {
version: 5,
service_provider_type: ServiceProviderType::Authenticator
}
);
assert_eq!(
upgraded_msg.data,
v5::response::AuthenticatorResponseData::Registered(v5::response::RegisteredResponse {
request_id,
reply: v5::registration::RegistredData {
wg_port,
pub_key,
private_ips: v5::registration::IpPair::new(ipv4, ipv6)
}
})
);
}
#[test]
fn upgrade_remaining_bandwidth_resp() {
let available_bandwidth = 42;
let remaining_bandwidth_data = Some(v4::registration::RemainingBandwidthData {
available_bandwidth,
});
let request_id = 123;
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
let msg = v4::response::AuthenticatorResponse::new_remaining_bandwidth(
remaining_bandwidth_data,
reply_to,
request_id,
);
let upgraded_msg = v5::response::AuthenticatorResponse::from(msg);
assert_eq!(
upgraded_msg.protocol,
Protocol {
version: 5,
service_provider_type: ServiceProviderType::Authenticator
}
);
assert_eq!(
upgraded_msg.data,
v5::response::AuthenticatorResponseData::RemainingBandwidth(
v5::response::RemainingBandwidthResponse {
request_id,
reply: Some(v5::registration::RemainingBandwidthData {
available_bandwidth,
})
}
)
);
}
}
@@ -0,0 +1,10 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod conversion;
pub mod registration;
pub mod request;
pub mod response;
pub mod topup;
pub const VERSION: u8 = 5;
@@ -0,0 +1,287 @@
// -2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::Error;
use base64::{engine::general_purpose, Engine};
use nym_credentials_interface::CredentialSpendingData;
use nym_network_defaults::constants::{WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6};
use nym_wireguard_types::PeerPublicKey;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::time::SystemTime;
use std::{fmt, ops::Deref, str::FromStr};
#[cfg(feature = "verify")]
use hmac::{Hmac, Mac};
#[cfg(feature = "verify")]
use nym_crypto::asymmetric::encryption::PrivateKey;
#[cfg(feature = "verify")]
use sha2::Sha256;
pub type PendingRegistrations = HashMap<PeerPublicKey, RegistrationData>;
pub type PrivateIPs = HashMap<IpPair, Taken>;
#[cfg(feature = "verify")]
pub type HmacSha256 = Hmac<Sha256>;
pub type Nonce = u64;
pub type Taken = Option<SystemTime>;
pub const BANDWIDTH_CAP_PER_DAY: u64 = 250 * 1024 * 1024 * 1024; // 250 GB
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct IpPair {
pub ipv4: Ipv4Addr,
pub ipv6: Ipv6Addr,
}
impl IpPair {
pub fn new(ipv4: Ipv4Addr, ipv6: Ipv6Addr) -> Self {
IpPair { ipv4, ipv6 }
}
}
impl From<(Ipv4Addr, Ipv6Addr)> for IpPair {
fn from((ipv4, ipv6): (Ipv4Addr, Ipv6Addr)) -> Self {
IpPair { ipv4, ipv6 }
}
}
impl fmt::Display for IpPair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {})", self.ipv4, self.ipv6)
}
}
impl From<IpAddr> for IpPair {
fn from(value: IpAddr) -> Self {
let (before_last_byte, last_byte) = match value {
std::net::IpAddr::V4(ipv4_addr) => (ipv4_addr.octets()[2], ipv4_addr.octets()[3]),
std::net::IpAddr::V6(ipv6_addr) => (ipv6_addr.octets()[14], ipv6_addr.octets()[15]),
};
let last_bytes = ((before_last_byte as u16) << 8) | last_byte as u16;
let ipv4 = Ipv4Addr::new(
WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[0],
WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[1],
before_last_byte,
last_byte,
);
let ipv6 = Ipv6Addr::new(
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[0],
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[1],
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[2],
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[3],
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[4],
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[5],
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[6],
last_bytes,
);
IpPair::new(ipv4, ipv6)
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct InitMessage {
/// Base64 encoded x25519 public key
pub pub_key: PeerPublicKey,
}
impl InitMessage {
pub fn new(pub_key: PeerPublicKey) -> Self {
InitMessage { pub_key }
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct FinalMessage {
/// Gateway client data
pub gateway_client: GatewayClient,
/// Ecash credential
pub credential: Option<CredentialSpendingData>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct RegistrationData {
pub nonce: u64,
pub gateway_data: GatewayClient,
pub wg_port: u16,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct RegistredData {
pub pub_key: PeerPublicKey,
pub private_ips: IpPair,
pub wg_port: u16,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct RemainingBandwidthData {
pub available_bandwidth: i64,
}
/// Client that wants to register sends its PublicKey bytes mac digest encrypted with a DH shared secret.
/// Gateway/Nym node can then verify pub_key payload using the same process
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct GatewayClient {
/// Base64 encoded x25519 public key
pub pub_key: PeerPublicKey,
/// Assigned private IPs (v4 and v6)
pub private_ips: IpPair,
/// Sha256 hmac on the data (alongside the prior nonce)
pub mac: ClientMac,
}
impl GatewayClient {
#[cfg(feature = "verify")]
pub fn new(
local_secret: &PrivateKey,
remote_public: x25519_dalek::PublicKey,
private_ips: IpPair,
nonce: u64,
) -> Self {
// convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek
#[allow(clippy::expect_used)]
let static_secret = x25519_dalek::StaticSecret::from(local_secret.to_bytes());
let local_public: x25519_dalek::PublicKey = (&static_secret).into();
let dh = static_secret.diffie_hellman(&remote_public);
// TODO: change that to use our nym_crypto::hmac module instead
#[allow(clippy::expect_used)]
let mut mac = HmacSha256::new_from_slice(dh.as_bytes())
.expect("x25519 shared secret is always 32 bytes long");
mac.update(local_public.as_bytes());
mac.update(private_ips.to_string().as_bytes());
mac.update(&nonce.to_le_bytes());
GatewayClient {
pub_key: PeerPublicKey::new(local_public),
private_ips,
mac: ClientMac(mac.finalize().into_bytes().to_vec()),
}
}
// Reusable secret should be gateways Wireguard PK
// Client should perform this step when generating its payload, using its own WG PK
#[cfg(feature = "verify")]
pub fn verify(&self, gateway_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
// convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek
#[allow(clippy::expect_used)]
let static_secret = x25519_dalek::StaticSecret::from(gateway_key.to_bytes());
let dh = static_secret.diffie_hellman(&self.pub_key);
// TODO: change that to use our nym_crypto::hmac module instead
#[allow(clippy::expect_used)]
let mut mac = HmacSha256::new_from_slice(dh.as_bytes())
.expect("x25519 shared secret is always 32 bytes long");
mac.update(self.pub_key.as_bytes());
mac.update(self.private_ips.to_string().as_bytes());
mac.update(&nonce.to_le_bytes());
mac.verify_slice(&self.mac)
.map_err(|source| Error::FailedClientMacVerification {
client: self.pub_key.to_string(),
source,
})
}
pub fn pub_key(&self) -> PeerPublicKey {
self.pub_key
}
}
// TODO: change the inner type into generic array of size HmacSha256::OutputSize
// TODO2: rely on our internal crypto/hmac
#[derive(Debug, Clone, PartialEq)]
pub struct ClientMac(Vec<u8>);
impl fmt::Display for ClientMac {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", general_purpose::STANDARD.encode(&self.0))
}
}
impl ClientMac {
#[allow(dead_code)]
pub fn new(mac: Vec<u8>) -> Self {
ClientMac(mac)
}
}
impl Deref for ClientMac {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl FromStr for ClientMac {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mac_bytes: Vec<u8> =
general_purpose::STANDARD
.decode(s)
.map_err(|source| Error::MalformedClientMac {
mac: s.to_string(),
source,
})?;
Ok(ClientMac(mac_bytes))
}
}
impl Serialize for ClientMac {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let encoded_key = general_purpose::STANDARD.encode(self.0.clone());
serializer.serialize_str(&encoded_key)
}
}
impl<'de> Deserialize<'de> for ClientMac {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let encoded_key = String::deserialize(deserializer)?;
ClientMac::from_str(&encoded_key).map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod tests {
use super::*;
use nym_crypto::asymmetric::encryption;
#[test]
fn create_ip_pair() {
let ipv4: IpAddr = Ipv4Addr::from_str("10.1.10.50").unwrap().into();
let ipv6: IpAddr = Ipv6Addr::from_str("fc01::0a32").unwrap().into();
assert_eq!(IpPair::from(ipv4), IpPair::from(ipv6));
}
#[test]
#[cfg(feature = "verify")]
fn client_request_roundtrip() {
let mut rng = rand::thread_rng();
let gateway_key_pair = encryption::KeyPair::new(&mut rng);
let client_key_pair = encryption::KeyPair::new(&mut rng);
let nonce = 1234567890;
let client = GatewayClient::new(
client_key_pair.private_key(),
x25519_dalek::PublicKey::from(gateway_key_pair.public_key().to_bytes()),
IpPair::new("10.0.0.42".parse().unwrap(), "fc00::42".parse().unwrap()),
nonce,
);
assert!(client.verify(gateway_key_pair.private_key(), nonce).is_ok())
}
}
@@ -0,0 +1,132 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::{
registration::{FinalMessage, InitMessage},
topup::TopUpMessage,
};
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
use nym_wireguard_types::PeerPublicKey;
use serde::{Deserialize, Serialize};
use crate::make_bincode_serializer;
use super::VERSION;
fn generate_random() -> u64 {
use rand::RngCore;
let mut rng = rand::rngs::OsRng;
rng.next_u64()
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct AuthenticatorRequest {
pub protocol: Protocol,
pub data: AuthenticatorRequestData,
pub request_id: u64,
}
impl AuthenticatorRequest {
pub fn from_reconstructed_message(
message: &nym_sphinx::receiver::ReconstructedMessage,
) -> Result<Self, bincode::Error> {
use bincode::Options;
make_bincode_serializer().deserialize(&message.message)
}
pub fn new_initial_request(init_message: InitMessage) -> (Self, u64) {
let request_id = generate_random();
(
Self {
protocol: Protocol {
service_provider_type: ServiceProviderType::Authenticator,
version: VERSION,
},
data: AuthenticatorRequestData::Initial(init_message),
request_id,
},
request_id,
)
}
pub fn new_final_request(final_message: FinalMessage) -> (Self, u64) {
let request_id = generate_random();
(
Self {
protocol: Protocol {
service_provider_type: ServiceProviderType::Authenticator,
version: VERSION,
},
data: AuthenticatorRequestData::Final(Box::new(final_message)),
request_id,
},
request_id,
)
}
pub fn new_query_request(peer_public_key: PeerPublicKey) -> (Self, u64) {
let request_id = generate_random();
(
Self {
protocol: Protocol {
service_provider_type: ServiceProviderType::Authenticator,
version: VERSION,
},
data: AuthenticatorRequestData::QueryBandwidth(peer_public_key),
request_id,
},
request_id,
)
}
pub fn new_topup_request(top_up_message: TopUpMessage) -> (Self, u64) {
let request_id = generate_random();
(
Self {
protocol: Protocol {
service_provider_type: ServiceProviderType::Authenticator,
version: VERSION,
},
data: AuthenticatorRequestData::TopUpBandwidth(Box::new(top_up_message)),
request_id,
},
request_id,
)
}
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
make_bincode_serializer().serialize(self)
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum AuthenticatorRequestData {
Initial(InitMessage),
Final(Box<FinalMessage>),
QueryBandwidth(PeerPublicKey),
TopUpBandwidth(Box<TopUpMessage>),
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
#[test]
fn check_first_bytes_protocol() {
let version = 5;
let data = AuthenticatorRequest {
protocol: Protocol {
version,
service_provider_type: ServiceProviderType::Authenticator,
},
data: AuthenticatorRequestData::Initial(InitMessage::new(
PeerPublicKey::from_str("yvNUDpT5l7W/xDhiu6HkqTHDQwbs/B3J5UrLmORl1EQ=").unwrap(),
)),
request_id: 1,
};
let bytes = *data.to_bytes().unwrap().first_chunk::<2>().unwrap();
assert_eq!(bytes, [version, ServiceProviderType::Authenticator as u8]);
}
}
@@ -0,0 +1,132 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::registration::{RegistrationData, RegistredData, RemainingBandwidthData};
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
use serde::{Deserialize, Serialize};
use crate::make_bincode_serializer;
use super::VERSION;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct AuthenticatorResponse {
pub protocol: Protocol,
pub data: AuthenticatorResponseData,
}
impl AuthenticatorResponse {
pub fn new_pending_registration_success(
registration_data: RegistrationData,
request_id: u64,
) -> Self {
Self {
protocol: Protocol {
service_provider_type: ServiceProviderType::Authenticator,
version: VERSION,
},
data: AuthenticatorResponseData::PendingRegistration(PendingRegistrationResponse {
reply: registration_data,
request_id,
}),
}
}
pub fn new_registered(registred_data: RegistredData, request_id: u64) -> Self {
Self {
protocol: Protocol {
service_provider_type: ServiceProviderType::Authenticator,
version: VERSION,
},
data: AuthenticatorResponseData::Registered(RegisteredResponse {
reply: registred_data,
request_id,
}),
}
}
pub fn new_remaining_bandwidth(
remaining_bandwidth_data: Option<RemainingBandwidthData>,
request_id: u64,
) -> Self {
Self {
protocol: Protocol {
service_provider_type: ServiceProviderType::Authenticator,
version: VERSION,
},
data: AuthenticatorResponseData::RemainingBandwidth(RemainingBandwidthResponse {
reply: remaining_bandwidth_data,
request_id,
}),
}
}
pub fn new_topup_bandwidth(
remaining_bandwidth_data: RemainingBandwidthData,
request_id: u64,
) -> Self {
Self {
protocol: Protocol {
service_provider_type: ServiceProviderType::Authenticator,
version: VERSION,
},
data: AuthenticatorResponseData::TopUpBandwidth(TopUpBandwidthResponse {
reply: remaining_bandwidth_data,
request_id,
}),
}
}
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
make_bincode_serializer().serialize(self)
}
pub fn from_reconstructed_message(
message: &nym_sphinx::receiver::ReconstructedMessage,
) -> Result<Self, bincode::Error> {
use bincode::Options;
make_bincode_serializer().deserialize(&message.message)
}
pub fn id(&self) -> Option<u64> {
match &self.data {
AuthenticatorResponseData::PendingRegistration(response) => Some(response.request_id),
AuthenticatorResponseData::Registered(response) => Some(response.request_id),
AuthenticatorResponseData::RemainingBandwidth(response) => Some(response.request_id),
AuthenticatorResponseData::TopUpBandwidth(response) => Some(response.request_id),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum AuthenticatorResponseData {
PendingRegistration(PendingRegistrationResponse),
Registered(RegisteredResponse),
RemainingBandwidth(RemainingBandwidthResponse),
TopUpBandwidth(TopUpBandwidthResponse),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct PendingRegistrationResponse {
pub request_id: u64,
pub reply: RegistrationData,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct RegisteredResponse {
pub request_id: u64,
pub reply: RegistredData,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct RemainingBandwidthResponse {
pub request_id: u64,
pub reply: Option<RemainingBandwidthData>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct TopUpBandwidthResponse {
pub request_id: u64,
pub reply: RemainingBandwidthData,
}
@@ -0,0 +1,15 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_credentials_interface::CredentialSpendingData;
use nym_wireguard_types::PeerPublicKey;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct TopUpMessage {
/// Base64 encoded x25519 public key
pub pub_key: PeerPublicKey,
/// Ecash credential
pub credential: CredentialSpendingData,
}
+1 -1
View File
@@ -50,7 +50,7 @@ const DEFAULT_MINIMUM_REPLY_SURB_THRESHOLD_BUFFER: usize = 0;
// define how much to request at once // define how much to request at once
// clients/client-core/src/client/replies/reply_controller.rs // clients/client-core/src/client/replies/reply_controller.rs
const DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 10; const DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 10;
const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 100; const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 50;
const DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE: u32 = 500; const DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE: u32 = 500;
@@ -33,10 +33,12 @@ pub enum PreparationError {
#[error(transparent)] #[error(transparent)]
NymTopologyError(#[from] NymTopologyError), NymTopologyError(#[from] NymTopologyError),
#[error("The received message cannot be sent using a single reply surb. It ended up getting split into {fragments} fragments.")] #[error("message too long for a single SURB, splitting into {fragments} fragments.")]
MessageTooLongForSingleSurb { fragments: usize }, MessageTooLongForSingleSurb { fragments: usize },
#[error("Not enough reply SURBs to send the message. We have {available} available and require at least {required}.")] #[error(
"not enough reply SURBs to send the message, available: {available} required: {required}."
)]
NotEnoughSurbs { available: usize, required: usize }, NotEnoughSurbs { available: usize, required: usize },
} }
@@ -746,7 +746,7 @@ where
.request_additional_reply_surbs(target, request_size) .request_additional_reply_surbs(target, request_size)
.await .await
{ {
warn!("failed to request additional surbs... - {err}") info!("{err}")
} }
} }
+7 -1
View File
@@ -21,6 +21,12 @@ serde_json = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
# used for decoding text responses (they were already implicitly included)
bytes = { workspace = true }
encoding_rs = { workspace = true }
mime = { workspace = true }
nym-bin-common = { path = "../bin-common" } nym-bin-common = { path = "../bin-common" }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies] [target."cfg(not(target_arch = \"wasm32\"))".dependencies]
@@ -32,4 +38,4 @@ workspace = true
features = ["tokio"] features = ["tokio"]
[dev-dependencies] [dev-dependencies]
tokio = { workspace = true, features=["rt", "macros"] } tokio = { workspace = true, features = ["rt", "macros"] }
+42 -14
View File
@@ -147,13 +147,13 @@ use thiserror::Error;
use tracing::{instrument, warn}; use tracing::{instrument, warn};
use url::Url; use url::Url;
use http::HeaderMap;
pub use reqwest::IntoUrl;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use std::net::SocketAddr; use std::net::SocketAddr;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use std::sync::Arc; use std::sync::Arc;
pub use reqwest::IntoUrl;
mod user_agent; mod user_agent;
pub use user_agent::UserAgent; pub use user_agent::UserAgent;
@@ -210,6 +210,12 @@ pub enum HttpClientError<E: Display = String> {
#[error("failed to resolve request. status: '{status}', additional error message: {error}")] #[error("failed to resolve request. status: '{status}', additional error message: {error}")]
EndpointFailure { status: StatusCode, error: E }, EndpointFailure { status: StatusCode, error: E },
#[error("failed to decode response body: {source} from {content}")]
ResponseDecodeFailure {
source: serde_json::Error,
content: String,
},
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
#[error("the request has timed out")] #[error("the request has timed out")]
RequestTimeout, RequestTimeout,
@@ -849,6 +855,26 @@ fn sanitize_url<K: AsRef<str>, V: AsRef<str>>(
url url
} }
fn decode_as_text(bytes: &bytes::Bytes, headers: HeaderMap) -> String {
use encoding_rs::{Encoding, UTF_8};
use mime::Mime;
let content_type = headers
.get(http::header::CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
.and_then(|value| value.parse::<Mime>().ok());
let encoding_name = content_type
.as_ref()
.and_then(|mime| mime.get_param("charset").map(|charset| charset.as_str()))
.unwrap_or("utf-8");
let encoding = Encoding::for_label(encoding_name.as_bytes()).unwrap_or(UTF_8);
let (text, _, _) = encoding.decode(bytes);
text.into_owned()
}
/// Attempt to parse a json object from an HTTP response /// Attempt to parse a json object from an HTTP response
#[instrument(level = "debug", skip_all)] #[instrument(level = "debug", skip_all)]
pub async fn parse_response<T, E>(res: Response, allow_empty: bool) -> Result<T, HttpClientError<E>> pub async fn parse_response<T, E>(res: Response, allow_empty: bool) -> Result<T, HttpClientError<E>>
@@ -864,21 +890,23 @@ where
return Err(HttpClientError::EmptyResponse { status }); return Err(HttpClientError::EmptyResponse { status });
} }
} }
let headers = res.headers().clone();
tracing::trace!("headers: {:?}", headers);
if res.status().is_success() { if res.status().is_success() {
#[cfg(debug_assertions)] // internally reqwest is first retrieving bytes and then performing parsing via serde_json
{ // (and similarly does the same thing for text())
let text = res.text().await.inspect_err(|err| { let full = res.bytes().await?;
tracing::error!("Couldn't even get response text: {err}"); match serde_json::from_slice(&full) {
})?; Ok(data) => Ok(data),
tracing::trace!("Result:\n{:#?}", text); Err(err) => {
let content = decode_as_text(&full, headers);
serde_json::from_str(&text) Err(HttpClientError::ResponseDecodeFailure {
.map_err(|err| HttpClientError::GenericRequestFailure(err.to_string())) source: err,
content,
})
}
} }
#[cfg(not(debug_assertions))]
Ok(res.json().await?)
} else if res.status() == StatusCode::NOT_FOUND { } else if res.status() == StatusCode::NOT_FOUND {
Err(HttpClientError::NotFound) Err(HttpClientError::NotFound)
} else { } else {
+1
View File
@@ -9,3 +9,4 @@ repository = { workspace = true }
[dependencies] [dependencies]
pem = { workspace = true } pem = { workspace = true }
tracing = { workspace = true }
+5
View File
@@ -6,6 +6,7 @@ use pem::Pem;
use std::fs::File; use std::fs::File;
use std::io::{self, Read, Write}; use std::io::{self, Read, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use tracing::debug;
pub mod traits; pub mod traits;
@@ -46,6 +47,10 @@ where
T: PemStorableKey, T: PemStorableKey,
P: AsRef<Path>, P: AsRef<Path>,
{ {
debug!(
"attempting to load key with the following pem type: {}",
T::pem_type()
);
let key_pem = read_pem_file(path)?; let key_pem = read_pem_file(path)?;
if T::pem_type() != key_pem.tag { if T::pem_type() != key_pem.tag {
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "explorer-api" name = "explorer-api"
version = "1.1.47" version = "1.1.48"
edition = "2021" edition = "2021"
license.workspace = true license.workspace = true
@@ -426,6 +426,11 @@ impl<R, S> FreshHandler<R, S> {
return Ok(2); return Ok(2);
} }
// a v4 gateway will understand v3 requests (aes256gcm-siv)
if client_protocol_version == 3 {
return Ok(3);
}
// we can't handle clients with higher protocol than ours // we can't handle clients with higher protocol than ours
// (perhaps we could try to negotiate downgrade on our end? sounds like a nice future improvement) // (perhaps we could try to negotiate downgrade on our end? sounds like a nice future improvement)
if client_protocol_version <= CURRENT_PROTOCOL_VERSION { if client_protocol_version <= CURRENT_PROTOCOL_VERSION {
+2 -2
View File
@@ -4,7 +4,7 @@
[package] [package]
name = "nym-api" name = "nym-api"
license = "GPL-3.0" license = "GPL-3.0"
version = "1.1.51" version = "1.1.53"
authors.workspace = true authors.workspace = true
edition = "2021" edition = "2021"
rust-version.workspace = true rust-version.workspace = true
@@ -144,4 +144,4 @@ rand_chacha = { workspace = true }
sha2 = "0.9" sha2 = "0.9"
[lints] [lints]
workspace = true workspace = true
+10 -7
View File
@@ -10,7 +10,7 @@ use nym_dkg::bte::keys::KeyPair as DkgKeyPair;
use rand::{CryptoRng, RngCore}; use rand::{CryptoRng, RngCore};
use std::path::Path; use std::path::Path;
use thiserror::__private::AsDisplay; use thiserror::__private::AsDisplay;
use tracing::warn; use tracing::{debug, warn};
pub(crate) fn init_bte_keypair<R: RngCore + CryptoRng>( pub(crate) fn init_bte_keypair<R: RngCore + CryptoRng>(
rng: &mut R, rng: &mut R,
@@ -39,17 +39,20 @@ pub(crate) fn load_bte_keypair(config: &config::EcashSigner) -> anyhow::Result<D
pub(crate) fn load_ecash_keypair_if_exists( pub(crate) fn load_ecash_keypair_if_exists(
config: &config::EcashSigner, config: &config::EcashSigner,
) -> anyhow::Result<Option<KeyPairWithEpoch>> { ) -> anyhow::Result<Option<KeyPairWithEpoch>> {
let storage_path = &config.storage_paths.ecash_key_path;
debug!(
"attempting to ecash keypair from {}",
storage_path.display()
);
if !config.storage_paths.ecash_key_path.exists() { if !config.storage_paths.ecash_key_path.exists() {
debug!("the provided filepath doesn't exist - the key won't be loaded");
return Ok(None); return Ok(None);
} }
if let Ok(ecash_key) = let ecash_key =
nym_pemstore::load_key::<KeyPairWithEpoch, _>(&config.storage_paths.ecash_key_path) nym_pemstore::load_key::<KeyPairWithEpoch, _>(&config.storage_paths.ecash_key_path)
{ .context("failed to load ecash key")?;
return Ok(Some(ecash_key)); Ok(Some(ecash_key))
}
bail!("ecash key load failure")
} }
// the keys can be considered valid if they were generated for the current dkg epoch // the keys can be considered valid if they were generated for the current dkg epoch
+5 -2
View File
@@ -33,14 +33,17 @@ impl DailyMerkleTree {
.into_iter() .into_iter()
.map(|l| (l.merkle_index, l)) .map(|l| (l.merkle_index, l))
.collect(); .collect();
let total_leaves = leaves.len();
let mut sorted_leaves = Vec::new(); let mut sorted_leaves = Vec::new();
for i in 0..leaves.len() { for i in 0..leaves.len() {
if let Some(next_leaf) = leaves.remove(&i) { if let Some(next_leaf) = leaves.remove(&i) {
sorted_leaves.push(next_leaf); sorted_leaves.push(next_leaf);
} else { } else {
let lost = leaves.len() - i + 1; let lost = total_leaves - i + 1;
error!("failed to produce consistent merkle tree. there was no leaf with index {i}. at least {lost} leaves got lost") error!("failed to produce consistent merkle tree. there was no leaf with index {i}. at least {lost} leaves got lost");
// we have to drop all data above that height because we can't rebuild the full tree
break;
} }
} }
+2 -2
View File
@@ -3,7 +3,7 @@
[package] [package]
name = "nym-node" name = "nym-node"
version = "1.6.0" version = "1.6.1"
authors.workspace = true authors.workspace = true
repository.workspace = true repository.workspace = true
homepage.workspace = true homepage.workspace = true
@@ -101,4 +101,4 @@ cargo_metadata = { workspace = true }
[lints] [lints]
workspace = true workspace = true
+1 -7
View File
@@ -50,10 +50,6 @@ pub struct Debug {
/// The maximum number of client connections the gateway will keep open at once. /// The maximum number of client connections the gateway will keep open at once.
pub maximum_open_connections: usize, pub maximum_open_connections: usize,
/// Specifies the minimum performance of mixnodes in the network that are to be used in internal topologies
/// of the services providers
pub minimum_mix_performance: u8,
/// Defines the maximum age of a signed authentication request before it's deemed too stale to process. /// Defines the maximum age of a signed authentication request before it's deemed too stale to process.
pub maximum_auth_request_age: Duration, pub maximum_auth_request_age: Duration,
@@ -65,10 +61,9 @@ pub struct Debug {
} }
impl Debug { impl Debug {
pub const DEFAULT_MAXIMUM_OPEN_CONNECTIONS: usize = 8192;
pub const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100; pub const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100;
pub const DEFAULT_MINIMUM_MIX_PERFORMANCE: u8 = 50;
pub const DEFAULT_MAXIMUM_AUTH_REQUEST_AGE: Duration = Duration::from_secs(30); pub const DEFAULT_MAXIMUM_AUTH_REQUEST_AGE: Duration = Duration::from_secs(30);
const DEFAULT_MAXIMUM_OPEN_CONNECTIONS: usize = 8192;
} }
impl Default for Debug { impl Default for Debug {
@@ -77,7 +72,6 @@ impl Default for Debug {
message_retrieval_limit: Self::DEFAULT_MESSAGE_RETRIEVAL_LIMIT, message_retrieval_limit: Self::DEFAULT_MESSAGE_RETRIEVAL_LIMIT,
maximum_open_connections: Self::DEFAULT_MAXIMUM_OPEN_CONNECTIONS, maximum_open_connections: Self::DEFAULT_MAXIMUM_OPEN_CONNECTIONS,
maximum_auth_request_age: Self::DEFAULT_MAXIMUM_AUTH_REQUEST_AGE, maximum_auth_request_age: Self::DEFAULT_MAXIMUM_AUTH_REQUEST_AGE,
minimum_mix_performance: Self::DEFAULT_MINIMUM_MIX_PERFORMANCE,
stale_messages: Default::default(), stale_messages: Default::default(),
client_bandwidth: Default::default(), client_bandwidth: Default::default(),
zk_nym_tickets: Default::default(), zk_nym_tickets: Default::default(),
+7 -4
View File
@@ -465,9 +465,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.5.0" version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@@ -1487,9 +1487,9 @@ checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.32" version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
] ]
@@ -3463,8 +3463,11 @@ name = "nym-http-api-client"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"bytes",
"encoding_rs",
"hickory-resolver", "hickory-resolver",
"http 1.1.0", "http 1.1.0",
"mime",
"nym-bin-common", "nym-bin-common",
"once_cell", "once_cell",
"reqwest 0.12.4", "reqwest 0.12.4",
+1
View File
@@ -58,6 +58,7 @@ tap = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
url = { workspace = true } url = { workspace = true }
toml = { workspace = true } toml = { workspace = true }
tempfile = { workspace = true }
# tcpproxy dependencies # tcpproxy dependencies
clap = { workspace = true, features = ["derive"] } clap = { workspace = true, features = ["derive"] }
+12 -3
View File
@@ -1,15 +1,16 @@
use nym_sdk::mixnet::{ use nym_sdk::mixnet::{
AnonymousSenderTag, MixnetClientBuilder, MixnetMessageSender, ReconstructedMessage, self, AnonymousSenderTag, MixnetClientBuilder, MixnetMessageSender, ReconstructedMessage,
StoragePaths, StoragePaths,
}; };
use std::path::PathBuf; use std::path::PathBuf;
use tempfile::TempDir;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
nym_bin_common::logging::setup_logging(); nym_bin_common::logging::setup_logging();
// Specify some config options // Specify some config options
let config_dir = PathBuf::from("/tmp/surb-example"); let config_dir: PathBuf = TempDir::new().unwrap().path().to_path_buf();
let storage_paths = StoragePaths::new_from_dir(&config_dir).unwrap(); let storage_paths = StoragePaths::new_from_dir(&config_dir).unwrap();
// Create the client with a storage backend, and enable it by giving it some paths. If keys // Create the client with a storage backend, and enable it by giving it some paths. If keys
@@ -28,8 +29,16 @@ async fn main() {
println!("\nOur client nym address is: {our_address}"); println!("\nOur client nym address is: {our_address}");
// Send a message through the mixnet to ourselves using our nym address // Send a message through the mixnet to ourselves using our nym address
// client
// .send_plain_message(*our_address, "hello there")
// .await
// .unwrap();
client client
.send_plain_message(*our_address, "hello there") .send_message(
*our_address,
"hello there",
mixnet::IncludedSurbs::Amount(40),
)
.await .await
.unwrap(); .unwrap();
@@ -111,16 +111,16 @@ pub(crate) async fn execute(args: &Request) -> Result<(), AuthenticatorError> {
let authenticator_recipient = Recipient::from_str(&args.authenticator_recipient)?; let authenticator_recipient = Recipient::from_str(&args.authenticator_recipient)?;
let (request, _) = match request_data { let (request, _) = match request_data {
AuthenticatorRequestData::Initial(init_message) => { AuthenticatorRequestData::Initial(init_message) => {
AuthenticatorRequest::new_initial_request(init_message, *mixnet_client.nym_address()) AuthenticatorRequest::new_initial_request(init_message)
} }
AuthenticatorRequestData::Final(final_message) => { AuthenticatorRequestData::Final(final_message) => {
AuthenticatorRequest::new_final_request(*final_message, *mixnet_client.nym_address()) AuthenticatorRequest::new_final_request(*final_message)
} }
AuthenticatorRequestData::QueryBandwidth(query_message) => { AuthenticatorRequestData::QueryBandwidth(query_message) => {
AuthenticatorRequest::new_query_request(query_message, *mixnet_client.nym_address()) AuthenticatorRequest::new_query_request(query_message)
} }
AuthenticatorRequestData::TopUpBandwidth(top_up_message) => { AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
AuthenticatorRequest::new_topup_request(*top_up_message, *mixnet_client.nym_address()) AuthenticatorRequest::new_topup_request(*top_up_message)
} }
}; };
mixnet_client mixnet_client
@@ -89,6 +89,9 @@ pub enum AuthenticatorError {
#[error("unknown version number")] #[error("unknown version number")]
UnknownVersion, UnknownVersion,
#[error("missing reply_to for old client")]
MissingReplyToForOldClient,
#[error("{0}")] #[error("{0}")]
PublicKey(#[from] nym_wireguard_types::Error), PublicKey(#[from] nym_wireguard_types::Error),
@@ -21,7 +21,7 @@ use nym_authenticator_requests::{
AuthenticatorRequest, AuthenticatorVersion, FinalMessage, InitMessage, AuthenticatorRequest, AuthenticatorVersion, FinalMessage, InitMessage,
QueryBandwidthMessage, TopUpMessage, QueryBandwidthMessage, TopUpMessage,
}, },
v1, v2, v3, v4, CURRENT_VERSION, v1, v2, v3, v4, v5, CURRENT_VERSION,
}; };
use nym_credential_verification::{ use nym_credential_verification::{
bandwidth_storage_manager::BandwidthStorageManager, ecash::EcashManager, bandwidth_storage_manager::BandwidthStorageManager, ecash::EcashManager,
@@ -42,7 +42,7 @@ use rand::{prelude::IteratorRandom, thread_rng};
use tokio::sync::RwLock; use tokio::sync::RwLock;
use tokio_stream::wrappers::IntervalStream; use tokio_stream::wrappers::IntervalStream;
type AuthenticatorHandleResult = Result<(Vec<u8>, Recipient)>; type AuthenticatorHandleResult = Result<(Vec<u8>, Option<Recipient>)>;
const DEFAULT_REGISTRATION_TIMEOUT_CHECK: Duration = Duration::from_secs(60); // 1 minute const DEFAULT_REGISTRATION_TIMEOUT_CHECK: Duration = Duration::from_secs(60); // 1 minute
pub(crate) struct RegistredAndFree { pub(crate) struct RegistredAndFree {
@@ -155,7 +155,7 @@ impl MixnetListener {
init_message: Box<dyn InitMessage + Send + Sync + 'static>, init_message: Box<dyn InitMessage + Send + Sync + 'static>,
protocol: Protocol, protocol: Protocol,
request_id: u64, request_id: u64,
reply_to: Recipient, reply_to: Option<Recipient>,
) -> AuthenticatorHandleResult { ) -> AuthenticatorHandleResult {
let remote_public = init_message.pub_key(); let remote_public = init_message.pub_key();
let nonce: u64 = fastrand::u64(..); let nonce: u64 = fastrand::u64(..);
@@ -178,7 +178,7 @@ impl MixnetListener {
wg_port: registration_data.wg_port, wg_port: registration_data.wg_port,
}, },
request_id, request_id,
reply_to, reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
) )
.to_bytes() .to_bytes()
.map_err(|err| { .map_err(|err| {
@@ -198,7 +198,7 @@ impl MixnetListener {
wg_port: registration_data.wg_port, wg_port: registration_data.wg_port,
}, },
request_id, request_id,
reply_to, reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
) )
.to_bytes() .to_bytes()
.map_err(|err| { .map_err(|err| {
@@ -218,7 +218,7 @@ impl MixnetListener {
wg_port: registration_data.wg_port, wg_port: registration_data.wg_port,
}, },
request_id, request_id,
reply_to, reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
) )
.to_bytes() .to_bytes()
.map_err(|err| { .map_err(|err| {
@@ -228,12 +228,26 @@ impl MixnetListener {
AuthenticatorVersion::V4 => { AuthenticatorVersion::V4 => {
v4::response::AuthenticatorResponse::new_pending_registration_success( v4::response::AuthenticatorResponse::new_pending_registration_success(
v4::registration::RegistrationData { v4::registration::RegistrationData {
nonce: registration_data.nonce,
gateway_data: registration_data.gateway_data.clone().into(),
wg_port: registration_data.wg_port,
},
request_id,
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
)
.to_bytes()
.map_err(|err| {
AuthenticatorError::FailedToSerializeResponsePacket { source: err }
})?
}
AuthenticatorVersion::V5 => {
v5::response::AuthenticatorResponse::new_pending_registration_success(
v5::registration::RegistrationData {
nonce: registration_data.nonce, nonce: registration_data.nonce,
gateway_data: registration_data.gateway_data.clone(), gateway_data: registration_data.gateway_data.clone(),
wg_port: registration_data.wg_port, wg_port: registration_data.wg_port,
}, },
request_id, request_id,
reply_to,
) )
.to_bytes() .to_bytes()
.map_err(|err| { .map_err(|err| {
@@ -272,7 +286,7 @@ impl MixnetListener {
private_ip: allowed_ipv4.into(), private_ip: allowed_ipv4.into(),
wg_port: self.config.authenticator.announced_port, wg_port: self.config.authenticator.announced_port,
}, },
reply_to, reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id, request_id,
) )
.to_bytes() .to_bytes()
@@ -285,7 +299,7 @@ impl MixnetListener {
private_ip: allowed_ipv4.into(), private_ip: allowed_ipv4.into(),
wg_port: self.config.authenticator.announced_port, wg_port: self.config.authenticator.announced_port,
}, },
reply_to, reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id, request_id,
) )
.to_bytes() .to_bytes()
@@ -298,7 +312,7 @@ impl MixnetListener {
private_ip: allowed_ipv4.into(), private_ip: allowed_ipv4.into(),
wg_port: self.config.authenticator.announced_port, wg_port: self.config.authenticator.announced_port,
}, },
reply_to, reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id, request_id,
) )
.to_bytes() .to_bytes()
@@ -311,7 +325,19 @@ impl MixnetListener {
private_ips: (allowed_ipv4, allowed_ipv6).into(), private_ips: (allowed_ipv4, allowed_ipv6).into(),
wg_port: self.config.authenticator.announced_port, wg_port: self.config.authenticator.announced_port,
}, },
reply_to, reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id,
)
.to_bytes()
.map_err(|err| {
AuthenticatorError::FailedToSerializeResponsePacket { source: err }
})?,
AuthenticatorVersion::V5 => v5::response::AuthenticatorResponse::new_registered(
v5::registration::RegistredData {
pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()),
private_ips: (allowed_ipv4, allowed_ipv6).into(),
wg_port: self.config.authenticator.announced_port,
},
request_id, request_id,
) )
.to_bytes() .to_bytes()
@@ -360,7 +386,7 @@ impl MixnetListener {
wg_port: registration_data.wg_port, wg_port: registration_data.wg_port,
}, },
request_id, request_id,
reply_to, reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
) )
.to_bytes() .to_bytes()
.map_err(|err| { .map_err(|err| {
@@ -380,7 +406,7 @@ impl MixnetListener {
wg_port: registration_data.wg_port, wg_port: registration_data.wg_port,
}, },
request_id, request_id,
reply_to, reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
) )
.to_bytes() .to_bytes()
.map_err(|err| { .map_err(|err| {
@@ -400,7 +426,7 @@ impl MixnetListener {
wg_port: registration_data.wg_port, wg_port: registration_data.wg_port,
}, },
request_id, request_id,
reply_to, reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
) )
.to_bytes() .to_bytes()
.map_err(|err| { .map_err(|err| {
@@ -410,12 +436,26 @@ impl MixnetListener {
AuthenticatorVersion::V4 => { AuthenticatorVersion::V4 => {
v4::response::AuthenticatorResponse::new_pending_registration_success( v4::response::AuthenticatorResponse::new_pending_registration_success(
v4::registration::RegistrationData { v4::registration::RegistrationData {
nonce: registration_data.nonce,
gateway_data: registration_data.gateway_data.into(),
wg_port: registration_data.wg_port,
},
request_id,
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
)
.to_bytes()
.map_err(|err| {
AuthenticatorError::FailedToSerializeResponsePacket { source: err }
})?
}
AuthenticatorVersion::V5 => {
v5::response::AuthenticatorResponse::new_pending_registration_success(
v5::registration::RegistrationData {
nonce: registration_data.nonce, nonce: registration_data.nonce,
gateway_data: registration_data.gateway_data, gateway_data: registration_data.gateway_data,
wg_port: registration_data.wg_port, wg_port: registration_data.wg_port,
}, },
request_id, request_id,
reply_to,
) )
.to_bytes() .to_bytes()
.map_err(|err| { .map_err(|err| {
@@ -433,7 +473,7 @@ impl MixnetListener {
final_message: Box<dyn FinalMessage + Send + Sync + 'static>, final_message: Box<dyn FinalMessage + Send + Sync + 'static>,
protocol: Protocol, protocol: Protocol,
request_id: u64, request_id: u64,
reply_to: Recipient, reply_to: Option<Recipient>,
) -> AuthenticatorHandleResult { ) -> AuthenticatorHandleResult {
let mut registred_and_free = self.registred_and_free.write().await; let mut registred_and_free = self.registred_and_free.write().await;
let registration_data = registred_and_free let registration_data = registred_and_free
@@ -500,7 +540,7 @@ impl MixnetListener {
private_ip: registration_data.gateway_data.private_ips.ipv4.into(), private_ip: registration_data.gateway_data.private_ips.ipv4.into(),
wg_port: registration_data.wg_port, wg_port: registration_data.wg_port,
}, },
reply_to, reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id, request_id,
) )
.to_bytes() .to_bytes()
@@ -511,7 +551,7 @@ impl MixnetListener {
private_ip: registration_data.gateway_data.private_ips.ipv4.into(), private_ip: registration_data.gateway_data.private_ips.ipv4.into(),
wg_port: registration_data.wg_port, wg_port: registration_data.wg_port,
}, },
reply_to, reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id, request_id,
) )
.to_bytes() .to_bytes()
@@ -522,18 +562,28 @@ impl MixnetListener {
private_ip: registration_data.gateway_data.private_ips.ipv4.into(), private_ip: registration_data.gateway_data.private_ips.ipv4.into(),
wg_port: registration_data.wg_port, wg_port: registration_data.wg_port,
}, },
reply_to, reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id, request_id,
) )
.to_bytes() .to_bytes()
.map_err(|err| AuthenticatorError::FailedToSerializeResponsePacket { source: err })?, .map_err(|err| AuthenticatorError::FailedToSerializeResponsePacket { source: err })?,
AuthenticatorVersion::V4 => v4::response::AuthenticatorResponse::new_registered( AuthenticatorVersion::V4 => v4::response::AuthenticatorResponse::new_registered(
v4::registration::RegistredData { v4::registration::RegistredData {
pub_key: registration_data.gateway_data.pub_key,
private_ips: registration_data.gateway_data.private_ips.into(),
wg_port: registration_data.wg_port,
},
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id,
)
.to_bytes()
.map_err(|err| AuthenticatorError::FailedToSerializeResponsePacket { source: err })?,
AuthenticatorVersion::V5 => v5::response::AuthenticatorResponse::new_registered(
v5::registration::RegistredData {
pub_key: registration_data.gateway_data.pub_key, pub_key: registration_data.gateway_data.pub_key,
private_ips: registration_data.gateway_data.private_ips, private_ips: registration_data.gateway_data.private_ips,
wg_port: registration_data.wg_port, wg_port: registration_data.wg_port,
}, },
reply_to,
request_id, request_id,
) )
.to_bytes() .to_bytes()
@@ -579,7 +629,7 @@ impl MixnetListener {
msg: Box<dyn QueryBandwidthMessage + Send + Sync + 'static>, msg: Box<dyn QueryBandwidthMessage + Send + Sync + 'static>,
protocol: Protocol, protocol: Protocol,
request_id: u64, request_id: u64,
reply_to: Recipient, reply_to: Option<Recipient>,
) -> AuthenticatorHandleResult { ) -> AuthenticatorHandleResult {
let bandwidth_data = self.peer_manager.query_bandwidth(msg).await?; let bandwidth_data = self.peer_manager.query_bandwidth(msg).await?;
let bytes = match AuthenticatorVersion::from(protocol) { let bytes = match AuthenticatorVersion::from(protocol) {
@@ -589,7 +639,7 @@ impl MixnetListener {
available_bandwidth: data.available_bandwidth as u64, available_bandwidth: data.available_bandwidth as u64,
suspended: false, suspended: false,
}), }),
reply_to, reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id, request_id,
) )
.to_bytes() .to_bytes()
@@ -602,7 +652,7 @@ impl MixnetListener {
bandwidth_data.map(|data| v2::registration::RemainingBandwidthData { bandwidth_data.map(|data| v2::registration::RemainingBandwidthData {
available_bandwidth: data.available_bandwidth, available_bandwidth: data.available_bandwidth,
}), }),
reply_to, reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id, request_id,
) )
.to_bytes() .to_bytes()
@@ -615,7 +665,7 @@ impl MixnetListener {
bandwidth_data.map(|data| v3::registration::RemainingBandwidthData { bandwidth_data.map(|data| v3::registration::RemainingBandwidthData {
available_bandwidth: data.available_bandwidth, available_bandwidth: data.available_bandwidth,
}), }),
reply_to, reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id, request_id,
) )
.to_bytes() .to_bytes()
@@ -628,7 +678,19 @@ impl MixnetListener {
bandwidth_data.map(|data| v4::registration::RemainingBandwidthData { bandwidth_data.map(|data| v4::registration::RemainingBandwidthData {
available_bandwidth: data.available_bandwidth, available_bandwidth: data.available_bandwidth,
}), }),
reply_to, reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id,
)
.to_bytes()
.map_err(|err| {
AuthenticatorError::FailedToSerializeResponsePacket { source: err }
})?
}
AuthenticatorVersion::V5 => {
v5::response::AuthenticatorResponse::new_remaining_bandwidth(
bandwidth_data.map(|data| v5::registration::RemainingBandwidthData {
available_bandwidth: data.available_bandwidth,
}),
request_id, request_id,
) )
.to_bytes() .to_bytes()
@@ -646,7 +708,7 @@ impl MixnetListener {
msg: Box<dyn TopUpMessage + Send + Sync + 'static>, msg: Box<dyn TopUpMessage + Send + Sync + 'static>,
protocol: Protocol, protocol: Protocol,
request_id: u64, request_id: u64,
reply_to: Recipient, reply_to: Option<Recipient>,
) -> AuthenticatorHandleResult { ) -> AuthenticatorHandleResult {
let Some(ecash_verifier) = self.ecash_verifier.clone() else { let Some(ecash_verifier) = self.ecash_verifier.clone() else {
return Err(AuthenticatorError::UnsupportedOperation); return Err(AuthenticatorError::UnsupportedOperation);
@@ -681,11 +743,19 @@ impl MixnetListener {
let available_bandwidth = verifier.verify().await?; let available_bandwidth = verifier.verify().await?;
let bytes = match AuthenticatorVersion::from(protocol) { let bytes = match AuthenticatorVersion::from(protocol) {
AuthenticatorVersion::V5 => v5::response::AuthenticatorResponse::new_topup_bandwidth(
v5::registration::RemainingBandwidthData {
available_bandwidth,
},
request_id,
)
.to_bytes()
.map_err(|err| AuthenticatorError::FailedToSerializeResponsePacket { source: err })?,
AuthenticatorVersion::V4 => v4::response::AuthenticatorResponse::new_topup_bandwidth( AuthenticatorVersion::V4 => v4::response::AuthenticatorResponse::new_topup_bandwidth(
v4::registration::RemainingBandwidthData { v4::registration::RemainingBandwidthData {
available_bandwidth, available_bandwidth,
}, },
reply_to, reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id, request_id,
) )
.to_bytes() .to_bytes()
@@ -694,7 +764,7 @@ impl MixnetListener {
v3::registration::RemainingBandwidthData { v3::registration::RemainingBandwidthData {
available_bandwidth, available_bandwidth,
}, },
reply_to, reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id, request_id,
) )
.to_bytes() .to_bytes()
@@ -762,10 +832,10 @@ impl MixnetListener {
async fn handle_response( async fn handle_response(
&self, &self,
response: Vec<u8>, response: Vec<u8>,
recipient: Recipient, recipient: Option<Recipient>,
sender_tag: Option<AnonymousSenderTag>, sender_tag: Option<AnonymousSenderTag>,
) -> Result<()> { ) -> Result<()> {
let input_message = create_input_message(recipient, sender_tag, response); let input_message = create_input_message(recipient, sender_tag, response)?;
self.mixnet_client self.mixnet_client
.send(input_message) .send(input_message)
.await .await
@@ -858,6 +928,17 @@ fn deserialize_request(reconstructed: &ReconstructedMessage) -> Result<Authentic
Err(AuthenticatorError::InvalidPacketType(request_type)) Err(AuthenticatorError::InvalidPacketType(request_type))
} }
} }
[5, request_type] => {
if request_type == ServiceProviderType::Authenticator as u8 {
v5::request::AuthenticatorRequest::from_reconstructed_message(reconstructed)
.map_err(|err| AuthenticatorError::FailedToDeserializeTaggedPacket {
source: err,
})
.map(Into::into)
} else {
Err(AuthenticatorError::InvalidPacketType(request_type))
}
}
[version, _] => { [version, _] => {
log::info!("Received packet with invalid version: v{version}"); log::info!("Received packet with invalid version: v{version}");
Err(AuthenticatorError::InvalidPacketVersion(version)) Err(AuthenticatorError::InvalidPacketVersion(version))
@@ -866,17 +947,30 @@ fn deserialize_request(reconstructed: &ReconstructedMessage) -> Result<Authentic
} }
fn create_input_message( fn create_input_message(
nym_address: Recipient, nym_address: Option<Recipient>,
reply_to_tag: Option<AnonymousSenderTag>, reply_to_tag: Option<AnonymousSenderTag>,
response_packet: Vec<u8>, response_packet: Vec<u8>,
) -> InputMessage { ) -> Result<InputMessage> {
let lane = TransmissionLane::General; let lane = TransmissionLane::General;
let packet_type = None; let packet_type = None;
if let Some(reply_to_tag) = reply_to_tag { if let Some(reply_to_tag) = reply_to_tag {
log::debug!("Creating message using SURB"); log::debug!("Creating message using SURB");
InputMessage::new_reply(reply_to_tag, response_packet, lane, packet_type) Ok(InputMessage::new_reply(
} else { reply_to_tag,
response_packet,
lane,
packet_type,
))
} else if let Some(nym_address) = nym_address {
log::debug!("Creating message using nym_address"); log::debug!("Creating message using nym_address");
InputMessage::new_regular(nym_address, response_packet, lane, packet_type) Ok(InputMessage::new_regular(
nym_address,
response_packet,
lane,
packet_type,
))
} else {
log::error!("No nym-address or sender tag provided");
Err(AuthenticatorError::MissingReplyToForOldClient)
} }
} }
@@ -7,7 +7,7 @@ use nym_sdk::mixnet::{AnonymousSenderTag, Recipient};
use crate::error::{IpPacketRouterError, Result}; use crate::error::{IpPacketRouterError, Result};
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum ConnectedClientId { pub(crate) enum ConnectedClientId {
AnonymousSenderTag(AnonymousSenderTag), AnonymousSenderTag(AnonymousSenderTag),
NymAddress(Box<Recipient>), NymAddress(Box<Recipient>),
@@ -59,23 +59,42 @@ impl ConnectedClients {
.any(|client| client.client_id == *client_id) .any(|client| client.client_id == *client_id)
} }
//fn lookup_ip_from_client_id(&self, client_id: &ConnectedClientId) -> Option<IpPair> { pub(crate) fn disconnect_client(&mut self, client_id: &ConnectedClientId) {
// self.clients_ipv4_mapping if let Some(ips) = self.lookup_ip_from_client_id(client_id) {
// .iter() log::debug!("Disconnect client that requested to do so: {ips}");
// .find_map(|(ipv4, connected_client)| { self.disconnect_client_handle(ips);
// if connected_client.client_id == *client_id { }
// Some(IpPair::new(*ipv4, connected_client.ipv6)) }
// } else {
// None fn disconnect_client_handle(&mut self, ips: IpPair) {
// } self.clients_ipv4_mapping.remove(&ips.ipv4);
// }) self.clients_ipv6_mapping.remove(&ips.ipv6);
//} self.tun_listener_connected_client_tx
// .send(ConnectedClientEvent::Disconnect(DisconnectEvent(ips)))
//fn lookup_client(&self, client_id: &ConnectedClientId) -> Option<&ConnectedClient> { .inspect_err(|err| {
// self.clients_ipv4_mapping log::error!("Failed to send disconnect event: {err}");
// .values() })
// .find(|connected_client| connected_client.client_id == *client_id) .ok();
//} }
pub(crate) fn lookup_ip_from_client_id(&self, client_id: &ConnectedClientId) -> Option<IpPair> {
self.clients_ipv4_mapping
.iter()
.find_map(|(ipv4, connected_client)| {
if connected_client.client_id == *client_id {
Some(IpPair::new(*ipv4, connected_client.ipv6))
} else {
None
}
})
}
#[allow(unused)]
fn lookup_client(&self, client_id: &ConnectedClientId) -> Option<&ConnectedClient> {
self.clients_ipv4_mapping
.values()
.find(|connected_client| connected_client.client_id == *client_id)
}
pub(crate) fn connect( pub(crate) fn connect(
&mut self, &mut self,
@@ -163,14 +182,7 @@ impl ConnectedClients {
) { ) {
for (ips, _) in &stopped_clients { for (ips, _) in &stopped_clients {
log::info!("Disconnect stopped client: {ips}"); log::info!("Disconnect stopped client: {ips}");
self.clients_ipv4_mapping.remove(&ips.ipv4); self.disconnect_client_handle(*ips);
self.clients_ipv6_mapping.remove(&ips.ipv6);
self.tun_listener_connected_client_tx
.send(ConnectedClientEvent::Disconnect(DisconnectEvent(*ips)))
.inspect_err(|err| {
log::error!("Failed to send disconnect event: {err}");
})
.ok();
} }
} }
@@ -180,14 +192,7 @@ impl ConnectedClients {
) { ) {
for (ips, _) in &inactive_clients { for (ips, _) in &inactive_clients {
log::info!("Disconnect inactive client: {ips}"); log::info!("Disconnect inactive client: {ips}");
self.clients_ipv4_mapping.remove(&ips.ipv4); self.disconnect_client_handle(*ips);
self.clients_ipv6_mapping.remove(&ips.ipv6);
self.tun_listener_connected_client_tx
.send(ConnectedClientEvent::Disconnect(DisconnectEvent(*ips)))
.inspect_err(|err| {
log::error!("Failed to send disconnect event: {err}");
})
.ok();
} }
} }
@@ -7,8 +7,8 @@ use std::time::Duration;
pub(crate) const DISCONNECT_TIMER_INTERVAL: Duration = Duration::from_secs(10); pub(crate) const DISCONNECT_TIMER_INTERVAL: Duration = Duration::from_secs(10);
// We consider a client inactive if it hasn't sent any mixnet packets in this duration // We consider a client inactive if it hasn't sent any mixnet packets in this duration
pub(crate) const CLIENT_MIXNET_INACTIVITY_TIMEOUT: Duration = Duration::from_secs(5 * 60); pub(crate) const CLIENT_MIXNET_INACTIVITY_TIMEOUT: Duration = Duration::from_secs(60);
// We consider a client handler inactive if it hasn't received any packets from the tun device in // We consider a client handler inactive if it hasn't received any packets from the tun device in
// this duration // this duration
pub(crate) const CLIENT_HANDLER_ACTIVITY_TIMEOUT: Duration = Duration::from_secs(10 * 60); pub(crate) const CLIENT_HANDLER_ACTIVITY_TIMEOUT: Duration = Duration::from_secs(5 * 60);
@@ -90,9 +90,6 @@ pub(crate) struct DynamicConnectSuccess {
#[derive(Clone, Debug, thiserror::Error)] #[derive(Clone, Debug, thiserror::Error)]
pub(crate) enum DynamicConnectFailureReason { pub(crate) enum DynamicConnectFailureReason {
#[error("client already connected")]
ClientAlreadyConnected,
#[error("no available ip address")] #[error("no available ip address")]
NoAvailableIp, NoAvailableIp,
@@ -119,9 +119,6 @@ impl From<DynamicConnectResponse> for DynamicConnectResponseReplyV6 {
impl From<DynamicConnectFailureReason> for DynamicConnectFailureReasonV6 { impl From<DynamicConnectFailureReason> for DynamicConnectFailureReasonV6 {
fn from(reason: DynamicConnectFailureReason) -> Self { fn from(reason: DynamicConnectFailureReason) -> Self {
match reason { match reason {
DynamicConnectFailureReason::ClientAlreadyConnected => {
DynamicConnectFailureReasonV6::RequestedNymAddressAlreadyInUse
}
DynamicConnectFailureReason::NoAvailableIp => { DynamicConnectFailureReason::NoAvailableIp => {
DynamicConnectFailureReasonV6::NoAvailableIp DynamicConnectFailureReasonV6::NoAvailableIp
} }
@@ -119,9 +119,6 @@ impl From<DynamicConnectResponse> for DynamicConnectResponseReplyV7 {
impl From<DynamicConnectFailureReason> for DynamicConnectFailureReasonV7 { impl From<DynamicConnectFailureReason> for DynamicConnectFailureReasonV7 {
fn from(reason: DynamicConnectFailureReason) -> Self { fn from(reason: DynamicConnectFailureReason) -> Self {
match reason { match reason {
DynamicConnectFailureReason::ClientAlreadyConnected => {
DynamicConnectFailureReasonV7::RequestedNymAddressAlreadyInUse
}
DynamicConnectFailureReason::NoAvailableIp => { DynamicConnectFailureReason::NoAvailableIp => {
DynamicConnectFailureReasonV7::NoAvailableIp DynamicConnectFailureReasonV7::NoAvailableIp
} }
@@ -82,9 +82,6 @@ impl From<DynamicConnectResponse> for ConnectResponseReplyV8 {
impl From<DynamicConnectFailureReason> for ConnectFailureReasonV8 { impl From<DynamicConnectFailureReason> for ConnectFailureReasonV8 {
fn from(reason: DynamicConnectFailureReason) -> Self { fn from(reason: DynamicConnectFailureReason) -> Self {
match reason { match reason {
DynamicConnectFailureReason::ClientAlreadyConnected => {
ConnectFailureReasonV8::ClientAlreadyConnected
}
DynamicConnectFailureReason::NoAvailableIp => ConnectFailureReasonV8::NoAvailableIp, DynamicConnectFailureReason::NoAvailableIp => ConnectFailureReasonV8::NoAvailableIp,
DynamicConnectFailureReason::Other(err) => ConnectFailureReasonV8::Other(err), DynamicConnectFailureReason::Other(err) => ConnectFailureReasonV8::Other(err),
} }
@@ -22,9 +22,9 @@ use crate::{
IpPacketRequest, PingRequest, StaticConnectRequest, IpPacketRequest, PingRequest, StaticConnectRequest,
}, },
response::{ response::{
DynamicConnectFailureReason, DynamicConnectSuccess, HealthResponse, InfoLevel, DisconnectFailureReason, DisconnectResponse, DynamicConnectFailureReason,
InfoResponse, InfoResponseReply, Response, StaticConnectFailureReason, DynamicConnectSuccess, HealthResponse, InfoLevel, InfoResponse, InfoResponseReply,
StaticConnectResponse, VersionedResponse, Response, StaticConnectFailureReason, StaticConnectResponse, VersionedResponse,
}, },
ClientVersion, ClientVersion,
}, },
@@ -225,7 +225,7 @@ impl MixnetListener {
})) }))
} }
async fn on_dynamic_connect_request( fn on_dynamic_connect_request(
&mut self, &mut self,
connect_request: DynamicConnectRequest, connect_request: DynamicConnectRequest,
) -> PacketHandleResult { ) -> PacketHandleResult {
@@ -242,13 +242,14 @@ impl MixnetListener {
.map(Duration::from_millis) .map(Duration::from_millis)
.unwrap_or(nym_ip_packet_requests::codec::BUFFER_TIMEOUT); .unwrap_or(nym_ip_packet_requests::codec::BUFFER_TIMEOUT);
if self.connected_clients.is_client_connected(&reply_to) { if let Some(ips) = self.connected_clients.lookup_ip_from_client_id(&reply_to) {
log::debug!("Reconnecting to the previous session");
return Ok(Some(VersionedResponse { return Ok(Some(VersionedResponse {
version, version,
reply_to, reply_to,
response: Response::DynamicConnect { response: Response::DynamicConnect {
request_id, request_id,
reply: DynamicConnectFailureReason::ClientAlreadyConnected.into(), reply: DynamicConnectSuccess { ips }.into(),
}, },
})); }));
} }
@@ -292,12 +293,47 @@ impl MixnetListener {
})) }))
} }
fn on_disconnect_request(&self, _disconnect_request: DisconnectRequest) -> PacketHandleResult { fn on_disconnect_request(
log::info!("Received disconnect request: not implemented, dropping"); &mut self,
Ok(None) disconnect_request: DisconnectRequest,
) -> PacketHandleResult {
log::info!(
"Received disconnect request from {}",
disconnect_request.sent_by
);
let version = disconnect_request.version;
let request_id = disconnect_request.request_id;
let client_id = disconnect_request.sent_by;
// Check if the client is connected
if !self.connected_clients.is_client_connected(&client_id) {
log::info!("Client {} is not connected, cannot disconnect", client_id);
return Ok(Some(VersionedResponse {
version,
reply_to: client_id,
response: Response::Disconnect {
request_id,
reply: DisconnectResponse::Failure(DisconnectFailureReason::ClientNotConnected),
},
}));
}
// Disconnect the client
log::info!("Disconnecting client {}", client_id);
self.connected_clients.disconnect_client(&client_id);
Ok(Some(VersionedResponse {
version,
reply_to: client_id,
response: Response::Disconnect {
request_id,
reply: DisconnectResponse::Success,
},
}))
} }
async fn on_ping_request(&self, ping_request: PingRequest) -> PacketHandleResult { fn on_ping_request(&self, ping_request: PingRequest) -> PacketHandleResult {
Ok(Some(VersionedResponse { Ok(Some(VersionedResponse {
version: ping_request.version, version: ping_request.version,
reply_to: ping_request.sent_by, reply_to: ping_request.sent_by,
@@ -307,7 +343,7 @@ impl MixnetListener {
})) }))
} }
async fn on_health_request(&self, health_request: HealthRequest) -> PacketHandleResult { fn on_health_request(&self, health_request: HealthRequest) -> PacketHandleResult {
Ok(Some(VersionedResponse { Ok(Some(VersionedResponse {
version: health_request.version, version: health_request.version,
reply_to: health_request.sent_by, reply_to: health_request.sent_by,
@@ -324,10 +360,10 @@ impl MixnetListener {
async fn on_control_request(&mut self, control_request: ControlRequest) -> PacketHandleResult { async fn on_control_request(&mut self, control_request: ControlRequest) -> PacketHandleResult {
match control_request { match control_request {
ControlRequest::StaticConnect(r) => self.on_static_connect_request(r).await, ControlRequest::StaticConnect(r) => self.on_static_connect_request(r).await,
ControlRequest::DynamicConnect(r) => self.on_dynamic_connect_request(r).await, ControlRequest::DynamicConnect(r) => self.on_dynamic_connect_request(r),
ControlRequest::Disconnect(r) => self.on_disconnect_request(r), ControlRequest::Disconnect(r) => self.on_disconnect_request(r),
ControlRequest::Ping(r) => self.on_ping_request(r).await, ControlRequest::Ping(r) => self.on_ping_request(r),
ControlRequest::Health(r) => self.on_health_request(r).await, ControlRequest::Health(r) => self.on_health_request(r),
} }
} }
@@ -103,7 +103,7 @@ impl TunListener {
.update(ConnectedClientEvent::Disconnect(DisconnectEvent(*ips))); .update(ConnectedClientEvent::Disconnect(DisconnectEvent(*ips)));
} }
} else { } else {
log::info!( log::debug!(
"dropping packet from network: no registered client for destination: {dst_addr}" "dropping packet from network: no registered client for destination: {dst_addr}"
); );
} }
@@ -4,7 +4,7 @@
[package] [package]
name = "nym-network-requester" name = "nym-network-requester"
license = "GPL-3.0" license = "GPL-3.0"
version = "1.1.50" version = "1.1.51"
authors.workspace = true authors.workspace = true
edition.workspace = true edition.workspace = true
rust-version = "1.70" rust-version = "1.70"
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "nym-cli" name = "nym-cli"
version = "1.1.49" version = "1.1.50"
authors.workspace = true authors.workspace = true
edition = "2021" edition = "2021"
license.workspace = true license.workspace = true
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "nymvisor" name = "nymvisor"
version = "0.1.14" version = "0.1.15"
authors.workspace = true authors.workspace = true
repository.workspace = true repository.workspace = true
homepage.workspace = true homepage.workspace = true