IPR request types v8 (#5498)
* IPR v8 request/response types * Remove signature for when we use sender tags * Remove unused * Address some review comments * Update license to GPL-3.0 for IPR Since the IPR can run as a binary, make sure it's license is GPL-3.0 * update cargo deny * Add back support for v6 * Tidy responses * Clippy * Fix compilation * Conversions * Conversions * Split response conversion * request split * Complete conversion switch * Remove commented out code * rustfmt * Remove unused conversions * Remove unused TryFrom * use from
This commit is contained in:
Generated
+1
-2
@@ -5955,7 +5955,6 @@ dependencies = [
|
||||
"reqwest 0.12.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tap",
|
||||
"thiserror 2.0.11",
|
||||
"time",
|
||||
"tokio",
|
||||
@@ -6289,7 +6288,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node-status-api"
|
||||
version = "1.0.0-rc.8"
|
||||
version = "1.0.2"
|
||||
dependencies = [
|
||||
"ammonia",
|
||||
"anyhow",
|
||||
|
||||
@@ -2,24 +2,18 @@ use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
|
||||
// The current version of the protocol.
|
||||
// The idea here is that we add new request response types at least one version before we start
|
||||
// using them.
|
||||
// Also, depending on the version in the client connect message the IPR could respond with a
|
||||
// matching older version.
|
||||
pub use v6::request;
|
||||
pub use v6::response;
|
||||
|
||||
pub mod codec;
|
||||
pub mod sign;
|
||||
pub mod v6;
|
||||
pub mod v7;
|
||||
pub mod v8;
|
||||
|
||||
// version 3: initial version
|
||||
// version 4: IPv6 support
|
||||
// version 5: Add severity level to info response
|
||||
// version 6: Increase the available IPs
|
||||
// version 7: Add signature support (for the future)
|
||||
pub const CURRENT_VERSION: u8 = 6;
|
||||
// version 8: Anonymous sends
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct IpPair {
|
||||
@@ -45,3 +39,9 @@ fn make_bincode_serializer() -> impl bincode::Options {
|
||||
.with_big_endian()
|
||||
.with_varint_encoding()
|
||||
}
|
||||
|
||||
fn generate_random() -> u64 {
|
||||
use rand::RngCore;
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
rng.next_u64()
|
||||
}
|
||||
|
||||
+18
-9
@@ -1,6 +1,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
// For reply protection, if a request is older than this, it will be rejected
|
||||
const MAX_REQUEST_AGE: Duration = Duration::from_secs(10);
|
||||
@@ -22,29 +23,37 @@ pub enum SignatureError {
|
||||
#[error("signature verification failed")]
|
||||
VerificationFailed {
|
||||
message: String,
|
||||
error: identity::SignatureError,
|
||||
error: ed25519::SignatureError,
|
||||
},
|
||||
}
|
||||
|
||||
pub trait SignedRequest {
|
||||
fn identity(&self) -> &identity::PublicKey;
|
||||
fn identity(&self) -> Option<&ed25519::PublicKey>;
|
||||
|
||||
fn request(&self) -> Result<Vec<u8>, SignatureError>;
|
||||
fn request_as_bytes(&self) -> Result<Vec<u8>, SignatureError>;
|
||||
|
||||
fn signature(&self) -> Option<&identity::Signature>;
|
||||
fn signature(&self) -> Option<&ed25519::Signature>;
|
||||
|
||||
fn timestamp(&self) -> time::OffsetDateTime;
|
||||
fn timestamp(&self) -> OffsetDateTime;
|
||||
|
||||
fn verify(&self) -> Result<(), SignatureError> {
|
||||
let identity = match self.identity() {
|
||||
Some(identity) => identity,
|
||||
None => {
|
||||
// If we are not revealing our identity, we don't need to verify anything
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(signature) = self.signature() {
|
||||
// First check that the request is recent enough
|
||||
if time::OffsetDateTime::now_utc() - self.timestamp() > MAX_REQUEST_AGE {
|
||||
if OffsetDateTime::now_utc() - self.timestamp() > MAX_REQUEST_AGE {
|
||||
return Err(SignatureError::RequestOutOfDate);
|
||||
}
|
||||
|
||||
let request_as_bytes = self.request()?;
|
||||
let request_as_bytes = self.request_as_bytes()?;
|
||||
|
||||
self.identity()
|
||||
identity
|
||||
.verify(request_as_bytes, signature)
|
||||
.map_err(|error| SignatureError::VerificationFailed {
|
||||
message: "signature verification failed".to_string(),
|
||||
@@ -1,69 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{v6, v7};
|
||||
|
||||
impl From<v7::response::StaticConnectFailureReason> for v6::response::StaticConnectFailureReason {
|
||||
fn from(failure: v7::response::StaticConnectFailureReason) -> Self {
|
||||
match failure {
|
||||
v7::response::StaticConnectFailureReason::RequestedIpAlreadyInUse => {
|
||||
v6::response::StaticConnectFailureReason::RequestedIpAlreadyInUse
|
||||
}
|
||||
v7::response::StaticConnectFailureReason::RequestedNymAddressAlreadyInUse => {
|
||||
v6::response::StaticConnectFailureReason::RequestedNymAddressAlreadyInUse
|
||||
}
|
||||
v7::response::StaticConnectFailureReason::OutOfDateTimestamp => {
|
||||
v6::response::StaticConnectFailureReason::Other("out of date timestamp".to_string())
|
||||
}
|
||||
v7::response::StaticConnectFailureReason::Other(reason) => {
|
||||
v6::response::StaticConnectFailureReason::Other(reason)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v7::response::DynamicConnectFailureReason> for v6::response::DynamicConnectFailureReason {
|
||||
fn from(failure: v7::response::DynamicConnectFailureReason) -> Self {
|
||||
match failure {
|
||||
v7::response::DynamicConnectFailureReason::RequestedNymAddressAlreadyInUse => {
|
||||
v6::response::DynamicConnectFailureReason::RequestedNymAddressAlreadyInUse
|
||||
}
|
||||
v7::response::DynamicConnectFailureReason::NoAvailableIp => {
|
||||
v6::response::DynamicConnectFailureReason::NoAvailableIp
|
||||
}
|
||||
v7::response::DynamicConnectFailureReason::Other(err) => {
|
||||
v6::response::DynamicConnectFailureReason::Other(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v7::response::InfoResponseReply> for v6::response::InfoResponseReply {
|
||||
fn from(reply: v7::response::InfoResponseReply) -> Self {
|
||||
match reply {
|
||||
v7::response::InfoResponseReply::Generic { msg } => {
|
||||
v6::response::InfoResponseReply::Generic { msg }
|
||||
}
|
||||
v7::response::InfoResponseReply::VersionMismatch {
|
||||
request_version,
|
||||
response_version,
|
||||
} => v6::response::InfoResponseReply::VersionMismatch {
|
||||
request_version,
|
||||
response_version,
|
||||
},
|
||||
v7::response::InfoResponseReply::ExitPolicyFilterCheckFailed { dst } => {
|
||||
v6::response::InfoResponseReply::ExitPolicyFilterCheckFailed { dst }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v7::response::InfoLevel> for v6::response::InfoLevel {
|
||||
fn from(level: v7::response::InfoLevel) -> Self {
|
||||
match level {
|
||||
v7::response::InfoLevel::Info => v6::response::InfoLevel::Info,
|
||||
v7::response::InfoLevel::Warn => v6::response::InfoLevel::Warn,
|
||||
v7::response::InfoLevel::Error => v6::response::InfoLevel::Error,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
pub mod conversion;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::{v6, v7};
|
||||
|
||||
impl From<v6::request::IpPacketRequest> for v7::request::IpPacketRequest {
|
||||
fn from(ip_packet_request: v6::request::IpPacketRequest) -> Self {
|
||||
Self {
|
||||
version: 7,
|
||||
data: ip_packet_request.data.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v6::request::IpPacketRequestData> for v7::request::IpPacketRequestData {
|
||||
fn from(ip_packet_request_data: v6::request::IpPacketRequestData) -> Self {
|
||||
match ip_packet_request_data {
|
||||
v6::request::IpPacketRequestData::StaticConnect(r) => {
|
||||
v7::request::IpPacketRequestData::StaticConnect(
|
||||
v7::request::SignedStaticConnectRequest {
|
||||
request: r.into(),
|
||||
signature: None,
|
||||
},
|
||||
)
|
||||
}
|
||||
v6::request::IpPacketRequestData::DynamicConnect(r) => {
|
||||
v7::request::IpPacketRequestData::DynamicConnect(
|
||||
v7::request::SignedDynamicConnectRequest {
|
||||
request: r.into(),
|
||||
signature: None,
|
||||
},
|
||||
)
|
||||
}
|
||||
v6::request::IpPacketRequestData::Disconnect(r) => {
|
||||
v7::request::IpPacketRequestData::Disconnect(v7::request::SignedDisconnectRequest {
|
||||
request: r.into(),
|
||||
signature: None,
|
||||
})
|
||||
}
|
||||
v6::request::IpPacketRequestData::Data(r) => {
|
||||
v7::request::IpPacketRequestData::Data(r.into())
|
||||
}
|
||||
v6::request::IpPacketRequestData::Ping(r) => {
|
||||
v7::request::IpPacketRequestData::Ping(r.into())
|
||||
}
|
||||
v6::request::IpPacketRequestData::Health(r) => {
|
||||
v7::request::IpPacketRequestData::Health(r.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v6::request::StaticConnectRequest> for v7::request::StaticConnectRequest {
|
||||
fn from(static_connect_request: v6::request::StaticConnectRequest) -> Self {
|
||||
Self {
|
||||
request_id: static_connect_request.request_id,
|
||||
ips: static_connect_request.ips,
|
||||
reply_to: static_connect_request.reply_to,
|
||||
reply_to_hops: static_connect_request.reply_to_hops,
|
||||
reply_to_avg_mix_delays: static_connect_request.reply_to_avg_mix_delays,
|
||||
buffer_timeout: static_connect_request.buffer_timeout,
|
||||
timestamp: OffsetDateTime::now_utc(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl From<v6::request::DynamicConnectRequest> for v7::request::DynamicConnectRequest {
|
||||
fn from(dynamic_connect_request: v6::request::DynamicConnectRequest) -> Self {
|
||||
Self {
|
||||
request_id: dynamic_connect_request.request_id,
|
||||
reply_to: dynamic_connect_request.reply_to,
|
||||
reply_to_hops: dynamic_connect_request.reply_to_hops,
|
||||
reply_to_avg_mix_delays: dynamic_connect_request.reply_to_avg_mix_delays,
|
||||
buffer_timeout: dynamic_connect_request.buffer_timeout,
|
||||
timestamp: OffsetDateTime::now_utc(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v6::request::DisconnectRequest> for v7::request::SignedDisconnectRequest {
|
||||
fn from(disconnect_request: v6::request::DisconnectRequest) -> Self {
|
||||
Self {
|
||||
request: disconnect_request.into(),
|
||||
signature: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v6::request::DisconnectRequest> for v7::request::DisconnectRequest {
|
||||
fn from(disconnect_request: v6::request::DisconnectRequest) -> Self {
|
||||
Self {
|
||||
request_id: disconnect_request.request_id,
|
||||
reply_to: disconnect_request.reply_to,
|
||||
timestamp: OffsetDateTime::now_utc(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v6::request::DataRequest> for v7::request::DataRequest {
|
||||
fn from(data_request: v6::request::DataRequest) -> Self {
|
||||
Self {
|
||||
ip_packets: data_request.ip_packets,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v6::request::PingRequest> for v7::request::PingRequest {
|
||||
fn from(ping_request: v6::request::PingRequest) -> Self {
|
||||
Self {
|
||||
request_id: ping_request.request_id,
|
||||
reply_to: ping_request.reply_to,
|
||||
timestamp: OffsetDateTime::now_utc(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v6::request::HealthRequest> for v7::request::HealthRequest {
|
||||
fn from(health_request: v6::request::HealthRequest) -> Self {
|
||||
Self {
|
||||
request_id: health_request.request_id,
|
||||
reply_to: health_request.reply_to,
|
||||
timestamp: OffsetDateTime::now_utc(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
pub mod conversion;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
pub mod signature;
|
||||
|
||||
pub const VERSION: u8 = 7;
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
use std::fmt;
|
||||
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::{make_bincode_serializer, IpPair};
|
||||
|
||||
use super::{
|
||||
signature::{SignatureError, SignedRequest},
|
||||
VERSION,
|
||||
use crate::{
|
||||
sign::{SignatureError, SignedRequest},
|
||||
IpPair,
|
||||
};
|
||||
|
||||
fn generate_random() -> u64 {
|
||||
use rand::RngCore;
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
rng.next_u64()
|
||||
}
|
||||
use super::VERSION;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct IpPacketRequest {
|
||||
pub version: u8,
|
||||
pub data: IpPacketRequestData,
|
||||
@@ -30,7 +26,7 @@ impl IpPacketRequest {
|
||||
reply_to_avg_mix_delays: Option<f64>,
|
||||
buffer_timeout: Option<u64>,
|
||||
) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
let request_id = crate::generate_random();
|
||||
(
|
||||
Self {
|
||||
version: VERSION,
|
||||
@@ -58,7 +54,7 @@ impl IpPacketRequest {
|
||||
reply_to_avg_mix_delays: Option<f64>,
|
||||
buffer_timeout: Option<u64>,
|
||||
) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
let request_id = crate::generate_random();
|
||||
(
|
||||
Self {
|
||||
version: VERSION,
|
||||
@@ -79,7 +75,7 @@ impl IpPacketRequest {
|
||||
}
|
||||
|
||||
pub fn new_disconnect_request(reply_to: Recipient) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
let request_id = crate::generate_random();
|
||||
(
|
||||
Self {
|
||||
version: VERSION,
|
||||
@@ -104,7 +100,7 @@ impl IpPacketRequest {
|
||||
}
|
||||
|
||||
pub fn new_ping(reply_to: Recipient) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
let request_id = crate::generate_random();
|
||||
(
|
||||
Self {
|
||||
version: VERSION,
|
||||
@@ -119,7 +115,7 @@ impl IpPacketRequest {
|
||||
}
|
||||
|
||||
pub fn new_health_request(reply_to: Recipient) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
let request_id = crate::generate_random();
|
||||
(
|
||||
Self {
|
||||
version: VERSION,
|
||||
@@ -155,16 +151,27 @@ impl IpPacketRequest {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify(&self) -> Result<(), SignatureError> {
|
||||
match &self.data {
|
||||
IpPacketRequestData::StaticConnect(request) => request.verify(),
|
||||
IpPacketRequestData::DynamicConnect(request) => request.verify(),
|
||||
IpPacketRequestData::Disconnect(request) => request.verify(),
|
||||
IpPacketRequestData::Data(_) => Ok(()),
|
||||
IpPacketRequestData::Ping(_) => Ok(()),
|
||||
IpPacketRequestData::Health(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
crate::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)
|
||||
crate::make_bincode_serializer().deserialize(&message.message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +186,19 @@ pub enum IpPacketRequestData {
|
||||
Health(HealthRequest),
|
||||
}
|
||||
|
||||
impl fmt::Display for IpPacketRequestData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
IpPacketRequestData::StaticConnect(_) => write!(f, "StaticConnect"),
|
||||
IpPacketRequestData::DynamicConnect(_) => write!(f, "DynamicConnect"),
|
||||
IpPacketRequestData::Disconnect(_) => write!(f, "Disconnect"),
|
||||
IpPacketRequestData::Data(_) => write!(f, "Data"),
|
||||
IpPacketRequestData::Ping(_) => write!(f, "Ping"),
|
||||
IpPacketRequestData::Health(_) => write!(f, "Health"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IpPacketRequestData {
|
||||
pub fn add_signature(&mut self, signature: identity::Signature) -> Option<identity::Signature> {
|
||||
match self {
|
||||
@@ -202,9 +222,9 @@ impl IpPacketRequestData {
|
||||
|
||||
pub fn signable_request(&self) -> Option<Result<Vec<u8>, SignatureError>> {
|
||||
match self {
|
||||
IpPacketRequestData::StaticConnect(request) => Some(request.request()),
|
||||
IpPacketRequestData::DynamicConnect(request) => Some(request.request()),
|
||||
IpPacketRequestData::Disconnect(request) => Some(request.request()),
|
||||
IpPacketRequestData::StaticConnect(request) => Some(request.request_as_bytes()),
|
||||
IpPacketRequestData::DynamicConnect(request) => Some(request.request_as_bytes()),
|
||||
IpPacketRequestData::Disconnect(request) => Some(request.request_as_bytes()),
|
||||
IpPacketRequestData::Data(_) => None,
|
||||
IpPacketRequestData::Ping(_) => None,
|
||||
IpPacketRequestData::Health(_) => None,
|
||||
@@ -242,7 +262,7 @@ pub struct StaticConnectRequest {
|
||||
impl StaticConnectRequest {
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
crate::make_bincode_serializer().serialize(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,11 +273,11 @@ pub struct SignedStaticConnectRequest {
|
||||
}
|
||||
|
||||
impl SignedRequest for SignedStaticConnectRequest {
|
||||
fn identity(&self) -> &identity::PublicKey {
|
||||
self.request.reply_to.identity()
|
||||
fn identity(&self) -> Option<&identity::PublicKey> {
|
||||
Some(self.request.reply_to.identity())
|
||||
}
|
||||
|
||||
fn request(&self) -> Result<Vec<u8>, SignatureError> {
|
||||
fn request_as_bytes(&self) -> Result<Vec<u8>, SignatureError> {
|
||||
self.request
|
||||
.to_bytes()
|
||||
.map_err(|error| SignatureError::RequestSerializationError {
|
||||
@@ -306,7 +326,7 @@ pub struct DynamicConnectRequest {
|
||||
impl DynamicConnectRequest {
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
crate::make_bincode_serializer().serialize(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,11 +337,11 @@ pub struct SignedDynamicConnectRequest {
|
||||
}
|
||||
|
||||
impl SignedRequest for SignedDynamicConnectRequest {
|
||||
fn identity(&self) -> &identity::PublicKey {
|
||||
self.request.reply_to.identity()
|
||||
fn identity(&self) -> Option<&identity::PublicKey> {
|
||||
Some(self.request.reply_to.identity())
|
||||
}
|
||||
|
||||
fn request(&self) -> Result<Vec<u8>, SignatureError> {
|
||||
fn request_as_bytes(&self) -> Result<Vec<u8>, SignatureError> {
|
||||
self.request
|
||||
.to_bytes()
|
||||
.map_err(|error| SignatureError::RequestSerializationError {
|
||||
@@ -355,7 +375,7 @@ pub struct DisconnectRequest {
|
||||
impl DisconnectRequest {
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
crate::make_bincode_serializer().serialize(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,11 +386,11 @@ pub struct SignedDisconnectRequest {
|
||||
}
|
||||
|
||||
impl SignedRequest for SignedDisconnectRequest {
|
||||
fn identity(&self) -> &identity::PublicKey {
|
||||
self.request.reply_to.identity()
|
||||
fn identity(&self) -> Option<&identity::PublicKey> {
|
||||
Some(self.request.reply_to.identity())
|
||||
}
|
||||
|
||||
fn request(&self) -> Result<Vec<u8>, SignatureError> {
|
||||
fn request_as_bytes(&self) -> Result<Vec<u8>, SignatureError> {
|
||||
self.request
|
||||
.to_bytes()
|
||||
.map_err(|error| SignatureError::RequestSerializationError {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
|
||||
pub const VERSION: u8 = 8;
|
||||
@@ -0,0 +1,536 @@
|
||||
use std::{fmt, sync::Arc};
|
||||
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::{
|
||||
sign::{SignatureError, SignedRequest},
|
||||
IpPair,
|
||||
};
|
||||
|
||||
use super::VERSION;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct IpPacketRequest {
|
||||
pub version: u8,
|
||||
pub data: IpPacketRequestData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum IpPacketRequestData {
|
||||
Data(DataRequest),
|
||||
Control(Box<ControlRequest>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum ControlRequest {
|
||||
StaticConnect(SignedStaticConnectRequest),
|
||||
DynamicConnect(SignedDynamicConnectRequest),
|
||||
Disconnect(SignedDisconnectRequest),
|
||||
Ping(PingRequest),
|
||||
Health(HealthRequest),
|
||||
}
|
||||
|
||||
// A data request is when the client wants to send an IP packet to a destination.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct DataRequest {
|
||||
pub ip_packets: bytes::Bytes,
|
||||
}
|
||||
|
||||
// A static connect request is when the client provides the internal IP address it will use on the
|
||||
// ip packet router.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct StaticConnectRequest {
|
||||
pub request_id: u64,
|
||||
|
||||
// The requested internal IP addresses.
|
||||
pub ips: IpPair,
|
||||
|
||||
// The maximum time in milliseconds the IPR should wait when filling up a mix packet
|
||||
// with ip packets.
|
||||
pub buffer_timeout: Option<u64>,
|
||||
|
||||
// Timestamp of when the request was sent by the client.
|
||||
pub timestamp: OffsetDateTime,
|
||||
|
||||
pub sender: SentBy,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct SignedStaticConnectRequest {
|
||||
pub request: StaticConnectRequest,
|
||||
pub signature: Option<ed25519::Signature>,
|
||||
}
|
||||
|
||||
// A dynamic connect request is when the client does not provide the internal IP address it will use
|
||||
// on the ip packet router, and instead requests one to be assigned to it.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct DynamicConnectRequest {
|
||||
pub request_id: u64,
|
||||
|
||||
// The maximum time in milliseconds the IPR should wait when filling up a mix packet
|
||||
// with ip packets.
|
||||
pub buffer_timeout: Option<u64>,
|
||||
|
||||
// Timestamp of when the request was sent by the client.
|
||||
pub timestamp: OffsetDateTime,
|
||||
|
||||
pub sender: SentBy,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct SignedDynamicConnectRequest {
|
||||
pub request: DynamicConnectRequest,
|
||||
pub signature: Option<ed25519::Signature>,
|
||||
}
|
||||
|
||||
// A disconnect request is when the client wants to disconnect from the ip packet router and free
|
||||
// up the allocated IP address.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct DisconnectRequest {
|
||||
pub request_id: u64,
|
||||
|
||||
// Timestamp of when the request was sent by the client.
|
||||
pub timestamp: OffsetDateTime,
|
||||
|
||||
pub sender: SentBy,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct SignedDisconnectRequest {
|
||||
pub request: DisconnectRequest,
|
||||
pub signature: Option<ed25519::Signature>,
|
||||
}
|
||||
|
||||
// A ping request is when the client wants to check if the ip packet router is still alive.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct PingRequest {
|
||||
pub request_id: u64,
|
||||
|
||||
pub sender: SentBy,
|
||||
|
||||
// Timestamp of when the request was sent by the client.
|
||||
pub timestamp: OffsetDateTime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct HealthRequest {
|
||||
pub request_id: u64,
|
||||
|
||||
pub sender: SentBy,
|
||||
|
||||
// Timestamp of when the request was sent by the client.
|
||||
pub timestamp: OffsetDateTime,
|
||||
}
|
||||
|
||||
// When constructing the request, use this return address. It has the keypair to be able to sign
|
||||
// the request if we reveal the sender.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ReturnAddress {
|
||||
AnonymousSenderTag,
|
||||
NymAddress {
|
||||
reply_to: Box<Recipient>,
|
||||
signing_keypair: Arc<ed25519::KeyPair>,
|
||||
},
|
||||
}
|
||||
|
||||
// The serialized sender field in the request, that does not contain the keypair.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum SentBy {
|
||||
AnonymousSenderTag,
|
||||
NymAddress(Box<Recipient>),
|
||||
}
|
||||
|
||||
impl IpPacketRequest {
|
||||
pub fn new_connect_request(
|
||||
ips: Option<IpPair>,
|
||||
buffer_timeout: Option<u64>,
|
||||
return_address: ReturnAddress,
|
||||
) -> Result<(Self, u64), SignatureError> {
|
||||
let request_id = rand::random();
|
||||
let timestamp = OffsetDateTime::now_utc();
|
||||
let sender = return_address.clone().into();
|
||||
let request = if let Some(ips) = ips {
|
||||
let request = StaticConnectRequest {
|
||||
request_id,
|
||||
ips,
|
||||
buffer_timeout,
|
||||
timestamp,
|
||||
sender,
|
||||
};
|
||||
let signature = return_address
|
||||
.signing_key()
|
||||
.map(|keypair| {
|
||||
request
|
||||
.to_bytes()
|
||||
.map(|bytes| keypair.private_key().sign(bytes))
|
||||
})
|
||||
.transpose()?;
|
||||
ControlRequest::StaticConnect(SignedStaticConnectRequest { request, signature })
|
||||
} else {
|
||||
let request = DynamicConnectRequest {
|
||||
request_id,
|
||||
buffer_timeout,
|
||||
timestamp,
|
||||
sender,
|
||||
};
|
||||
let signature = return_address
|
||||
.signing_key()
|
||||
.map(|keypair| {
|
||||
request
|
||||
.to_bytes()
|
||||
.map(|bytes| keypair.private_key().sign(bytes))
|
||||
})
|
||||
.transpose()?;
|
||||
ControlRequest::DynamicConnect(SignedDynamicConnectRequest { request, signature })
|
||||
};
|
||||
let request = Self {
|
||||
version: VERSION,
|
||||
data: IpPacketRequestData::Control(Box::new(request)),
|
||||
};
|
||||
Ok((request, request_id))
|
||||
}
|
||||
|
||||
pub fn new_disconnect_request(
|
||||
return_address: ReturnAddress,
|
||||
) -> Result<(Self, u64), SignatureError> {
|
||||
let request_id = rand::random();
|
||||
let timestamp = OffsetDateTime::now_utc();
|
||||
let sender = return_address.clone().into();
|
||||
let request = DisconnectRequest {
|
||||
request_id,
|
||||
timestamp,
|
||||
sender,
|
||||
};
|
||||
let signature = return_address
|
||||
.signing_key()
|
||||
.map(|keypair| {
|
||||
request
|
||||
.to_bytes()
|
||||
.map(|bytes| keypair.private_key().sign(bytes))
|
||||
})
|
||||
.transpose()?;
|
||||
let request = Self {
|
||||
version: VERSION,
|
||||
data: IpPacketRequestData::Control(Box::new(ControlRequest::Disconnect(
|
||||
SignedDisconnectRequest { request, signature },
|
||||
))),
|
||||
};
|
||||
Ok((request, request_id))
|
||||
}
|
||||
|
||||
pub fn new_data_request(ip_packets: bytes::Bytes) -> Self {
|
||||
Self {
|
||||
version: VERSION,
|
||||
data: IpPacketRequestData::Data(DataRequest { ip_packets }),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_ping(return_address: ReturnAddress) -> (Self, u64) {
|
||||
let request_id = rand::random();
|
||||
let timestamp = OffsetDateTime::now_utc();
|
||||
let sender = return_address.into();
|
||||
let ping_request = PingRequest {
|
||||
request_id,
|
||||
sender,
|
||||
timestamp,
|
||||
};
|
||||
let request = Self {
|
||||
version: VERSION,
|
||||
data: IpPacketRequestData::Control(Box::new(ControlRequest::Ping(ping_request))),
|
||||
};
|
||||
(request, request_id)
|
||||
}
|
||||
|
||||
pub fn new_health_request(return_address: ReturnAddress) -> (Self, u64) {
|
||||
let request_id = rand::random();
|
||||
let timestamp = OffsetDateTime::now_utc();
|
||||
let sender = return_address.into();
|
||||
let health_request = HealthRequest {
|
||||
request_id,
|
||||
sender,
|
||||
timestamp,
|
||||
};
|
||||
let request = Self {
|
||||
version: VERSION,
|
||||
data: IpPacketRequestData::Control(Box::new(ControlRequest::Health(health_request))),
|
||||
};
|
||||
(request, request_id)
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Option<u64> {
|
||||
match self.data {
|
||||
IpPacketRequestData::Control(ref c) => Some(c.id()),
|
||||
IpPacketRequestData::Data(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify(&self) -> Result<(), SignatureError> {
|
||||
match &self.data {
|
||||
IpPacketRequestData::Control(c) => c.verify(),
|
||||
IpPacketRequestData::Data(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
crate::make_bincode_serializer().serialize(self)
|
||||
}
|
||||
|
||||
pub fn from_reconstructed_message(
|
||||
message: &nym_sphinx::receiver::ReconstructedMessage,
|
||||
) -> Result<Self, bincode::Error> {
|
||||
use bincode::Options;
|
||||
crate::make_bincode_serializer().deserialize(&message.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for IpPacketRequest {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"IpPacketRequest {{ version: {}, data: {} }}",
|
||||
self.version, self.data
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for IpPacketRequestData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
IpPacketRequestData::Data(_) => write!(f, "Data"),
|
||||
IpPacketRequestData::Control(c) => write!(f, "Control({})", c),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ControlRequest {
|
||||
fn id(&self) -> u64 {
|
||||
match self {
|
||||
ControlRequest::StaticConnect(request) => request.request.request_id,
|
||||
ControlRequest::DynamicConnect(request) => request.request.request_id,
|
||||
ControlRequest::Disconnect(request) => request.request.request_id,
|
||||
ControlRequest::Ping(request) => request.request_id,
|
||||
ControlRequest::Health(request) => request.request_id,
|
||||
}
|
||||
}
|
||||
|
||||
fn verify(&self) -> Result<(), SignatureError> {
|
||||
match self {
|
||||
ControlRequest::StaticConnect(request) => request.verify(),
|
||||
ControlRequest::DynamicConnect(request) => request.verify(),
|
||||
ControlRequest::Disconnect(request) => request.verify(),
|
||||
ControlRequest::Ping(_) => Ok(()),
|
||||
ControlRequest::Health(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ControlRequest {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ControlRequest::StaticConnect(_) => write!(f, "StaticConnect"),
|
||||
ControlRequest::DynamicConnect(_) => write!(f, "DynamicConnect"),
|
||||
ControlRequest::Disconnect(_) => write!(f, "Disconnect"),
|
||||
ControlRequest::Ping(_) => write!(f, "Ping"),
|
||||
ControlRequest::Health(_) => write!(f, "Health"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StaticConnectRequest {
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, SignatureError> {
|
||||
use bincode::Options;
|
||||
crate::make_bincode_serializer()
|
||||
.serialize(self)
|
||||
.map_err(|error| SignatureError::RequestSerializationError {
|
||||
message: "failed to serialize request to binary".to_string(),
|
||||
error,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl SignedRequest for SignedStaticConnectRequest {
|
||||
fn request_as_bytes(&self) -> Result<Vec<u8>, SignatureError> {
|
||||
self.request.to_bytes()
|
||||
}
|
||||
|
||||
fn timestamp(&self) -> OffsetDateTime {
|
||||
self.request.timestamp
|
||||
}
|
||||
|
||||
fn identity(&self) -> Option<&ed25519::PublicKey> {
|
||||
self.request.sender.identity()
|
||||
}
|
||||
|
||||
fn signature(&self) -> Option<&ed25519::Signature> {
|
||||
self.signature.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl DynamicConnectRequest {
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, SignatureError> {
|
||||
use bincode::Options;
|
||||
crate::make_bincode_serializer()
|
||||
.serialize(self)
|
||||
.map_err(|error| SignatureError::RequestSerializationError {
|
||||
message: "failed to serialize request to binary".to_string(),
|
||||
error,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl SignedRequest for SignedDynamicConnectRequest {
|
||||
fn request_as_bytes(&self) -> Result<Vec<u8>, SignatureError> {
|
||||
self.request.to_bytes()
|
||||
}
|
||||
|
||||
fn timestamp(&self) -> OffsetDateTime {
|
||||
self.request.timestamp
|
||||
}
|
||||
|
||||
fn identity(&self) -> Option<&ed25519::PublicKey> {
|
||||
self.request.sender.identity()
|
||||
}
|
||||
|
||||
fn signature(&self) -> Option<&ed25519::Signature> {
|
||||
self.signature.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl DisconnectRequest {
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, SignatureError> {
|
||||
use bincode::Options;
|
||||
crate::make_bincode_serializer()
|
||||
.serialize(self)
|
||||
.map_err(|error| SignatureError::RequestSerializationError {
|
||||
message: "failed to serialize request to binary".to_string(),
|
||||
error,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl SignedRequest for SignedDisconnectRequest {
|
||||
fn request_as_bytes(&self) -> Result<Vec<u8>, SignatureError> {
|
||||
self.request.to_bytes()
|
||||
}
|
||||
|
||||
fn timestamp(&self) -> OffsetDateTime {
|
||||
self.request.timestamp
|
||||
}
|
||||
|
||||
fn identity(&self) -> Option<&ed25519::PublicKey> {
|
||||
self.request.sender.identity()
|
||||
}
|
||||
|
||||
fn signature(&self) -> Option<&ed25519::Signature> {
|
||||
self.signature.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl SentBy {
|
||||
fn identity(&self) -> Option<&ed25519::PublicKey> {
|
||||
match self {
|
||||
SentBy::AnonymousSenderTag => None,
|
||||
SentBy::NymAddress(recipient) => Some(recipient.identity()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Recipient> for SentBy {
|
||||
fn from(recipient: Recipient) -> Self {
|
||||
SentBy::NymAddress(Box::new(recipient))
|
||||
}
|
||||
}
|
||||
|
||||
impl ReturnAddress {
|
||||
fn signing_key(&self) -> Option<&ed25519::KeyPair> {
|
||||
match self {
|
||||
ReturnAddress::AnonymousSenderTag => None,
|
||||
ReturnAddress::NymAddress {
|
||||
signing_keypair, ..
|
||||
} => Some(signing_keypair.as_ref()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ReturnAddress> for SentBy {
|
||||
fn from(return_address: ReturnAddress) -> Self {
|
||||
match return_address {
|
||||
ReturnAddress::AnonymousSenderTag => SentBy::AnonymousSenderTag,
|
||||
ReturnAddress::NymAddress { reply_to, .. } => SentBy::NymAddress(reply_to),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use time::macros::datetime;
|
||||
|
||||
use super::*;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn check_size_of_request() {
|
||||
let connect = IpPacketRequest {
|
||||
version: 4,
|
||||
data: IpPacketRequestData::Control(Box::new(ControlRequest::StaticConnect(
|
||||
SignedStaticConnectRequest {
|
||||
request: StaticConnectRequest {
|
||||
request_id: 123,
|
||||
ips: IpPair::new(
|
||||
Ipv4Addr::from_str("10.0.0.1").unwrap(),
|
||||
Ipv6Addr::from_str("fc00::1").unwrap(),
|
||||
),
|
||||
buffer_timeout: None,
|
||||
timestamp: datetime!(2024-01-01 12:59:59.5 UTC),
|
||||
sender: SentBy::AnonymousSenderTag,
|
||||
},
|
||||
signature: None,
|
||||
},
|
||||
))),
|
||||
};
|
||||
assert_eq!(connect.to_bytes().unwrap().len(), 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_size_of_data() {
|
||||
let data = IpPacketRequest {
|
||||
version: 4,
|
||||
data: IpPacketRequestData::Data(DataRequest {
|
||||
ip_packets: bytes::Bytes::from(vec![1u8; 32]),
|
||||
}),
|
||||
};
|
||||
assert_eq!(data.to_bytes().unwrap().len(), 35);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_and_deserialize_data_request() {
|
||||
let data = IpPacketRequest {
|
||||
version: 4,
|
||||
data: IpPacketRequestData::Data(DataRequest {
|
||||
ip_packets: bytes::Bytes::from(vec![1, 2, 4, 2, 5]),
|
||||
}),
|
||||
};
|
||||
|
||||
let serialized = data.to_bytes().unwrap();
|
||||
let deserialized = IpPacketRequest::from_reconstructed_message(
|
||||
&nym_sphinx::receiver::ReconstructedMessage {
|
||||
message: serialized,
|
||||
sender_tag: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(deserialized.version, 4);
|
||||
assert_eq!(
|
||||
deserialized.data,
|
||||
IpPacketRequestData::Data(DataRequest {
|
||||
ip_packets: bytes::Bytes::from(vec![1, 2, 4, 2, 5]),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
use nym_bin_common::build_information::BinaryBuildInformationOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{make_bincode_serializer, IpPair};
|
||||
|
||||
use super::VERSION;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct IpPacketResponse {
|
||||
pub version: u8,
|
||||
pub data: IpPacketResponseData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum IpPacketResponseData {
|
||||
Data(DataResponse),
|
||||
Control(Box<ControlResponse>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DataResponse {
|
||||
pub ip_packet: bytes::Bytes,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum ControlResponse {
|
||||
// Response for a static connect request
|
||||
StaticConnect(StaticConnectResponse),
|
||||
|
||||
// Response for a dynamic connect request
|
||||
DynamicConnect(DynamicConnectResponse),
|
||||
|
||||
// Response for a disconnect initiqated by the client
|
||||
Disconnect(DisconnectResponse),
|
||||
|
||||
// Message from the server that the client got disconnected without the client initiating it
|
||||
UnrequestedDisconnect(UnrequestedDisconnect),
|
||||
|
||||
// Response to ping request
|
||||
Pong(PongResponse),
|
||||
|
||||
// Response for a health request
|
||||
Health(Box<HealthResponse>),
|
||||
|
||||
// Info response. This can be anything from informative messages to errors
|
||||
Info(InfoResponse),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct StaticConnectResponse {
|
||||
pub request_id: u64,
|
||||
pub reply: StaticConnectResponseReply,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum StaticConnectResponseReply {
|
||||
Success,
|
||||
Failure(StaticConnectFailureReason),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
|
||||
pub enum StaticConnectFailureReason {
|
||||
#[error("requested ip address is already in use")]
|
||||
RequestedIpAlreadyInUse,
|
||||
|
||||
#[error("client is already connected")]
|
||||
ClientAlreadyConnected,
|
||||
|
||||
#[error("request timestamp is out of date")]
|
||||
OutOfDateTimestamp,
|
||||
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DynamicConnectResponse {
|
||||
pub request_id: u64,
|
||||
pub reply: DynamicConnectResponseReply,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum DynamicConnectResponseReply {
|
||||
Success(DynamicConnectSuccess),
|
||||
Failure(DynamicConnectFailureReason),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DynamicConnectSuccess {
|
||||
pub ips: IpPair,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
|
||||
pub enum DynamicConnectFailureReason {
|
||||
#[error("client is already connected")]
|
||||
ClientAlreadyConnected,
|
||||
|
||||
#[error("no available ip address")]
|
||||
NoAvailableIp,
|
||||
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DisconnectResponse {
|
||||
pub request_id: u64,
|
||||
pub reply: DisconnectResponseReply,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum DisconnectResponseReply {
|
||||
Success,
|
||||
Failure(DisconnectFailureReason),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
|
||||
pub enum DisconnectFailureReason {
|
||||
#[error("client is not connected")]
|
||||
ClientNotConnected,
|
||||
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct UnrequestedDisconnect {
|
||||
pub reason: UnrequestedDisconnectReason,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
|
||||
pub enum UnrequestedDisconnectReason {
|
||||
#[error("client mixnet traffic timeout")]
|
||||
ClientMixnetTrafficTimeout,
|
||||
|
||||
#[error("client tun traffic timeout")]
|
||||
ClientTunTrafficTimeout,
|
||||
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct PongResponse {
|
||||
pub request_id: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct HealthResponse {
|
||||
pub request_id: u64,
|
||||
pub reply: HealthResponseReply,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct HealthResponseReply {
|
||||
// Return the binary build information of the IPR
|
||||
pub build_info: BinaryBuildInformationOwned,
|
||||
|
||||
// Return if the IPR has performed a successful routing test.
|
||||
pub routable: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct InfoResponse {
|
||||
pub request_id: u64,
|
||||
pub reply: InfoResponseReply,
|
||||
pub level: InfoLevel,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
|
||||
pub enum InfoResponseReply {
|
||||
#[error("{msg}")]
|
||||
Generic { msg: String },
|
||||
|
||||
#[error(
|
||||
"version mismatch: response is v{request_version} and response is v{response_version}"
|
||||
)]
|
||||
VersionMismatch {
|
||||
request_version: u8,
|
||||
response_version: u8,
|
||||
},
|
||||
|
||||
#[error("destination failed exit policy filter check: {dst}")]
|
||||
ExitPolicyFilterCheckFailed { dst: String },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum InfoLevel {
|
||||
Info,
|
||||
Warn,
|
||||
Error,
|
||||
}
|
||||
|
||||
impl IpPacketResponse {
|
||||
pub fn new_ip_packet(ip_packet: bytes::Bytes) -> Self {
|
||||
Self {
|
||||
version: VERSION,
|
||||
data: IpPacketResponseData::Data(DataResponse { ip_packet }),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Option<u64> {
|
||||
match &self.data {
|
||||
IpPacketResponseData::Data(_) => None,
|
||||
IpPacketResponseData::Control(response) => response.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)
|
||||
}
|
||||
}
|
||||
|
||||
impl IpPacketResponseData {
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ControlResponse {
|
||||
fn id(&self) -> Option<u64> {
|
||||
match self {
|
||||
ControlResponse::StaticConnect(response) => Some(response.request_id),
|
||||
ControlResponse::DynamicConnect(response) => Some(response.request_id),
|
||||
ControlResponse::Disconnect(response) => Some(response.request_id),
|
||||
ControlResponse::UnrequestedDisconnect(_) => None,
|
||||
ControlResponse::Pong(response) => Some(response.request_id),
|
||||
ControlResponse::Health(response) => Some(response.request_id),
|
||||
ControlResponse::Info(response) => Some(response.request_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StaticConnectResponseReply {
|
||||
pub fn is_success(&self) -> bool {
|
||||
match self {
|
||||
StaticConnectResponseReply::Success => true,
|
||||
StaticConnectResponseReply::Failure(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DynamicConnectResponseReply {
|
||||
pub fn is_success(&self) -> bool {
|
||||
match self {
|
||||
DynamicConnectResponseReply::Success(_) => true,
|
||||
DynamicConnectResponseReply::Failure(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,6 +122,7 @@ exceptions = [
|
||||
{ allow = ["GPL-3.0"], crate = "nym-network-requester" },
|
||||
{ allow = ["GPL-3.0"], crate = "nym-node" },
|
||||
{ allow = ["GPL-3.0"], crate = "nym-validator-rewarder" },
|
||||
{ allow = ["GPL-3.0"], crate = "nym-ip-packet-router" },
|
||||
]
|
||||
|
||||
# Some crates don't have (easily) machine readable licensing information,
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::mixnet::traits::MixnetMessageSender;
|
||||
use crate::{Error, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::{ready, Stream, StreamExt};
|
||||
use log::error;
|
||||
use log::{debug, error};
|
||||
use nym_client_core::client::base_client::GatewayConnection;
|
||||
use nym_client_core::client::mix_traffic::ClientRequestSender;
|
||||
use nym_client_core::client::{
|
||||
@@ -12,7 +12,7 @@ use nym_client_core::client::{
|
||||
received_buffer::ReconstructedMessagesReceiver,
|
||||
};
|
||||
use nym_client_core::config::ForgetMe;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_requests::ClientRequest;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::{params::PacketType, receiver::ReconstructedMessage};
|
||||
@@ -32,7 +32,7 @@ pub struct MixnetClient {
|
||||
/// The nym address of this connected client.
|
||||
pub(crate) nym_address: Recipient,
|
||||
|
||||
pub(crate) identity_keys: Arc<identity::KeyPair>,
|
||||
pub(crate) identity_keys: Arc<ed25519::KeyPair>,
|
||||
|
||||
/// Input to the client from the users perspective. This can be either data to send or control
|
||||
/// messages.
|
||||
@@ -67,7 +67,7 @@ impl MixnetClient {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn new(
|
||||
nym_address: Recipient,
|
||||
identity_keys: Arc<identity::KeyPair>,
|
||||
identity_keys: Arc<ed25519::KeyPair>,
|
||||
client_input: ClientInput,
|
||||
client_output: ClientOutput,
|
||||
client_state: ClientState,
|
||||
@@ -125,8 +125,13 @@ impl MixnetClient {
|
||||
self.client_request_sender.clone()
|
||||
}
|
||||
|
||||
/// Get the client's identity keys.
|
||||
pub fn identity_keypair(&self) -> Arc<ed25519::KeyPair> {
|
||||
self.identity_keys.clone()
|
||||
}
|
||||
|
||||
/// Sign a message with the client's private identity key.
|
||||
pub fn sign(&self, data: &[u8]) -> identity::Signature {
|
||||
pub fn sign(&self, data: &[u8]) -> ed25519::Signature {
|
||||
self.identity_keys.private_key().sign(data)
|
||||
}
|
||||
|
||||
@@ -274,7 +279,9 @@ impl Stream for MixnetClient {
|
||||
}
|
||||
Poll::Ready(Some(next))
|
||||
} else {
|
||||
error!("the reconstructed messages vector is empty - please let the developers know if you see this message");
|
||||
// I *think* this happens for SURBs, but I'm not 100% sure. Nonetheless it's
|
||||
// beneign, but let's log it here anyway as a reminder
|
||||
debug!("the reconstructed messages vector is empty");
|
||||
cx.waker().wake_by_ref();
|
||||
Poll::Pending
|
||||
}
|
||||
|
||||
@@ -30,7 +30,9 @@ use nym_credential_verification::{
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_crypto::asymmetric::x25519::KeyPair;
|
||||
use nym_gateway_requests::models::CredentialSpendingRequest;
|
||||
use nym_sdk::mixnet::{InputMessage, MixnetMessageSender, Recipient, TransmissionLane};
|
||||
use nym_sdk::mixnet::{
|
||||
AnonymousSenderTag, InputMessage, MixnetMessageSender, Recipient, TransmissionLane,
|
||||
};
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
use nym_sphinx::receiver::ReconstructedMessage;
|
||||
use nym_task::TaskHandle;
|
||||
@@ -757,9 +759,13 @@ impl MixnetListener {
|
||||
}
|
||||
|
||||
// When an incoming mixnet message triggers a response that we send back.
|
||||
async fn handle_response(&self, response: Vec<u8>, recipient: Recipient) -> Result<()> {
|
||||
let input_message =
|
||||
InputMessage::new_regular(recipient, response, TransmissionLane::General, None);
|
||||
async fn handle_response(
|
||||
&self,
|
||||
response: Vec<u8>,
|
||||
recipient: Recipient,
|
||||
sender_tag: Option<AnonymousSenderTag>,
|
||||
) -> Result<()> {
|
||||
let input_message = create_input_message(recipient, sender_tag, response);
|
||||
self.mixnet_client
|
||||
.send(input_message)
|
||||
.await
|
||||
@@ -782,9 +788,10 @@ impl MixnetListener {
|
||||
}
|
||||
msg = self.mixnet_client.next() => {
|
||||
if let Some(msg) = msg {
|
||||
let sender_tag = msg.sender_tag;
|
||||
match self.on_reconstructed_message(msg).await {
|
||||
Ok((response, recipient)) => {
|
||||
if let Err(err) = self.handle_response(response, recipient).await {
|
||||
if let Err(err) = self.handle_response(response, recipient, sender_tag).await {
|
||||
log::error!("Mixnet listener failed to handle response: {err}");
|
||||
}
|
||||
}
|
||||
@@ -857,3 +864,19 @@ fn deserialize_request(reconstructed: &ReconstructedMessage) -> Result<Authentic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_input_message(
|
||||
nym_address: Recipient,
|
||||
reply_to_tag: Option<AnonymousSenderTag>,
|
||||
response_packet: Vec<u8>,
|
||||
) -> 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 {
|
||||
log::debug!("Creating message using nym_address");
|
||||
InputMessage::new_regular(nym_address, response_packet, lane, packet_type)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
license = "GPL-3.0"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
@@ -38,7 +38,6 @@ rand = { workspace = true }
|
||||
reqwest.workspace = true
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
tap.workspace = true
|
||||
thiserror = { workspace = true }
|
||||
time = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::cli::CliIpPacketRouterClient;
|
||||
use nym_bin_common::output_format::OutputFormat;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::cli::CliIpPacketRouterClient;
|
||||
use nym_client_core::cli_helpers::client_import_coin_index_signatures::{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::cli::CliIpPacketRouterClient;
|
||||
use nym_client_core::cli_helpers::client_import_credential::{
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::cli::CliIpPacketRouterClient;
|
||||
use nym_client_core::cli_helpers::client_import_expiration_date_signatures::{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::cli::CliIpPacketRouterClient;
|
||||
use nym_client_core::cli_helpers::client_import_master_verification_key::{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use clap::{Args, Subcommand};
|
||||
use nym_client_core::cli_helpers::client_import_coin_index_signatures::CommonClientImportCoinIndexSignaturesArgs;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::cli::CliIpPacketRouterClient;
|
||||
use nym_bin_common::output_format::OutputFormat;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::cli::CliIpPacketRouterClient;
|
||||
use nym_bin_common::output_format::OutputFormat;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::cli::CliIpPacketRouterClient;
|
||||
use nym_client_core::cli_helpers::client_switch_gateway::{
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use nym_ip_packet_requests::v8::request::SentBy;
|
||||
use nym_sdk::mixnet::{AnonymousSenderTag, Recipient};
|
||||
|
||||
use crate::error::{IpPacketRouterError, Result};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub(crate) enum ConnectedClientId {
|
||||
AnonymousSenderTag(AnonymousSenderTag),
|
||||
NymAddress(Box<Recipient>),
|
||||
}
|
||||
|
||||
impl ConnectedClientId {
|
||||
pub(crate) fn into_nym_address(self) -> Result<Recipient> {
|
||||
match self {
|
||||
ConnectedClientId::NymAddress(nym_address) => Ok(*nym_address),
|
||||
ConnectedClientId::AnonymousSenderTag(_) => Err(IpPacketRouterError::InvalidReplyTo),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ConnectedClientId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ConnectedClientId::NymAddress(nym_address) => write!(f, "{nym_address}"),
|
||||
ConnectedClientId::AnonymousSenderTag(tag) => write!(f, "{tag}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Recipient> for ConnectedClientId {
|
||||
fn from(nym_address: Recipient) -> Self {
|
||||
ConnectedClientId::NymAddress(Box::new(nym_address))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AnonymousSenderTag> for ConnectedClientId {
|
||||
fn from(tag: AnonymousSenderTag) -> Self {
|
||||
ConnectedClientId::AnonymousSenderTag(tag)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<(SentBy, Option<AnonymousSenderTag>)> for ConnectedClientId {
|
||||
type Error = IpPacketRouterError;
|
||||
|
||||
fn try_from((sent_by, sender_tag): (SentBy, Option<AnonymousSenderTag>)) -> Result<Self> {
|
||||
match sent_by {
|
||||
SentBy::NymAddress(nym_address) => Ok(ConnectedClientId::NymAddress(nym_address)),
|
||||
SentBy::AnonymousSenderTag => sender_tag
|
||||
.map(ConnectedClientId::AnonymousSenderTag)
|
||||
.ok_or(IpPacketRouterError::InvalidReplyTo),
|
||||
}
|
||||
}
|
||||
}
|
||||
+50
-36
@@ -1,14 +1,25 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use bytes::Bytes;
|
||||
use nym_ip_packet_requests::codec::MultiIpPacketCodec;
|
||||
use nym_sdk::mixnet::{MixnetMessageSender, Recipient};
|
||||
use nym_ip_packet_requests::{
|
||||
codec::MultiIpPacketCodec, v6::response::IpPacketResponse as IpPacketResponseV6,
|
||||
v7::response::IpPacketResponse as IpPacketResponseV7,
|
||||
v8::response::IpPacketResponse as IpPacketResponseV8,
|
||||
};
|
||||
use nym_sdk::mixnet::MixnetMessageSender;
|
||||
use tokio::{
|
||||
sync::{mpsc, oneshot},
|
||||
time::interval,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
clients::ConnectedClientId,
|
||||
constants::CLIENT_HANDLER_ACTIVITY_TIMEOUT,
|
||||
error::{IpPacketRouterError, Result},
|
||||
mixnet_listener::SupportedClientVersion,
|
||||
messages::ClientVersion,
|
||||
util::create_message::create_input_message,
|
||||
};
|
||||
|
||||
@@ -19,17 +30,17 @@ use crate::{
|
||||
// This handler is spawned as a task, and it listens to IP packets passed from the tun_listener,
|
||||
// encodes it, and then sends to mixnet.
|
||||
pub(crate) struct ConnectedClientHandler {
|
||||
// The address of the client that this handler is connected to
|
||||
nym_address: Recipient,
|
||||
// The client that sent the packets
|
||||
sent_by: ConnectedClientId,
|
||||
|
||||
// Channel to receive packets from the tun_listener
|
||||
forward_from_tun_rx: tokio::sync::mpsc::UnboundedReceiver<Vec<u8>>,
|
||||
forward_from_tun_rx: mpsc::UnboundedReceiver<Vec<u8>>,
|
||||
|
||||
// Channel to send packets to the mixnet
|
||||
mixnet_client_sender: nym_sdk::mixnet::MixnetClientSender,
|
||||
|
||||
// Channel to receive close signal
|
||||
close_rx: tokio::sync::oneshot::Receiver<()>,
|
||||
close_rx: oneshot::Receiver<()>,
|
||||
|
||||
// Interval to check for activity timeout
|
||||
activity_timeout: tokio::time::Interval,
|
||||
@@ -38,31 +49,33 @@ pub(crate) struct ConnectedClientHandler {
|
||||
encoder: MultiIpPacketCodec,
|
||||
|
||||
// The version of the client
|
||||
client_version: SupportedClientVersion,
|
||||
client_version: ClientVersion,
|
||||
}
|
||||
|
||||
impl ConnectedClientHandler {
|
||||
pub(crate) fn start(
|
||||
reply_to: Recipient,
|
||||
buffer_timeout: std::time::Duration,
|
||||
client_version: SupportedClientVersion,
|
||||
client_id: ConnectedClientId,
|
||||
buffer_timeout: Duration,
|
||||
client_version: ClientVersion,
|
||||
mixnet_client_sender: nym_sdk::mixnet::MixnetClientSender,
|
||||
) -> (
|
||||
tokio::sync::mpsc::UnboundedSender<Vec<u8>>,
|
||||
tokio::sync::oneshot::Sender<()>,
|
||||
mpsc::UnboundedSender<Vec<u8>>,
|
||||
oneshot::Sender<()>,
|
||||
tokio::task::JoinHandle<()>,
|
||||
) {
|
||||
let (close_tx, close_rx) = tokio::sync::oneshot::channel();
|
||||
let (forward_from_tun_tx, forward_from_tun_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
log::debug!("Starting connected client handler for: {}", client_id);
|
||||
log::debug!("client version: {:?}", client_version);
|
||||
let (close_tx, close_rx) = oneshot::channel();
|
||||
let (forward_from_tun_tx, forward_from_tun_rx) = mpsc::unbounded_channel();
|
||||
|
||||
// Reset so that we don't get the first tick immediately
|
||||
let mut activity_timeout = tokio::time::interval(CLIENT_HANDLER_ACTIVITY_TIMEOUT);
|
||||
let mut activity_timeout = interval(CLIENT_HANDLER_ACTIVITY_TIMEOUT);
|
||||
activity_timeout.reset();
|
||||
|
||||
let encoder = MultiIpPacketCodec::new(buffer_timeout);
|
||||
|
||||
let connected_client_handler = ConnectedClientHandler {
|
||||
nym_address: reply_to,
|
||||
sent_by: client_id,
|
||||
forward_from_tun_rx,
|
||||
mixnet_client_sender,
|
||||
close_rx,
|
||||
@@ -80,20 +93,18 @@ impl ConnectedClientHandler {
|
||||
(forward_from_tun_tx, close_tx, handle)
|
||||
}
|
||||
|
||||
async fn send_packets_to_mixnet(&mut self, packets: Bytes) -> Result<()> {
|
||||
let response_packet = match self.client_version {
|
||||
SupportedClientVersion::V6 => {
|
||||
nym_ip_packet_requests::v6::response::IpPacketResponse::new_ip_packet(packets)
|
||||
.to_bytes()
|
||||
}
|
||||
SupportedClientVersion::V7 => {
|
||||
nym_ip_packet_requests::v7::response::IpPacketResponse::new_ip_packet(packets)
|
||||
.to_bytes()
|
||||
}
|
||||
async fn create_ip_packet(&self, packets: Bytes) -> Result<Vec<u8>> {
|
||||
match self.client_version {
|
||||
ClientVersion::V6 => IpPacketResponseV6::new_ip_packet(packets).to_bytes(),
|
||||
ClientVersion::V7 => IpPacketResponseV7::new_ip_packet(packets).to_bytes(),
|
||||
ClientVersion::V8 => IpPacketResponseV8::new_ip_packet(packets).to_bytes(),
|
||||
}
|
||||
.map_err(|err| IpPacketRouterError::FailedToSerializeResponsePacket { source: err })?;
|
||||
.map_err(|err| IpPacketRouterError::FailedToSerializeResponsePacket { source: err })
|
||||
}
|
||||
|
||||
let input_message = create_input_message(self.nym_address, response_packet);
|
||||
async fn send_packets_to_mixnet(&mut self, packets: Bytes) -> Result<()> {
|
||||
let response_packet = self.create_ip_packet(packets).await?;
|
||||
let input_message = create_input_message(&self.sent_by, response_packet);
|
||||
|
||||
self.mixnet_client_sender
|
||||
.send(input_message)
|
||||
@@ -123,17 +134,20 @@ impl ConnectedClientHandler {
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = &mut self.close_rx => {
|
||||
log::info!("client handler stopping: received close: {}", self.nym_address);
|
||||
log::info!("client handler stopping: received close: {}", self.sent_by);
|
||||
break;
|
||||
},
|
||||
_ = self.activity_timeout.tick() => {
|
||||
log::info!("client handler stopping: activity timeout: {}", self.nym_address);
|
||||
log::info!("client handler stopping: activity timeout: {}", self.sent_by);
|
||||
break;
|
||||
},
|
||||
Some(packets) = self.encoder.buffer_timeout() => {
|
||||
if let Err(err) = self.handle_buffer_timeout(packets).await {
|
||||
log::error!("client handler: failed to handle buffer timeout: {err}");
|
||||
}
|
||||
packets = self.encoder.buffer_timeout() => match packets {
|
||||
Some(packets) => {
|
||||
if let Err(err) = self.handle_buffer_timeout(packets).await {
|
||||
log::error!("client handler: failed to handle buffer timeout: {err}");
|
||||
}
|
||||
},
|
||||
None => log::trace!("no packets to send"),
|
||||
},
|
||||
packet = self.forward_from_tun_rx.recv() => match packet {
|
||||
Some(packet) => {
|
||||
@@ -0,0 +1,257 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr},
|
||||
sync::Arc,
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use nym_ip_packet_requests::IpPair;
|
||||
use tokio::sync::{mpsc, oneshot, RwLock};
|
||||
|
||||
use crate::{
|
||||
constants::CLIENT_MIXNET_INACTIVITY_TIMEOUT,
|
||||
error::{IpPacketRouterError, Result},
|
||||
tun_listener,
|
||||
};
|
||||
|
||||
use super::ConnectedClientId;
|
||||
|
||||
pub(crate) struct ConnectedClients {
|
||||
// The set of connected clients
|
||||
clients_ipv4_mapping: HashMap<Ipv4Addr, ConnectedClient>,
|
||||
clients_ipv6_mapping: HashMap<Ipv6Addr, ConnectedClient>,
|
||||
|
||||
// Notify the tun listener when a new client connects or disconnects
|
||||
tun_listener_connected_client_tx: mpsc::UnboundedSender<ConnectedClientEvent>,
|
||||
}
|
||||
|
||||
impl ConnectedClients {
|
||||
pub(crate) fn new() -> (Self, tun_listener::ConnectedClientsListener) {
|
||||
let (connected_client_tx, connected_client_rx) = mpsc::unbounded_channel();
|
||||
(
|
||||
Self {
|
||||
clients_ipv4_mapping: Default::default(),
|
||||
clients_ipv6_mapping: Default::default(),
|
||||
tun_listener_connected_client_tx: connected_client_tx,
|
||||
},
|
||||
tun_listener::ConnectedClientsListener::new(connected_client_rx),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn is_ip_connected(&self, ips: &IpPair) -> bool {
|
||||
self.clients_ipv4_mapping.contains_key(&ips.ipv4)
|
||||
|| self.clients_ipv6_mapping.contains_key(&ips.ipv6)
|
||||
}
|
||||
|
||||
pub(crate) fn get_client_from_ip_mut(&mut self, ip: &IpAddr) -> Option<&mut ConnectedClient> {
|
||||
match ip {
|
||||
IpAddr::V4(ip) => self.clients_ipv4_mapping.get_mut(ip),
|
||||
IpAddr::V6(ip) => self.clients_ipv6_mapping.get_mut(ip),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_client_connected(&self, client_id: &ConnectedClientId) -> bool {
|
||||
self.clients_ipv4_mapping
|
||||
.values()
|
||||
.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 connect(
|
||||
&mut self,
|
||||
ips: IpPair,
|
||||
client_id: ConnectedClientId,
|
||||
forward_from_tun_tx: mpsc::UnboundedSender<Vec<u8>>,
|
||||
close_tx: oneshot::Sender<()>,
|
||||
handle: tokio::task::JoinHandle<()>,
|
||||
) {
|
||||
// The map of connected clients that the mixnet listener keeps track of. It monitors
|
||||
// activity and disconnects clients that have been inactive for too long.
|
||||
let client = ConnectedClient {
|
||||
client_id: client_id.clone(),
|
||||
ipv6: ips.ipv6,
|
||||
last_activity: Arc::new(RwLock::new(Instant::now())),
|
||||
close_tx: Arc::new(CloseTx {
|
||||
client_id,
|
||||
inner: Some(close_tx),
|
||||
}),
|
||||
handle: Arc::new(handle),
|
||||
};
|
||||
|
||||
log::info!("Inserting {} and {}", ips.ipv4, ips.ipv6);
|
||||
self.clients_ipv4_mapping.insert(ips.ipv4, client.clone());
|
||||
self.clients_ipv6_mapping.insert(ips.ipv6, client);
|
||||
|
||||
// Send the connected client info to the tun listener, which will use it to forward packets
|
||||
// to the connected client handler.
|
||||
self.tun_listener_connected_client_tx
|
||||
.send(ConnectedClientEvent::Connect(Box::new(ConnectEvent {
|
||||
ips,
|
||||
forward_from_tun_tx,
|
||||
})))
|
||||
.inspect_err(|err| {
|
||||
log::error!("Failed to send connected client event: {err}");
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
pub(crate) async fn update_activity(&mut self, ips: &IpPair) -> Result<()> {
|
||||
if let Some(client) = self.clients_ipv4_mapping.get(&ips.ipv4) {
|
||||
*client.last_activity.write().await = Instant::now();
|
||||
Ok(())
|
||||
} else {
|
||||
Err(IpPacketRouterError::FailedToUpdateClientActivity)
|
||||
}
|
||||
}
|
||||
|
||||
// Identify connected client handlers that have stopped without being told to stop
|
||||
pub(crate) fn get_finished_client_handlers(&mut self) -> Vec<(IpPair, ConnectedClientId)> {
|
||||
self.clients_ipv4_mapping
|
||||
.iter_mut()
|
||||
.filter_map(|(ip, connected_client)| {
|
||||
if connected_client.handle.is_finished() {
|
||||
Some((
|
||||
IpPair::new(*ip, connected_client.ipv6),
|
||||
connected_client.client_id.clone(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) async fn get_inactive_clients(&mut self) -> Vec<(IpPair, ConnectedClientId)> {
|
||||
let now = Instant::now();
|
||||
let mut ret = vec![];
|
||||
for (ip, connected_client) in self.clients_ipv4_mapping.iter() {
|
||||
if now.duration_since(*connected_client.last_activity.read().await)
|
||||
> CLIENT_MIXNET_INACTIVITY_TIMEOUT
|
||||
{
|
||||
ret.push((
|
||||
IpPair::new(*ip, connected_client.ipv6),
|
||||
connected_client.client_id.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
pub(crate) fn disconnect_stopped_client_handlers(
|
||||
&mut self,
|
||||
stopped_clients: Vec<(IpPair, ConnectedClientId)>,
|
||||
) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn disconnect_inactive_clients(
|
||||
&mut self,
|
||||
inactive_clients: Vec<(IpPair, ConnectedClientId)>,
|
||||
) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn find_new_ip(&self) -> Option<IpPair> {
|
||||
crate::util::generate_new_ip::find_new_ips(
|
||||
&self.clients_ipv4_mapping,
|
||||
&self.clients_ipv6_mapping,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct CloseTx {
|
||||
// pub(crate) nym_address: Recipient,
|
||||
pub(crate) client_id: ConnectedClientId,
|
||||
// Send to connected clients listener to stop. This is option only because we need to take
|
||||
// ownership of it when the client is dropped.
|
||||
pub(crate) inner: Option<oneshot::Sender<()>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ConnectedClient {
|
||||
// The nym address of the connected client that we are communicating with on the other side of
|
||||
// the mixnet
|
||||
// pub(crate) nym_address: Recipient,
|
||||
pub(crate) client_id: ConnectedClientId,
|
||||
|
||||
// The assigned IPv6 address of this client
|
||||
pub(crate) ipv6: Ipv6Addr,
|
||||
|
||||
// Keep track of last activity so we can disconnect inactive clients
|
||||
pub(crate) last_activity: Arc<RwLock<Instant>>,
|
||||
|
||||
// Channel to send close signal to the connected client handler
|
||||
// This is currently unused because the disconnect command it not yet implemented.
|
||||
#[allow(unused)]
|
||||
pub(crate) close_tx: Arc<CloseTx>,
|
||||
|
||||
// Handle for the connected client handler
|
||||
pub(crate) handle: Arc<tokio::task::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl ConnectedClient {
|
||||
pub(crate) async fn update_activity(&self) {
|
||||
*self.last_activity.write().await = Instant::now();
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CloseTx {
|
||||
fn drop(&mut self) {
|
||||
log::debug!("signal to close client: {}", self.client_id);
|
||||
if let Some(close_tx) = self.inner.take() {
|
||||
close_tx.send(()).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum ConnectedClientEvent {
|
||||
Disconnect(DisconnectEvent),
|
||||
Connect(Box<ConnectEvent>),
|
||||
}
|
||||
|
||||
pub(crate) struct DisconnectEvent(pub(crate) IpPair);
|
||||
|
||||
pub(crate) struct ConnectEvent {
|
||||
pub(crate) ips: IpPair,
|
||||
pub(crate) forward_from_tun_tx: mpsc::UnboundedSender<Vec<u8>>,
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
mod client_id;
|
||||
mod connected_client_handler;
|
||||
mod connected_clients;
|
||||
|
||||
pub(crate) use client_id::ConnectedClientId;
|
||||
pub(crate) use connected_client_handler::ConnectedClientHandler;
|
||||
pub(crate) use connected_clients::{
|
||||
ConnectEvent, ConnectedClientEvent, ConnectedClients, DisconnectEvent,
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::config::default_config_filepath;
|
||||
use crate::config::old_config_v1::ConfigV1;
|
||||
|
||||
@@ -199,6 +199,7 @@ impl Default for IpPacketRouter {
|
||||
fn default() -> Self {
|
||||
IpPacketRouter {
|
||||
disable_poisson_rate: true,
|
||||
#[allow(clippy::expect_used)]
|
||||
upstream_exit_policy_url: Some(
|
||||
mainnet::EXIT_POLICY_URL
|
||||
.parse()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::config::persistence::IpPacketRouterPaths;
|
||||
use crate::config::Config;
|
||||
@@ -76,6 +76,7 @@ impl Default for IpPacketRouterV1 {
|
||||
fn default() -> Self {
|
||||
IpPacketRouterV1 {
|
||||
disable_poisson_rate: true,
|
||||
#[allow(clippy::expect_used)]
|
||||
upstream_exit_policy_url: Some(
|
||||
mainnet::EXIT_POLICY_URL
|
||||
.parse()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_client_core::config::disk_persistence::CommonClientPaths;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pub(crate) const CONFIG_TEMPLATE: &str =
|
||||
// While using normal toml marshalling would have been way simpler with less overhead,
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
// We routinely check if any clients needs to be disconnected at this interval
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::net::SocketAddr;
|
||||
|
||||
pub use nym_client_core::error::ClientCoreError;
|
||||
use nym_exit_policy::PolicyError;
|
||||
use nym_id::NymIdError;
|
||||
use nym_ip_packet_requests::sign::SignatureError;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum IpPacketRouterError {
|
||||
@@ -90,12 +94,13 @@ pub enum IpPacketRouterError {
|
||||
EmptyPacket,
|
||||
|
||||
#[error("failed to verify request: {source}")]
|
||||
FailedToVerifyRequest {
|
||||
source: nym_ip_packet_requests::v7::signature::SignatureError,
|
||||
},
|
||||
FailedToVerifyRequest { source: SignatureError },
|
||||
|
||||
#[error("client is connected with an invalid version: {version}")]
|
||||
InvalidConnectedClientVersion { version: u8 },
|
||||
|
||||
#[error("invalid reply-to address in the response")]
|
||||
InvalidReplyTo,
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, IpPacketRouterError>;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#![cfg_attr(not(target_os = "linux"), allow(dead_code))]
|
||||
#![cfg_attr(not(target_os = "linux"), allow(unused_imports))]
|
||||
|
||||
@@ -11,11 +14,7 @@ use nym_client_core::{
|
||||
use nym_sdk::mixnet::Recipient;
|
||||
use nym_task::{TaskClient, TaskHandle};
|
||||
|
||||
use crate::{
|
||||
config::Config,
|
||||
error::IpPacketRouterError,
|
||||
request_filter::{self, RequestFilter},
|
||||
};
|
||||
use crate::{config::Config, error::IpPacketRouterError, request_filter::RequestFilter};
|
||||
|
||||
pub struct OnStartData {
|
||||
// to add more fields as required
|
||||
@@ -127,7 +126,10 @@ impl IpPacketRouter {
|
||||
pub async fn run_service_provider(self) -> Result<(), IpPacketRouterError> {
|
||||
// Used to notify tasks to shutdown. Not all tasks fully supports this (yet).
|
||||
|
||||
use crate::{mixnet_listener, tun_listener};
|
||||
use crate::{
|
||||
clients::ConnectedClients, mixnet_listener::MixnetListener,
|
||||
request_filter::RequestFilter, tun_listener::TunListener,
|
||||
};
|
||||
let task_handle: TaskHandle = self.shutdown.map(Into::into).unwrap_or_default();
|
||||
|
||||
// Connect to the mixnet
|
||||
@@ -157,19 +159,19 @@ impl IpPacketRouter {
|
||||
|
||||
// Channel used by the IpPacketRouter to signal connected and disconnected clients to the
|
||||
// TunListener
|
||||
let (connected_clients, connected_clients_rx) = mixnet_listener::ConnectedClients::new();
|
||||
let (connected_clients, connected_clients_rx) = ConnectedClients::new();
|
||||
|
||||
let tun_listener = tun_listener::TunListener {
|
||||
let tun_listener = TunListener {
|
||||
tun_reader,
|
||||
task_client: task_handle.get_handle(),
|
||||
connected_clients: connected_clients_rx,
|
||||
};
|
||||
tun_listener.start();
|
||||
|
||||
let request_filter = request_filter::RequestFilter::new(&self.config).await?;
|
||||
let request_filter = RequestFilter::new(&self.config).await?;
|
||||
request_filter.start_update_tasks().await;
|
||||
|
||||
let mixnet_listener = mixnet_listener::MixnetListener {
|
||||
let mixnet_listener = MixnetListener {
|
||||
_config: self.config,
|
||||
request_filter: request_filter.clone(),
|
||||
tun_writer,
|
||||
|
||||
@@ -1,17 +1,26 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
#![warn(clippy::panic)]
|
||||
#![cfg_attr(not(target_os = "linux"), allow(dead_code))]
|
||||
#![cfg_attr(not(target_os = "linux"), allow(unused_imports))]
|
||||
|
||||
pub use crate::config::Config;
|
||||
pub use ip_packet_router::{IpPacketRouter, OnStartData};
|
||||
|
||||
pub mod config;
|
||||
mod connected_client_handler;
|
||||
mod constants;
|
||||
pub mod error;
|
||||
pub mod request_filter;
|
||||
|
||||
pub(crate) mod messages;
|
||||
pub(crate) mod non_linux_dummy;
|
||||
|
||||
mod clients;
|
||||
mod constants;
|
||||
mod ip_packet_router;
|
||||
mod mixnet_client;
|
||||
mod mixnet_listener;
|
||||
pub(crate) mod non_linux_dummy;
|
||||
pub mod request_filter;
|
||||
mod tun_listener;
|
||||
mod util;
|
||||
|
||||
pub use crate::config::Config;
|
||||
pub use ip_packet_router::{IpPacketRouter, OnStartData};
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod cli;
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pub(crate) mod request;
|
||||
pub(crate) mod response;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub(crate) enum ClientVersion {
|
||||
V6,
|
||||
V7,
|
||||
V8,
|
||||
}
|
||||
|
||||
impl ClientVersion {
|
||||
pub(crate) fn into_u8(self) -> u8 {
|
||||
match self {
|
||||
ClientVersion::V6 => 6,
|
||||
ClientVersion::V7 => 7,
|
||||
ClientVersion::V8 => 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
mod v6;
|
||||
mod v7;
|
||||
mod v8;
|
||||
|
||||
use nym_ip_packet_requests::{
|
||||
v6::request::IpPacketRequest as IpPacketRequestV6,
|
||||
v7::request::IpPacketRequest as IpPacketRequestV7,
|
||||
v8::request::IpPacketRequest as IpPacketRequestV8, IpPair,
|
||||
};
|
||||
use nym_sdk::mixnet::ReconstructedMessage;
|
||||
use std::fmt;
|
||||
|
||||
use crate::{clients::ConnectedClientId, error::IpPacketRouterError};
|
||||
|
||||
use super::ClientVersion;
|
||||
|
||||
// The internal representation of the request after deserialization, valid for all versions
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub(crate) enum IpPacketRequest {
|
||||
Data(DataRequest),
|
||||
Control(ControlRequest),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub(crate) struct DataRequest {
|
||||
pub(crate) version: ClientVersion,
|
||||
pub(crate) ip_packets: bytes::Bytes,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub(crate) enum ControlRequest {
|
||||
StaticConnect(StaticConnectRequest),
|
||||
DynamicConnect(DynamicConnectRequest),
|
||||
Disconnect(DisconnectRequest),
|
||||
Ping(PingRequest),
|
||||
Health(HealthRequest),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub(crate) struct StaticConnectRequest {
|
||||
pub(crate) version: ClientVersion,
|
||||
pub(crate) request_id: u64,
|
||||
pub(crate) sent_by: ConnectedClientId,
|
||||
pub(crate) ips: IpPair,
|
||||
pub(crate) buffer_timeout: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub(crate) struct DynamicConnectRequest {
|
||||
pub(crate) version: ClientVersion,
|
||||
pub(crate) request_id: u64,
|
||||
pub(crate) sent_by: ConnectedClientId,
|
||||
pub(crate) buffer_timeout: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub(crate) struct DisconnectRequest {
|
||||
pub(crate) version: ClientVersion,
|
||||
pub(crate) request_id: u64,
|
||||
pub(crate) sent_by: ConnectedClientId,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub(crate) struct PingRequest {
|
||||
pub(crate) version: ClientVersion,
|
||||
pub(crate) request_id: u64,
|
||||
pub(crate) sent_by: ConnectedClientId,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub(crate) struct HealthRequest {
|
||||
pub(crate) version: ClientVersion,
|
||||
pub(crate) request_id: u64,
|
||||
pub(crate) sent_by: ConnectedClientId,
|
||||
}
|
||||
|
||||
impl TryFrom<&ReconstructedMessage> for IpPacketRequest {
|
||||
type Error = IpPacketRouterError;
|
||||
|
||||
fn try_from(reconstructed: &ReconstructedMessage) -> Result<Self, Self::Error> {
|
||||
let request_version = *reconstructed
|
||||
.message
|
||||
.first()
|
||||
.ok_or(IpPacketRouterError::EmptyPacket)?;
|
||||
|
||||
match request_version {
|
||||
6 => {
|
||||
let request_v6 = IpPacketRequestV6::from_reconstructed_message(reconstructed)
|
||||
.map_err(
|
||||
|source| IpPacketRouterError::FailedToDeserializeTaggedPacket { source },
|
||||
)?;
|
||||
Ok(IpPacketRequest::from(request_v6))
|
||||
}
|
||||
7 => {
|
||||
let request_v7 = IpPacketRequestV7::from_reconstructed_message(reconstructed)
|
||||
.map_err(
|
||||
|source| IpPacketRouterError::FailedToDeserializeTaggedPacket { source },
|
||||
)?;
|
||||
request_v7
|
||||
.verify()
|
||||
.map_err(|source| IpPacketRouterError::FailedToVerifyRequest { source })?;
|
||||
Ok(IpPacketRequest::from(request_v7))
|
||||
}
|
||||
8 => {
|
||||
let request_v8 = IpPacketRequestV8::from_reconstructed_message(reconstructed)
|
||||
.map_err(
|
||||
|source| IpPacketRouterError::FailedToDeserializeTaggedPacket { source },
|
||||
)?;
|
||||
request_v8
|
||||
.verify()
|
||||
.map_err(|source| IpPacketRouterError::FailedToVerifyRequest { source })?;
|
||||
IpPacketRequest::try_from((request_v8, reconstructed.sender_tag))
|
||||
}
|
||||
_ => {
|
||||
log::info!("Received packet with invalid version: v{request_version}");
|
||||
Err(IpPacketRouterError::InvalidPacketVersion(request_version))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for IpPacketRequest {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
IpPacketRequest::Data(_) => write!(f, "Data"),
|
||||
IpPacketRequest::Control(control) => write!(f, "{control}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ControlRequest {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ControlRequest::StaticConnect(_) => write!(f, "StaticConnect"),
|
||||
ControlRequest::DynamicConnect(_) => write!(f, "DynamicConnect"),
|
||||
ControlRequest::Disconnect(_) => write!(f, "Disconnect"),
|
||||
ControlRequest::Ping(_) => write!(f, "Ping"),
|
||||
ControlRequest::Health(_) => write!(f, "Health"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_ip_packet_requests::v6::request::{
|
||||
DataRequest as DataRequestV6, DisconnectRequest as DisconnectRequestV6,
|
||||
DynamicConnectRequest as DynamicConnectRequestV6, HealthRequest as HealthRequestV6,
|
||||
IpPacketRequest as IpPacketRequestV6, IpPacketRequestData as IpPacketRequestDataV6,
|
||||
PingRequest as PingRequestV6, StaticConnectRequest as StaticConnectRequestV6,
|
||||
};
|
||||
|
||||
use super::{
|
||||
ClientVersion, ControlRequest, DataRequest, DisconnectRequest, DynamicConnectRequest,
|
||||
HealthRequest, IpPacketRequest, PingRequest, StaticConnectRequest,
|
||||
};
|
||||
|
||||
impl From<IpPacketRequestV6> for IpPacketRequest {
|
||||
fn from(request: IpPacketRequestV6) -> Self {
|
||||
let version = ClientVersion::V6;
|
||||
match request.data {
|
||||
IpPacketRequestDataV6::Data(inner) => Self::Data((inner, version).into()),
|
||||
IpPacketRequestDataV6::StaticConnect(inner) => {
|
||||
Self::Control(ControlRequest::StaticConnect((inner, version).into()))
|
||||
}
|
||||
IpPacketRequestDataV6::DynamicConnect(inner) => {
|
||||
Self::Control(ControlRequest::DynamicConnect((inner, version).into()))
|
||||
}
|
||||
IpPacketRequestDataV6::Disconnect(inner) => {
|
||||
Self::Control(ControlRequest::Disconnect((inner, version).into()))
|
||||
}
|
||||
IpPacketRequestDataV6::Ping(inner) => {
|
||||
Self::Control(ControlRequest::Ping((inner, version).into()))
|
||||
}
|
||||
IpPacketRequestDataV6::Health(inner) => {
|
||||
Self::Control(ControlRequest::Health((inner, version).into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(DataRequestV6, ClientVersion)> for DataRequest {
|
||||
fn from((request, version): (DataRequestV6, ClientVersion)) -> Self {
|
||||
Self {
|
||||
version,
|
||||
ip_packets: request.ip_packets,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(StaticConnectRequestV6, ClientVersion)> for StaticConnectRequest {
|
||||
fn from((request, version): (StaticConnectRequestV6, ClientVersion)) -> Self {
|
||||
Self {
|
||||
version,
|
||||
request_id: request.request_id,
|
||||
sent_by: request.reply_to.into(),
|
||||
ips: request.ips,
|
||||
buffer_timeout: request.buffer_timeout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(DynamicConnectRequestV6, ClientVersion)> for DynamicConnectRequest {
|
||||
fn from((request, version): (DynamicConnectRequestV6, ClientVersion)) -> Self {
|
||||
Self {
|
||||
version,
|
||||
request_id: request.request_id,
|
||||
sent_by: request.reply_to.into(),
|
||||
buffer_timeout: request.buffer_timeout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(DisconnectRequestV6, ClientVersion)> for DisconnectRequest {
|
||||
fn from((request, version): (DisconnectRequestV6, ClientVersion)) -> Self {
|
||||
Self {
|
||||
version,
|
||||
request_id: request.request_id,
|
||||
sent_by: request.reply_to.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(PingRequestV6, ClientVersion)> for PingRequest {
|
||||
fn from((request, version): (PingRequestV6, ClientVersion)) -> Self {
|
||||
Self {
|
||||
version,
|
||||
request_id: request.request_id,
|
||||
sent_by: request.reply_to.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(HealthRequestV6, ClientVersion)> for HealthRequest {
|
||||
fn from((request, version): (HealthRequestV6, ClientVersion)) -> Self {
|
||||
Self {
|
||||
version,
|
||||
request_id: request.request_id,
|
||||
sent_by: request.reply_to.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_ip_packet_requests::v7::request::{
|
||||
DataRequest as DataRequestV7, DisconnectRequest as DisconnectRequestV7,
|
||||
DynamicConnectRequest as DynamicConnectRequestV7, HealthRequest as HealthRequestV7,
|
||||
IpPacketRequest as IpPacketRequestV7, IpPacketRequestData as IpPacketRequestDataV7,
|
||||
PingRequest as PingRequestV7, StaticConnectRequest as StaticConnectRequestV7,
|
||||
};
|
||||
|
||||
use super::{
|
||||
ClientVersion, ControlRequest, DataRequest, DisconnectRequest, DynamicConnectRequest,
|
||||
HealthRequest, IpPacketRequest, PingRequest, StaticConnectRequest,
|
||||
};
|
||||
|
||||
impl From<IpPacketRequestV7> for IpPacketRequest {
|
||||
fn from(request: IpPacketRequestV7) -> Self {
|
||||
let version = ClientVersion::V7;
|
||||
match request.data {
|
||||
IpPacketRequestDataV7::Data(inner) => Self::Data((inner, version).into()),
|
||||
IpPacketRequestDataV7::StaticConnect(inner) => Self::Control(
|
||||
ControlRequest::StaticConnect((inner.request, version).into()),
|
||||
),
|
||||
IpPacketRequestDataV7::DynamicConnect(inner) => Self::Control(
|
||||
ControlRequest::DynamicConnect((inner.request, version).into()),
|
||||
),
|
||||
IpPacketRequestDataV7::Disconnect(inner) => {
|
||||
Self::Control(ControlRequest::Disconnect((inner.request, version).into()))
|
||||
}
|
||||
IpPacketRequestDataV7::Ping(inner) => {
|
||||
Self::Control(ControlRequest::Ping((inner, version).into()))
|
||||
}
|
||||
IpPacketRequestDataV7::Health(inner) => {
|
||||
Self::Control(ControlRequest::Health((inner, version).into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(DataRequestV7, ClientVersion)> for DataRequest {
|
||||
fn from((request, version): (DataRequestV7, ClientVersion)) -> Self {
|
||||
Self {
|
||||
version,
|
||||
ip_packets: request.ip_packets,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(StaticConnectRequestV7, ClientVersion)> for StaticConnectRequest {
|
||||
fn from((request, version): (StaticConnectRequestV7, ClientVersion)) -> Self {
|
||||
Self {
|
||||
version,
|
||||
request_id: request.request_id,
|
||||
sent_by: request.reply_to.into(),
|
||||
ips: request.ips,
|
||||
buffer_timeout: request.buffer_timeout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(DynamicConnectRequestV7, ClientVersion)> for DynamicConnectRequest {
|
||||
fn from((request, version): (DynamicConnectRequestV7, ClientVersion)) -> Self {
|
||||
Self {
|
||||
version,
|
||||
request_id: request.request_id,
|
||||
sent_by: request.reply_to.into(),
|
||||
buffer_timeout: request.buffer_timeout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(DisconnectRequestV7, ClientVersion)> for DisconnectRequest {
|
||||
fn from((request, version): (DisconnectRequestV7, ClientVersion)) -> Self {
|
||||
Self {
|
||||
version,
|
||||
request_id: request.request_id,
|
||||
sent_by: request.reply_to.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(PingRequestV7, ClientVersion)> for PingRequest {
|
||||
fn from((request, version): (PingRequestV7, ClientVersion)) -> Self {
|
||||
Self {
|
||||
version,
|
||||
request_id: request.request_id,
|
||||
sent_by: request.reply_to.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(HealthRequestV7, ClientVersion)> for HealthRequest {
|
||||
fn from((request, version): (HealthRequestV7, ClientVersion)) -> Self {
|
||||
Self {
|
||||
version,
|
||||
request_id: request.request_id,
|
||||
sent_by: request.reply_to.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_ip_packet_requests::v8::request::{
|
||||
ControlRequest as ControlRequestV8, DataRequest as DataRequestV8,
|
||||
DisconnectRequest as DisconnectRequestV8, DynamicConnectRequest as DynamicConnectRequestV8,
|
||||
HealthRequest as HealthRequestV8, IpPacketRequest as IpPacketRequestV8,
|
||||
IpPacketRequestData as IpPacketRequestDataV8, PingRequest as PingRequestV8,
|
||||
StaticConnectRequest as StaticConnectRequestV8,
|
||||
};
|
||||
use nym_sdk::mixnet::AnonymousSenderTag;
|
||||
|
||||
use crate::error::IpPacketRouterError;
|
||||
|
||||
use super::{
|
||||
ClientVersion, ConnectedClientId, ControlRequest, DataRequest, DisconnectRequest,
|
||||
DynamicConnectRequest, HealthRequest, IpPacketRequest, PingRequest, StaticConnectRequest,
|
||||
};
|
||||
|
||||
impl TryFrom<(IpPacketRequestV8, Option<AnonymousSenderTag>)> for IpPacketRequest {
|
||||
type Error = IpPacketRouterError;
|
||||
|
||||
fn try_from(
|
||||
(request, sender_tag): (IpPacketRequestV8, Option<AnonymousSenderTag>),
|
||||
) -> Result<Self, Self::Error> {
|
||||
let version = ClientVersion::V8;
|
||||
Ok(match request.data {
|
||||
IpPacketRequestDataV8::Data(inner) => Self::Data((inner, version).into()),
|
||||
IpPacketRequestDataV8::Control(inner) => {
|
||||
Self::Control((*inner, sender_tag, version).try_into()?)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(DataRequestV8, ClientVersion)> for DataRequest {
|
||||
fn from((request, version): (DataRequestV8, ClientVersion)) -> Self {
|
||||
Self {
|
||||
version,
|
||||
ip_packets: request.ip_packets,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<(ControlRequestV8, Option<AnonymousSenderTag>, ClientVersion)> for ControlRequest {
|
||||
type Error = IpPacketRouterError;
|
||||
|
||||
fn try_from(
|
||||
(request, sender_tag, version): (
|
||||
ControlRequestV8,
|
||||
Option<AnonymousSenderTag>,
|
||||
ClientVersion,
|
||||
),
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(match request {
|
||||
ControlRequestV8::StaticConnect(inner) => {
|
||||
ControlRequest::StaticConnect((inner.request, sender_tag, version).try_into()?)
|
||||
}
|
||||
ControlRequestV8::DynamicConnect(inner) => {
|
||||
ControlRequest::DynamicConnect((inner.request, sender_tag, version).try_into()?)
|
||||
}
|
||||
ControlRequestV8::Disconnect(inner) => {
|
||||
ControlRequest::Disconnect((inner.request, sender_tag, version).try_into()?)
|
||||
}
|
||||
ControlRequestV8::Ping(inner) => {
|
||||
ControlRequest::Ping((inner, sender_tag, version).try_into()?)
|
||||
}
|
||||
ControlRequestV8::Health(inner) => {
|
||||
ControlRequest::Health((inner, sender_tag, version).try_into()?)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl
|
||||
TryFrom<(
|
||||
StaticConnectRequestV8,
|
||||
Option<AnonymousSenderTag>,
|
||||
ClientVersion,
|
||||
)> for StaticConnectRequest
|
||||
{
|
||||
type Error = IpPacketRouterError;
|
||||
|
||||
fn try_from(
|
||||
(request, sender_tag, version): (
|
||||
StaticConnectRequestV8,
|
||||
Option<AnonymousSenderTag>,
|
||||
ClientVersion,
|
||||
),
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
version,
|
||||
request_id: request.request_id,
|
||||
sent_by: ConnectedClientId::try_from((request.sender, sender_tag))?,
|
||||
ips: request.ips,
|
||||
buffer_timeout: request.buffer_timeout,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl
|
||||
TryFrom<(
|
||||
DynamicConnectRequestV8,
|
||||
Option<AnonymousSenderTag>,
|
||||
ClientVersion,
|
||||
)> for DynamicConnectRequest
|
||||
{
|
||||
type Error = IpPacketRouterError;
|
||||
|
||||
fn try_from(
|
||||
(request, sender_tag, version): (
|
||||
DynamicConnectRequestV8,
|
||||
Option<AnonymousSenderTag>,
|
||||
ClientVersion,
|
||||
),
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
version,
|
||||
request_id: request.request_id,
|
||||
sent_by: ConnectedClientId::try_from((request.sender, sender_tag))?,
|
||||
buffer_timeout: request.buffer_timeout,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl
|
||||
TryFrom<(
|
||||
DisconnectRequestV8,
|
||||
Option<AnonymousSenderTag>,
|
||||
ClientVersion,
|
||||
)> for DisconnectRequest
|
||||
{
|
||||
type Error = IpPacketRouterError;
|
||||
|
||||
fn try_from(
|
||||
(request, sender_tag, version): (
|
||||
DisconnectRequestV8,
|
||||
Option<AnonymousSenderTag>,
|
||||
ClientVersion,
|
||||
),
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
version,
|
||||
request_id: request.request_id,
|
||||
sent_by: ConnectedClientId::try_from((request.sender, sender_tag))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<(PingRequestV8, Option<AnonymousSenderTag>, ClientVersion)> for PingRequest {
|
||||
type Error = IpPacketRouterError;
|
||||
|
||||
fn try_from(
|
||||
(request, sender_tag, version): (PingRequestV8, Option<AnonymousSenderTag>, ClientVersion),
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
version,
|
||||
request_id: request.request_id,
|
||||
sent_by: ConnectedClientId::try_from((request.sender, sender_tag))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<(HealthRequestV8, Option<AnonymousSenderTag>, ClientVersion)> for HealthRequest {
|
||||
type Error = IpPacketRouterError;
|
||||
|
||||
fn try_from(
|
||||
(request, sender_tag, version): (
|
||||
HealthRequestV8,
|
||||
Option<AnonymousSenderTag>,
|
||||
ClientVersion,
|
||||
),
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
version,
|
||||
request_id: request.request_id,
|
||||
sent_by: ConnectedClientId::try_from((request.sender, sender_tag))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
mod v6;
|
||||
mod v7;
|
||||
mod v8;
|
||||
|
||||
use nym_bin_common::build_information::BinaryBuildInformationOwned;
|
||||
use nym_ip_packet_requests::{
|
||||
v6::response::IpPacketResponse as IpPacketResponseV6,
|
||||
v7::response::IpPacketResponse as IpPacketResponseV7,
|
||||
v8::response::IpPacketResponse as IpPacketResponseV8, IpPair,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
clients::ConnectedClientId,
|
||||
error::{IpPacketRouterError, Result},
|
||||
};
|
||||
|
||||
use super::ClientVersion;
|
||||
|
||||
pub(crate) struct VersionedResponse {
|
||||
pub(crate) version: ClientVersion,
|
||||
pub(crate) reply_to: ConnectedClientId,
|
||||
pub(crate) response: Response,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum Response {
|
||||
StaticConnect {
|
||||
request_id: u64,
|
||||
reply: StaticConnectResponse,
|
||||
},
|
||||
DynamicConnect {
|
||||
request_id: u64,
|
||||
reply: DynamicConnectResponse,
|
||||
},
|
||||
// Disconnect is not yet implemented
|
||||
#[allow(unused)]
|
||||
Disconnect {
|
||||
request_id: u64,
|
||||
reply: DisconnectResponse,
|
||||
},
|
||||
Pong {
|
||||
request_id: u64,
|
||||
},
|
||||
Health {
|
||||
request_id: u64,
|
||||
reply: Box<HealthResponse>,
|
||||
},
|
||||
Info {
|
||||
request_id: u64,
|
||||
reply: InfoResponse,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum StaticConnectResponse {
|
||||
Success,
|
||||
Failure(StaticConnectFailureReason),
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug, Clone)]
|
||||
pub(crate) enum StaticConnectFailureReason {
|
||||
#[error("requested ip address is already in use")]
|
||||
RequestedIpAlreadyInUse,
|
||||
|
||||
#[error("client already connected")]
|
||||
ClientAlreadyConnected,
|
||||
|
||||
#[allow(unused)]
|
||||
#[error("request timestamp is out of date")]
|
||||
OutOfDateTimestamp,
|
||||
|
||||
#[allow(unused)]
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum DynamicConnectResponse {
|
||||
Success(DynamicConnectSuccess),
|
||||
Failure(DynamicConnectFailureReason),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct DynamicConnectSuccess {
|
||||
pub(crate) ips: IpPair,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, thiserror::Error)]
|
||||
pub(crate) enum DynamicConnectFailureReason {
|
||||
#[error("client already connected")]
|
||||
ClientAlreadyConnected,
|
||||
|
||||
#[error("no available ip address")]
|
||||
NoAvailableIp,
|
||||
|
||||
#[allow(unused)]
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
// Disconnect is not yet implemented
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum DisconnectResponse {
|
||||
Success,
|
||||
Failure(DisconnectFailureReason),
|
||||
}
|
||||
|
||||
// Disconnect is not yet implemented
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
pub(crate) enum DisconnectFailureReason {
|
||||
#[error("requested client is not currently connected")]
|
||||
ClientNotConnected,
|
||||
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct HealthResponse {
|
||||
pub(crate) build_info: BinaryBuildInformationOwned,
|
||||
pub(crate) routable: Option<bool>,
|
||||
}
|
||||
|
||||
impl VersionedResponse {
|
||||
pub(crate) fn try_into_bytes(self) -> Result<Vec<u8>> {
|
||||
match self.version {
|
||||
ClientVersion::V6 => IpPacketResponseV6::try_from(self)?.to_bytes(),
|
||||
ClientVersion::V7 => IpPacketResponseV7::try_from(self)?.to_bytes(),
|
||||
ClientVersion::V8 => IpPacketResponseV8::from(self).to_bytes(),
|
||||
}
|
||||
.map_err(|err| IpPacketRouterError::FailedToSerializeResponsePacket { source: err })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct InfoResponse {
|
||||
pub(crate) reply: InfoResponseReply,
|
||||
pub(crate) level: InfoLevel,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, thiserror::Error)]
|
||||
pub(crate) enum InfoResponseReply {
|
||||
#[allow(unused)]
|
||||
#[error("{msg}")]
|
||||
Generic { msg: String },
|
||||
|
||||
#[allow(unused)]
|
||||
#[error(
|
||||
"version mismatch: response is v{request_version} and response is v{response_version}"
|
||||
)]
|
||||
VersionMismatch {
|
||||
request_version: u8,
|
||||
response_version: u8,
|
||||
},
|
||||
|
||||
#[error("destination failed exit policy filter check: {dst}")]
|
||||
ExitPolicyFilterCheckFailed { dst: String },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum InfoLevel {
|
||||
#[allow(unused)]
|
||||
Info,
|
||||
Warn,
|
||||
#[allow(unused)]
|
||||
Error,
|
||||
}
|
||||
|
||||
impl From<StaticConnectFailureReason> for StaticConnectResponse {
|
||||
fn from(failure: StaticConnectFailureReason) -> Self {
|
||||
StaticConnectResponse::Failure(failure)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DynamicConnectSuccess> for DynamicConnectResponse {
|
||||
fn from(success: DynamicConnectSuccess) -> Self {
|
||||
DynamicConnectResponse::Success(success)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DynamicConnectFailureReason> for DynamicConnectResponse {
|
||||
fn from(failure: DynamicConnectFailureReason) -> Self {
|
||||
DynamicConnectResponse::Failure(failure)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_ip_packet_requests::v6::response::{
|
||||
DisconnectFailureReason as DisconnectFailureReasonV6,
|
||||
DisconnectResponse as DisconnectResponseV6,
|
||||
DisconnectResponseReply as DisconnectResponseReplyV6,
|
||||
DynamicConnectFailureReason as DynamicConnectFailureReasonV6,
|
||||
DynamicConnectResponse as DynamicConnectResponseV6,
|
||||
DynamicConnectResponseReply as DynamicConnectResponseReplyV6,
|
||||
DynamicConnectSuccess as DynamicConnectSuccessV6, HealthResponse as HealthResponseV6,
|
||||
HealthResponseReply as HealthResponseReplyV6, InfoLevel as InfoLevelV6,
|
||||
InfoResponse as InfoResponseV6, InfoResponseReply as InfoResponseReplyV6,
|
||||
IpPacketResponse as IpPacketResponseV6, IpPacketResponseData as IpPacketResponseDataV6,
|
||||
PongResponse as PongResponseV6, StaticConnectFailureReason as StaticConnectFailureReasonV6,
|
||||
StaticConnectResponse as StaticConnectResponseV6,
|
||||
StaticConnectResponseReply as StaticConnectResponseReplyV6,
|
||||
};
|
||||
|
||||
use crate::error::IpPacketRouterError;
|
||||
|
||||
use super::{
|
||||
DisconnectFailureReason, DisconnectResponse, DynamicConnectFailureReason,
|
||||
DynamicConnectResponse, DynamicConnectSuccess, HealthResponse, InfoLevel, InfoResponseReply,
|
||||
Response, StaticConnectFailureReason, StaticConnectResponse, VersionedResponse,
|
||||
};
|
||||
|
||||
impl TryFrom<VersionedResponse> for IpPacketResponseV6 {
|
||||
type Error = IpPacketRouterError;
|
||||
|
||||
fn try_from(response: VersionedResponse) -> std::result::Result<Self, Self::Error> {
|
||||
let version = response.version.into_u8();
|
||||
let reply_to = response.reply_to.into_nym_address()?;
|
||||
let data = match response.response {
|
||||
Response::StaticConnect { request_id, reply } => {
|
||||
IpPacketResponseDataV6::StaticConnect(StaticConnectResponseV6 {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: reply.into(),
|
||||
})
|
||||
}
|
||||
Response::DynamicConnect { request_id, reply } => {
|
||||
IpPacketResponseDataV6::DynamicConnect(DynamicConnectResponseV6 {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: reply.into(),
|
||||
})
|
||||
}
|
||||
Response::Disconnect { request_id, reply } => {
|
||||
IpPacketResponseDataV6::Disconnect(DisconnectResponseV6 {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: reply.into(),
|
||||
})
|
||||
}
|
||||
Response::Pong { request_id } => IpPacketResponseDataV6::Pong(PongResponseV6 {
|
||||
request_id,
|
||||
reply_to,
|
||||
}),
|
||||
Response::Health { request_id, reply } => {
|
||||
IpPacketResponseDataV6::Health(HealthResponseV6 {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: (*reply).into(),
|
||||
})
|
||||
}
|
||||
Response::Info { request_id, reply } => IpPacketResponseDataV6::Info(InfoResponseV6 {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: reply.reply.into(),
|
||||
level: reply.level.into(),
|
||||
}),
|
||||
};
|
||||
Ok(IpPacketResponseV6 { version, data })
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StaticConnectResponse> for StaticConnectResponseReplyV6 {
|
||||
fn from(response: StaticConnectResponse) -> Self {
|
||||
match response {
|
||||
StaticConnectResponse::Success => StaticConnectResponseReplyV6::Success,
|
||||
StaticConnectResponse::Failure(err) => {
|
||||
StaticConnectResponseReplyV6::Failure(err.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StaticConnectFailureReason> for StaticConnectFailureReasonV6 {
|
||||
fn from(reason: StaticConnectFailureReason) -> Self {
|
||||
match reason {
|
||||
StaticConnectFailureReason::RequestedIpAlreadyInUse => {
|
||||
StaticConnectFailureReasonV6::RequestedIpAlreadyInUse
|
||||
}
|
||||
StaticConnectFailureReason::ClientAlreadyConnected => {
|
||||
StaticConnectFailureReasonV6::RequestedNymAddressAlreadyInUse
|
||||
}
|
||||
StaticConnectFailureReason::OutOfDateTimestamp => {
|
||||
StaticConnectFailureReasonV6::Other("unexpected timestamp".to_string())
|
||||
}
|
||||
StaticConnectFailureReason::Other(err) => StaticConnectFailureReasonV6::Other(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DynamicConnectResponse> for DynamicConnectResponseReplyV6 {
|
||||
fn from(response: DynamicConnectResponse) -> Self {
|
||||
match response {
|
||||
DynamicConnectResponse::Success(DynamicConnectSuccess { ips }) => {
|
||||
DynamicConnectResponseReplyV6::Success(DynamicConnectSuccessV6 { ips })
|
||||
}
|
||||
DynamicConnectResponse::Failure(err) => {
|
||||
DynamicConnectResponseReplyV6::Failure(err.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DynamicConnectFailureReason> for DynamicConnectFailureReasonV6 {
|
||||
fn from(reason: DynamicConnectFailureReason) -> Self {
|
||||
match reason {
|
||||
DynamicConnectFailureReason::ClientAlreadyConnected => {
|
||||
DynamicConnectFailureReasonV6::RequestedNymAddressAlreadyInUse
|
||||
}
|
||||
DynamicConnectFailureReason::NoAvailableIp => {
|
||||
DynamicConnectFailureReasonV6::NoAvailableIp
|
||||
}
|
||||
DynamicConnectFailureReason::Other(err) => DynamicConnectFailureReasonV6::Other(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DisconnectResponse> for DisconnectResponseReplyV6 {
|
||||
fn from(response: DisconnectResponse) -> Self {
|
||||
match response {
|
||||
DisconnectResponse::Success => DisconnectResponseReplyV6::Success,
|
||||
DisconnectResponse::Failure(err) => DisconnectResponseReplyV6::Failure(err.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DisconnectFailureReason> for DisconnectFailureReasonV6 {
|
||||
fn from(reason: DisconnectFailureReason) -> Self {
|
||||
match reason {
|
||||
DisconnectFailureReason::ClientNotConnected => {
|
||||
DisconnectFailureReasonV6::RequestedNymAddressNotConnected
|
||||
}
|
||||
DisconnectFailureReason::Other(err) => DisconnectFailureReasonV6::Other(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HealthResponse> for HealthResponseReplyV6 {
|
||||
fn from(response: HealthResponse) -> Self {
|
||||
HealthResponseReplyV6 {
|
||||
build_info: response.build_info,
|
||||
routable: response.routable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InfoResponseReply> for InfoResponseReplyV6 {
|
||||
fn from(reply: InfoResponseReply) -> Self {
|
||||
match reply {
|
||||
InfoResponseReply::Generic { msg } => InfoResponseReplyV6::Generic { msg },
|
||||
InfoResponseReply::VersionMismatch {
|
||||
request_version,
|
||||
response_version,
|
||||
} => InfoResponseReplyV6::VersionMismatch {
|
||||
request_version,
|
||||
response_version,
|
||||
},
|
||||
InfoResponseReply::ExitPolicyFilterCheckFailed { dst } => {
|
||||
InfoResponseReplyV6::ExitPolicyFilterCheckFailed { dst }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InfoLevel> for InfoLevelV6 {
|
||||
fn from(level: InfoLevel) -> Self {
|
||||
match level {
|
||||
InfoLevel::Info => InfoLevelV6::Info,
|
||||
InfoLevel::Warn => InfoLevelV6::Warn,
|
||||
InfoLevel::Error => InfoLevelV6::Error,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_ip_packet_requests::v7::response::{
|
||||
DisconnectFailureReason as DisconnectFailureReasonV7,
|
||||
DisconnectResponse as DisconnectResponseV7,
|
||||
DisconnectResponseReply as DisconnectResponseReplyV7,
|
||||
DynamicConnectFailureReason as DynamicConnectFailureReasonV7,
|
||||
DynamicConnectResponse as DynamicConnectResponseV7,
|
||||
DynamicConnectResponseReply as DynamicConnectResponseReplyV7,
|
||||
DynamicConnectSuccess as DynamicConnectSuccessV7, HealthResponse as HealthResponseV7,
|
||||
HealthResponseReply as HealthResponseReplyV7, InfoLevel as InfoLevelV7,
|
||||
InfoResponse as InfoResponseV7, InfoResponseReply as InfoResponseReplyV7,
|
||||
IpPacketResponse as IpPacketResponseV7, IpPacketResponseData as IpPacketResponseDataV7,
|
||||
PongResponse as PongResponseV7, StaticConnectFailureReason as StaticConnectFailureReasonV7,
|
||||
StaticConnectResponse as StaticConnectResponseV7,
|
||||
StaticConnectResponseReply as StaticConnectResponseReplyV7,
|
||||
};
|
||||
|
||||
use crate::error::IpPacketRouterError;
|
||||
|
||||
use super::{
|
||||
DisconnectFailureReason, DisconnectResponse, DynamicConnectFailureReason,
|
||||
DynamicConnectResponse, DynamicConnectSuccess, HealthResponse, InfoLevel, InfoResponseReply,
|
||||
Response, StaticConnectFailureReason, StaticConnectResponse, VersionedResponse,
|
||||
};
|
||||
|
||||
impl TryFrom<VersionedResponse> for IpPacketResponseV7 {
|
||||
type Error = IpPacketRouterError;
|
||||
|
||||
fn try_from(response: VersionedResponse) -> std::result::Result<Self, Self::Error> {
|
||||
let version = response.version.into_u8();
|
||||
let reply_to = response.reply_to.into_nym_address()?;
|
||||
let data = match response.response {
|
||||
Response::StaticConnect { request_id, reply } => {
|
||||
IpPacketResponseDataV7::StaticConnect(StaticConnectResponseV7 {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: reply.into(),
|
||||
})
|
||||
}
|
||||
Response::DynamicConnect { request_id, reply } => {
|
||||
IpPacketResponseDataV7::DynamicConnect(DynamicConnectResponseV7 {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: reply.into(),
|
||||
})
|
||||
}
|
||||
Response::Disconnect { request_id, reply } => {
|
||||
IpPacketResponseDataV7::Disconnect(DisconnectResponseV7 {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: reply.into(),
|
||||
})
|
||||
}
|
||||
Response::Pong { request_id } => IpPacketResponseDataV7::Pong(PongResponseV7 {
|
||||
request_id,
|
||||
reply_to,
|
||||
}),
|
||||
Response::Health { request_id, reply } => {
|
||||
IpPacketResponseDataV7::Health(HealthResponseV7 {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: (*reply).into(),
|
||||
})
|
||||
}
|
||||
Response::Info { request_id, reply } => IpPacketResponseDataV7::Info(InfoResponseV7 {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: reply.reply.into(),
|
||||
level: reply.level.into(),
|
||||
}),
|
||||
};
|
||||
Ok(IpPacketResponseV7 { version, data })
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StaticConnectResponse> for StaticConnectResponseReplyV7 {
|
||||
fn from(response: StaticConnectResponse) -> Self {
|
||||
match response {
|
||||
StaticConnectResponse::Success => StaticConnectResponseReplyV7::Success,
|
||||
StaticConnectResponse::Failure(err) => {
|
||||
StaticConnectResponseReplyV7::Failure(err.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StaticConnectFailureReason> for StaticConnectFailureReasonV7 {
|
||||
fn from(reason: StaticConnectFailureReason) -> Self {
|
||||
match reason {
|
||||
StaticConnectFailureReason::RequestedIpAlreadyInUse => {
|
||||
StaticConnectFailureReasonV7::RequestedIpAlreadyInUse
|
||||
}
|
||||
StaticConnectFailureReason::ClientAlreadyConnected => {
|
||||
StaticConnectFailureReasonV7::RequestedNymAddressAlreadyInUse
|
||||
}
|
||||
StaticConnectFailureReason::OutOfDateTimestamp => {
|
||||
StaticConnectFailureReasonV7::OutOfDateTimestamp
|
||||
}
|
||||
StaticConnectFailureReason::Other(err) => StaticConnectFailureReasonV7::Other(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DynamicConnectResponse> for DynamicConnectResponseReplyV7 {
|
||||
fn from(response: DynamicConnectResponse) -> Self {
|
||||
match response {
|
||||
DynamicConnectResponse::Success(DynamicConnectSuccess { ips }) => {
|
||||
DynamicConnectResponseReplyV7::Success(DynamicConnectSuccessV7 { ips })
|
||||
}
|
||||
DynamicConnectResponse::Failure(err) => {
|
||||
DynamicConnectResponseReplyV7::Failure(err.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DynamicConnectFailureReason> for DynamicConnectFailureReasonV7 {
|
||||
fn from(reason: DynamicConnectFailureReason) -> Self {
|
||||
match reason {
|
||||
DynamicConnectFailureReason::ClientAlreadyConnected => {
|
||||
DynamicConnectFailureReasonV7::RequestedNymAddressAlreadyInUse
|
||||
}
|
||||
DynamicConnectFailureReason::NoAvailableIp => {
|
||||
DynamicConnectFailureReasonV7::NoAvailableIp
|
||||
}
|
||||
DynamicConnectFailureReason::Other(err) => DynamicConnectFailureReasonV7::Other(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DisconnectResponse> for DisconnectResponseReplyV7 {
|
||||
fn from(response: DisconnectResponse) -> Self {
|
||||
match response {
|
||||
DisconnectResponse::Success => DisconnectResponseReplyV7::Success,
|
||||
DisconnectResponse::Failure(err) => DisconnectResponseReplyV7::Failure(err.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DisconnectFailureReason> for DisconnectFailureReasonV7 {
|
||||
fn from(reason: DisconnectFailureReason) -> Self {
|
||||
match reason {
|
||||
DisconnectFailureReason::ClientNotConnected => {
|
||||
DisconnectFailureReasonV7::RequestedNymAddressNotConnected
|
||||
}
|
||||
DisconnectFailureReason::Other(err) => DisconnectFailureReasonV7::Other(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HealthResponse> for HealthResponseReplyV7 {
|
||||
fn from(response: HealthResponse) -> Self {
|
||||
HealthResponseReplyV7 {
|
||||
build_info: response.build_info,
|
||||
routable: response.routable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InfoResponseReply> for InfoResponseReplyV7 {
|
||||
fn from(reply: InfoResponseReply) -> Self {
|
||||
match reply {
|
||||
InfoResponseReply::Generic { msg } => InfoResponseReplyV7::Generic { msg },
|
||||
InfoResponseReply::VersionMismatch {
|
||||
request_version,
|
||||
response_version,
|
||||
} => InfoResponseReplyV7::VersionMismatch {
|
||||
request_version,
|
||||
response_version,
|
||||
},
|
||||
InfoResponseReply::ExitPolicyFilterCheckFailed { dst } => {
|
||||
InfoResponseReplyV7::ExitPolicyFilterCheckFailed { dst }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InfoLevel> for InfoLevelV7 {
|
||||
fn from(level: InfoLevel) -> Self {
|
||||
match level {
|
||||
InfoLevel::Info => InfoLevelV7::Info,
|
||||
InfoLevel::Warn => InfoLevelV7::Warn,
|
||||
InfoLevel::Error => InfoLevelV7::Error,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_ip_packet_requests::v8::response::{
|
||||
ControlResponse as ControlResponseV8, DisconnectFailureReason as DisconnectFailureReasonV8,
|
||||
DisconnectResponse as DisconnectResponseV8,
|
||||
DisconnectResponseReply as DisconnectResponseReplyV8,
|
||||
DynamicConnectFailureReason as DynamicConnectFailureReasonV8,
|
||||
DynamicConnectResponse as DynamicConnectResponseV8,
|
||||
DynamicConnectResponseReply as DynamicConnectResponseReplyV8,
|
||||
DynamicConnectSuccess as DynamicConnectSuccessV8, HealthResponse as HealthResponseV8,
|
||||
HealthResponseReply as HealthResponseReplyV8, InfoLevel as InfoLevelV8,
|
||||
InfoResponse as InfoResponseV8, InfoResponseReply as InfoResponseReplyV8,
|
||||
IpPacketResponse as IpPacketResponseV8, IpPacketResponseData as IpPacketResponseDataV8,
|
||||
PongResponse as PongResponseV8, StaticConnectFailureReason as StaticConnectFailureReasonV8,
|
||||
StaticConnectResponse as StaticConnectResponseV8,
|
||||
StaticConnectResponseReply as StaticConnectResponseReplyV8,
|
||||
};
|
||||
|
||||
use super::{
|
||||
DisconnectFailureReason, DisconnectResponse, DynamicConnectFailureReason,
|
||||
DynamicConnectResponse, DynamicConnectSuccess, HealthResponse, InfoLevel, InfoResponseReply,
|
||||
Response, StaticConnectFailureReason, StaticConnectResponse, VersionedResponse,
|
||||
};
|
||||
|
||||
impl From<VersionedResponse> for IpPacketResponseV8 {
|
||||
fn from(response: VersionedResponse) -> Self {
|
||||
let version = response.version.into_u8();
|
||||
let data =
|
||||
match response.response {
|
||||
Response::StaticConnect { request_id, reply } => IpPacketResponseDataV8::Control(
|
||||
Box::new(ControlResponseV8::StaticConnect(StaticConnectResponseV8 {
|
||||
request_id,
|
||||
reply: reply.into(),
|
||||
})),
|
||||
),
|
||||
Response::DynamicConnect { request_id, reply } => {
|
||||
IpPacketResponseDataV8::Control(Box::new(ControlResponseV8::DynamicConnect(
|
||||
DynamicConnectResponseV8 {
|
||||
request_id,
|
||||
reply: reply.into(),
|
||||
},
|
||||
)))
|
||||
}
|
||||
Response::Disconnect { request_id, reply } => IpPacketResponseDataV8::Control(
|
||||
Box::new(ControlResponseV8::Disconnect(DisconnectResponseV8 {
|
||||
request_id,
|
||||
reply: reply.into(),
|
||||
})),
|
||||
),
|
||||
Response::Pong { request_id } => IpPacketResponseDataV8::Control(Box::new(
|
||||
ControlResponseV8::Pong(PongResponseV8 { request_id }),
|
||||
)),
|
||||
Response::Health { request_id, reply } => IpPacketResponseDataV8::Control(
|
||||
Box::new(ControlResponseV8::Health(Box::new(HealthResponseV8 {
|
||||
request_id,
|
||||
reply: (*reply).into(),
|
||||
}))),
|
||||
),
|
||||
Response::Info { request_id, reply } => IpPacketResponseDataV8::Control(Box::new(
|
||||
ControlResponseV8::Info(InfoResponseV8 {
|
||||
request_id,
|
||||
reply: reply.reply.into(),
|
||||
level: reply.level.into(),
|
||||
}),
|
||||
)),
|
||||
};
|
||||
|
||||
IpPacketResponseV8 { version, data }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StaticConnectResponse> for StaticConnectResponseReplyV8 {
|
||||
fn from(reply: StaticConnectResponse) -> Self {
|
||||
match reply {
|
||||
StaticConnectResponse::Success => StaticConnectResponseReplyV8::Success,
|
||||
StaticConnectResponse::Failure(err) => {
|
||||
StaticConnectResponseReplyV8::Failure(err.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StaticConnectFailureReason> for StaticConnectFailureReasonV8 {
|
||||
fn from(reason: StaticConnectFailureReason) -> Self {
|
||||
match reason {
|
||||
StaticConnectFailureReason::RequestedIpAlreadyInUse => {
|
||||
StaticConnectFailureReasonV8::RequestedIpAlreadyInUse
|
||||
}
|
||||
StaticConnectFailureReason::ClientAlreadyConnected => {
|
||||
StaticConnectFailureReasonV8::ClientAlreadyConnected
|
||||
}
|
||||
StaticConnectFailureReason::OutOfDateTimestamp => {
|
||||
StaticConnectFailureReasonV8::OutOfDateTimestamp
|
||||
}
|
||||
StaticConnectFailureReason::Other(err) => StaticConnectFailureReasonV8::Other(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DynamicConnectResponse> for DynamicConnectResponseReplyV8 {
|
||||
fn from(reply: DynamicConnectResponse) -> Self {
|
||||
match reply {
|
||||
DynamicConnectResponse::Success(DynamicConnectSuccess { ips }) => {
|
||||
DynamicConnectResponseReplyV8::Success(DynamicConnectSuccessV8 { ips })
|
||||
}
|
||||
DynamicConnectResponse::Failure(err) => {
|
||||
DynamicConnectResponseReplyV8::Failure(err.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DynamicConnectFailureReason> for DynamicConnectFailureReasonV8 {
|
||||
fn from(reason: DynamicConnectFailureReason) -> Self {
|
||||
match reason {
|
||||
DynamicConnectFailureReason::ClientAlreadyConnected => {
|
||||
DynamicConnectFailureReasonV8::ClientAlreadyConnected
|
||||
}
|
||||
DynamicConnectFailureReason::NoAvailableIp => {
|
||||
DynamicConnectFailureReasonV8::NoAvailableIp
|
||||
}
|
||||
DynamicConnectFailureReason::Other(err) => DynamicConnectFailureReasonV8::Other(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DisconnectResponse> for DisconnectResponseReplyV8 {
|
||||
fn from(reply: DisconnectResponse) -> Self {
|
||||
match reply {
|
||||
DisconnectResponse::Success => DisconnectResponseReplyV8::Success,
|
||||
DisconnectResponse::Failure(err) => DisconnectResponseReplyV8::Failure(err.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DisconnectFailureReason> for DisconnectFailureReasonV8 {
|
||||
fn from(reason: DisconnectFailureReason) -> Self {
|
||||
match reason {
|
||||
DisconnectFailureReason::ClientNotConnected => {
|
||||
DisconnectFailureReasonV8::ClientNotConnected
|
||||
}
|
||||
DisconnectFailureReason::Other(err) => DisconnectFailureReasonV8::Other(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HealthResponse> for HealthResponseReplyV8 {
|
||||
fn from(response: HealthResponse) -> Self {
|
||||
HealthResponseReplyV8 {
|
||||
build_info: response.build_info,
|
||||
routable: response.routable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InfoResponseReply> for InfoResponseReplyV8 {
|
||||
fn from(reply: InfoResponseReply) -> Self {
|
||||
match reply {
|
||||
InfoResponseReply::Generic { msg } => InfoResponseReplyV8::Generic { msg },
|
||||
InfoResponseReply::VersionMismatch {
|
||||
request_version,
|
||||
response_version,
|
||||
} => InfoResponseReplyV8::VersionMismatch {
|
||||
request_version,
|
||||
response_version,
|
||||
},
|
||||
InfoResponseReply::ExitPolicyFilterCheckFailed { dst } => {
|
||||
InfoResponseReplyV8::ExitPolicyFilterCheckFailed { dst }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InfoLevel> for InfoLevelV8 {
|
||||
fn from(level: InfoLevel) -> Self {
|
||||
match level {
|
||||
InfoLevel::Info => InfoLevelV8::Info,
|
||||
InfoLevel::Warn => InfoLevelV8::Warn,
|
||||
InfoLevel::Error => InfoLevelV8::Error,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_client_core::{config::disk_persistence::CommonClientPaths, TopologyProvider};
|
||||
use nym_sdk::{GatewayTransceiver, NymNetworkDetails};
|
||||
use nym_task::TaskClient;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::io::Error;
|
||||
use std::pin::Pin;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::net::SocketAddr;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::error::IpPacketRouterError;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
|
||||
@@ -5,34 +8,27 @@ use nym_ip_packet_requests::IpPair;
|
||||
use nym_task::TaskClient;
|
||||
#[cfg(target_os = "linux")]
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::{
|
||||
error::Result,
|
||||
mixnet_listener::{self},
|
||||
util::parse_ip::parse_dst_addr,
|
||||
};
|
||||
use crate::clients::{ConnectEvent, ConnectedClientEvent, DisconnectEvent};
|
||||
use crate::{error::Result, util::parse_ip::parse_dst_addr};
|
||||
|
||||
// The TUN listener keeps a local map of the connected clients that has its state updated by the
|
||||
// mixnet listener. Basically it's just so that we don't have to have mutexes around shared state.
|
||||
// It's even ok if this is slightly out of date
|
||||
pub(crate) struct ConnectedClientMirror {
|
||||
pub(crate) forward_from_tun_tx: tokio::sync::mpsc::UnboundedSender<Vec<u8>>,
|
||||
pub(crate) ips: IpPair,
|
||||
struct ConnectedClientMirror {
|
||||
forward_from_tun_tx: mpsc::UnboundedSender<Vec<u8>>,
|
||||
ips: IpPair,
|
||||
}
|
||||
|
||||
pub(crate) struct ConnectedClientsListener {
|
||||
clients_ipv4: HashMap<Ipv4Addr, ConnectedClientMirror>,
|
||||
clients_ipv6: HashMap<Ipv6Addr, ConnectedClientMirror>,
|
||||
connected_client_rx:
|
||||
tokio::sync::mpsc::UnboundedReceiver<mixnet_listener::ConnectedClientEvent>,
|
||||
connected_client_rx: mpsc::UnboundedReceiver<ConnectedClientEvent>,
|
||||
}
|
||||
|
||||
impl ConnectedClientsListener {
|
||||
pub(crate) fn new(
|
||||
connected_client_rx: tokio::sync::mpsc::UnboundedReceiver<
|
||||
mixnet_listener::ConnectedClientEvent,
|
||||
>,
|
||||
) -> Self {
|
||||
pub(crate) fn new(connected_client_rx: mpsc::UnboundedReceiver<ConnectedClientEvent>) -> Self {
|
||||
ConnectedClientsListener {
|
||||
clients_ipv4: HashMap::new(),
|
||||
clients_ipv6: HashMap::new(),
|
||||
@@ -40,17 +36,17 @@ impl ConnectedClientsListener {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get(&self, ip: &IpAddr) -> Option<&ConnectedClientMirror> {
|
||||
fn get(&self, ip: &IpAddr) -> Option<&ConnectedClientMirror> {
|
||||
match ip {
|
||||
IpAddr::V4(ip) => self.clients_ipv4.get(ip),
|
||||
IpAddr::V6(ip) => self.clients_ipv6.get(ip),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn update(&mut self, event: mixnet_listener::ConnectedClientEvent) {
|
||||
pub(crate) fn update(&mut self, event: ConnectedClientEvent) {
|
||||
match event {
|
||||
mixnet_listener::ConnectedClientEvent::Connect(connected_event) => {
|
||||
let mixnet_listener::ConnectEvent {
|
||||
ConnectedClientEvent::Connect(connected_event) => {
|
||||
let ConnectEvent {
|
||||
ips,
|
||||
forward_from_tun_tx,
|
||||
} = *connected_event;
|
||||
@@ -70,9 +66,7 @@ impl ConnectedClientsListener {
|
||||
},
|
||||
);
|
||||
}
|
||||
mixnet_listener::ConnectedClientEvent::Disconnect(
|
||||
mixnet_listener::DisconnectEvent(ips),
|
||||
) => {
|
||||
ConnectedClientEvent::Disconnect(DisconnectEvent(ips)) => {
|
||||
log::trace!("Disconnect client: {ips}");
|
||||
self.clients_ipv4.remove(&ips.ipv4);
|
||||
self.clients_ipv6.remove(&ips.ipv6);
|
||||
@@ -106,9 +100,7 @@ impl TunListener {
|
||||
if forward_from_tun_tx.send(packet).is_err() {
|
||||
log::warn!("Failed to forward packet to connected client {dst_addr}: disconnecting it from tun listener");
|
||||
self.connected_clients
|
||||
.update(mixnet_listener::ConnectedClientEvent::Disconnect(
|
||||
mixnet_listener::DisconnectEvent(*ips),
|
||||
));
|
||||
.update(ConnectedClientEvent::Disconnect(DisconnectEvent(*ips)));
|
||||
}
|
||||
} else {
|
||||
log::info!(
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
use nym_sdk::mixnet::{InputMessage, Recipient};
|
||||
use nym_sdk::mixnet::InputMessage;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
|
||||
use crate::clients::ConnectedClientId;
|
||||
|
||||
pub(crate) fn create_input_message(
|
||||
nym_address: Recipient,
|
||||
recipient: &ConnectedClientId,
|
||||
response_packet: Vec<u8>,
|
||||
) -> InputMessage {
|
||||
let lane = TransmissionLane::General;
|
||||
let packet_type = None;
|
||||
InputMessage::new_regular(nym_address, response_packet, lane, packet_type)
|
||||
match recipient {
|
||||
ConnectedClientId::NymAddress(recipient) => {
|
||||
InputMessage::new_regular(**recipient, response_packet, lane, packet_type)
|
||||
}
|
||||
ConnectedClientId::AnonymousSenderTag(tag) => {
|
||||
InputMessage::new_reply(*tag, response_packet, lane, packet_type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user