From a4005c7d81794880bbeb330b29f173f577c81eeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Mon, 20 May 2024 10:48:58 +0200 Subject: [PATCH 1/6] Add v7 request/responses --- common/ip-packet-requests/src/lib.rs | 2 + common/ip-packet-requests/src/v7/mod.rs | 2 + common/ip-packet-requests/src/v7/request.rs | 297 ++++++++++++++ common/ip-packet-requests/src/v7/response.rs | 403 +++++++++++++++++++ 4 files changed, 704 insertions(+) create mode 100644 common/ip-packet-requests/src/v7/mod.rs create mode 100644 common/ip-packet-requests/src/v7/request.rs create mode 100644 common/ip-packet-requests/src/v7/response.rs diff --git a/common/ip-packet-requests/src/lib.rs b/common/ip-packet-requests/src/lib.rs index e4d555bcf7..b15c1703b3 100644 --- a/common/ip-packet-requests/src/lib.rs +++ b/common/ip-packet-requests/src/lib.rs @@ -12,11 +12,13 @@ pub use v6::response; pub mod codec; pub mod v6; +pub mod v7; // 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 pub const CURRENT_VERSION: u8 = 6; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] diff --git a/common/ip-packet-requests/src/v7/mod.rs b/common/ip-packet-requests/src/v7/mod.rs new file mode 100644 index 0000000000..e0062185c4 --- /dev/null +++ b/common/ip-packet-requests/src/v7/mod.rs @@ -0,0 +1,2 @@ +pub mod request; +pub mod response; diff --git a/common/ip-packet-requests/src/v7/request.rs b/common/ip-packet-requests/src/v7/request.rs new file mode 100644 index 0000000000..3bf1cd5d98 --- /dev/null +++ b/common/ip-packet-requests/src/v7/request.rs @@ -0,0 +1,297 @@ +use nym_sphinx::addressing::clients::Recipient; +use serde::{Deserialize, Serialize}; + +use crate::{make_bincode_serializer, IpPair, CURRENT_VERSION}; + +fn generate_random() -> u64 { + use rand::RngCore; + let mut rng = rand::rngs::OsRng; + rng.next_u64() +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct IpPacketRequest { + pub version: u8, + pub data: IpPacketRequestData, +} + +impl IpPacketRequest { + pub fn new_static_connect_request( + ips: IpPair, + reply_to: Recipient, + reply_to_hops: Option, + reply_to_avg_mix_delays: Option, + buffer_timeout: Option, + ) -> (Self, u64) { + let request_id = generate_random(); + ( + Self { + version: CURRENT_VERSION, + data: IpPacketRequestData::StaticConnect(StaticConnectRequest { + request_id, + ips, + reply_to, + reply_to_hops, + reply_to_avg_mix_delays, + buffer_timeout, + }), + }, + request_id, + ) + } + + pub fn new_dynamic_connect_request( + reply_to: Recipient, + reply_to_hops: Option, + reply_to_avg_mix_delays: Option, + buffer_timeout: Option, + ) -> (Self, u64) { + let request_id = generate_random(); + ( + Self { + version: CURRENT_VERSION, + data: IpPacketRequestData::DynamicConnect(DynamicConnectRequest { + request_id, + reply_to, + reply_to_hops, + reply_to_avg_mix_delays, + buffer_timeout, + }), + }, + request_id, + ) + } + + pub fn new_disconnect_request(reply_to: Recipient) -> (Self, u64) { + let request_id = generate_random(); + ( + Self { + version: CURRENT_VERSION, + data: IpPacketRequestData::Disconnect(DisconnectRequest { + request_id, + reply_to, + }), + }, + request_id, + ) + } + + pub fn new_data_request(ip_packets: bytes::Bytes) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketRequestData::Data(DataRequest { ip_packets }), + } + } + + pub fn new_ping(reply_to: Recipient) -> (Self, u64) { + let request_id = generate_random(); + ( + Self { + version: CURRENT_VERSION, + data: IpPacketRequestData::Ping(PingRequest { + request_id, + reply_to, + }), + }, + request_id, + ) + } + + pub fn new_health_request(reply_to: Recipient) -> (Self, u64) { + let request_id = generate_random(); + ( + Self { + version: CURRENT_VERSION, + data: IpPacketRequestData::Health(HealthRequest { + request_id, + reply_to, + }), + }, + request_id, + ) + } + + pub fn id(&self) -> Option { + match &self.data { + IpPacketRequestData::StaticConnect(request) => Some(request.request_id), + IpPacketRequestData::DynamicConnect(request) => Some(request.request_id), + IpPacketRequestData::Disconnect(request) => Some(request.request_id), + IpPacketRequestData::Data(_) => None, + IpPacketRequestData::Ping(request) => Some(request.request_id), + IpPacketRequestData::Health(request) => Some(request.request_id), + } + } + + pub fn recipient(&self) -> Option<&Recipient> { + match &self.data { + IpPacketRequestData::StaticConnect(request) => Some(&request.reply_to), + IpPacketRequestData::DynamicConnect(request) => Some(&request.reply_to), + IpPacketRequestData::Disconnect(request) => Some(&request.reply_to), + IpPacketRequestData::Data(_) => None, + IpPacketRequestData::Ping(request) => Some(&request.reply_to), + IpPacketRequestData::Health(request) => Some(&request.reply_to), + } + } + + pub fn to_bytes(&self) -> Result, bincode::Error> { + use bincode::Options; + make_bincode_serializer().serialize(self) + } + + pub fn from_reconstructed_message( + message: &nym_sphinx::receiver::ReconstructedMessage, + ) -> Result { + use bincode::Options; + make_bincode_serializer().deserialize(&message.message) + } +} + +#[allow(clippy::large_enum_variant)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub enum IpPacketRequestData { + StaticConnect(StaticConnectRequest), + DynamicConnect(DynamicConnectRequest), + Disconnect(DisconnectRequest), + Data(DataRequest), + Ping(PingRequest), + Health(HealthRequest), +} + +// 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, + + pub ips: IpPair, + + // The nym-address the response should be sent back to + pub reply_to: Recipient, + + // The number of mix node hops that responses should take, in addition to the entry and exit + // node. Zero means only client -> entry -> exit -> client. + pub reply_to_hops: Option, + + // The average delay at each mix node, in milliseconds. Currently this is not supported by the + // ip packet router. + pub reply_to_avg_mix_delays: Option, + + // The maximum time in milliseconds the IPR should wait when filling up a mix packet + // with ip packets. + pub buffer_timeout: Option, +} + +// 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 nym-address the response should be sent back to + pub reply_to: Recipient, + + // The number of mix node hops that responses should take, in addition to the entry and exit + // node. Zero means only client -> entry -> exit -> client. + pub reply_to_hops: Option, + + // The average delay at each mix node, in milliseconds. Currently this is not supported by the + // ip packet router. + pub reply_to_avg_mix_delays: Option, + + // The maximum time in milliseconds the IPR should wait when filling up a mix packet + // with ip packets. + pub buffer_timeout: Option, +} + +// 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, + // The nym-address the response should be sent back to + pub reply_to: Recipient, +} + +// 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 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, + // The nym-address the response should be sent back to + pub reply_to: Recipient, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct HealthRequest { + pub request_id: u64, + // The nym-address the response should be sent back to + pub reply_to: Recipient, +} + +#[cfg(test)] +mod tests { + use super::*; + use std::net::{Ipv4Addr, Ipv6Addr}; + use std::str::FromStr; + + #[test] + fn check_size_of_request() { + let connect = IpPacketRequest { + version: 4, + data: IpPacketRequestData::StaticConnect( + StaticConnectRequest { + request_id: 123, + ips: IpPair::new(Ipv4Addr::from_str("10.0.0.1").unwrap(), Ipv6Addr::from_str("2001:db8:a160::1").unwrap()), + reply_to: Recipient::try_from_base58_string("D1rrpsysCGCYXy9saP8y3kmNpGtJZUXN9SvFoUcqAsM9.9Ssso1ea5NfkbMASdiseDSjTN1fSWda5SgEVjdSN4CvV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN").unwrap(), + reply_to_hops: None, + reply_to_avg_mix_delays: None, + buffer_timeout: None, + }, + ), + }; + assert_eq!(connect.to_bytes().unwrap().len(), 123); + } + + #[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]), + }) + ); + } +} diff --git a/common/ip-packet-requests/src/v7/response.rs b/common/ip-packet-requests/src/v7/response.rs new file mode 100644 index 0000000000..c788a8974e --- /dev/null +++ b/common/ip-packet-requests/src/v7/response.rs @@ -0,0 +1,403 @@ +use nym_sphinx::addressing::clients::Recipient; +use serde::{Deserialize, Serialize}; + +use crate::{make_bincode_serializer, IpPair, CURRENT_VERSION}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct IpPacketResponse { + pub version: u8, + pub data: IpPacketResponseData, +} + +impl IpPacketResponse { + pub fn new_static_connect_success(request_id: u64, reply_to: Recipient) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketResponseData::StaticConnect(StaticConnectResponse { + request_id, + reply_to, + reply: StaticConnectResponseReply::Success, + }), + } + } + + pub fn new_static_connect_failure( + request_id: u64, + reply_to: Recipient, + reason: StaticConnectFailureReason, + ) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketResponseData::StaticConnect(StaticConnectResponse { + request_id, + reply_to, + reply: StaticConnectResponseReply::Failure(reason), + }), + } + } + + pub fn new_dynamic_connect_success(request_id: u64, reply_to: Recipient, ips: IpPair) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketResponseData::DynamicConnect(DynamicConnectResponse { + request_id, + reply_to, + reply: DynamicConnectResponseReply::Success(DynamicConnectSuccess { ips }), + }), + } + } + + pub fn new_dynamic_connect_failure( + request_id: u64, + reply_to: Recipient, + reason: DynamicConnectFailureReason, + ) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketResponseData::DynamicConnect(DynamicConnectResponse { + request_id, + reply_to, + reply: DynamicConnectResponseReply::Failure(reason), + }), + } + } + + pub fn new_disconnect_success(request_id: u64, reply_to: Recipient) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketResponseData::Disconnect(DisconnectResponse { + request_id, + reply_to, + reply: DisconnectResponseReply::Success, + }), + } + } + + pub fn new_disconnect_failure( + request_id: u64, + reply_to: Recipient, + reason: DisconnectFailureReason, + ) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketResponseData::Disconnect(DisconnectResponse { + request_id, + reply_to, + reply: DisconnectResponseReply::Failure(reason), + }), + } + } + + pub fn new_unrequested_disconnect( + reply_to: Recipient, + reason: UnrequestedDisconnectReason, + ) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketResponseData::UnrequestedDisconnect(UnrequestedDisconnect { + reply_to, + reason, + }), + } + } + + pub fn new_ip_packet(ip_packet: bytes::Bytes) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketResponseData::Data(DataResponse { ip_packet }), + } + } + + pub fn new_version_mismatch( + request_id: u64, + reply_to: Recipient, + request_version: u8, + our_version: u8, + ) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketResponseData::Info(InfoResponse { + request_id, + reply_to, + reply: InfoResponseReply::VersionMismatch { + request_version, + response_version: our_version, + }, + level: InfoLevel::Error, + }), + } + } + + pub fn new_data_info_response( + reply_to: Recipient, + reply: InfoResponseReply, + level: InfoLevel, + ) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketResponseData::Info(InfoResponse { + request_id: 0, + reply_to, + reply, + level, + }), + } + } + + pub fn new_pong(request_id: u64, reply_to: Recipient) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketResponseData::Pong(PongResponse { + request_id, + reply_to, + }), + } + } + + pub fn new_health_response( + request_id: u64, + reply_to: Recipient, + build_info: nym_bin_common::build_information::BinaryBuildInformationOwned, + routable: Option, + ) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketResponseData::Health(HealthResponse { + request_id, + reply_to, + reply: HealthResponseReply { + build_info, + routable, + }, + }), + } + } + + pub fn id(&self) -> Option { + match &self.data { + IpPacketResponseData::StaticConnect(response) => Some(response.request_id), + IpPacketResponseData::DynamicConnect(response) => Some(response.request_id), + IpPacketResponseData::Disconnect(response) => Some(response.request_id), + IpPacketResponseData::UnrequestedDisconnect(_) => None, + IpPacketResponseData::Data(_) => None, + IpPacketResponseData::Pong(response) => Some(response.request_id), + IpPacketResponseData::Health(response) => Some(response.request_id), + IpPacketResponseData::Info(response) => Some(response.request_id), + } + } + + pub fn recipient(&self) -> Option<&Recipient> { + match &self.data { + IpPacketResponseData::StaticConnect(response) => Some(&response.reply_to), + IpPacketResponseData::DynamicConnect(response) => Some(&response.reply_to), + IpPacketResponseData::Disconnect(response) => Some(&response.reply_to), + IpPacketResponseData::UnrequestedDisconnect(response) => Some(&response.reply_to), + IpPacketResponseData::Data(_) => None, + IpPacketResponseData::Pong(response) => Some(&response.reply_to), + IpPacketResponseData::Health(response) => Some(&response.reply_to), + IpPacketResponseData::Info(response) => Some(&response.reply_to), + } + } + + pub fn to_bytes(&self) -> Result, bincode::Error> { + use bincode::Options; + make_bincode_serializer().serialize(self) + } + + pub fn from_reconstructed_message( + message: &nym_sphinx::receiver::ReconstructedMessage, + ) -> Result { + use bincode::Options; + make_bincode_serializer().deserialize(&message.message) + } +} + +#[allow(clippy::large_enum_variant)] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum IpPacketResponseData { + // 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 a data request + Data(DataResponse), + + // Response to ping request + Pong(PongResponse), + + // Response for a health request + Health(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_to: Recipient, + pub reply: StaticConnectResponseReply, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum StaticConnectResponseReply { + Success, + Failure(StaticConnectFailureReason), +} + +impl StaticConnectResponseReply { + pub fn is_success(&self) -> bool { + match self { + StaticConnectResponseReply::Success => true, + StaticConnectResponseReply::Failure(_) => false, + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)] +pub enum StaticConnectFailureReason { + #[error("requested ip address is already in use")] + RequestedIpAlreadyInUse, + #[error("requested nym-address is already in use")] + RequestedNymAddressAlreadyInUse, + #[error("{0}")] + Other(String), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct DynamicConnectResponse { + pub request_id: u64, + pub reply_to: Recipient, + pub reply: DynamicConnectResponseReply, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum DynamicConnectResponseReply { + Success(DynamicConnectSuccess), + Failure(DynamicConnectFailureReason), +} + +impl DynamicConnectResponseReply { + pub fn is_success(&self) -> bool { + match self { + DynamicConnectResponseReply::Success(_) => true, + DynamicConnectResponseReply::Failure(_) => false, + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct DynamicConnectSuccess { + pub ips: IpPair, +} + +#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)] +pub enum DynamicConnectFailureReason { + #[error("requested nym-address is already in use")] + RequestedNymAddressAlreadyInUse, + #[error("no available ip address")] + NoAvailableIp, + #[error("{0}")] + Other(String), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct DisconnectResponse { + pub request_id: u64, + pub reply_to: Recipient, + 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("requested nym-address is not currently connected")] + RequestedNymAddressNotConnected, + #[error("{0}")] + Other(String), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct UnrequestedDisconnect { + pub reply_to: Recipient, + 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 DataResponse { + pub ip_packet: bytes::Bytes, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct PongResponse { + pub request_id: u64, + pub reply_to: Recipient, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct HealthResponse { + pub request_id: u64, + pub reply_to: Recipient, + pub reply: HealthResponseReply, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct HealthResponseReply { + // Return the binary build information of the IPR + pub build_info: nym_bin_common::build_information::BinaryBuildInformationOwned, + // Return if the IPR has performed a successful routing test. + pub routable: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct InfoResponse { + pub request_id: u64, + pub reply_to: Recipient, + 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, +} From e29c76678dee79cef3ebe53f578502c3ae683a71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Mon, 20 May 2024 10:52:19 +0200 Subject: [PATCH 2/6] Add optional signature field to requests and responses --- common/ip-packet-requests/src/lib.rs | 2 +- common/ip-packet-requests/src/v7/request.rs | 14 ++++++++++++++ common/ip-packet-requests/src/v7/response.rs | 20 ++++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/common/ip-packet-requests/src/lib.rs b/common/ip-packet-requests/src/lib.rs index b15c1703b3..1da258c256 100644 --- a/common/ip-packet-requests/src/lib.rs +++ b/common/ip-packet-requests/src/lib.rs @@ -18,7 +18,7 @@ pub mod v7; // version 4: IPv6 support // version 5: Add severity level to info response // version 6: Increase the available IPs -// version 7: Add signature support +// version 7: Add signature support (for the future) pub const CURRENT_VERSION: u8 = 6; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] diff --git a/common/ip-packet-requests/src/v7/request.rs b/common/ip-packet-requests/src/v7/request.rs index 3bf1cd5d98..f10f5f507e 100644 --- a/common/ip-packet-requests/src/v7/request.rs +++ b/common/ip-packet-requests/src/v7/request.rs @@ -13,6 +13,7 @@ fn generate_random() -> u64 { pub struct IpPacketRequest { pub version: u8, pub data: IpPacketRequestData, + pub signature: Option>, } impl IpPacketRequest { @@ -35,6 +36,7 @@ impl IpPacketRequest { reply_to_avg_mix_delays, buffer_timeout, }), + signature: None, }, request_id, ) @@ -57,6 +59,7 @@ impl IpPacketRequest { reply_to_avg_mix_delays, buffer_timeout, }), + signature: None, }, request_id, ) @@ -71,6 +74,7 @@ impl IpPacketRequest { request_id, reply_to, }), + signature: None, }, request_id, ) @@ -80,6 +84,7 @@ impl IpPacketRequest { Self { version: CURRENT_VERSION, data: IpPacketRequestData::Data(DataRequest { ip_packets }), + signature: None, } } @@ -92,6 +97,7 @@ impl IpPacketRequest { request_id, reply_to, }), + signature: None, }, request_id, ) @@ -106,6 +112,7 @@ impl IpPacketRequest { request_id, reply_to, }), + signature: None, }, request_id, ) @@ -157,6 +164,13 @@ pub enum IpPacketRequestData { Health(HealthRequest), } +impl IpPacketRequestData { + pub fn to_bytes(&self) -> Result, bincode::Error> { + use bincode::Options; + make_bincode_serializer().serialize(self) + } +} + // 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)] diff --git a/common/ip-packet-requests/src/v7/response.rs b/common/ip-packet-requests/src/v7/response.rs index c788a8974e..d3e6a01cef 100644 --- a/common/ip-packet-requests/src/v7/response.rs +++ b/common/ip-packet-requests/src/v7/response.rs @@ -7,6 +7,7 @@ use crate::{make_bincode_serializer, IpPair, CURRENT_VERSION}; pub struct IpPacketResponse { pub version: u8, pub data: IpPacketResponseData, + pub signature: Option>, } impl IpPacketResponse { @@ -18,6 +19,7 @@ impl IpPacketResponse { reply_to, reply: StaticConnectResponseReply::Success, }), + signature: None, } } @@ -33,6 +35,7 @@ impl IpPacketResponse { reply_to, reply: StaticConnectResponseReply::Failure(reason), }), + signature: None, } } @@ -44,6 +47,7 @@ impl IpPacketResponse { reply_to, reply: DynamicConnectResponseReply::Success(DynamicConnectSuccess { ips }), }), + signature: None, } } @@ -59,6 +63,7 @@ impl IpPacketResponse { reply_to, reply: DynamicConnectResponseReply::Failure(reason), }), + signature: None, } } @@ -70,6 +75,7 @@ impl IpPacketResponse { reply_to, reply: DisconnectResponseReply::Success, }), + signature: None, } } @@ -85,6 +91,7 @@ impl IpPacketResponse { reply_to, reply: DisconnectResponseReply::Failure(reason), }), + signature: None, } } @@ -98,6 +105,7 @@ impl IpPacketResponse { reply_to, reason, }), + signature: None, } } @@ -105,6 +113,7 @@ impl IpPacketResponse { Self { version: CURRENT_VERSION, data: IpPacketResponseData::Data(DataResponse { ip_packet }), + signature: None, } } @@ -125,6 +134,7 @@ impl IpPacketResponse { }, level: InfoLevel::Error, }), + signature: None, } } @@ -141,6 +151,7 @@ impl IpPacketResponse { reply, level, }), + signature: None, } } @@ -151,6 +162,7 @@ impl IpPacketResponse { request_id, reply_to, }), + signature: None, } } @@ -170,6 +182,7 @@ impl IpPacketResponse { routable, }, }), + signature: None, } } @@ -240,6 +253,13 @@ pub enum IpPacketResponseData { Info(InfoResponse), } +impl IpPacketResponseData { + pub fn to_bytes(&self) -> Result, bincode::Error> { + use bincode::Options; + make_bincode_serializer().serialize(self) + } +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct StaticConnectResponse { pub request_id: u64, From 68ee2d747d67f291cc0d959203e6eb59875d8d85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Mon, 20 May 2024 11:53:00 +0200 Subject: [PATCH 3/6] Add timestamps to requests --- Cargo.lock | 1 + common/ip-packet-requests/Cargo.toml | 1 + common/ip-packet-requests/src/v7/request.rs | 24 +++++++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 555d3e03bb..7687b63437 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4770,6 +4770,7 @@ dependencies = [ "rand 0.8.5", "serde", "thiserror", + "time", "tokio", "tokio-util", ] diff --git a/common/ip-packet-requests/Cargo.toml b/common/ip-packet-requests/Cargo.toml index e4b1f7311a..d25578e87f 100644 --- a/common/ip-packet-requests/Cargo.toml +++ b/common/ip-packet-requests/Cargo.toml @@ -16,5 +16,6 @@ nym-sphinx = { path = "../nymsphinx" } rand = "0.8.5" serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } +time = { workspace = true } tokio = { workspace = true, features = ["time"] } tokio-util = { workspace = true, features = ["codec"] } diff --git a/common/ip-packet-requests/src/v7/request.rs b/common/ip-packet-requests/src/v7/request.rs index f10f5f507e..a73b884972 100644 --- a/common/ip-packet-requests/src/v7/request.rs +++ b/common/ip-packet-requests/src/v7/request.rs @@ -1,5 +1,6 @@ use nym_sphinx::addressing::clients::Recipient; use serde::{Deserialize, Serialize}; +use time::OffsetDateTime; use crate::{make_bincode_serializer, IpPair, CURRENT_VERSION}; @@ -35,6 +36,7 @@ impl IpPacketRequest { reply_to_hops, reply_to_avg_mix_delays, buffer_timeout, + timestamp: OffsetDateTime::now_utc(), }), signature: None, }, @@ -58,6 +60,7 @@ impl IpPacketRequest { reply_to_hops, reply_to_avg_mix_delays, buffer_timeout, + timestamp: OffsetDateTime::now_utc(), }), signature: None, }, @@ -73,6 +76,7 @@ impl IpPacketRequest { data: IpPacketRequestData::Disconnect(DisconnectRequest { request_id, reply_to, + timestamp: OffsetDateTime::now_utc(), }), signature: None, }, @@ -96,6 +100,7 @@ impl IpPacketRequest { data: IpPacketRequestData::Ping(PingRequest { request_id, reply_to, + timestamp: OffsetDateTime::now_utc(), }), signature: None, }, @@ -111,6 +116,7 @@ impl IpPacketRequest { data: IpPacketRequestData::Health(HealthRequest { request_id, reply_to, + timestamp: OffsetDateTime::now_utc(), }), signature: None, }, @@ -193,6 +199,9 @@ pub struct StaticConnectRequest { // The maximum time in milliseconds the IPR should wait when filling up a mix packet // with ip packets. pub buffer_timeout: Option, + + // Timestamp of when the request was sent by the client. + pub timestamp: OffsetDateTime, } // A dynamic connect request is when the client does not provide the internal IP address it will use @@ -215,6 +224,9 @@ pub struct DynamicConnectRequest { // The maximum time in milliseconds the IPR should wait when filling up a mix packet // with ip packets. pub buffer_timeout: Option, + + // Timestamp of when the request was sent by the client. + pub timestamp: OffsetDateTime, } // A disconnect request is when the client wants to disconnect from the ip packet router and free @@ -222,8 +234,12 @@ pub struct DynamicConnectRequest { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct DisconnectRequest { pub request_id: u64, + // The nym-address the response should be sent back to pub reply_to: Recipient, + + // Timestamp of when the request was sent by the client. + pub timestamp: OffsetDateTime, } // A data request is when the client wants to send an IP packet to a destination. @@ -236,15 +252,23 @@ pub struct DataRequest { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct PingRequest { pub request_id: u64, + // The nym-address the response should be sent back to pub reply_to: Recipient, + + // 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, + // The nym-address the response should be sent back to pub reply_to: Recipient, + + // Timestamp of when the request was sent by the client. + pub timestamp: OffsetDateTime, } #[cfg(test)] From 0f448360255d06ef6b812c30eb758a4644ebe22e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Mon, 20 May 2024 12:04:05 +0200 Subject: [PATCH 4/6] Move signature one layer down --- common/ip-packet-requests/src/v7/request.rs | 48 +++++++++++++-------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/common/ip-packet-requests/src/v7/request.rs b/common/ip-packet-requests/src/v7/request.rs index a73b884972..b7cddc9215 100644 --- a/common/ip-packet-requests/src/v7/request.rs +++ b/common/ip-packet-requests/src/v7/request.rs @@ -14,7 +14,6 @@ fn generate_random() -> u64 { pub struct IpPacketRequest { pub version: u8, pub data: IpPacketRequestData, - pub signature: Option>, } impl IpPacketRequest { @@ -26,19 +25,22 @@ impl IpPacketRequest { buffer_timeout: Option, ) -> (Self, u64) { let request_id = generate_random(); + let request = StaticConnectRequest { + request_id, + ips, + reply_to, + reply_to_hops, + reply_to_avg_mix_delays, + buffer_timeout, + timestamp: OffsetDateTime::now_utc(), + }; ( Self { version: CURRENT_VERSION, - data: IpPacketRequestData::StaticConnect(StaticConnectRequest { - request_id, - ips, - reply_to, - reply_to_hops, - reply_to_avg_mix_delays, - buffer_timeout, - timestamp: OffsetDateTime::now_utc(), + data: IpPacketRequestData::StaticConnect(SignedStaticConnectRequest { + request, + signature: None, }), - signature: None, }, request_id, ) @@ -62,7 +64,6 @@ impl IpPacketRequest { buffer_timeout, timestamp: OffsetDateTime::now_utc(), }), - signature: None, }, request_id, ) @@ -78,7 +79,6 @@ impl IpPacketRequest { reply_to, timestamp: OffsetDateTime::now_utc(), }), - signature: None, }, request_id, ) @@ -88,7 +88,6 @@ impl IpPacketRequest { Self { version: CURRENT_VERSION, data: IpPacketRequestData::Data(DataRequest { ip_packets }), - signature: None, } } @@ -102,7 +101,6 @@ impl IpPacketRequest { reply_to, timestamp: OffsetDateTime::now_utc(), }), - signature: None, }, request_id, ) @@ -118,7 +116,6 @@ impl IpPacketRequest { reply_to, timestamp: OffsetDateTime::now_utc(), }), - signature: None, }, request_id, ) @@ -126,7 +123,7 @@ impl IpPacketRequest { pub fn id(&self) -> Option { match &self.data { - IpPacketRequestData::StaticConnect(request) => Some(request.request_id), + IpPacketRequestData::StaticConnect(request) => Some(request.request.request_id), IpPacketRequestData::DynamicConnect(request) => Some(request.request_id), IpPacketRequestData::Disconnect(request) => Some(request.request_id), IpPacketRequestData::Data(_) => None, @@ -137,7 +134,7 @@ impl IpPacketRequest { pub fn recipient(&self) -> Option<&Recipient> { match &self.data { - IpPacketRequestData::StaticConnect(request) => Some(&request.reply_to), + IpPacketRequestData::StaticConnect(request) => Some(&request.request.reply_to), IpPacketRequestData::DynamicConnect(request) => Some(&request.reply_to), IpPacketRequestData::Disconnect(request) => Some(&request.reply_to), IpPacketRequestData::Data(_) => None, @@ -162,7 +159,7 @@ impl IpPacketRequest { #[allow(clippy::large_enum_variant)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub enum IpPacketRequestData { - StaticConnect(StaticConnectRequest), + StaticConnect(SignedStaticConnectRequest), DynamicConnect(DynamicConnectRequest), Disconnect(DisconnectRequest), Data(DataRequest), @@ -175,6 +172,15 @@ impl IpPacketRequestData { use bincode::Options; make_bincode_serializer().serialize(self) } + + pub fn add_signature(&mut self, signature: Vec) { + match self { + IpPacketRequestData::StaticConnect(request) => { + request.signature = Some(signature); + } + _ => panic!("Attempted to sign a non-signable request"), + } + } } // A static connect request is when the client provides the internal IP address it will use on the @@ -204,6 +210,12 @@ pub struct StaticConnectRequest { pub timestamp: OffsetDateTime, } +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct SignedStaticConnectRequest { + pub request: StaticConnectRequest, + pub signature: Option>, +} + // 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)] From 8f083ff91e218c8b6ecacc36342e38e5ae601b9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Mon, 20 May 2024 12:18:28 +0200 Subject: [PATCH 5/6] Add signatures to dynamic connect and disconect, and remove from response --- common/ip-packet-requests/src/v7/request.rs | 132 +++++++++++++------ common/ip-packet-requests/src/v7/response.rs | 13 -- 2 files changed, 90 insertions(+), 55 deletions(-) diff --git a/common/ip-packet-requests/src/v7/request.rs b/common/ip-packet-requests/src/v7/request.rs index b7cddc9215..c6730f391e 100644 --- a/common/ip-packet-requests/src/v7/request.rs +++ b/common/ip-packet-requests/src/v7/request.rs @@ -25,20 +25,19 @@ impl IpPacketRequest { buffer_timeout: Option, ) -> (Self, u64) { let request_id = generate_random(); - let request = StaticConnectRequest { - request_id, - ips, - reply_to, - reply_to_hops, - reply_to_avg_mix_delays, - buffer_timeout, - timestamp: OffsetDateTime::now_utc(), - }; ( Self { version: CURRENT_VERSION, data: IpPacketRequestData::StaticConnect(SignedStaticConnectRequest { - request, + request: StaticConnectRequest { + request_id, + ips, + reply_to, + reply_to_hops, + reply_to_avg_mix_delays, + buffer_timeout, + timestamp: OffsetDateTime::now_utc(), + }, signature: None, }), }, @@ -56,13 +55,16 @@ impl IpPacketRequest { ( Self { version: CURRENT_VERSION, - data: IpPacketRequestData::DynamicConnect(DynamicConnectRequest { - request_id, - reply_to, - reply_to_hops, - reply_to_avg_mix_delays, - buffer_timeout, - timestamp: OffsetDateTime::now_utc(), + data: IpPacketRequestData::DynamicConnect(SignedDynamicConnectRequest { + request: DynamicConnectRequest { + request_id, + reply_to, + reply_to_hops, + reply_to_avg_mix_delays, + buffer_timeout, + timestamp: OffsetDateTime::now_utc(), + }, + signature: None, }), }, request_id, @@ -74,10 +76,13 @@ impl IpPacketRequest { ( Self { version: CURRENT_VERSION, - data: IpPacketRequestData::Disconnect(DisconnectRequest { - request_id, - reply_to, - timestamp: OffsetDateTime::now_utc(), + data: IpPacketRequestData::Disconnect(SignedDisconnectRequest { + request: DisconnectRequest { + request_id, + reply_to, + timestamp: OffsetDateTime::now_utc(), + }, + signature: None, }), }, request_id, @@ -124,8 +129,8 @@ impl IpPacketRequest { pub fn id(&self) -> Option { match &self.data { IpPacketRequestData::StaticConnect(request) => Some(request.request.request_id), - IpPacketRequestData::DynamicConnect(request) => Some(request.request_id), - IpPacketRequestData::Disconnect(request) => Some(request.request_id), + IpPacketRequestData::DynamicConnect(request) => Some(request.request.request_id), + IpPacketRequestData::Disconnect(request) => Some(request.request.request_id), IpPacketRequestData::Data(_) => None, IpPacketRequestData::Ping(request) => Some(request.request_id), IpPacketRequestData::Health(request) => Some(request.request_id), @@ -135,8 +140,8 @@ impl IpPacketRequest { pub fn recipient(&self) -> Option<&Recipient> { match &self.data { IpPacketRequestData::StaticConnect(request) => Some(&request.request.reply_to), - IpPacketRequestData::DynamicConnect(request) => Some(&request.reply_to), - IpPacketRequestData::Disconnect(request) => Some(&request.reply_to), + IpPacketRequestData::DynamicConnect(request) => Some(&request.request.reply_to), + IpPacketRequestData::Disconnect(request) => Some(&request.request.reply_to), IpPacketRequestData::Data(_) => None, IpPacketRequestData::Ping(request) => Some(&request.reply_to), IpPacketRequestData::Health(request) => Some(&request.reply_to), @@ -160,25 +165,31 @@ impl IpPacketRequest { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub enum IpPacketRequestData { StaticConnect(SignedStaticConnectRequest), - DynamicConnect(DynamicConnectRequest), - Disconnect(DisconnectRequest), + DynamicConnect(SignedDynamicConnectRequest), + Disconnect(SignedDisconnectRequest), Data(DataRequest), Ping(PingRequest), Health(HealthRequest), } impl IpPacketRequestData { - pub fn to_bytes(&self) -> Result, bincode::Error> { - use bincode::Options; - make_bincode_serializer().serialize(self) - } - - pub fn add_signature(&mut self, signature: Vec) { + pub fn add_signature(&mut self, signature: Vec) -> Option> { match self { IpPacketRequestData::StaticConnect(request) => { request.signature = Some(signature); + request.signature.clone() } - _ => panic!("Attempted to sign a non-signable request"), + IpPacketRequestData::DynamicConnect(request) => { + request.signature = Some(signature); + request.signature.clone() + } + IpPacketRequestData::Disconnect(request) => { + request.signature = Some(signature); + request.signature.clone() + } + IpPacketRequestData::Data(_) + | IpPacketRequestData::Ping(_) + | IpPacketRequestData::Health(_) => None, } } } @@ -210,6 +221,13 @@ pub struct StaticConnectRequest { pub timestamp: OffsetDateTime, } +impl StaticConnectRequest { + pub fn to_bytes(&self) -> Result, bincode::Error> { + use bincode::Options; + make_bincode_serializer().serialize(self) + } +} + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct SignedStaticConnectRequest { pub request: StaticConnectRequest, @@ -241,6 +259,19 @@ pub struct DynamicConnectRequest { pub timestamp: OffsetDateTime, } +impl DynamicConnectRequest { + pub fn to_bytes(&self) -> Result, bincode::Error> { + use bincode::Options; + make_bincode_serializer().serialize(self) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct SignedDynamicConnectRequest { + pub request: DynamicConnectRequest, + pub signature: Option>, +} + // 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)] @@ -254,6 +285,19 @@ pub struct DisconnectRequest { pub timestamp: OffsetDateTime, } +impl DisconnectRequest { + pub fn to_bytes(&self) -> Result, bincode::Error> { + use bincode::Options; + make_bincode_serializer().serialize(self) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct SignedDisconnectRequest { + pub request: DisconnectRequest, + pub signature: Option>, +} + // 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 { @@ -294,14 +338,18 @@ mod tests { let connect = IpPacketRequest { version: 4, data: IpPacketRequestData::StaticConnect( - StaticConnectRequest { - request_id: 123, - ips: IpPair::new(Ipv4Addr::from_str("10.0.0.1").unwrap(), Ipv6Addr::from_str("2001:db8:a160::1").unwrap()), - reply_to: Recipient::try_from_base58_string("D1rrpsysCGCYXy9saP8y3kmNpGtJZUXN9SvFoUcqAsM9.9Ssso1ea5NfkbMASdiseDSjTN1fSWda5SgEVjdSN4CvV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN").unwrap(), - reply_to_hops: None, - reply_to_avg_mix_delays: None, - buffer_timeout: None, - }, + SignedStaticConnectRequest { + request: StaticConnectRequest { + request_id: 123, + ips: IpPair::new(Ipv4Addr::from_str("10.0.0.1").unwrap(), Ipv6Addr::from_str("2001:db8:a160::1").unwrap()), + reply_to: Recipient::try_from_base58_string("D1rrpsysCGCYXy9saP8y3kmNpGtJZUXN9SvFoUcqAsM9.9Ssso1ea5NfkbMASdiseDSjTN1fSWda5SgEVjdSN4CvV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN").unwrap(), + reply_to_hops: None, + reply_to_avg_mix_delays: None, + buffer_timeout: None, + timestamp: OffsetDateTime::now_utc(), + }, + signature: None, + } ), }; assert_eq!(connect.to_bytes().unwrap().len(), 123); diff --git a/common/ip-packet-requests/src/v7/response.rs b/common/ip-packet-requests/src/v7/response.rs index d3e6a01cef..13312dc5cd 100644 --- a/common/ip-packet-requests/src/v7/response.rs +++ b/common/ip-packet-requests/src/v7/response.rs @@ -7,7 +7,6 @@ use crate::{make_bincode_serializer, IpPair, CURRENT_VERSION}; pub struct IpPacketResponse { pub version: u8, pub data: IpPacketResponseData, - pub signature: Option>, } impl IpPacketResponse { @@ -19,7 +18,6 @@ impl IpPacketResponse { reply_to, reply: StaticConnectResponseReply::Success, }), - signature: None, } } @@ -35,7 +33,6 @@ impl IpPacketResponse { reply_to, reply: StaticConnectResponseReply::Failure(reason), }), - signature: None, } } @@ -47,7 +44,6 @@ impl IpPacketResponse { reply_to, reply: DynamicConnectResponseReply::Success(DynamicConnectSuccess { ips }), }), - signature: None, } } @@ -63,7 +59,6 @@ impl IpPacketResponse { reply_to, reply: DynamicConnectResponseReply::Failure(reason), }), - signature: None, } } @@ -75,7 +70,6 @@ impl IpPacketResponse { reply_to, reply: DisconnectResponseReply::Success, }), - signature: None, } } @@ -91,7 +85,6 @@ impl IpPacketResponse { reply_to, reply: DisconnectResponseReply::Failure(reason), }), - signature: None, } } @@ -105,7 +98,6 @@ impl IpPacketResponse { reply_to, reason, }), - signature: None, } } @@ -113,7 +105,6 @@ impl IpPacketResponse { Self { version: CURRENT_VERSION, data: IpPacketResponseData::Data(DataResponse { ip_packet }), - signature: None, } } @@ -134,7 +125,6 @@ impl IpPacketResponse { }, level: InfoLevel::Error, }), - signature: None, } } @@ -151,7 +141,6 @@ impl IpPacketResponse { reply, level, }), - signature: None, } } @@ -162,7 +151,6 @@ impl IpPacketResponse { request_id, reply_to, }), - signature: None, } } @@ -182,7 +170,6 @@ impl IpPacketResponse { routable, }, }), - signature: None, } } From b59c41d9cd3406ce22561b966e0667b25d37ca25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Mon, 20 May 2024 12:24:39 +0200 Subject: [PATCH 6/6] Fix test --- common/ip-packet-requests/src/v7/request.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/ip-packet-requests/src/v7/request.rs b/common/ip-packet-requests/src/v7/request.rs index c6730f391e..9efeaf2507 100644 --- a/common/ip-packet-requests/src/v7/request.rs +++ b/common/ip-packet-requests/src/v7/request.rs @@ -352,7 +352,7 @@ mod tests { } ), }; - assert_eq!(connect.to_bytes().unwrap().len(), 123); + assert_eq!(connect.to_bytes().unwrap().len(), 139); } #[test]