Merge pull request #5602 from nymtech/merge/release/2025.4-dorina-patched

merge release/2025.4-dorina-patched into develop
This commit is contained in:
Jędrzej Stuczyński
2025-03-11 10:36:50 +00:00
committed by GitHub
44 changed files with 1518 additions and 180 deletions
+22
View File
@@ -4,6 +4,28 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [2025.4-dorina-patched] (2025-03-06)
- use legacy crypto for constructing SURB headers ([#5579])
- 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])
[#5579]: https://github.com/nymtech/nym/pull/5579
[#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
+14 -10
View File
@@ -2433,7 +2433,7 @@ dependencies = [
[[package]]
name = "explorer-api"
version = "1.1.47"
version = "1.1.48"
dependencies = [
"chrono",
"clap",
@@ -4786,7 +4786,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]]
name = "nym-api"
version = "1.1.51"
version = "1.1.53"
dependencies = [
"anyhow",
"async-trait",
@@ -5035,7 +5035,7 @@ dependencies = [
[[package]]
name = "nym-cli"
version = "1.1.49"
version = "1.1.50"
dependencies = [
"anyhow",
"base64 0.22.1",
@@ -5118,7 +5118,7 @@ dependencies = [
[[package]]
name = "nym-client"
version = "1.1.49"
version = "1.1.50"
dependencies = [
"bs58",
"clap",
@@ -5880,8 +5880,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",
@@ -6159,7 +6162,7 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.1.50"
version = "1.1.51"
dependencies = [
"addr",
"anyhow",
@@ -6210,7 +6213,7 @@ dependencies = [
[[package]]
name = "nym-node"
version = "1.6.0"
version = "1.6.2"
dependencies = [
"anyhow",
"arc-swap",
@@ -6495,6 +6498,7 @@ name = "nym-pemstore"
version = "0.3.0"
dependencies = [
"pem",
"tracing",
]
[[package]]
@@ -6594,7 +6598,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.1.49"
version = "1.1.50"
dependencies = [
"bs58",
"clap",
@@ -7199,7 +7203,7 @@ dependencies = [
[[package]]
name = "nymvisor"
version = "0.1.14"
version = "0.1.15"
dependencies = [
"anyhow",
"bytes",
@@ -9448,9 +9452,9 @@ dependencies = [
[[package]]
name = "sphinx-packet"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "535f2c430778bf59c22249fcc1ed6d384129eb2f0f694706015d636c688f9ac6"
checksum = "c23047e0cf36ff6904603f499fd13153425cdf5ba47bfbaedbc999da0bd92f4e"
dependencies = [
"aes",
"arrayref",
+2 -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"
@@ -321,7 +322,7 @@ serde_with = "3.9.0"
serde_yaml = "0.9.25"
sha2 = "0.10.8"
si-scale = "0.2.3"
sphinx-packet = "0.3.1"
sphinx-packet = "=0.3.2"
sqlx = "0.7.4"
strum = "0.26"
strum_macros = "0.26"
+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 {
@@ -7,7 +7,7 @@ use nym_sphinx_addressing::clients::Recipient;
use nym_sphinx_addressing::nodes::{NymNodeRoutingAddress, MAX_NODE_ADDRESS_UNPADDED_LEN};
use nym_sphinx_params::packet_sizes::PacketSize;
use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm};
use nym_sphinx_types::{NymPacket, SURBMaterial, SphinxError, SURB};
use nym_sphinx_types::{NymPacket, SURBMaterial, SphinxError, SURB, UPDATED_LEGACY_VERSION};
use nym_topology::{NymRouteProvider, NymTopologyError};
use rand::{CryptoRng, RngCore};
use serde::de::{Error as SerdeError, Visitor};
@@ -101,7 +101,8 @@ impl ReplySurb {
let delays = nym_sphinx_routing::generate_hop_delays(average_delay, route.len());
let destination = recipient.as_sphinx_destination();
let surb_material = SURBMaterial::new(route, delays, destination);
let surb_material =
SURBMaterial::new(route, delays, destination).with_version(UPDATED_LEGACY_VERSION);
// this can't fail as we know we have a valid route to gateway and have correct number of delays
Ok(ReplySurb {
+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.2"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
@@ -101,4 +101,4 @@ cargo_metadata = { workspace = true }
[lints]
workspace = true
workspace = true
+1 -1
View File
@@ -68,7 +68,7 @@ impl Debug {
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;
pub const DEFAULT_MAXIMUM_OPEN_CONNECTIONS: usize = 8192;
}
impl Default for Debug {
+8 -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",
@@ -3554,6 +3557,7 @@ name = "nym-pemstore"
version = "0.3.0"
dependencies = [
"pem",
"tracing",
]
[[package]]
@@ -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