Merge remote-tracking branch 'origin/release/v1.1.21' into develop

This commit is contained in:
Jon Häggblad
2023-06-09 09:54:53 +02:00
13 changed files with 323 additions and 32 deletions
Generated
+14
View File
@@ -389,6 +389,15 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72"
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "bip32"
version = "0.3.0"
@@ -4049,6 +4058,7 @@ dependencies = [
name = "nym-network-requester"
version = "1.1.20"
dependencies = [
"anyhow",
"async-file-watcher",
"async-trait",
"bs58",
@@ -4225,6 +4235,7 @@ dependencies = [
"log",
"nym-bin-common",
"nym-sdk",
"nym-socks5-requests",
"nym-sphinx-anonymous-replies",
"serde",
"serde_json",
@@ -4330,10 +4341,13 @@ dependencies = [
name = "nym-socks5-requests"
version = "0.1.0"
dependencies = [
"bincode",
"log",
"nym-service-providers-common",
"nym-sphinx-addressing",
"serde",
"serde_json",
"tap",
"thiserror",
]
@@ -85,6 +85,14 @@ impl MixnetResponseListener {
.unwrap();
Ok(())
}
Socks5ResponseContent::Query(response) => {
error!("received a query response which we don't know how to handle yet!");
error!("got: {:?}", response);
// I guess we'd need another channel here to forward those to where they need to go
Ok(())
}
}
}
+6 -4
View File
@@ -7,9 +7,11 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
thiserror = { workspace = true }
bincode = "1.3.3"
log = { workspace = true }
nym-service-providers-common = { path = "../../../service-providers/common" }
nym-sphinx-addressing = { path = "../../../common/nymsphinx/addressing" }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
nym-sphinx-addressing = { path = "../../../common/nymsphinx/addressing" }
nym-service-providers-common = { path = "../../../service-providers/common" }
tap = { workspace = true }
thiserror = { workspace = true }
+12
View File
@@ -32,6 +32,18 @@ pub enum Socks5RequestError {
#[error(transparent)]
ProviderInterfaceError(#[from] ServiceProviderMessagingError),
#[error("received unsupported request protocol version: {protocol_version}")]
UnsupportedProtocolVersion {
protocol_version: <Socks5Request as interface::ServiceProviderRequest>::ProtocolVersion,
},
}
fn make_bincode_serializer() -> impl bincode::Options {
use bincode::Options;
bincode::DefaultOptions::new()
.with_big_endian()
.with_varint_encoding()
}
#[cfg(test)]
+89 -8
View File
@@ -1,10 +1,12 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{Socks5ProtocolVersion, Socks5RequestError, Socks5Response};
use crate::{make_bincode_serializer, Socks5ProtocolVersion, Socks5RequestError, Socks5Response};
use nym_service_providers_common::interface::{Serializable, ServiceProviderRequest};
use nym_sphinx_addressing::clients::{Recipient, RecipientFormattingError};
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
use tap::TapFallible;
use thiserror::Error;
pub type ConnectionId = u64;
@@ -15,6 +17,7 @@ pub type RemoteAddress = String;
pub enum RequestFlag {
Connect = 0,
Send = 1,
Query = 2,
}
impl TryFrom<u8> for RequestFlag {
@@ -24,6 +27,7 @@ impl TryFrom<u8> for RequestFlag {
match value {
_ if value == (RequestFlag::Connect as u8) => Ok(Self::Connect),
_ if value == (RequestFlag::Send as u8) => Ok(Self::Send),
_ if value == (RequestFlag::Query as u8) => Ok(Self::Query),
value => Err(RequestDeserializationError::UnknownRequestFlag { value }),
}
}
@@ -51,6 +55,12 @@ pub enum RequestDeserializationError {
#[error("malformed return address - {0}")]
MalformedReturnAddress(RecipientFormattingError),
#[error("failed to deserialize query request: {source}")]
QueryDeserializationError {
#[from]
source: bincode::Error,
},
}
impl RequestDeserializationError {
@@ -59,7 +69,7 @@ impl RequestDeserializationError {
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConnectRequest {
// TODO: is connection_id redundant now?
pub conn_id: ConnectionId,
@@ -67,13 +77,19 @@ pub struct ConnectRequest {
pub return_address: Option<Recipient>,
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SendRequest {
pub conn_id: ConnectionId,
pub data: Vec<u8>,
pub local_closed: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum QueryRequest {
OpenProxy,
Description,
}
#[derive(Debug, Clone)]
pub struct Socks5Request {
pub protocol_version: Socks5ProtocolVersion,
@@ -108,6 +124,13 @@ impl Serializable for Socks5Request {
}
let protocol_version = Socks5ProtocolVersion::from(b[0]);
if protocol_version > Self::max_supported_version() {
return Err(Socks5RequestError::UnsupportedProtocolVersion { protocol_version });
}
// TODO: handle the case then protocol version if less then the current one. Then we should
// make sure to only respond with the same version
Ok(Socks5Request {
protocol_version,
content: Socks5RequestContent::try_from_bytes(&b[1..])?,
@@ -166,11 +189,21 @@ impl Socks5Request {
content: Socks5RequestContent::new_send(conn_id, data, local_closed),
}
}
pub fn new_query(
protocol_version: Socks5ProtocolVersion,
query: QueryRequest,
) -> Socks5Request {
Socks5Request {
protocol_version,
content: Socks5RequestContent::Query(query),
}
}
}
/// A request from a SOCKS5 client that a Nym Socks5 service provider should
/// take an action for an application using a (probably local) Nym Socks5 proxy.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Socks5RequestContent {
/// Start a new TCP connection to the specified `RemoteAddress` and send
/// the request data up the connection.
@@ -179,6 +212,8 @@ pub enum Socks5RequestContent {
/// Re-use an existing TCP connection, sending more request data up it.
Send(SendRequest),
Query(QueryRequest),
}
impl Socks5RequestContent {
@@ -228,12 +263,13 @@ impl Socks5RequestContent {
return Err(RequestDeserializationError::NoData);
}
if b.len() < 9 {
return Err(RequestDeserializationError::ConnectionIdTooShort);
}
let conn_id = u64::from_be_bytes([b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8]]);
match RequestFlag::try_from(b[0])? {
RequestFlag::Connect => {
if b.len() < 9 {
return Err(RequestDeserializationError::ConnectionIdTooShort);
}
let conn_id = u64::from_be_bytes([b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8]]);
let connect_request_bytes = &b[9..];
// we need to be able to read at least 2 bytes that specify address length
@@ -279,6 +315,11 @@ impl Socks5RequestContent {
))
}
RequestFlag::Send => {
if b.len() < 9 {
return Err(RequestDeserializationError::ConnectionIdTooShort);
}
let conn_id = u64::from_be_bytes([b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8]]);
let local_closed = b[9] != 0;
let data = b[10..].to_vec();
@@ -288,6 +329,11 @@ impl Socks5RequestContent {
local_closed,
}))
}
RequestFlag::Query => {
use bincode::Options;
let query = make_bincode_serializer().deserialize(&b[1..])?;
Ok(Socks5RequestContent::Query(query))
}
}
}
@@ -317,6 +363,19 @@ impl Socks5RequestContent {
.chain(std::iter::once(req.local_closed as u8))
.chain(req.data.into_iter())
.collect(),
Socks5RequestContent::Query(query) => {
use bincode::Options;
let query_bytes: Vec<u8> = make_bincode_serializer()
.serialize(&query)
.tap_err(|err| {
log::error!("Failed to serialize query request: {:?}: {err}", query);
})
.unwrap_or_default();
std::iter::once(RequestFlag::Query as u8)
.chain(query_bytes.into_iter())
.collect()
}
}
}
}
@@ -611,4 +670,26 @@ mod request_deserialization_tests {
}
}
}
#[cfg(test)]
mod serialize_query_request {
use super::*;
#[test]
fn serialize_there_and_back() {
let open_proxy = Socks5RequestContent::Query(QueryRequest::OpenProxy);
let bytes_open_proxy = open_proxy.clone().into_bytes();
assert_eq!(bytes_open_proxy, vec![2, 0]);
let description = Socks5RequestContent::Query(QueryRequest::Description);
let bytes_description = description.clone().into_bytes();
assert_eq!(bytes_description, vec![2, 1]);
let open_proxy2 = Socks5RequestContent::try_from_bytes(&bytes_open_proxy).unwrap();
let description2 = Socks5RequestContent::try_from_bytes(&bytes_description).unwrap();
assert_eq!(open_proxy, open_proxy2);
assert_eq!(description, description2);
}
}
}
+83 -14
View File
@@ -1,8 +1,10 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{ConnectionId, Socks5ProtocolVersion, Socks5RequestError};
use crate::{make_bincode_serializer, ConnectionId, Socks5ProtocolVersion, Socks5RequestError};
use nym_service_providers_common::interface::{Serializable, ServiceProviderResponse};
use serde::{Deserialize, Serialize};
use tap::TapFallible;
use thiserror::Error;
// don't start tags from 0 for easier backwards compatibility since `NetworkData`
@@ -13,6 +15,7 @@ use thiserror::Error;
pub enum ResponseFlag {
NetworkData = 1,
ConnectionError = 2,
Query = 3,
}
impl TryFrom<u8> for ResponseFlag {
@@ -22,12 +25,13 @@ impl TryFrom<u8> for ResponseFlag {
match value {
_ if value == (ResponseFlag::NetworkData as u8) => Ok(Self::NetworkData),
_ if value == (ResponseFlag::ConnectionError as u8) => Ok(Self::ConnectionError),
_ if value == (ResponseFlag::Query as u8) => Ok(Self::Query),
value => Err(ResponseDeserializationError::UnknownResponseFlag { value }),
}
}
}
#[derive(Debug, Error, PartialEq, Eq)]
#[derive(Debug, Error)]
pub enum ResponseDeserializationError {
#[error("not enough bytes to recover the connection id")]
ConnectionIdTooShort,
@@ -43,6 +47,12 @@ pub enum ResponseDeserializationError {
#[from]
source: std::string::FromUtf8Error,
},
#[error("failed to deserialize query response: {source}")]
QueryDeserializationError {
#[from]
source: bincode::Error,
},
}
#[derive(Debug)]
@@ -135,12 +145,23 @@ impl Socks5Response {
content: Socks5ResponseContent::new_connection_error(connection_id, error_message),
}
}
pub fn new_query(
protocol_version: Socks5ProtocolVersion,
query_response: QueryResponse,
) -> Socks5Response {
Socks5Response {
protocol_version,
content: Socks5ResponseContent::Query(query_response),
}
}
}
#[derive(Debug)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Socks5ResponseContent {
NetworkData(NetworkData),
ConnectionError(ConnectionError),
Query(QueryResponse),
}
impl Socks5ResponseContent {
@@ -175,6 +196,18 @@ impl Socks5ResponseContent {
.chain(res.into_bytes().into_iter())
.collect()
}
Socks5ResponseContent::Query(query) => {
use bincode::Options;
let query_bytes: Vec<u8> = make_bincode_serializer()
.serialize(&query)
.tap_err(|err| {
log::error!("Failed to serialize query response: {:?}: {err}", query);
})
.unwrap_or_default();
std::iter::once(ResponseFlag::Query as u8)
.chain(query_bytes.into_iter())
.collect()
}
}
}
@@ -193,6 +226,11 @@ impl Socks5ResponseContent {
ResponseFlag::ConnectionError => Ok(Socks5ResponseContent::ConnectionError(
ConnectionError::try_from_bytes(&b[1..])?,
)),
ResponseFlag::Query => {
use bincode::Options;
let query = make_bincode_serializer().deserialize(&b[1..])?;
Ok(Socks5ResponseContent::Query(query))
}
}
}
}
@@ -200,7 +238,7 @@ impl Socks5ResponseContent {
/// A remote network network data response retrieved by the Socks5 service provider. This
/// can be serialized and sent back through the mixnet to the requesting
/// application.
#[derive(Debug)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NetworkData {
pub data: Vec<u8>,
pub connection_id: ConnectionId,
@@ -264,7 +302,7 @@ impl NetworkData {
}
}
#[derive(Debug)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ConnectionError {
pub connection_id: ConnectionId,
pub network_requester_error: String,
@@ -318,6 +356,12 @@ impl ConnectionError {
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum QueryResponse {
OpenProxy(bool),
Description(String),
}
#[cfg(test)]
mod tests {
use super::*;
@@ -330,19 +374,18 @@ mod tests {
fn fails_when_zero_bytes_are_supplied() {
let response_bytes = Vec::new();
assert_eq!(
ResponseDeserializationError::NoData,
NetworkData::try_from_bytes(&response_bytes).unwrap_err()
);
let err = NetworkData::try_from_bytes(&response_bytes).unwrap_err();
assert!(matches!(err, ResponseDeserializationError::NoData));
}
#[test]
fn fails_when_connection_id_bytes_are_too_short() {
let response_bytes = vec![0, 1, 2, 3, 4, 5, 6];
assert_eq!(
let err = NetworkData::try_from_bytes(&response_bytes).unwrap_err();
assert!(matches!(
err,
ResponseDeserializationError::ConnectionIdTooShort,
NetworkData::try_from_bytes(&response_bytes).unwrap_err()
);
));
}
#[test]
@@ -396,11 +439,14 @@ mod tests {
#[test]
fn deserialization_errors() {
let err = ConnectionError::try_from_bytes(&[]).err().unwrap();
assert_eq!(err, ResponseDeserializationError::NoData);
assert!(matches!(err, ResponseDeserializationError::NoData));
let bytes: [u8; 5] = [1, 2, 3, 4, 5];
let err = ConnectionError::try_from_bytes(&bytes).err().unwrap();
assert_eq!(err, ResponseDeserializationError::ConnectionIdTooShort);
assert!(matches!(
err,
ResponseDeserializationError::ConnectionIdTooShort
));
let bytes: Vec<u8> = 42u64
.to_be_bytes()
@@ -414,4 +460,27 @@ mod tests {
));
}
}
#[cfg(test)]
mod serialize_query_response {
use super::*;
#[test]
fn serialize_there_and_back() {
let open_proxy = Socks5ResponseContent::Query(QueryResponse::OpenProxy(true));
let bytes_open_proxy = open_proxy.clone().into_bytes();
assert_eq!(bytes_open_proxy, vec![3, 0, 1]);
let description =
Socks5ResponseContent::Query(QueryResponse::Description("foo".to_string()));
let bytes_description = description.clone().into_bytes();
assert_eq!(bytes_description, vec![3, 1, 3, 102, 111, 111]);
let open_proxy2 = Socks5ResponseContent::try_from_bytes(&bytes_open_proxy).unwrap();
let description2 = Socks5ResponseContent::try_from_bytes(&bytes_description).unwrap();
assert_eq!(open_proxy, open_proxy2);
assert_eq!(description, description2);
}
}
}
+12
View File
@@ -217,6 +217,15 @@ version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "bip32"
version = "0.3.0"
@@ -3683,10 +3692,13 @@ dependencies = [
name = "nym-socks5-requests"
version = "0.1.0"
dependencies = [
"bincode",
"log",
"nym-service-providers-common",
"nym-sphinx-addressing",
"serde",
"serde_json",
"tap",
"thiserror",
]
+1 -1
View File
@@ -21,5 +21,5 @@ anyhow = "1.0.68"
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
nym-sdk = { path = "../../sdk/rust/nym-sdk" }
nym-socks5-requests = { path = "../../common/socks5/requests" }
@@ -195,10 +195,10 @@ impl ControlResponse {
// (unless the serde's macro is bugged but at this point we're already out of luck)
serde_json::to_vec(&info).unwrap()
}
ControlResponse::Error(error_response) => serde_json::to_vec(&error_response).unwrap(),
ControlResponse::SupportedRequestVersions(supported_versions) => {
serde_json::to_vec(&supported_versions).unwrap()
}
ControlResponse::Error(error_response) => serde_json::to_vec(&error_response).unwrap(),
}
}
}
@@ -22,6 +22,7 @@ pub enum ResponseContent<T: ServiceProviderRequest = EmptyMessage> {
ProviderData(T::Response),
}
#[derive(Debug)]
#[repr(u8)]
pub enum ResponseTag {
/// Value tag representing [`Control`] variant of the [`Response`]
@@ -54,3 +54,4 @@ nym-client-websocket-requests = { path = "../../clients/native/websocket-request
[dev-dependencies]
tempfile = "3.5.0"
anyhow = "1.0.68"
@@ -0,0 +1,73 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_sdk::mixnet::{IncludedSurbs, MixnetClient, Recipient, ReconstructedMessage};
use nym_service_providers_common::interface::{
ProviderInterfaceVersion, Request, Response, ResponseContent,
};
use nym_socks5_requests::{QueryRequest, Socks5Request, Socks5Response};
fn parse_response(received: Vec<ReconstructedMessage>) -> Socks5Response {
assert_eq!(received.len(), 1);
let response: Response<Socks5Request> = Response::try_from_bytes(&received[0].message).unwrap();
match response.content {
ResponseContent::Control(control) => panic!("unexpected control response: {:?}", control),
ResponseContent::ProviderData(data) => data,
}
}
async fn wait_for_response(client: &mut MixnetClient) -> Socks5Response {
loop {
let next = client.wait_for_messages().await.unwrap();
if !next.is_empty() {
return parse_response(next);
}
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
//nym_bin_common::logging::setup_logging();
let mut client = MixnetClient::connect_new().await.unwrap();
let provider: Recipient = "AN8eLxYWFitCkMn92zim3PrPszxJZDYyFFKP7qnnAAew.8UAxL3LwQBis6WpM3GGXaqKGaVdnLCpGJWumHT6KNdTH@77TSuVU8d1oXKbPzjec2xh4i3Wj5WwUyy9Lr36sm8gZm".parse().unwrap();
use nym_socks5_requests::Socks5ProtocolVersion;
let open_proxy_request = Request::new_provider_data(
ProviderInterfaceVersion::new_current(),
Socks5Request::new_query(
Socks5ProtocolVersion::new_current(),
QueryRequest::OpenProxy,
),
);
let description_request = Request::new_provider_data(
ProviderInterfaceVersion::new_current(),
Socks5Request::new_query(
Socks5ProtocolVersion::new_current(),
QueryRequest::Description,
),
);
println!("Sending 'OpenProxy' query...");
client
.send_bytes(
provider,
open_proxy_request.into_bytes(),
IncludedSurbs::new(10), //crashes??
)
.await;
let response = wait_for_response(&mut client).await;
println!("response to 'OpenProxy' query: {response:#?}");
println!("Sending 'Description' query...");
client
.send_bytes(
provider,
description_request.into_bytes(),
IncludedSurbs::none(),
)
.await;
let response = wait_for_response(&mut client).await;
println!("response to 'Description' query: {response:#?}");
Ok(())
}
@@ -25,8 +25,9 @@ use nym_socks5_proxy_helpers::connection_controller::{
};
use nym_socks5_proxy_helpers::proxy_runner::{MixProxyReader, MixProxySender};
use nym_socks5_requests::{
ConnectRequest, ConnectionId, NetworkData, SendRequest, Socks5ProtocolVersion,
Socks5ProviderRequest, Socks5Request, Socks5RequestContent, Socks5Response,
ConnectRequest, ConnectionId, NetworkData, QueryRequest, QueryResponse, SendRequest,
Socks5ProtocolVersion, Socks5ProviderRequest, Socks5Request, Socks5RequestContent,
Socks5Response,
};
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
@@ -67,7 +68,6 @@ struct NRServiceProvider {
controller_sender: ControllerSender,
mix_input_sender: MixProxySender<MixnetMessage>,
//shared_lane_queue_lengths: LaneQueueLengths,
stats_collector: Option<ServiceStatisticsCollector>,
shutdown: TaskManager,
}
@@ -149,6 +149,7 @@ impl ServiceProvider<Socks5Request> for NRServiceProvider {
}
self.handle_proxy_send(req)
}
Socks5RequestContent::Query(query) => return self.handle_query(query),
}
Ok(None)
@@ -255,7 +256,6 @@ impl NRServiceProviderBuilder {
mixnet_client,
controller_sender,
mix_input_sender,
//shared_lane_queue_lengths: mixnet_client.shared_lane_queue_lengths(),
stats_collector,
shutdown,
};
@@ -477,6 +477,24 @@ impl NRServiceProvider {
fn handle_proxy_send(&mut self, req: SendRequest) {
self.controller_sender.unbounded_send(req.into()).unwrap()
}
fn handle_query(
&self,
query: QueryRequest,
) -> Result<Option<Socks5Response>, NetworkRequesterError> {
let protocol_version = Socks5ProtocolVersion::default();
let response = match query {
QueryRequest::OpenProxy => Socks5Response::new_query(
protocol_version,
QueryResponse::OpenProxy(self.open_proxy),
),
QueryRequest::Description => Socks5Response::new_query(
protocol_version,
QueryResponse::Description("Description (placeholder)".to_string()),
),
};
Ok(Some(response))
}
}
// Helper function to create the mixnet client.