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]
## [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)
- fixed sphinx version metrics registration ([#5546])
Generated
+13 -8
View File
@@ -2433,7 +2433,7 @@ dependencies = [
[[package]]
name = "explorer-api"
version = "1.1.47"
version = "1.1.48"
dependencies = [
"chrono",
"clap",
@@ -4780,7 +4780,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]]
name = "nym-api"
version = "1.1.51"
version = "1.1.53"
dependencies = [
"anyhow",
"async-trait",
@@ -5029,7 +5029,7 @@ dependencies = [
[[package]]
name = "nym-cli"
version = "1.1.49"
version = "1.1.50"
dependencies = [
"anyhow",
"base64 0.22.1",
@@ -5112,7 +5112,7 @@ dependencies = [
[[package]]
name = "nym-client"
version = "1.1.49"
version = "1.1.50"
dependencies = [
"bs58",
"clap",
@@ -5874,8 +5874,11 @@ name = "nym-http-api-client"
version = "0.1.0"
dependencies = [
"async-trait",
"bytes",
"encoding_rs",
"hickory-resolver",
"http 1.2.0",
"mime",
"nym-bin-common",
"once_cell",
"reqwest 0.12.4",
@@ -6153,7 +6156,7 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.1.50"
version = "1.1.51"
dependencies = [
"addr",
"anyhow",
@@ -6204,7 +6207,7 @@ dependencies = [
[[package]]
name = "nym-node"
version = "1.6.0"
version = "1.6.1"
dependencies = [
"anyhow",
"arc-swap",
@@ -6489,6 +6492,7 @@ name = "nym-pemstore"
version = "0.3.0"
dependencies = [
"pem",
"tracing",
]
[[package]]
@@ -6536,6 +6540,7 @@ dependencies = [
"reqwest 0.12.4",
"serde",
"tap",
"tempfile",
"thiserror 2.0.12",
"tokio",
"tokio-stream",
@@ -6587,7 +6592,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.1.49"
version = "1.1.50"
dependencies = [
"bs58",
"clap",
@@ -7192,7 +7197,7 @@ dependencies = [
[[package]]
name = "nymvisor"
version = "0.1.14"
version = "0.1.15"
dependencies = [
"anyhow",
"bytes",
+1
View File
@@ -241,6 +241,7 @@ doc-comment = "0.3"
dotenvy = "0.15.6"
ecdsa = "0.16"
ed25519-dalek = "2.1"
encoding_rs = "0.8.35"
env_logger = "0.11.6"
envy = "0.4"
etherparse = "0.13.0"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
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>"]
description = "Implementation of the Nym Client"
edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.49"
version = "1.1.50"
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"
edition = "2021"
+3 -2
View File
@@ -6,14 +6,15 @@ pub mod v1;
pub mod v2;
pub mod v3;
pub mod v4;
pub mod v5;
mod error;
mod util;
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 {
use bincode::Options;
+92 -20
View File
@@ -8,8 +8,8 @@ use nym_sphinx::addressing::clients::Recipient;
use nym_wireguard_types::PeerPublicKey;
use crate::{
v1, v2, v3,
v4::{self, registration::IpPair},
v1, v2, v3, v4,
v5::{self, registration::IpPair},
Error,
};
@@ -19,6 +19,7 @@ pub enum AuthenticatorVersion {
V2,
V3,
V4,
V5,
UNKNOWN,
}
@@ -34,6 +35,8 @@ impl From<Protocol> for AuthenticatorVersion {
AuthenticatorVersion::V3
} else if value.version == v4::VERSION {
AuthenticatorVersion::V4
} else if value.version == v5::VERSION {
AuthenticatorVersion::V5
} else {
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 {
fn pub_key(&self) -> PeerPublicKey;
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)
}
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 {
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 {
Initial {
msg: Box<dyn InitMessage + Send + Sync + 'static>,
protocol: Protocol,
reply_to: Recipient,
reply_to: Option<Recipient>,
request_id: u64,
},
Final {
msg: Box<dyn FinalMessage + Send + Sync + 'static>,
protocol: Protocol,
reply_to: Recipient,
reply_to: Option<Recipient>,
request_id: u64,
},
QueryBandwidth {
msg: Box<dyn QueryBandwidthMessage + Send + Sync + 'static>,
protocol: Protocol,
reply_to: Recipient,
reply_to: Option<Recipient>,
request_id: u64,
},
TopUpBandwidth {
msg: Box<dyn TopUpMessage + Send + Sync + 'static>,
protocol: Protocol,
reply_to: Recipient,
reply_to: Option<Recipient>,
request_id: u64,
},
}
@@ -218,7 +255,7 @@ impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
version: value.version,
service_provider_type: ServiceProviderType::Authenticator,
},
reply_to: value.reply_to,
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v1::request::AuthenticatorRequestData::Final(gateway_client) => Self::Final {
@@ -227,7 +264,7 @@ impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
version: value.version,
service_provider_type: ServiceProviderType::Authenticator,
},
reply_to: value.reply_to,
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v1::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
@@ -237,7 +274,7 @@ impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
version: value.version,
service_provider_type: ServiceProviderType::Authenticator,
},
reply_to: value.reply_to,
reply_to: Some(value.reply_to),
request_id: value.request_id,
}
}
@@ -251,20 +288,20 @@ impl From<v2::request::AuthenticatorRequest> for AuthenticatorRequest {
v2::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: value.protocol,
reply_to: value.reply_to,
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v2::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message,
protocol: value.protocol,
reply_to: value.reply_to,
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v2::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: value.protocol,
reply_to: value.reply_to,
reply_to: Some(value.reply_to),
request_id: value.request_id,
}
}
@@ -278,20 +315,20 @@ impl From<v3::request::AuthenticatorRequest> for AuthenticatorRequest {
v3::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: value.protocol,
reply_to: value.reply_to,
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v3::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message,
protocol: value.protocol,
reply_to: value.reply_to,
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v3::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: value.protocol,
reply_to: value.reply_to,
reply_to: Some(value.reply_to),
request_id: value.request_id,
}
}
@@ -299,7 +336,7 @@ impl From<v3::request::AuthenticatorRequest> for AuthenticatorRequest {
Self::TopUpBandwidth {
msg: top_up_message,
protocol: value.protocol,
reply_to: value.reply_to,
reply_to: Some(value.reply_to),
request_id: value.request_id,
}
}
@@ -313,20 +350,20 @@ impl From<v4::request::AuthenticatorRequest> for AuthenticatorRequest {
v4::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: value.protocol,
reply_to: value.reply_to,
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v4::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message,
protocol: value.protocol,
reply_to: value.reply_to,
reply_to: Some(value.reply_to),
request_id: value.request_id,
},
v4::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: value.protocol,
reply_to: value.reply_to,
reply_to: Some(value.reply_to),
request_id: value.request_id,
}
}
@@ -334,7 +371,42 @@ impl From<v4::request::AuthenticatorRequest> for AuthenticatorRequest {
Self::TopUpBandwidth {
msg: top_up_message,
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,
}
}
@@ -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
// clients/client-core/src/client/replies/reply_controller.rs
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;
@@ -33,10 +33,12 @@ pub enum PreparationError {
#[error(transparent)]
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 },
#[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 },
}
@@ -746,7 +746,7 @@ where
.request_additional_reply_surbs(target, request_size)
.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 }
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" }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
@@ -32,4 +38,4 @@ workspace = true
features = ["tokio"]
[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 url::Url;
use http::HeaderMap;
pub use reqwest::IntoUrl;
#[cfg(not(target_arch = "wasm32"))]
use std::net::SocketAddr;
#[cfg(not(target_arch = "wasm32"))]
use std::sync::Arc;
pub use reqwest::IntoUrl;
mod user_agent;
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}")]
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")]
#[error("the request has timed out")]
RequestTimeout,
@@ -849,6 +855,26 @@ fn sanitize_url<K: AsRef<str>, V: AsRef<str>>(
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
#[instrument(level = "debug", skip_all)]
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 });
}
}
let headers = res.headers().clone();
tracing::trace!("headers: {:?}", headers);
if res.status().is_success() {
#[cfg(debug_assertions)]
{
let text = res.text().await.inspect_err(|err| {
tracing::error!("Couldn't even get response text: {err}");
})?;
tracing::trace!("Result:\n{:#?}", text);
serde_json::from_str(&text)
.map_err(|err| HttpClientError::GenericRequestFailure(err.to_string()))
// internally reqwest is first retrieving bytes and then performing parsing via serde_json
// (and similarly does the same thing for text())
let full = res.bytes().await?;
match serde_json::from_slice(&full) {
Ok(data) => Ok(data),
Err(err) => {
let content = decode_as_text(&full, headers);
Err(HttpClientError::ResponseDecodeFailure {
source: err,
content,
})
}
}
#[cfg(not(debug_assertions))]
Ok(res.json().await?)
} else if res.status() == StatusCode::NOT_FOUND {
Err(HttpClientError::NotFound)
} else {
+1
View File
@@ -9,3 +9,4 @@ repository = { workspace = true }
[dependencies]
pem = { workspace = true }
tracing = { workspace = true }
+5
View File
@@ -6,6 +6,7 @@ use pem::Pem;
use std::fs::File;
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use tracing::debug;
pub mod traits;
@@ -46,6 +47,10 @@ where
T: PemStorableKey,
P: AsRef<Path>,
{
debug!(
"attempting to load key with the following pem type: {}",
T::pem_type()
);
let key_pem = read_pem_file(path)?;
if T::pem_type() != key_pem.tag {
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "explorer-api"
version = "1.1.47"
version = "1.1.48"
edition = "2021"
license.workspace = true
@@ -426,6 +426,11 @@ impl<R, S> FreshHandler<R, S> {
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
// (perhaps we could try to negotiate downgrade on our end? sounds like a nice future improvement)
if client_protocol_version <= CURRENT_PROTOCOL_VERSION {
+2 -2
View File
@@ -4,7 +4,7 @@
[package]
name = "nym-api"
license = "GPL-3.0"
version = "1.1.51"
version = "1.1.53"
authors.workspace = true
edition = "2021"
rust-version.workspace = true
@@ -144,4 +144,4 @@ rand_chacha = { workspace = true }
sha2 = "0.9"
[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 std::path::Path;
use thiserror::__private::AsDisplay;
use tracing::warn;
use tracing::{debug, warn};
pub(crate) fn init_bte_keypair<R: RngCore + CryptoRng>(
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(
config: &config::EcashSigner,
) -> 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() {
debug!("the provided filepath doesn't exist - the key won't be loaded");
return Ok(None);
}
if let Ok(ecash_key) =
let ecash_key =
nym_pemstore::load_key::<KeyPairWithEpoch, _>(&config.storage_paths.ecash_key_path)
{
return Ok(Some(ecash_key));
}
bail!("ecash key load failure")
.context("failed to load ecash key")?;
Ok(Some(ecash_key))
}
// 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()
.map(|l| (l.merkle_index, l))
.collect();
let total_leaves = leaves.len();
let mut sorted_leaves = Vec::new();
for i in 0..leaves.len() {
if let Some(next_leaf) = leaves.remove(&i) {
sorted_leaves.push(next_leaf);
} else {
let lost = leaves.len() - i + 1;
error!("failed to produce consistent merkle tree. there was no leaf with index {i}. at least {lost} leaves got lost")
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");
// 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]
name = "nym-node"
version = "1.6.0"
version = "1.6.1"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
@@ -101,4 +101,4 @@ cargo_metadata = { workspace = true }
[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.
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.
pub maximum_auth_request_age: Duration,
@@ -65,10 +61,9 @@ pub struct Debug {
}
impl Debug {
pub const DEFAULT_MAXIMUM_OPEN_CONNECTIONS: usize = 8192;
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);
const DEFAULT_MAXIMUM_OPEN_CONNECTIONS: usize = 8192;
}
impl Default for Debug {
@@ -77,7 +72,6 @@ impl Default for Debug {
message_retrieval_limit: Self::DEFAULT_MESSAGE_RETRIEVAL_LIMIT,
maximum_open_connections: Self::DEFAULT_MAXIMUM_OPEN_CONNECTIONS,
maximum_auth_request_age: Self::DEFAULT_MAXIMUM_AUTH_REQUEST_AGE,
minimum_mix_performance: Self::DEFAULT_MINIMUM_MIX_PERFORMANCE,
stale_messages: Default::default(),
client_bandwidth: Default::default(),
zk_nym_tickets: Default::default(),
+7 -4
View File
@@ -465,9 +465,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.5.0"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
dependencies = [
"serde",
]
@@ -1487,9 +1487,9 @@ checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
[[package]]
name = "encoding_rs"
version = "0.8.32"
version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394"
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
"cfg-if",
]
@@ -3463,8 +3463,11 @@ name = "nym-http-api-client"
version = "0.1.0"
dependencies = [
"async-trait",
"bytes",
"encoding_rs",
"hickory-resolver",
"http 1.1.0",
"mime",
"nym-bin-common",
"once_cell",
"reqwest 0.12.4",
+1
View File
@@ -58,6 +58,7 @@ tap = { workspace = true }
thiserror = { workspace = true }
url = { workspace = true }
toml = { workspace = true }
tempfile = { workspace = true }
# tcpproxy dependencies
clap = { workspace = true, features = ["derive"] }
+12 -3
View File
@@ -1,15 +1,16 @@
use nym_sdk::mixnet::{
AnonymousSenderTag, MixnetClientBuilder, MixnetMessageSender, ReconstructedMessage,
self, AnonymousSenderTag, MixnetClientBuilder, MixnetMessageSender, ReconstructedMessage,
StoragePaths,
};
use std::path::PathBuf;
use tempfile::TempDir;
#[tokio::main]
async fn main() {
nym_bin_common::logging::setup_logging();
// 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();
// 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}");
// Send a message through the mixnet to ourselves using our nym address
// client
// .send_plain_message(*our_address, "hello there")
// .await
// .unwrap();
client
.send_plain_message(*our_address, "hello there")
.send_message(
*our_address,
"hello there",
mixnet::IncludedSurbs::Amount(40),
)
.await
.unwrap();
@@ -111,16 +111,16 @@ pub(crate) async fn execute(args: &Request) -> Result<(), AuthenticatorError> {
let authenticator_recipient = Recipient::from_str(&args.authenticator_recipient)?;
let (request, _) = match request_data {
AuthenticatorRequestData::Initial(init_message) => {
AuthenticatorRequest::new_initial_request(init_message, *mixnet_client.nym_address())
AuthenticatorRequest::new_initial_request(init_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) => {
AuthenticatorRequest::new_query_request(query_message, *mixnet_client.nym_address())
AuthenticatorRequest::new_query_request(query_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
@@ -89,6 +89,9 @@ pub enum AuthenticatorError {
#[error("unknown version number")]
UnknownVersion,
#[error("missing reply_to for old client")]
MissingReplyToForOldClient,
#[error("{0}")]
PublicKey(#[from] nym_wireguard_types::Error),
@@ -21,7 +21,7 @@ use nym_authenticator_requests::{
AuthenticatorRequest, AuthenticatorVersion, FinalMessage, InitMessage,
QueryBandwidthMessage, TopUpMessage,
},
v1, v2, v3, v4, CURRENT_VERSION,
v1, v2, v3, v4, v5, CURRENT_VERSION,
};
use nym_credential_verification::{
bandwidth_storage_manager::BandwidthStorageManager, ecash::EcashManager,
@@ -42,7 +42,7 @@ use rand::{prelude::IteratorRandom, thread_rng};
use tokio::sync::RwLock;
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
pub(crate) struct RegistredAndFree {
@@ -155,7 +155,7 @@ impl MixnetListener {
init_message: Box<dyn InitMessage + Send + Sync + 'static>,
protocol: Protocol,
request_id: u64,
reply_to: Recipient,
reply_to: Option<Recipient>,
) -> AuthenticatorHandleResult {
let remote_public = init_message.pub_key();
let nonce: u64 = fastrand::u64(..);
@@ -178,7 +178,7 @@ impl MixnetListener {
wg_port: registration_data.wg_port,
},
request_id,
reply_to,
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
)
.to_bytes()
.map_err(|err| {
@@ -198,7 +198,7 @@ impl MixnetListener {
wg_port: registration_data.wg_port,
},
request_id,
reply_to,
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
)
.to_bytes()
.map_err(|err| {
@@ -218,7 +218,7 @@ impl MixnetListener {
wg_port: registration_data.wg_port,
},
request_id,
reply_to,
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
)
.to_bytes()
.map_err(|err| {
@@ -228,12 +228,26 @@ impl MixnetListener {
AuthenticatorVersion::V4 => {
v4::response::AuthenticatorResponse::new_pending_registration_success(
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,
gateway_data: registration_data.gateway_data.clone(),
wg_port: registration_data.wg_port,
},
request_id,
reply_to,
)
.to_bytes()
.map_err(|err| {
@@ -272,7 +286,7 @@ impl MixnetListener {
private_ip: allowed_ipv4.into(),
wg_port: self.config.authenticator.announced_port,
},
reply_to,
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id,
)
.to_bytes()
@@ -285,7 +299,7 @@ impl MixnetListener {
private_ip: allowed_ipv4.into(),
wg_port: self.config.authenticator.announced_port,
},
reply_to,
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id,
)
.to_bytes()
@@ -298,7 +312,7 @@ impl MixnetListener {
private_ip: allowed_ipv4.into(),
wg_port: self.config.authenticator.announced_port,
},
reply_to,
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id,
)
.to_bytes()
@@ -311,7 +325,19 @@ impl MixnetListener {
private_ips: (allowed_ipv4, allowed_ipv6).into(),
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,
)
.to_bytes()
@@ -360,7 +386,7 @@ impl MixnetListener {
wg_port: registration_data.wg_port,
},
request_id,
reply_to,
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
)
.to_bytes()
.map_err(|err| {
@@ -380,7 +406,7 @@ impl MixnetListener {
wg_port: registration_data.wg_port,
},
request_id,
reply_to,
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
)
.to_bytes()
.map_err(|err| {
@@ -400,7 +426,7 @@ impl MixnetListener {
wg_port: registration_data.wg_port,
},
request_id,
reply_to,
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
)
.to_bytes()
.map_err(|err| {
@@ -410,12 +436,26 @@ impl MixnetListener {
AuthenticatorVersion::V4 => {
v4::response::AuthenticatorResponse::new_pending_registration_success(
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,
gateway_data: registration_data.gateway_data,
wg_port: registration_data.wg_port,
},
request_id,
reply_to,
)
.to_bytes()
.map_err(|err| {
@@ -433,7 +473,7 @@ impl MixnetListener {
final_message: Box<dyn FinalMessage + Send + Sync + 'static>,
protocol: Protocol,
request_id: u64,
reply_to: Recipient,
reply_to: Option<Recipient>,
) -> AuthenticatorHandleResult {
let mut registred_and_free = self.registred_and_free.write().await;
let registration_data = registred_and_free
@@ -500,7 +540,7 @@ impl MixnetListener {
private_ip: registration_data.gateway_data.private_ips.ipv4.into(),
wg_port: registration_data.wg_port,
},
reply_to,
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id,
)
.to_bytes()
@@ -511,7 +551,7 @@ impl MixnetListener {
private_ip: registration_data.gateway_data.private_ips.ipv4.into(),
wg_port: registration_data.wg_port,
},
reply_to,
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id,
)
.to_bytes()
@@ -522,18 +562,28 @@ impl MixnetListener {
private_ip: registration_data.gateway_data.private_ips.ipv4.into(),
wg_port: registration_data.wg_port,
},
reply_to,
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id,
)
.to_bytes()
.map_err(|err| AuthenticatorError::FailedToSerializeResponsePacket { source: err })?,
AuthenticatorVersion::V4 => v4::response::AuthenticatorResponse::new_registered(
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,
private_ips: registration_data.gateway_data.private_ips,
wg_port: registration_data.wg_port,
},
reply_to,
request_id,
)
.to_bytes()
@@ -579,7 +629,7 @@ impl MixnetListener {
msg: Box<dyn QueryBandwidthMessage + Send + Sync + 'static>,
protocol: Protocol,
request_id: u64,
reply_to: Recipient,
reply_to: Option<Recipient>,
) -> AuthenticatorHandleResult {
let bandwidth_data = self.peer_manager.query_bandwidth(msg).await?;
let bytes = match AuthenticatorVersion::from(protocol) {
@@ -589,7 +639,7 @@ impl MixnetListener {
available_bandwidth: data.available_bandwidth as u64,
suspended: false,
}),
reply_to,
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id,
)
.to_bytes()
@@ -602,7 +652,7 @@ impl MixnetListener {
bandwidth_data.map(|data| v2::registration::RemainingBandwidthData {
available_bandwidth: data.available_bandwidth,
}),
reply_to,
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id,
)
.to_bytes()
@@ -615,7 +665,7 @@ impl MixnetListener {
bandwidth_data.map(|data| v3::registration::RemainingBandwidthData {
available_bandwidth: data.available_bandwidth,
}),
reply_to,
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id,
)
.to_bytes()
@@ -628,7 +678,19 @@ impl MixnetListener {
bandwidth_data.map(|data| v4::registration::RemainingBandwidthData {
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,
)
.to_bytes()
@@ -646,7 +708,7 @@ impl MixnetListener {
msg: Box<dyn TopUpMessage + Send + Sync + 'static>,
protocol: Protocol,
request_id: u64,
reply_to: Recipient,
reply_to: Option<Recipient>,
) -> AuthenticatorHandleResult {
let Some(ecash_verifier) = self.ecash_verifier.clone() else {
return Err(AuthenticatorError::UnsupportedOperation);
@@ -681,11 +743,19 @@ impl MixnetListener {
let available_bandwidth = verifier.verify().await?;
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(
v4::registration::RemainingBandwidthData {
available_bandwidth,
},
reply_to,
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id,
)
.to_bytes()
@@ -694,7 +764,7 @@ impl MixnetListener {
v3::registration::RemainingBandwidthData {
available_bandwidth,
},
reply_to,
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id,
)
.to_bytes()
@@ -762,10 +832,10 @@ impl MixnetListener {
async fn handle_response(
&self,
response: Vec<u8>,
recipient: Recipient,
recipient: Option<Recipient>,
sender_tag: Option<AnonymousSenderTag>,
) -> Result<()> {
let input_message = create_input_message(recipient, sender_tag, response);
let input_message = create_input_message(recipient, sender_tag, response)?;
self.mixnet_client
.send(input_message)
.await
@@ -858,6 +928,17 @@ fn deserialize_request(reconstructed: &ReconstructedMessage) -> Result<Authentic
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, _] => {
log::info!("Received packet with invalid version: v{version}");
Err(AuthenticatorError::InvalidPacketVersion(version))
@@ -866,17 +947,30 @@ fn deserialize_request(reconstructed: &ReconstructedMessage) -> Result<Authentic
}
fn create_input_message(
nym_address: Recipient,
nym_address: Option<Recipient>,
reply_to_tag: Option<AnonymousSenderTag>,
response_packet: Vec<u8>,
) -> InputMessage {
) -> Result<InputMessage> {
let lane = TransmissionLane::General;
let packet_type = None;
if let Some(reply_to_tag) = reply_to_tag {
log::debug!("Creating message using SURB");
InputMessage::new_reply(reply_to_tag, response_packet, lane, packet_type)
} else {
Ok(InputMessage::new_reply(
reply_to_tag,
response_packet,
lane,
packet_type,
))
} else if let Some(nym_address) = 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};
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum ConnectedClientId {
AnonymousSenderTag(AnonymousSenderTag),
NymAddress(Box<Recipient>),
@@ -59,23 +59,42 @@ impl ConnectedClients {
.any(|client| client.client_id == *client_id)
}
//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
// }
// })
//}
//
//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 disconnect_client(&mut self, client_id: &ConnectedClientId) {
if let Some(ips) = self.lookup_ip_from_client_id(client_id) {
log::debug!("Disconnect client that requested to do so: {ips}");
self.disconnect_client_handle(ips);
}
}
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)))
.inspect_err(|err| {
log::error!("Failed to send disconnect event: {err}");
})
.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(
&mut self,
@@ -163,14 +182,7 @@ impl ConnectedClients {
) {
for (ips, _) in &stopped_clients {
log::info!("Disconnect stopped client: {ips}");
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)))
.inspect_err(|err| {
log::error!("Failed to send disconnect event: {err}");
})
.ok();
self.disconnect_client_handle(*ips);
}
}
@@ -180,14 +192,7 @@ impl ConnectedClients {
) {
for (ips, _) in &inactive_clients {
log::info!("Disconnect inactive client: {ips}");
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)))
.inspect_err(|err| {
log::error!("Failed to send disconnect event: {err}");
})
.ok();
self.disconnect_client_handle(*ips);
}
}
@@ -7,8 +7,8 @@ use std::time::Duration;
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
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
// 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)]
pub(crate) enum DynamicConnectFailureReason {
#[error("client already connected")]
ClientAlreadyConnected,
#[error("no available ip address")]
NoAvailableIp,
@@ -119,9 +119,6 @@ impl From<DynamicConnectResponse> for DynamicConnectResponseReplyV6 {
impl From<DynamicConnectFailureReason> for DynamicConnectFailureReasonV6 {
fn from(reason: DynamicConnectFailureReason) -> Self {
match reason {
DynamicConnectFailureReason::ClientAlreadyConnected => {
DynamicConnectFailureReasonV6::RequestedNymAddressAlreadyInUse
}
DynamicConnectFailureReason::NoAvailableIp => {
DynamicConnectFailureReasonV6::NoAvailableIp
}
@@ -119,9 +119,6 @@ impl From<DynamicConnectResponse> for DynamicConnectResponseReplyV7 {
impl From<DynamicConnectFailureReason> for DynamicConnectFailureReasonV7 {
fn from(reason: DynamicConnectFailureReason) -> Self {
match reason {
DynamicConnectFailureReason::ClientAlreadyConnected => {
DynamicConnectFailureReasonV7::RequestedNymAddressAlreadyInUse
}
DynamicConnectFailureReason::NoAvailableIp => {
DynamicConnectFailureReasonV7::NoAvailableIp
}
@@ -82,9 +82,6 @@ impl From<DynamicConnectResponse> for ConnectResponseReplyV8 {
impl From<DynamicConnectFailureReason> for ConnectFailureReasonV8 {
fn from(reason: DynamicConnectFailureReason) -> Self {
match reason {
DynamicConnectFailureReason::ClientAlreadyConnected => {
ConnectFailureReasonV8::ClientAlreadyConnected
}
DynamicConnectFailureReason::NoAvailableIp => ConnectFailureReasonV8::NoAvailableIp,
DynamicConnectFailureReason::Other(err) => ConnectFailureReasonV8::Other(err),
}
@@ -22,9 +22,9 @@ use crate::{
IpPacketRequest, PingRequest, StaticConnectRequest,
},
response::{
DynamicConnectFailureReason, DynamicConnectSuccess, HealthResponse, InfoLevel,
InfoResponse, InfoResponseReply, Response, StaticConnectFailureReason,
StaticConnectResponse, VersionedResponse,
DisconnectFailureReason, DisconnectResponse, DynamicConnectFailureReason,
DynamicConnectSuccess, HealthResponse, InfoLevel, InfoResponse, InfoResponseReply,
Response, StaticConnectFailureReason, StaticConnectResponse, VersionedResponse,
},
ClientVersion,
},
@@ -225,7 +225,7 @@ impl MixnetListener {
}))
}
async fn on_dynamic_connect_request(
fn on_dynamic_connect_request(
&mut self,
connect_request: DynamicConnectRequest,
) -> PacketHandleResult {
@@ -242,13 +242,14 @@ impl MixnetListener {
.map(Duration::from_millis)
.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 {
version,
reply_to,
response: Response::DynamicConnect {
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 {
log::info!("Received disconnect request: not implemented, dropping");
Ok(None)
fn on_disconnect_request(
&mut self,
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 {
version: ping_request.version,
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 {
version: health_request.version,
reply_to: health_request.sent_by,
@@ -324,10 +360,10 @@ impl MixnetListener {
async fn on_control_request(&mut self, control_request: ControlRequest) -> PacketHandleResult {
match control_request {
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::Ping(r) => self.on_ping_request(r).await,
ControlRequest::Health(r) => self.on_health_request(r).await,
ControlRequest::Ping(r) => self.on_ping_request(r),
ControlRequest::Health(r) => self.on_health_request(r),
}
}
@@ -103,7 +103,7 @@ impl TunListener {
.update(ConnectedClientEvent::Disconnect(DisconnectEvent(*ips)));
}
} else {
log::info!(
log::debug!(
"dropping packet from network: no registered client for destination: {dst_addr}"
);
}
@@ -4,7 +4,7 @@
[package]
name = "nym-network-requester"
license = "GPL-3.0"
version = "1.1.50"
version = "1.1.51"
authors.workspace = true
edition.workspace = true
rust-version = "1.70"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-cli"
version = "1.1.49"
version = "1.1.50"
authors.workspace = true
edition = "2021"
license.workspace = true
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nymvisor"
version = "0.1.14"
version = "0.1.15"
authors.workspace = true
repository.workspace = true
homepage.workspace = true