Feature/sphinx socket packet encoder (#245)

* Ability to send sphinx packets of different sizes + more efficient decoding

* Closing connection on connection corruption

* Missing semicolons

* Missing license notices

* Default for packetsize
This commit is contained in:
Jędrzej Stuczyński
2020-05-27 10:08:08 +01:00
committed by GitHub
parent aeb1a4b22e
commit 860a69b246
30 changed files with 817 additions and 492 deletions
+32 -42
View File
@@ -15,29 +15,32 @@
use crate::node::packet_processing::{MixProcessingResult, PacketProcessor};
use futures::channel::mpsc;
use log::*;
use nymsphinx::framing::SphinxCodec;
use nymsphinx::SphinxPacket;
use std::io;
use std::net::SocketAddr;
use tokio::prelude::*;
use tokio::runtime::Handle;
use tokio::stream::StreamExt;
use tokio::task::JoinHandle;
use tokio_util::codec::Framed;
async fn process_received_packet(
packet_data: [u8; nymsphinx::PACKET_SIZE],
sphinx_packet: SphinxPacket,
packet_processor: PacketProcessor,
forwarding_channel: mpsc::UnboundedSender<(SocketAddr, Vec<u8>)>,
forwarding_channel: mpsc::UnboundedSender<(SocketAddr, SphinxPacket)>,
) {
// all processing incl. delay was done, the only thing left is to forward it
match packet_processor.process_sphinx_packet(packet_data).await {
match packet_processor.process_sphinx_packet(sphinx_packet).await {
Err(e) => debug!("We failed to process received sphinx packet - {:?}", e),
Ok(res) => match res {
MixProcessingResult::ForwardHop(hop_address, hop_data) => {
MixProcessingResult::ForwardHop(hop_address, forward_packet) => {
// send our data to tcp client for forwarding. If forwarding fails, then it fails,
// it's not like we can do anything about it
//
// in unbounded_send() failed it means that the receiver channel was disconnected
// and hence something weird must have happened without a way of recovering
forwarding_channel
.unbounded_send((hop_address, hop_data))
.unbounded_send((hop_address, forward_packet))
.unwrap();
packet_processor.report_sent(hop_address);
}
@@ -49,57 +52,44 @@ async fn process_received_packet(
}
async fn process_socket_connection(
mut socket: tokio::net::TcpStream,
socket: tokio::net::TcpStream,
packet_processor: PacketProcessor,
forwarding_channel: mpsc::UnboundedSender<(SocketAddr, Vec<u8>)>,
forwarding_channel: mpsc::UnboundedSender<(SocketAddr, SphinxPacket)>,
) {
let mut buf = [0u8; nymsphinx::PACKET_SIZE];
loop {
match socket.read_exact(&mut buf).await {
// socket closed
Ok(n) if n == 0 => {
trace!("Remote connection closed.");
return;
}
Ok(n) => {
// If I understand it correctly, this if should never be executed as if `read_exact`
// does not fill buffer, it will throw UnexpectedEof?
if n != nymsphinx::PACKET_SIZE {
warn!("read data of different length than expected sphinx packet size - {} (expected {})", n, nymsphinx::PACKET_SIZE);
continue;
}
// we must be able to handle multiple packets from same connection independently
let mut framed = Framed::new(socket, SphinxCodec);
while let Some(sphinx_packet) = framed.next().await {
match sphinx_packet {
Ok(sphinx_packet) => {
// we *really* need a worker pool here, because if we receive too many packets,
// we will spawn too many tasks and starve CPU due to context switching.
// (because presumably tokio has some concept of context switching in its
// scheduler)
tokio::spawn(process_received_packet(
buf,
// note: processing_data is relatively cheap (and safe) to clone -
// it contains arc to private key and metrics reporter (which is just
// a single mpsc unbounded sender)
sphinx_packet,
packet_processor.clone(),
forwarding_channel.clone(),
))
));
}
Err(e) => {
if e.kind() == io::ErrorKind::UnexpectedEof {
debug!("Read buffer was not fully filled. Most likely the client ({:?}) closed the connection.\
Also closing the connection on this end.", socket.peer_addr())
} else {
warn!(
"failed to read from socket (source: {:?}). Closing the connection; err = {:?}",
socket.peer_addr(),
e
);
}
Err(err) => {
error!(
"The socket connection got corrupted with error: {:?}. Closing the socket",
err
);
return;
}
};
}
}
info!(
"Closing connection from {:?}",
framed.into_inner().peer_addr()
);
}
pub(crate) fn run_socket_listener(
handle: &Handle,
addr: SocketAddr,
packet_processor: PacketProcessor,
forwarding_channel: mpsc::UnboundedSender<(SocketAddr, Vec<u8>)>,
forwarding_channel: mpsc::UnboundedSender<(SocketAddr, SphinxPacket)>,
) -> JoinHandle<io::Result<()>> {
let handle_clone = handle.clone();
handle.spawn(async move {
+3 -2
View File
@@ -18,6 +18,7 @@ use crypto::encryption;
use directory_client::presence::Topology;
use futures::channel::mpsc;
use log::*;
use nymsphinx::SphinxPacket;
use std::net::SocketAddr;
use tokio::runtime::Runtime;
use topology::NymTopology;
@@ -71,7 +72,7 @@ impl MixNode {
fn start_socket_listener(
&self,
metrics_reporter: metrics::MetricsReporter,
forwarding_channel: mpsc::UnboundedSender<(SocketAddr, Vec<u8>)>,
forwarding_channel: mpsc::UnboundedSender<(SocketAddr, SphinxPacket)>,
) {
info!("Starting socket listener...");
// this is the only location where our private key is going to be copied
@@ -87,7 +88,7 @@ impl MixNode {
);
}
fn start_packet_forwarder(&mut self) -> mpsc::UnboundedSender<(SocketAddr, Vec<u8>)> {
fn start_packet_forwarder(&mut self) -> mpsc::UnboundedSender<(SocketAddr, SphinxPacket)> {
info!("Starting packet forwarder...");
self.runtime
.enter(|| {
+10 -6
View File
@@ -15,14 +15,15 @@
use futures::channel::mpsc;
use futures::StreamExt;
use log::*;
use nymsphinx::SphinxPacket;
use std::net::SocketAddr;
use std::time::Duration;
use tokio::runtime::Handle;
pub(crate) struct PacketForwarder {
tcp_client: multi_tcp_client::Client,
conn_tx: mpsc::UnboundedSender<(SocketAddr, Vec<u8>)>,
conn_rx: mpsc::UnboundedReceiver<(SocketAddr, Vec<u8>)>,
tcp_client: mixnet_client::Client,
conn_tx: mpsc::UnboundedSender<(SocketAddr, SphinxPacket)>,
conn_rx: mpsc::UnboundedReceiver<(SocketAddr, SphinxPacket)>,
}
impl PacketForwarder {
@@ -31,7 +32,7 @@ impl PacketForwarder {
maximum_reconnection_backoff: Duration,
initial_connection_timeout: Duration,
) -> PacketForwarder {
let tcp_client_config = multi_tcp_client::Config::new(
let tcp_client_config = mixnet_client::Config::new(
initial_reconnection_backoff,
maximum_reconnection_backoff,
initial_connection_timeout,
@@ -40,13 +41,16 @@ impl PacketForwarder {
let (conn_tx, conn_rx) = mpsc::unbounded();
PacketForwarder {
tcp_client: multi_tcp_client::Client::new(tcp_client_config),
tcp_client: mixnet_client::Client::new(tcp_client_config),
conn_tx,
conn_rx,
}
}
pub(crate) fn start(mut self, handle: &Handle) -> mpsc::UnboundedSender<(SocketAddr, Vec<u8>)> {
pub(crate) fn start(
mut self,
handle: &Handle,
) -> mpsc::UnboundedSender<(SocketAddr, SphinxPacket)> {
// TODO: what to do with the lost JoinHandle?
let sender_channel = self.conn_tx.clone();
handle.spawn(async move {
+3 -8
View File
@@ -32,7 +32,7 @@ pub enum MixProcessingError {
}
pub enum MixProcessingResult {
ForwardHop(SocketAddr, Vec<u8>),
ForwardHop(SocketAddr, SphinxPacket),
#[allow(dead_code)]
LoopMessage,
}
@@ -87,21 +87,16 @@ impl PacketProcessor {
// Delay packet for as long as required
tokio::time::delay_for(delay.to_duration()).await;
Ok(MixProcessingResult::ForwardHop(
next_hop_address,
packet.to_bytes(),
))
Ok(MixProcessingResult::ForwardHop(next_hop_address, packet))
}
pub(crate) async fn process_sphinx_packet(
&self,
raw_packet_data: [u8; nymsphinx::PACKET_SIZE],
packet: SphinxPacket,
) -> Result<MixProcessingResult, MixProcessingError> {
// we received something resembling a sphinx packet, report it!
self.metrics_reporter.report_received();
let packet = SphinxPacket::from_bytes(&raw_packet_data)?;
match packet.process(self.secret_key.deref().inner()) {
Ok(ProcessedPacket::ProcessedPacketForwardHop(packet, address, delay)) => {
self.process_forward_hop(packet, address, delay).await