Files
nym/common/socks5/requests/src/request.rs
T
Jędrzej Stuczyński 0f7280c227 Fixed most recent nightly clippy warnings (#817)
* Fixed most recent nightly clippy warnings

* Missing wasm32 changes
2021-10-15 08:57:16 +01:00

486 lines
17 KiB
Rust

use nymsphinx_addressing::clients::{Recipient, RecipientFormattingError};
use std::convert::TryFrom;
use std::fmt::{self};
pub type ConnectionId = u64;
pub type RemoteAddress = String;
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum RequestFlag {
Connect = 0,
Send = 1,
}
#[derive(Debug)]
pub enum RequestError {
AddressLengthTooShort,
AddressTooShort,
ConnectionIdTooShort,
NoData,
UnknownRequestFlag,
ReturnAddressTooShort,
MalformedReturnAddress(RecipientFormattingError),
}
impl fmt::Display for RequestError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
match self {
RequestError::AddressLengthTooShort => {
write!(f, "not enough bytes to recover the length of the address")
}
RequestError::AddressTooShort => write!(f, "not enough bytes to recover the address"),
RequestError::ConnectionIdTooShort => {
write!(f, "not enough bytes to recover the connection id")
}
RequestError::NoData => write!(f, "no data provided"),
RequestError::UnknownRequestFlag => write!(f, "request of unknown type"),
RequestError::ReturnAddressTooShort => write!(f, "too short return address"),
RequestError::MalformedReturnAddress(recipient_err) => {
write!(f, "malformed return address - {}", recipient_err)
}
}
}
}
impl std::error::Error for RequestError {}
impl RequestError {
pub fn is_malformed_return(&self) -> bool {
matches!(self, RequestError::MalformedReturnAddress(_))
}
}
impl TryFrom<u8> for RequestFlag {
type Error = RequestError;
fn try_from(value: u8) -> Result<RequestFlag, RequestError> {
match value {
_ if value == (RequestFlag::Connect as u8) => Ok(Self::Connect),
_ if value == (RequestFlag::Send as u8) => Ok(Self::Send),
_ => Err(RequestError::UnknownRequestFlag),
}
}
}
#[derive(Debug)]
pub struct ConnectRequest {
pub conn_id: ConnectionId,
pub remote_addr: RemoteAddress,
pub return_address: Recipient,
}
/// 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)]
pub enum Request {
/// Start a new TCP connection to the specified `RemoteAddress` and send
/// the request data up the connection.
/// All responses produced on this `ConnectionId` should come back to the specified `Recipient`
Connect(Box<ConnectRequest>),
/// Re-use an existing TCP connection, sending more request data up it.
Send(ConnectionId, Vec<u8>, bool),
}
impl Request {
/// Construct a new Request::Connect instance
pub fn new_connect(
conn_id: ConnectionId,
remote_addr: RemoteAddress,
return_address: Recipient,
) -> Request {
Request::Connect(Box::new(ConnectRequest {
conn_id,
remote_addr,
return_address,
}))
}
/// Construct a new Request::Send instance
pub fn new_send(conn_id: ConnectionId, data: Vec<u8>, local_closed: bool) -> Request {
Request::Send(conn_id, data, local_closed)
}
/// Deserialize the request type, connection id, destination address and port,
/// and the request body from bytes.
///
/// Serialized bytes looks like this:
///
/// --------------------------------------------------------------------------------------
/// request_flag | connection_id | address_length | remote_address_bytes | request_data |
/// 1 | 8 | 2 | address_length | ... |
/// --------------------------------------------------------------------------------------
///
/// The request_flag tells us whether this is a new connection request (`new_connect`),
/// an already-established connection we should send up (`new_send`), or
/// a request to close an established connection (`new_close`).
pub fn try_from_bytes(b: &[u8]) -> Result<Request, RequestError> {
// each request needs to at least contain flag and ConnectionId
if b.is_empty() {
return Err(RequestError::NoData);
}
if b.len() < 9 {
return Err(RequestError::ConnectionIdTooShort);
}
let connection_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 => {
let connect_request_bytes = &b[9..];
// we need to be able to read at least 2 bytes that specify address length
if connect_request_bytes.len() < 2 {
return Err(RequestError::AddressLengthTooShort);
}
let address_length =
u16::from_be_bytes([connect_request_bytes[0], connect_request_bytes[1]])
as usize;
if connect_request_bytes.len() < 2 + address_length {
return Err(RequestError::AddressTooShort);
}
let address_start = 2;
let address_end = address_start + address_length;
let address_bytes = &connect_request_bytes[address_start..address_end];
let remote_address = String::from_utf8_lossy(address_bytes).to_string();
// just a temporary reference to mid-slice for ease of use
let recipient_data_bytes = &connect_request_bytes[address_end..];
if recipient_data_bytes.len() != Recipient::LEN {
return Err(RequestError::ReturnAddressTooShort);
}
let mut return_bytes = [0u8; Recipient::LEN];
return_bytes.copy_from_slice(&recipient_data_bytes[..Recipient::LEN]);
let return_address = Recipient::try_from_bytes(return_bytes)
.map_err(RequestError::MalformedReturnAddress)?;
Ok(Request::new_connect(
connection_id,
remote_address,
return_address,
))
}
RequestFlag::Send => {
let local_closed = b[9] != 0;
let data = b[10..].as_ref().to_vec();
Ok(Request::Send(connection_id, data, local_closed))
}
}
}
/// Serialize a Socks5 request into bytes, so that it can be packed inside
/// a Sphinx packet, sent through the mixnet, and deserialized by the Socks5
/// service provider which will make the request.
pub fn into_bytes(self) -> Vec<u8> {
match self {
// connect is: CONN_FLAG || CONN_ID || REMOTE_LEN || REMOTE || RETURN
Request::Connect(req) => {
let remote_address_bytes = req.remote_addr.into_bytes();
let remote_address_bytes_len = remote_address_bytes.len() as u16;
std::iter::once(RequestFlag::Connect as u8)
.chain(req.conn_id.to_be_bytes().iter().cloned())
.chain(remote_address_bytes_len.to_be_bytes().iter().cloned())
.chain(remote_address_bytes.into_iter())
.chain(req.return_address.to_bytes().iter().cloned())
.collect()
}
Request::Send(conn_id, data, local_closed) => std::iter::once(RequestFlag::Send as u8)
.chain(conn_id.to_be_bytes().iter().cloned())
.chain(std::iter::once(local_closed as u8))
.chain(data.into_iter())
.collect(),
}
}
}
#[cfg(test)]
mod request_deserialization_tests {
use super::*;
mod all_request_types {
use super::*;
#[test]
fn returns_error_when_zero_bytes() {
let request_bytes = Vec::new();
match Request::try_from_bytes(&request_bytes).unwrap_err() {
RequestError::NoData => {}
_ => unreachable!(),
}
}
#[test]
fn returns_error_when_connection_id_too_short() {
let request_bytes = [RequestFlag::Connect as u8, 1, 2, 3, 4, 5, 6, 7].to_vec(); // 7 bytes connection id
match Request::try_from_bytes(&request_bytes).unwrap_err() {
RequestError::ConnectionIdTooShort => {}
_ => unreachable!(),
}
}
}
#[cfg(test)]
mod sending_data_over_a_new_connection {
use super::*;
#[test]
fn returns_error_when_address_length_is_too_short() {
let request_bytes1 = [RequestFlag::Connect as u8, 1, 2, 3, 4, 5, 6, 7, 8].to_vec(); // 8 bytes connection id, 0 bytes address length (2 were expected)
let request_bytes2 = [RequestFlag::Connect as u8, 1, 2, 3, 4, 5, 6, 7, 8, 0].to_vec(); // 8 bytes connection id, 1 bytes address length (2 were expected)
match Request::try_from_bytes(&request_bytes1).unwrap_err() {
RequestError::AddressLengthTooShort => {}
_ => unreachable!(),
}
match Request::try_from_bytes(&request_bytes2).unwrap_err() {
RequestError::AddressLengthTooShort => {}
_ => unreachable!(),
}
}
#[test]
fn returns_error_when_address_too_short_for_given_address_length() {
let request_bytes = [RequestFlag::Connect as u8, 1, 2, 3, 4, 5, 6, 7, 8, 0, 1].to_vec(); // 8 bytes connection id, 2 bytes address length, missing address
match Request::try_from_bytes(&request_bytes).unwrap_err() {
RequestError::AddressTooShort => {}
_ => unreachable!(),
}
}
#[test]
fn returns_error_for_when_return_address_is_too_short() {
// this one has "foo.com" remote address and correct 8 bytes of connection_id
let request_bytes_prefix = [
RequestFlag::Connect as u8,
1,
2,
3,
4,
5,
6,
7,
8,
0,
7,
102,
111,
111,
46,
99,
111,
109,
];
let recipient = Recipient::try_from_base58_string("CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@4sBbL1ngf1vtNqykydQKTFh26sQCw888GpUqvPvyNB4f").unwrap();
let recipient_bytes = recipient.to_bytes();
// take only part of actual recipient
let request_bytes: Vec<_> = request_bytes_prefix
.iter()
.cloned()
.chain(recipient_bytes.iter().take(40).cloned())
.collect();
match Request::try_from_bytes(&request_bytes).unwrap_err() {
RequestError::ReturnAddressTooShort => {}
_ => unreachable!(),
}
}
#[test]
fn returns_error_for_when_return_address_is_malformed() {
// this one has "foo.com" remote address and correct 8 bytes of connection_id
let request_bytes_prefix = [
RequestFlag::Connect as u8,
1,
2,
3,
4,
5,
6,
7,
8,
0,
7,
102,
111,
111,
46,
99,
111,
109,
];
let recipient = Recipient::try_from_base58_string("CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@4sBbL1ngf1vtNqykydQKTFh26sQCw888GpUqvPvyNB4f").unwrap();
let mut recipient_bytes = recipient.to_bytes();
// mess up few bytes
recipient_bytes[0] = 255;
recipient_bytes[15] ^= 1;
recipient_bytes[31] ^= 1;
let request_bytes: Vec<_> = request_bytes_prefix
.iter()
.cloned()
.chain(recipient_bytes.iter().cloned())
.collect();
assert!(Request::try_from_bytes(&request_bytes)
.unwrap_err()
.is_malformed_return());
}
#[test]
fn works_when_request_is_sized_properly_even_without_data() {
// this one has "foo.com" remote address, correct 8 bytes of connection_id, and 0 bytes request data
let request_bytes = [
RequestFlag::Connect as u8,
1,
2,
3,
4,
5,
6,
7,
8,
0,
7,
102,
111,
111,
46,
99,
111,
109,
]
.to_vec();
let recipient = Recipient::try_from_base58_string("CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@4sBbL1ngf1vtNqykydQKTFh26sQCw888GpUqvPvyNB4f").unwrap();
let recipient_bytes = recipient.to_bytes();
let request_bytes: Vec<_> = request_bytes
.into_iter()
.chain(recipient_bytes.iter().cloned())
.collect();
let request = Request::try_from_bytes(&request_bytes).unwrap();
match request {
Request::Connect(req) => {
assert_eq!("foo.com".to_string(), req.remote_addr);
assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), req.conn_id);
assert_eq!(
req.return_address.to_bytes().to_vec(),
recipient.to_bytes().to_vec()
);
}
_ => unreachable!(),
}
}
#[test]
fn works_when_request_is_sized_properly_and_has_data() {
// this one has a 1-byte remote address, correct 8 bytes of connection_id, and 3 bytes request data
let request_bytes = [
RequestFlag::Connect as u8,
1,
2,
3,
4,
5,
6,
7,
8,
0,
7,
102,
111,
111,
46,
99,
111,
109,
]
.to_vec();
let recipient = Recipient::try_from_base58_string("CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@4sBbL1ngf1vtNqykydQKTFh26sQCw888GpUqvPvyNB4f").unwrap();
let recipient_bytes = recipient.to_bytes();
let request_bytes: Vec<_> = request_bytes
.into_iter()
.chain(recipient_bytes.iter().cloned())
.collect();
let request = Request::try_from_bytes(&request_bytes).unwrap();
match request {
Request::Connect(req) => {
assert_eq!("foo.com".to_string(), req.remote_addr);
assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), req.conn_id);
assert_eq!(
req.return_address.to_bytes().to_vec(),
recipient.to_bytes().to_vec()
);
}
_ => unreachable!(),
}
}
}
#[cfg(test)]
mod sending_additional_data_over_an_existing_connection {
use super::*;
#[test]
fn works_when_request_is_sized_properly_even_without_data() {
// correct 8 bytes of connection_id, 1 byte of local_closed and 0 bytes request data
let request_bytes = [RequestFlag::Send as u8, 1, 2, 3, 4, 5, 6, 7, 8, 0].to_vec();
let request = Request::try_from_bytes(&request_bytes).unwrap();
match request {
Request::Send(conn_id, data, local_closed) => {
assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), conn_id);
assert_eq!(Vec::<u8>::new(), data);
assert!(!local_closed)
}
_ => unreachable!(),
}
}
#[test]
fn works_when_request_is_sized_properly_and_has_data() {
// correct 8 bytes of connection_id, 1 byte of local_closed and 3 bytes request data (all 255)
let request_bytes = [
RequestFlag::Send as u8,
1,
2,
3,
4,
5,
6,
7,
8,
0,
255,
255,
255,
]
.to_vec();
let request = Request::try_from_bytes(&request_bytes).unwrap();
match request {
Request::Send(conn_id, data, local_closed) => {
assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), conn_id);
assert_eq!(vec![255, 255, 255], data);
assert!(!local_closed)
}
_ => unreachable!(),
}
}
}
}