From db792514fba2a6794282a17e27db77bdebf1c15d Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Tue, 21 Apr 2020 17:29:42 +0100 Subject: [PATCH 01/70] Initial draft for ClientsHandler --- gateway/src/clients_handler.rs | 65 ++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 gateway/src/clients_handler.rs diff --git a/gateway/src/clients_handler.rs b/gateway/src/clients_handler.rs new file mode 100644 index 00000000000..e5328c254cb --- /dev/null +++ b/gateway/src/clients_handler.rs @@ -0,0 +1,65 @@ +use futures::channel::{mpsc, oneshot}; +use futures::StreamExt; +use log::*; +use nymsphinx::DestinationAddressBytes; +use std::collections::HashMap; +use tokio::task::JoinHandle; + +type temp_websocket_channel = mpsc::UnboundedSender<()>; +type temp_AuthToken = String; +type temp_ledger = String; + +pub(crate) type ClientsHandlerRequestSsender = mpsc::UnboundedSender; +pub(crate) type ClientsHandlerRequestReceiver = mpsc::UnboundedReceiver; + +pub(crate) type ClientsHandlerResponseSender = oneshot::Sender; +pub(crate) type ClientsHandlerResponseReceiver = oneshot::Receiver; + +pub(crate) enum ClientsHandlerRequest { + // client + Register( + DestinationAddressBytes, + temp_websocket_channel, + ClientsHandlerResponseSender, + ), + Authenticate( + temp_AuthToken, + temp_websocket_channel, + ClientsHandlerResponseSender, + ), + + // mix + IsOnline(DestinationAddressBytes, ClientsHandlerResponseSender), +} + +pub(crate) enum ClientsHandlerResponse { + Register(Option), + Authenticate(bool), + IsOnline(Option), +} + +pub(crate) struct ClientsHandler { + open_connections: HashMap, // clients_ledger: unimplemented!(), + clients_ledger: temp_ledger, + request_receiver_channel: ClientsHandlerRequestReceiver, +} + +impl ClientsHandler { + pub(crate) fn new(request_receiver_channel: ClientsHandlerRequestReceiver) -> Self { + ClientsHandler { + open_connections: HashMap::new(), + clients_ledger: "TEMPORARY".into(), + request_receiver_channel, + } + } + + pub(crate) async fn run(&mut self) { + while let Some(request) = self.request_receiver_channel.next().await { + // handle request + } + } + + pub(crate) fn start(&'static mut self) -> JoinHandle<()> { + tokio::spawn(async move { self.run().await }) + } +} From 38b9deac7d2c63c3be3af8ac6c24b7701def9aa6 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 22 Apr 2020 10:49:43 +0100 Subject: [PATCH 02/70] Created listener struct --- .../{ => client_handling}/clients_handler.rs | 0 gateway/src/listener.rs | 56 ++++++++++++------- 2 files changed, 36 insertions(+), 20 deletions(-) rename gateway/src/{ => client_handling}/clients_handler.rs (100%) diff --git a/gateway/src/clients_handler.rs b/gateway/src/client_handling/clients_handler.rs similarity index 100% rename from gateway/src/clients_handler.rs rename to gateway/src/client_handling/clients_handler.rs diff --git a/gateway/src/listener.rs b/gateway/src/listener.rs index cb915667bc4..236f936e06b 100644 --- a/gateway/src/listener.rs +++ b/gateway/src/listener.rs @@ -12,31 +12,47 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::mixnet_client; -use futures::lock::Mutex; -use futures_util::StreamExt; +use crate::client_handling::clients_handler::ClientsHandlerRequestSender; +use crate::client_handling::websocket::connection_handler::Handle; use log::*; -use multi_tcp_client::Client as MultiClient; use std::net::SocketAddr; -use std::sync::Arc; -use tokio::net::TcpStream; -use tokio_tungstenite::accept_async; -use tungstenite::Result; +use tokio::task::JoinHandle; -pub async fn handle_connection( - peer: SocketAddr, - stream: TcpStream, - client_ref: Arc>, -) -> Result<()> { - let mut ws_stream = accept_async(stream).await.expect("Failed to accept"); +pub(crate) struct Listener { + address: SocketAddr, + clients_handler_sender: ClientsHandlerRequestSender, +} + +impl Listener { + pub(crate) fn new( + address: SocketAddr, + clients_handler_sender: ClientsHandlerRequestSender, + ) -> Self { + Listener { + address, + clients_handler_sender, + } + } - info!("New WebSocket connection: {}", peer); + pub(crate) async fn run(&mut self) { + info!("Starting websocket listener at {}", self.address); + let mut tcp_listener = tokio::net::TcpListener::bind(self.address) + .await + .expect("Failed to start websocket listener"); - while let Some(msg) = ws_stream.next().await { - let msg = msg?; - if msg.is_binary() { - mixnet_client::forward_to_mixnode(msg.into_data(), Arc::clone(&client_ref)).await; + loop { + match tcp_listener.accept().await { + Ok((socket, remote_addr)) => { + trace!("received a socket connection from {}", remote_addr); + let mut handle = Handle::new(socket, self.clients_handler_sender.clone()); + tokio::spawn(async move { handle.start_handling().await }); + } + Err(e) => warn!("failed to get client: {:?}", e), + } } } - Ok(()) + + pub(crate) fn start(&'static mut self) -> JoinHandle<()> { + tokio::spawn(async move { self.run().await }) + } } From 3b04e58505434cdd293b3c08bc0a535043f16165 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 22 Apr 2020 10:49:50 +0100 Subject: [PATCH 03/70] typo --- gateway/src/client_handling/clients_handler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gateway/src/client_handling/clients_handler.rs b/gateway/src/client_handling/clients_handler.rs index e5328c254cb..7f0c301d36a 100644 --- a/gateway/src/client_handling/clients_handler.rs +++ b/gateway/src/client_handling/clients_handler.rs @@ -9,7 +9,7 @@ type temp_websocket_channel = mpsc::UnboundedSender<()>; type temp_AuthToken = String; type temp_ledger = String; -pub(crate) type ClientsHandlerRequestSsender = mpsc::UnboundedSender; +pub(crate) type ClientsHandlerRequestSender = mpsc::UnboundedSender; pub(crate) type ClientsHandlerRequestReceiver = mpsc::UnboundedReceiver; pub(crate) type ClientsHandlerResponseSender = oneshot::Sender; From 3a761cc7ffd3448edfea2b5031af96a99e26db77 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 22 Apr 2020 10:50:02 +0100 Subject: [PATCH 04/70] Stateful websocket connection handler --- .../websocket/connection_handler.rs | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 gateway/src/client_handling/websocket/connection_handler.rs diff --git a/gateway/src/client_handling/websocket/connection_handler.rs b/gateway/src/client_handling/websocket/connection_handler.rs new file mode 100644 index 00000000000..fb83a1073d2 --- /dev/null +++ b/gateway/src/client_handling/websocket/connection_handler.rs @@ -0,0 +1,74 @@ +use crate::client_handling::clients_handler::ClientsHandlerRequestSender; +use log::*; +use tokio::{prelude::*, stream::StreamExt}; +use tokio_tungstenite::{ + tungstenite::{protocol::Message, Error as WsError}, + WebSocketStream, +}; + +enum SocketStream { + RawTCP(S), + UpgradedWebSocket(WebSocketStream), + Invalid, +} + +pub(crate) struct Handle { + socket_connection: SocketStream, + clients_handler_sender: ClientsHandlerRequestSender, +} + +impl Handle +where + S: AsyncRead + AsyncWrite + Unpin, +{ + // for time being we assume handle is always constructed from raw socket. + // if we decide we want to change it, that's not too difficult + pub(crate) fn new(conn: S, clients_handler_sender: ClientsHandlerRequestSender) -> Self { + Handle { + socket_connection: SocketStream::RawTCP(conn), + clients_handler_sender, + } + } + + async fn perform_websocket_handshake(&mut self) { + self.socket_connection = + match std::mem::replace(&mut self.socket_connection, SocketStream::Invalid) { + SocketStream::RawTCP(conn) => { + // TODO: perhaps in the future, rather than panic here (and uncleanly shut tcp stream) + // return a result with an error? + let ws_stream = tokio_tungstenite::accept_async(conn) + .await + .expect("Failed to perform websocket handshake"); + SocketStream::UpgradedWebSocket(ws_stream) + } + other => other, + } + } + + async fn next_websocket_request(&mut self) -> Option> { + match self.socket_connection { + SocketStream::UpgradedWebSocket(ref mut ws_stream) => ws_stream.next().await, + _ => panic!("impossible state - websocket handshake was somehow reverted"), + } + } + + async fn listen_for_requests(&mut self) { + trace!("Started listening for incoming requests..."); + + while let Some(msg) = self.next_websocket_request().await { + // start handling here + // let msg = msg?; + // if msg.is_binary() { + // mixnet_client::forward_to_mixnode(msg.into_data(), Arc::clone(&client_ref)).await; + // } + } + + trace!("The stream was closed!"); + } + + pub(crate) async fn start_handling(&mut self) { + self.perform_websocket_handshake().await; + trace!("Managed to perform websocket handshake!"); + self.listen_for_requests().await; + } +} From 5a16db7c5bbc60e4bc88fdf0f78bfc547e475d37 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 22 Apr 2020 10:50:23 +0100 Subject: [PATCH 05/70] Exposing modules --- gateway/src/client_handling/mod.rs | 2 ++ gateway/src/client_handling/websocket/mod.rs | 3 +++ 2 files changed, 5 insertions(+) create mode 100644 gateway/src/client_handling/mod.rs create mode 100644 gateway/src/client_handling/websocket/mod.rs diff --git a/gateway/src/client_handling/mod.rs b/gateway/src/client_handling/mod.rs new file mode 100644 index 00000000000..e0d386bcdf0 --- /dev/null +++ b/gateway/src/client_handling/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod clients_handler; +pub(crate) mod websocket; diff --git a/gateway/src/client_handling/websocket/mod.rs b/gateway/src/client_handling/websocket/mod.rs new file mode 100644 index 00000000000..78d4312a0e4 --- /dev/null +++ b/gateway/src/client_handling/websocket/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod connection_handler; +pub(crate) mod listener; +pub(crate) mod types; From 4043d4ea570f8e86fee36ffe9a16d1ce9c89ed49 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 22 Apr 2020 10:50:47 +0100 Subject: [PATCH 06/70] Depdendencies updates --- gateway/Cargo.toml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index dcbed977b9f..19ef98e9752 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -9,15 +9,17 @@ edition = "2018" [dependencies] dotenv = "0.15.0" futures = "0.3" -futures-channel = "0.3" -futures-util = { version = "0.3", default-features = false, features = ["async-await", "sink", "std"] } +#futures-channel = "0.3" +#futures-util = { version = "0.3", default-features = false, features = ["async-await", "sink", "std"] } log = "0.4" -multi-tcp-client = { path = "../common/client-libs/multi-tcp-client" } pretty_env_logger = "0.3" +serde = { version = "1.0.104", features = ["derive"] } +#serde_json = "1.0.44" tokio = { version = "0.2", features = ["full"] } tokio-tungstenite = "0.10.1" # internal +multi-tcp-client = { path = "../common/client-libs/multi-tcp-client" } nymsphinx = { path = "../common/nymsphinx" } From e67203538c7b5f9bcb3f710fba6f292af8111edd Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 22 Apr 2020 11:00:14 +0100 Subject: [PATCH 07/70] Moved listener to correct file + made start consume listener --- gateway/src/{ => client_handling/websocket}/listener.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename gateway/src/{ => client_handling/websocket}/listener.rs (96%) diff --git a/gateway/src/listener.rs b/gateway/src/client_handling/websocket/listener.rs similarity index 96% rename from gateway/src/listener.rs rename to gateway/src/client_handling/websocket/listener.rs index 236f936e06b..e7cef58a97a 100644 --- a/gateway/src/listener.rs +++ b/gateway/src/client_handling/websocket/listener.rs @@ -52,7 +52,7 @@ impl Listener { } } - pub(crate) fn start(&'static mut self) -> JoinHandle<()> { + pub(crate) fn start(mut self) -> JoinHandle<()> { tokio::spawn(async move { self.run().await }) } } From c4a7d6821740ef4b30c50ac678ee47511de228bd Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 22 Apr 2020 11:00:29 +0100 Subject: [PATCH 08/70] Main starting new listener --- gateway/src/main.rs | 30 +++++------------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/gateway/src/main.rs b/gateway/src/main.rs index b4ee2b4f8e2..04cb67613ab 100644 --- a/gateway/src/main.rs +++ b/gateway/src/main.rs @@ -12,41 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -use futures::lock::Mutex; +use crate::client_handling::websocket::listener::Listener; use log::*; -use multi_tcp_client::Client as MultiClient; -use std::net::SocketAddr; -use std::sync::Arc; -use tokio::net::{TcpListener, TcpStream}; -use tokio_tungstenite::tungstenite::Error; -mod listener; +mod client_handling; mod mixnet_client; -async fn accept_connection(peer: SocketAddr, stream: TcpStream, client: Arc>) { - if let Err(e) = listener::handle_connection(peer, stream, client).await { - match e { - Error::ConnectionClosed | Error::Protocol(_) | Error::Utf8 => (), - err => error!("Error processing connection: {}", err), - } - } -} - #[tokio::main] async fn main() { dotenv::dotenv().ok(); setup_logging(); - let addr = "127.0.0.1:1793"; - let mut listener = TcpListener::bind(&addr).await.expect("Can't listen"); + let addr = "127.0.0.1:1793".parse().unwrap(); info!("Listening on: {}", addr); - let client_ref = mixnet_client::new(); - - while let Ok((stream, _)) = listener.accept().await { - let peer = stream - .peer_addr() - .expect("connected streams should have a peer address"); - info!("Peer address: {}", peer); + let (dummy_clients_handler_tx, _) = futures::channel::mpsc::unbounded(); + Listener::new(addr, dummy_clients_handler_tx).start(); tokio::spawn(accept_connection(peer, stream, Arc::clone(&client_ref))); } From 72cead5830473ee03139fe99ee4eb7447507a7a0 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 22 Apr 2020 11:00:38 +0100 Subject: [PATCH 09/70] Catching sigint --- gateway/src/main.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/gateway/src/main.rs b/gateway/src/main.rs index 04cb67613ab..5dff943a7f7 100644 --- a/gateway/src/main.rs +++ b/gateway/src/main.rs @@ -28,8 +28,16 @@ async fn main() { let (dummy_clients_handler_tx, _) = futures::channel::mpsc::unbounded(); Listener::new(addr, dummy_clients_handler_tx).start(); - tokio::spawn(accept_connection(peer, stream, Arc::clone(&client_ref))); + if let Err(e) = tokio::signal::ctrl_c().await { + error!( + "There was an error while capturing SIGINT - {:?}. We will terminate regardless", + e + ); } + + println!( + "Received SIGINT - the provider will terminate now (threads are not YET nicely stopped)" + ); } fn setup_logging() { From fe28c3ba655f3202961af46d735c3b4d209b1191 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 22 Apr 2020 16:04:28 +0100 Subject: [PATCH 10/70] Copied client storage from provider into gateway --- gateway/src/storage/mod.rs | 222 +++++++++++++++++++++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 gateway/src/storage/mod.rs diff --git a/gateway/src/storage/mod.rs b/gateway/src/storage/mod.rs new file mode 100644 index 00000000000..b488dea5c83 --- /dev/null +++ b/gateway/src/storage/mod.rs @@ -0,0 +1,222 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use futures::lock::Mutex; +use futures::StreamExt; +use log::*; +use nymsphinx::{DestinationAddressBytes, SURBIdentifier}; +use rand::Rng; +//use sfw_provider_requests::DUMMY_MESSAGE_CONTENT; +use std::io; +use std::path::PathBuf; +use std::sync::Arc; +use tokio::fs; +use tokio::fs::File; +use tokio::prelude::*; + +const DUMMY_MESSAGE_CONTENT: &'static [u8] = b"foomp"; + +fn dummy_message() -> ClientFile { + ClientFile { + content: DUMMY_MESSAGE_CONTENT.to_vec(), + path: Default::default(), + } +} + +#[derive(Clone, Debug)] +pub struct ClientFile { + content: Vec, + path: PathBuf, +} + +impl ClientFile { + fn new(content: Vec, path: PathBuf) -> Self { + ClientFile { content, path } + } + + pub(crate) fn into_tuple(self) -> (Vec, PathBuf) { + (self.content, self.path) + } +} + +pub struct StoreData { + client_address: DestinationAddressBytes, + message: Vec, +} + +impl StoreData { + pub(crate) fn new(client_address: DestinationAddressBytes, message: Vec) -> Self { + StoreData { + client_address, + message, + } + } +} + +// TODO: replace with proper database... +// Note: you should NEVER create more than a single instance of this using 'new()'. +// You should always use .clone() to create additional instances +#[derive(Clone, Debug)] +pub struct ClientStorage { + inner: Arc>, +} + +// even though the data inside is extremely cheap to copy, we have to have a single mutex, +// so might as well store the data behind it +pub struct ClientStorageInner { + message_retrieval_limit: usize, + filename_length: u16, + main_store_path_dir: PathBuf, +} + +// TODO: change it to some generic implementation to inject fs (or even better - proper database) +impl ClientStorage { + pub(crate) fn new(message_limit: usize, filename_len: u16, main_store_dir: PathBuf) -> Self { + ClientStorage { + inner: Arc::new(Mutex::new(ClientStorageInner { + message_retrieval_limit: message_limit, + filename_length: filename_len, + main_store_path_dir: main_store_dir, + })), + } + } + + // TODO: does this method really require locking? + // The worst that can happen is client sending 2 requests: to pull messages and register + // if register does not lock, then under specific timing pull messages will fail, + // but can simply be retried with no issues + pub(crate) async fn create_storage_dir( + &self, + client_address: DestinationAddressBytes, + ) -> io::Result<()> { + let inner_data = self.inner.lock().await; + + let client_dir_name = client_address.to_base58_string(); + let full_store_dir = inner_data.main_store_path_dir.join(client_dir_name); + fs::create_dir_all(full_store_dir).await + } + + pub(crate) fn generate_random_file_name(length: usize) -> String { + rand::thread_rng() + .sample_iter(&rand::distributions::Alphanumeric) + .take(length) + .collect::() + } + + pub(crate) async fn store_processed_data(&self, store_data: StoreData) -> io::Result<()> { + let inner_data = self.inner.lock().await; + + let client_dir_name = store_data.client_address.to_base58_string(); + let full_store_dir = inner_data.main_store_path_dir.join(client_dir_name); + let full_store_path = full_store_dir.join(Self::generate_random_file_name( + inner_data.filename_length as usize, + )); + debug!( + "going to store: {:?} in file: {:?}", + store_data.message, full_store_path + ); + + let mut file = File::create(full_store_path).await?; + file.write_all(store_data.message.as_ref()).await + } + + pub(crate) async fn retrieve_client_files( + &self, + client_address: DestinationAddressBytes, + ) -> io::Result> { + let inner_data = self.inner.lock().await; + + let client_dir_name = client_address.to_base58_string(); + let full_store_dir = inner_data.main_store_path_dir.join(client_dir_name); + + trace!("going to lookup: {:?}!", full_store_dir); + if !full_store_dir.exists() { + return Err(io::Error::new( + io::ErrorKind::NotFound, + "Target client does not exist", + )); + } + + let mut msgs = Vec::new(); + let mut read_dir = fs::read_dir(full_store_dir).await?; + + while let Some(dir_entry) = read_dir.next().await { + if let Ok(dir_entry) = dir_entry { + if !Self::is_valid_file(&dir_entry).await { + continue; + } + // Do not delete the file itself here! + // Only do it after client has received it + let client_file = + ClientFile::new(fs::read(dir_entry.path()).await?, dir_entry.path()); + msgs.push(client_file) + } + if msgs.len() == inner_data.message_retrieval_limit { + break; + } + } + + let dummy_message = dummy_message(); + + // make sure we always return as many messages as we need + if msgs.len() != inner_data.message_retrieval_limit as usize { + msgs = msgs + .into_iter() + .chain(std::iter::repeat(dummy_message)) + .take(inner_data.message_retrieval_limit) + .collect(); + } + + Ok(msgs) + } + + async fn is_valid_file(entry: &fs::DirEntry) -> bool { + let metadata = match entry.metadata().await { + Ok(meta) => meta, + Err(e) => { + error!( + "potentially corrupted client inbox! ({:?} - failed to read its metadata - {:?}", + entry.path(), + e, + ); + return false; + } + }; + + let is_file = metadata.is_file(); + if !is_file { + error!( + "potentially corrupted client inbox! - found a non-file - {:?}", + entry.path() + ); + } + + is_file + } + + pub(crate) async fn delete_files(&self, file_paths: Vec) -> io::Result<()> { + let dummy_message = dummy_message(); + let _guard = self.inner.lock().await; + + for file_path in file_paths { + if file_path == dummy_message.path { + continue; + } + if let Err(e) = fs::remove_file(file_path).await { + error!("Failed to delete client message! - {:?}", e) + } + } + Ok(()) + } +} From c405ddc9316d963e25876c438cdc0efbc28962c6 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 22 Apr 2020 16:05:20 +0100 Subject: [PATCH 11/70] Exposed websocket listener type for nicer import path --- gateway/src/client_handling/websocket/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/gateway/src/client_handling/websocket/mod.rs b/gateway/src/client_handling/websocket/mod.rs index 78d4312a0e4..2e46cff6f02 100644 --- a/gateway/src/client_handling/websocket/mod.rs +++ b/gateway/src/client_handling/websocket/mod.rs @@ -1,3 +1,4 @@ pub(crate) mod connection_handler; pub(crate) mod listener; pub(crate) mod types; +pub(crate) use listener::Listener; From dda664cb21ca9859881669e088b3978f493045d9 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 22 Apr 2020 16:05:48 +0100 Subject: [PATCH 12/70] Defined websocket message receiver concrete type --- gateway/src/client_handling/websocket/message_receiver.rs | 4 ++++ gateway/src/client_handling/websocket/mod.rs | 2 ++ 2 files changed, 6 insertions(+) create mode 100644 gateway/src/client_handling/websocket/message_receiver.rs diff --git a/gateway/src/client_handling/websocket/message_receiver.rs b/gateway/src/client_handling/websocket/message_receiver.rs new file mode 100644 index 00000000000..568b0d367d1 --- /dev/null +++ b/gateway/src/client_handling/websocket/message_receiver.rs @@ -0,0 +1,4 @@ +use futures::channel::mpsc; + +pub(crate) type MixMessageSender = mpsc::UnboundedSender>>; +pub(crate) type MixMessageReceiver = mpsc::UnboundedReceiver>>; diff --git a/gateway/src/client_handling/websocket/mod.rs b/gateway/src/client_handling/websocket/mod.rs index 2e46cff6f02..4ff6f227a06 100644 --- a/gateway/src/client_handling/websocket/mod.rs +++ b/gateway/src/client_handling/websocket/mod.rs @@ -1,4 +1,6 @@ pub(crate) mod connection_handler; pub(crate) mod listener; pub(crate) mod types; +pub(crate) mod message_receiver; + pub(crate) use listener::Listener; From c74c1adc88309d2ab359a63c3e660bd8c3ae177a Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 22 Apr 2020 16:06:15 +0100 Subject: [PATCH 13/70] Client ledger struct without implementation --- gateway/src/client_handling/ledger.rs | 162 ++++++++++++++++++++++++++ gateway/src/client_handling/mod.rs | 1 + 2 files changed, 163 insertions(+) create mode 100644 gateway/src/client_handling/ledger.rs diff --git a/gateway/src/client_handling/ledger.rs b/gateway/src/client_handling/ledger.rs new file mode 100644 index 00000000000..dd19b429c65 --- /dev/null +++ b/gateway/src/client_handling/ledger.rs @@ -0,0 +1,162 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//use directory_client::presence::providers::MixProviderClient; +//use log::*; +//use nymsphinx::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH}; +//use sfw_provider_requests::auth_token::{AuthToken, AUTH_TOKEN_SIZE}; +//use std::path::PathBuf; + +#[derive(Debug)] +pub(crate) enum ClientLedgerError { + DbReadError(sled::Error), + DbWriteError(sled::Error), + DbOpenError(sled::Error), +} + +#[derive(Debug, Clone)] +// Note: you should NEVER create more than a single instance of this using 'new()'. +// You should always use .clone() to create additional instances +pub(crate) struct ClientLedger { + db: sled::Db, +} +// +//impl ClientLedger { +// pub(crate) fn load(file: PathBuf) -> Result { +// let db = match sled::open(file) { +// Err(e) => return Err(ClientLedgerError::DbOpenError(e)), +// Ok(db) => db, +// }; +// +// let ledger = ClientLedger { db }; +// +// ledger.db.iter().keys().for_each(|key| { +// println!( +// "key: {:?}", +// ledger +// .read_destination_address_bytes(key.unwrap()) +// .to_base58_string() +// ); +// }); +// +// Ok(ledger) +// } +// +// fn read_auth_token(&self, raw_token: sled::IVec) -> AuthToken { +// let token_bytes_ref = raw_token.as_ref(); +// // if this fails it means we have some database corruption and we +// // absolutely can't continue +// if token_bytes_ref.len() != AUTH_TOKEN_SIZE { +// error!("CLIENT LEDGER DATA CORRUPTION - TOKEN HAS INVALID LENGTH"); +// panic!("CLIENT LEDGER DATA CORRUPTION - TOKEN HAS INVALID LENGTH"); +// } +// +// let mut token_bytes = [0u8; AUTH_TOKEN_SIZE]; +// token_bytes.copy_from_slice(token_bytes_ref); +// AuthToken::from_bytes(token_bytes) +// } +// +// fn read_destination_address_bytes( +// &self, +// raw_destination: sled::IVec, +// ) -> DestinationAddressBytes { +// let destination_ref = raw_destination.as_ref(); +// // if this fails it means we have some database corruption and we +// // absolutely can't continue +// if destination_ref.len() != DESTINATION_ADDRESS_LENGTH { +// error!("CLIENT LEDGER DATA CORRUPTION - CLIENT ADDRESS HAS INVALID LENGTH"); +// panic!("CLIENT LEDGER DATA CORRUPTION - CLIENT ADDRESS HAS INVALID LENGTH"); +// } +// +// let mut destination_bytes = [0u8; DESTINATION_ADDRESS_LENGTH]; +// destination_bytes.copy_from_slice(destination_ref); +// DestinationAddressBytes::from_bytes(destination_bytes) +// } +// +// pub(crate) fn verify_token( +// &self, +// auth_token: &AuthToken, +// client_address: &DestinationAddressBytes, +// ) -> Result { +// match self.db.get(&client_address.to_bytes()) { +// Err(e) => Err(ClientLedgerError::DbReadError(e)), +// Ok(token) => match token { +// Some(token_ivec) => Ok(&self.read_auth_token(token_ivec) == auth_token), +// None => Ok(false), +// }, +// } +// } +// +// pub(crate) fn insert_token( +// &mut self, +// auth_token: AuthToken, +// client_address: DestinationAddressBytes, +// ) -> Result, ClientLedgerError> { +// let insertion_result = match self +// .db +// .insert(&client_address.to_bytes(), &auth_token.to_bytes()) +// { +// Err(e) => Err(ClientLedgerError::DbWriteError(e)), +// Ok(existing_token) => { +// Ok(existing_token.map(|existing_token| self.read_auth_token(existing_token))) +// } +// }; +// +// // registration doesn't happen that often so might as well flush it to the disk to be sure +// self.db.flush().unwrap(); +// insertion_result +// } +// +// pub(crate) fn remove_token( +// &mut self, +// client_address: &DestinationAddressBytes, +// ) -> Result, ClientLedgerError> { +// let removal_result = match self.db.remove(&client_address.to_bytes()) { +// Err(e) => Err(ClientLedgerError::DbWriteError(e)), +// Ok(existing_token) => { +// Ok(existing_token.map(|existing_token| self.read_auth_token(existing_token))) +// } +// }; +// +// // removing of tokens happens extremely rarely, so flush is also fine here +// self.db.flush().unwrap(); +// removal_result +// } +// +// pub(crate) fn current_clients(&self) -> Result, ClientLedgerError> { +// let clients = self.db.iter().keys(); +// +// let mut client_vec = Vec::new(); +// for client in clients { +// match client { +// Err(e) => return Err(ClientLedgerError::DbWriteError(e)), +// Ok(client_entry) => client_vec.push(MixProviderClient { +// pub_key: self +// .read_destination_address_bytes(client_entry) +// .to_base58_string(), +// }), +// } +// } +// +// Ok(client_vec) +// } +// +// #[cfg(test)] +// pub(crate) fn create_temporary() -> Self { +// let cfg = sled::Config::new().temporary(true); +// ClientLedger { +// db: cfg.open().unwrap(), +// } +// } +//} diff --git a/gateway/src/client_handling/mod.rs b/gateway/src/client_handling/mod.rs index e0d386bcdf0..15706c869da 100644 --- a/gateway/src/client_handling/mod.rs +++ b/gateway/src/client_handling/mod.rs @@ -1,2 +1,3 @@ pub(crate) mod clients_handler; +pub(crate) mod ledger; pub(crate) mod websocket; From 0dd2c9505cb69ccf844f1c60c65aa59b0ad4c6cb Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 22 Apr 2020 16:06:26 +0100 Subject: [PATCH 14/70] ClientsHandler using more concrete types --- gateway/src/client_handling/clients_handler.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/gateway/src/client_handling/clients_handler.rs b/gateway/src/client_handling/clients_handler.rs index 7f0c301d36a..91bd1aba0ea 100644 --- a/gateway/src/client_handling/clients_handler.rs +++ b/gateway/src/client_handling/clients_handler.rs @@ -1,3 +1,5 @@ +use crate::client_handling::ledger::ClientLedger; +use crate::client_handling::websocket::message_receiver::MixMessageSender; use futures::channel::{mpsc, oneshot}; use futures::StreamExt; use log::*; @@ -5,9 +7,7 @@ use nymsphinx::DestinationAddressBytes; use std::collections::HashMap; use tokio::task::JoinHandle; -type temp_websocket_channel = mpsc::UnboundedSender<()>; type temp_AuthToken = String; -type temp_ledger = String; pub(crate) type ClientsHandlerRequestSender = mpsc::UnboundedSender; pub(crate) type ClientsHandlerRequestReceiver = mpsc::UnboundedReceiver; @@ -19,12 +19,12 @@ pub(crate) enum ClientsHandlerRequest { // client Register( DestinationAddressBytes, - temp_websocket_channel, + MixMessageSender, ClientsHandlerResponseSender, ), Authenticate( temp_AuthToken, - temp_websocket_channel, + MixMessageSender, ClientsHandlerResponseSender, ), @@ -35,12 +35,12 @@ pub(crate) enum ClientsHandlerRequest { pub(crate) enum ClientsHandlerResponse { Register(Option), Authenticate(bool), - IsOnline(Option), + IsOnline(Option), } pub(crate) struct ClientsHandler { - open_connections: HashMap, // clients_ledger: unimplemented!(), - clients_ledger: temp_ledger, + open_connections: HashMap, // clients_ledger: unimplemented!(), + clients_ledger: ClientLedger, request_receiver_channel: ClientsHandlerRequestReceiver, } @@ -48,8 +48,8 @@ impl ClientsHandler { pub(crate) fn new(request_receiver_channel: ClientsHandlerRequestReceiver) -> Self { ClientsHandler { open_connections: HashMap::new(), - clients_ledger: "TEMPORARY".into(), request_receiver_channel, + clients_ledger: unimplemented!(), } } @@ -59,7 +59,7 @@ impl ClientsHandler { } } - pub(crate) fn start(&'static mut self) -> JoinHandle<()> { + pub(crate) fn start(mut self) -> JoinHandle<()> { tokio::spawn(async move { self.run().await }) } } From 6c8d30da224fdb371f672d436e2f64d5b8b08534 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 22 Apr 2020 16:06:55 +0100 Subject: [PATCH 15/70] Mixnet sender + receiver and exposed listener type --- gateway/src/mixnet_handling/mod.rs | 4 ++ .../src/mixnet_handling/receiver/listener.rs | 51 +++++++++++++++++++ gateway/src/mixnet_handling/receiver/mod.rs | 17 +++++++ 3 files changed, 72 insertions(+) create mode 100644 gateway/src/mixnet_handling/mod.rs create mode 100644 gateway/src/mixnet_handling/receiver/listener.rs create mode 100644 gateway/src/mixnet_handling/receiver/mod.rs diff --git a/gateway/src/mixnet_handling/mod.rs b/gateway/src/mixnet_handling/mod.rs new file mode 100644 index 00000000000..22092565613 --- /dev/null +++ b/gateway/src/mixnet_handling/mod.rs @@ -0,0 +1,4 @@ +pub(crate) mod receiver; +pub(crate) mod sender; + +pub(crate) use receiver::listener::Listener; diff --git a/gateway/src/mixnet_handling/receiver/listener.rs b/gateway/src/mixnet_handling/receiver/listener.rs new file mode 100644 index 00000000000..52a5c13cb7b --- /dev/null +++ b/gateway/src/mixnet_handling/receiver/listener.rs @@ -0,0 +1,51 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::mixnet_handling::receiver::connection_handler::Handle; +use crate::mixnet_handling::receiver::packet_processing::PacketProcessor; +use log::*; +use std::net::SocketAddr; +use tokio::task::JoinHandle; + +pub(crate) struct Listener { + address: SocketAddr, +} + +impl Listener { + pub(crate) fn new(address: SocketAddr) -> Self { + Listener { address } + } + + pub(crate) async fn run(&mut self, packet_processor: PacketProcessor) { + info!("Starting mixnet listener at {}", self.address); + let mut tcp_listener = tokio::net::TcpListener::bind(self.address) + .await + .expect("Failed to start mixnet listener"); + + loop { + match tcp_listener.accept().await { + Ok((socket, remote_addr)) => { + trace!("received a socket connection from {}", remote_addr); + let mut handle = Handle::new(remote_addr, socket, packet_processor.clone()); + tokio::spawn(async move { handle.start_handling().await }); + } + Err(e) => warn!("failed to get client: {:?}", e), + } + } + } + + pub(crate) fn start(mut self, packet_processor: PacketProcessor) -> JoinHandle<()> { + tokio::spawn(async move { self.run(packet_processor).await }) + } +} diff --git a/gateway/src/mixnet_handling/receiver/mod.rs b/gateway/src/mixnet_handling/receiver/mod.rs new file mode 100644 index 00000000000..2f49616cb23 --- /dev/null +++ b/gateway/src/mixnet_handling/receiver/mod.rs @@ -0,0 +1,17 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub(crate) mod connection_handler; +pub(crate) mod listener; +pub(crate) mod packet_processing; From 22e4e9a97c5853c0093b0b6b597231dd6a467f33 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 22 Apr 2020 16:07:20 +0100 Subject: [PATCH 16/70] Handling mix packets --- .../receiver/connection_handler.rs | 88 +++++++ .../receiver/packet_processing.rs | 217 ++++++++++++++++++ 2 files changed, 305 insertions(+) create mode 100644 gateway/src/mixnet_handling/receiver/connection_handler.rs create mode 100644 gateway/src/mixnet_handling/receiver/packet_processing.rs diff --git a/gateway/src/mixnet_handling/receiver/connection_handler.rs b/gateway/src/mixnet_handling/receiver/connection_handler.rs new file mode 100644 index 00000000000..4255f9beffc --- /dev/null +++ b/gateway/src/mixnet_handling/receiver/connection_handler.rs @@ -0,0 +1,88 @@ +use crate::client_handling::clients_handler::{ + ClientsHandlerRequest, ClientsHandlerRequestSender, ClientsHandlerResponse, +}; +use crate::client_handling::websocket::message_receiver::MixMessageSender; +use crate::mixnet_handling::receiver::packet_processing::PacketProcessor; +use futures::channel::{mpsc, oneshot}; +use log::*; +use nymsphinx::DestinationAddressBytes; +use std::collections::HashMap; +use std::net::SocketAddr; +use tokio::{io::AsyncReadExt, prelude::*}; + +pub(crate) struct Handle { + peer_address: SocketAddr, + socket_connection: S, + packet_processor: PacketProcessor, +} + +impl Handle +where + S: AsyncRead + AsyncWrite + Unpin + 'static, +{ + // for time being we assume handle is always constructed from raw socket. + // if we decide we want to change it, that's not too difficult + pub(crate) fn new( + peer_address: SocketAddr, + conn: S, + packet_processor: PacketProcessor, + ) -> Self { + Handle { + peer_address, + socket_connection: conn, + packet_processor, + } + } + + async fn process_received_packet( + packet_data: [u8; nymsphinx::PACKET_SIZE], + mut packet_processor: PacketProcessor, + ) { + match packet_processor.process_sphinx_packet(packet_data).await { + Ok(_) => trace!("successfully processed [and forwarded/stored] a final hop packet"), + Err(e) => debug!("We failed to process received sphinx packet - {:?}", e), + } + } + + pub(crate) async fn start_handling(&mut self) { + let mut buf = [0u8; nymsphinx::PACKET_SIZE]; + loop { + match self.socket_connection.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 { + error!("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 + // TODO: but WE NEED to have some worker pool so that we do not spawn too many + // tasks + tokio::spawn(Self::process_received_packet( + buf, + self.packet_processor.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.", self.peer_address) + } else { + warn!( + "failed to read from socket (source: {:?}). Closing the connection; err = {:?}", + self.peer_address, + e + ); + } + return; + } + }; + } + } +} diff --git a/gateway/src/mixnet_handling/receiver/packet_processing.rs b/gateway/src/mixnet_handling/receiver/packet_processing.rs new file mode 100644 index 00000000000..64a2092ec2e --- /dev/null +++ b/gateway/src/mixnet_handling/receiver/packet_processing.rs @@ -0,0 +1,217 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::client_handling::clients_handler::{ + ClientsHandlerRequest, ClientsHandlerRequestSender, ClientsHandlerResponse, +}; +use crate::client_handling::websocket::message_receiver::MixMessageSender; +use crate::storage::{ClientStorage, StoreData}; +use crypto::encryption; +use futures::channel::oneshot; +use futures::lock::Mutex; +use log::*; +use mix_client::packet::LOOP_COVER_MESSAGE_PAYLOAD; +use nymsphinx::{DestinationAddressBytes, Payload, ProcessedPacket, SURBIdentifier, SphinxPacket}; +use std::collections::HashMap; +use std::io; +use std::ops::Deref; +use std::sync::Arc; + +#[derive(Debug)] +pub enum MixProcessingError { + ReceivedForwardHopError, + NonMatchingRecipient, + InvalidPayload, + SphinxProcessingError, + IOError(String), +} + +pub enum MixProcessingResult { + #[allow(dead_code)] + ForwardHop, + FinalHop, +} + +impl From for MixProcessingError { + // for time being just have a single error instance for all possible results of nymsphinx::ProcessingError + fn from(_: nymsphinx::ProcessingError) -> Self { + use MixProcessingError::*; + + SphinxProcessingError + } +} + +impl From for MixProcessingError { + fn from(e: io::Error) -> Self { + use MixProcessingError::*; + + IOError(e.to_string()) + } +} + +// PacketProcessor contains all data required to correctly unwrap and store sphinx packets +#[derive(Clone)] +pub struct PacketProcessor { + secret_key: Arc, + // TODO: later investigate some concurrent hashmap solutions or perhaps RWLocks. + // Right now Mutex is the simplest and fastest to implement approach + available_socket_senders_cache: Arc>>, + client_store: ClientStorage, + clients_handler_sender: ClientsHandlerRequestSender, +} + +impl PacketProcessor { + pub(crate) fn new( + secret_key: encryption::PrivateKey, + clients_handler_sender: ClientsHandlerRequestSender, + client_store: ClientStorage, + ) -> Self { + PacketProcessor { + available_socket_senders_cache: Arc::new(Mutex::new(HashMap::new())), + clients_handler_sender, + client_store, + secret_key: Arc::new(secret_key), + } + } + + fn try_push_message_to_client( + &self, + sender_channel: Option, + message: Vec, + ) -> bool { + match sender_channel { + None => false, + Some(sender_channel) => sender_channel.unbounded_send(vec![message]).is_ok(), + } + } + + async fn try_to_obtain_client_ws_message_sender( + &mut self, + client_address: DestinationAddressBytes, + ) -> Option { + let mut cache_guard = self.available_socket_senders_cache.lock().await; + + if let Some(sender) = cache_guard.get(&client_address) { + if !sender.is_closed() { + return Some(sender.clone()); + } else { + cache_guard.remove(&client_address); + } + } + + // do not block other readers to the cache while we are doing some blocking work here + drop(cache_guard); + + // if we got here it means that either we have no sender channel for this client or it's closed + // so we must refresh it from the source, i.e. ClientsHandler + let (res_sender, res_receiver) = oneshot::channel(); + let clients_handler_request = + ClientsHandlerRequest::IsOnline(client_address.clone(), res_sender); + self.clients_handler_sender + .unbounded_send(clients_handler_request) + .unwrap(); // the receiver MUST BE alive + + let client_sender = match res_receiver.await.unwrap() { + ClientsHandlerResponse::IsOnline(client_sender) => client_sender, + _ => panic!("received response to wrong query!"), // again, this should NEVER happen + }; + + if client_sender.is_none() { + return None; + } + + let client_sender = client_sender.unwrap(); + // finally re-acquire the lock to update the cache + let mut cache_guard = self.available_socket_senders_cache.lock().await; + cache_guard.insert(client_address, client_sender.clone()); + + Some(client_sender) + } + + pub(crate) async fn store_processed_packet_payload( + &self, + client_address: DestinationAddressBytes, + message: Vec, + ) -> io::Result<()> { + trace!( + "Storing received packet for {:?} on the disk...", + client_address.to_base58_string() + ); + // we are temporarily ignoring and not storing obvious loop cover traffic messages to + // not cause our sfw-provider to run out of disk space too quickly. + // Eventually this is going to get removed and be replaced by a quota system described in: + // https://github.com/nymtech/nym/issues/137 + if message == LOOP_COVER_MESSAGE_PAYLOAD { + debug!("Received a loop cover message - not going to store it"); + return Ok(()); + } + + let store_data = StoreData::new(client_address, message); + self.client_store.store_processed_data(store_data).await + } + + pub(crate) fn unwrap_sphinx_packet( + &self, + raw_packet_data: [u8; nymsphinx::PACKET_SIZE], + ) -> Result<(DestinationAddressBytes, Vec), MixProcessingError> { + let packet = SphinxPacket::from_bytes(&raw_packet_data)?; + + match packet.process(self.secret_key.deref().inner()) { + Ok(ProcessedPacket::ProcessedPacketForwardHop(_, _, _)) => { + warn!("Received a forward hop message - those are not implemented for providers"); + Err(MixProcessingError::ReceivedForwardHopError) + } + Ok(ProcessedPacket::ProcessedPacketFinalHop(client_address, surb_id, payload)) => { + // in our current design, we do not care about the 'surb_id' in the header + // as it will always be empty anyway + let (payload_destination, message) = payload + .try_recover_destination_and_plaintext() + .ok_or_else(|| MixProcessingError::InvalidPayload)?; + if client_address != payload_destination { + return Err(MixProcessingError::NonMatchingRecipient); + } + Ok((client_address, message)) + } + Err(e) => { + warn!("Failed to unwrap Sphinx packet: {:?}", e); + Err(MixProcessingError::SphinxProcessingError) + } + } + } + + pub(crate) async fn process_sphinx_packet( + &mut self, + raw_packet_data: [u8; nymsphinx::PACKET_SIZE], + ) -> Result<(), MixProcessingError> { + let (client_address, plaintext) = self.unwrap_sphinx_packet(raw_packet_data)?; + + let client_sender = self + .try_to_obtain_client_ws_message_sender(client_address.clone()) + .await; + + // TODO: think of a way to prevent having to clone the plaintext here, perhaps make channels use references? + // this will, again, take slightly more time, so it's an issue for later + if !self.try_push_message_to_client(client_sender, plaintext.clone()) { + Ok(self + .store_processed_packet_payload(client_address, plaintext) + .await?) + } else { + trace!( + "Managed to push received packet for {:?} to websocket connection!", + client_address.to_base58_string() + ); + Ok(()) + } + } +} From 062c1f8452248c045a2df2435fd4cdb9b0224a99 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 22 Apr 2020 16:07:27 +0100 Subject: [PATCH 17/70] Ability to forward mix packets --- gateway/src/mixnet_handling/sender/mod.rs | 63 +++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 gateway/src/mixnet_handling/sender/mod.rs diff --git a/gateway/src/mixnet_handling/sender/mod.rs b/gateway/src/mixnet_handling/sender/mod.rs new file mode 100644 index 00000000000..e6dc32e0f02 --- /dev/null +++ b/gateway/src/mixnet_handling/sender/mod.rs @@ -0,0 +1,63 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// TODO: code is nearly identical to mixnode::node::packet_forwarding -> perhaps it should be put to common? + +use futures::channel::mpsc; +use futures::StreamExt; +use log::*; +use std::net::SocketAddr; +use std::time::Duration; + +pub(crate) struct PacketForwarder { + tcp_client: multi_tcp_client::Client, + conn_tx: mpsc::UnboundedSender<(SocketAddr, Vec)>, + conn_rx: mpsc::UnboundedReceiver<(SocketAddr, Vec)>, +} + +impl PacketForwarder { + pub(crate) fn new( + initial_reconnection_backoff: Duration, + maximum_reconnection_backoff: Duration, + initial_connection_timeout: Duration, + ) -> PacketForwarder { + let tcp_client_config = multi_tcp_client::Config::new( + initial_reconnection_backoff, + maximum_reconnection_backoff, + initial_connection_timeout, + ); + + let (conn_tx, conn_rx) = mpsc::unbounded(); + + PacketForwarder { + tcp_client: multi_tcp_client::Client::new(tcp_client_config), + conn_tx, + conn_rx, + } + } + + pub(crate) fn start(mut self) -> mpsc::UnboundedSender<(SocketAddr, Vec)> { + // TODO: what to do with the lost JoinHandle - do we even care? + let sender_channel = self.conn_tx.clone(); + tokio::spawn(async move { + while let Some((address, packet)) = self.conn_rx.next().await { + trace!("Going to forward packet to {:?}", address); + // as a mix node we don't care about responses, we just want to fire packets + // as quickly as possible + self.tcp_client.send(address, packet, false).await.unwrap(); // if we're not waiting for response, we MUST get an Ok + } + }); + sender_channel + } +} From 0a3a43e695733984bda481fb2e7c581641a8259d Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 22 Apr 2020 16:07:41 +0100 Subject: [PATCH 18/70] "starting" both listeners at main --- gateway/src/main.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/gateway/src/main.rs b/gateway/src/main.rs index 5dff943a7f7..dff9e54de77 100644 --- a/gateway/src/main.rs +++ b/gateway/src/main.rs @@ -12,11 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::client_handling::websocket::listener::Listener; +use crate::client_handling::websocket; +use crate::mixnet_handling::receiver::packet_processing::PacketProcessor; +use crate::storage::ClientStorage; use log::*; mod client_handling; mod mixnet_client; +mod mixnet_handling; +pub(crate) mod storage; #[tokio::main] async fn main() { @@ -26,7 +30,16 @@ async fn main() { info!("Listening on: {}", addr); let (dummy_clients_handler_tx, _) = futures::channel::mpsc::unbounded(); - Listener::new(addr, dummy_clients_handler_tx).start(); + let client_storage = ClientStorage::new(42, 42, "foomp".into()); + let dummy_keypair = crypto::encryption::KeyPair::new(); + let dummy_mix_packet_processor = PacketProcessor::new( + dummy_keypair.private_key().to_owned(), + dummy_clients_handler_tx.clone(), + client_storage, + ); + + websocket::Listener::new(addr, dummy_clients_handler_tx.clone()).start(); + mixnet_handling::Listener::new(addr).start(dummy_mix_packet_processor); if let Err(e) = tokio::signal::ctrl_c().await { error!( From 7e081f21ff3a1d01c63b2db0d19043ff22496cab Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 22 Apr 2020 16:07:53 +0100 Subject: [PATCH 19/70] Depedencies updates --- Cargo.lock | 7 +++++-- gateway/Cargo.toml | 6 ++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 13a9d7f4a29..e29462985da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -899,14 +899,17 @@ dependencies = [ name = "gateway" version = "0.1.0" dependencies = [ + "crypto", "dotenv", "futures 0.3.4", - "futures-channel", - "futures-util", "log 0.4.8", + "mix-client", "multi-tcp-client", "nymsphinx", "pretty_env_logger", + "rand 0.7.3", + "serde", + "sled", "tokio 0.2.16", "tokio-tungstenite", "tungstenite", diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index 19ef98e9752..0fc5e0c9cb7 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -9,16 +9,18 @@ edition = "2018" [dependencies] dotenv = "0.15.0" futures = "0.3" -#futures-channel = "0.3" -#futures-util = { version = "0.3", default-features = false, features = ["async-await", "sink", "std"] } log = "0.4" pretty_env_logger = "0.3" +rand = "0.7.2" serde = { version = "1.0.104", features = ["derive"] } #serde_json = "1.0.44" +sled = "0.31" tokio = { version = "0.2", features = ["full"] } tokio-tungstenite = "0.10.1" # internal +crypto = { path = "../common/crypto" } +mix-client = { path = "../common/client-libs/mix-client" } # to be removed very soon multi-tcp-client = { path = "../common/client-libs/multi-tcp-client" } nymsphinx = { path = "../common/nymsphinx" } From 00bcde8c0325fc925c0ebf4c4dc3cb3300f87790 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 22 Apr 2020 16:08:20 +0100 Subject: [PATCH 20/70] Initial type definitions for client messages --- .../src/client_handling/websocket/types.rs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 gateway/src/client_handling/websocket/types.rs diff --git a/gateway/src/client_handling/websocket/types.rs b/gateway/src/client_handling/websocket/types.rs new file mode 100644 index 00000000000..1fcf2d135cd --- /dev/null +++ b/gateway/src/client_handling/websocket/types.rs @@ -0,0 +1,42 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use serde::{Deserialize, Serialize}; +use std::convert::TryFrom; +use tokio_tungstenite::tungstenite::protocol::Message; + +#[derive(Serialize, Deserialize, Debug)] +pub(crate) enum Request { + Send, + Register { + address: String + }, + Authenticate { + token: String + }, +} + +#[derive(Serialize, Deserialize, Debug)] +pub(crate) enum Response { + Send, + Register { + token: String + }, + Authenticate { + status: bool + }, + Error { + message: String + }, +} \ No newline at end of file From b617bb5ec492e60e32f43690748431f6084cd6c4 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Thu, 23 Apr 2020 11:03:50 +0100 Subject: [PATCH 21/70] Initial "gateway-requests" with AuthToken --- Cargo.toml | 1 + gateway/gateway-requests/Cargo.toml | 11 ++ gateway/gateway-requests/src/auth_token.rs | 125 ++++++++++++++++++ gateway/gateway-requests/src/requests/mod.rs | 19 +++ gateway/gateway-requests/src/responses/mod.rs | 18 +++ 5 files changed, 174 insertions(+) create mode 100644 gateway/gateway-requests/Cargo.toml create mode 100644 gateway/gateway-requests/src/auth_token.rs create mode 100644 gateway/gateway-requests/src/requests/mod.rs create mode 100644 gateway/gateway-requests/src/responses/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 3c73502e567..271cc6de666 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ members = [ "common/pemstore", "common/topology", "gateway", + "gateway/gateway-requests", "mixnode", "sfw-provider", "sfw-provider/sfw-provider-requests", diff --git a/gateway/gateway-requests/Cargo.toml b/gateway/gateway-requests/Cargo.toml new file mode 100644 index 00000000000..e312b4fdb30 --- /dev/null +++ b/gateway/gateway-requests/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "gateway-requests" +version = "0.1.0" +authors = ["Jedrzej Stuczynski "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bs58 = "0.3" +nymsphinx = { path = "../../common/nymsphinx" } diff --git a/gateway/gateway-requests/src/auth_token.rs b/gateway/gateway-requests/src/auth_token.rs new file mode 100644 index 00000000000..92768c1dbe1 --- /dev/null +++ b/gateway/gateway-requests/src/auth_token.rs @@ -0,0 +1,125 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub const AUTH_TOKEN_SIZE: usize = 32; + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub struct AuthToken([u8; AUTH_TOKEN_SIZE]); + +#[derive(Debug)] +pub enum AuthTokenConversionError { + InvalidStringError, + StringOfInvalidLengthError, +} + +impl AuthToken { + pub fn from_bytes(bytes: [u8; AUTH_TOKEN_SIZE]) -> Self { + AuthToken(bytes) + } + + pub fn to_bytes(&self) -> [u8; AUTH_TOKEN_SIZE] { + self.0 + } + + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } + + pub fn try_from_base58_string>( + val: S, + ) -> Result { + let decoded = match bs58::decode(val.into()).into_vec() { + Ok(decoded) => decoded, + Err(_) => return Err(AuthTokenConversionError::InvalidStringError), + }; + + if decoded.len() != AUTH_TOKEN_SIZE { + return Err(AuthTokenConversionError::StringOfInvalidLengthError); + } + + let mut token = [0u8; AUTH_TOKEN_SIZE]; + token.copy_from_slice(&decoded[..]); + Ok(AuthToken(token)) + } + + pub fn to_base58_string(&self) -> String { + bs58::encode(self.0).into_string() + } +} + +impl Into for AuthToken { + fn into(self) -> String { + self.to_base58_string() + } +} + +#[cfg(test)] +mod auth_token_conversion { + use super::*; + + #[test] + fn it_is_possible_to_recover_it_from_valid_b58_string() { + let auth_token = AuthToken([42; AUTH_TOKEN_SIZE]); + let auth_token_string = auth_token.to_base58_string(); + assert_eq!( + auth_token, + AuthToken::try_from_base58_string(auth_token_string).unwrap() + ) + } + + #[test] + fn it_is_possible_to_recover_it_from_valid_b58_str_ref() { + let auth_token = AuthToken([42; AUTH_TOKEN_SIZE]); + let auth_token_string = auth_token.to_base58_string(); + let auth_token_str_ref: &str = &auth_token_string; + assert_eq!( + auth_token, + AuthToken::try_from_base58_string(auth_token_str_ref).unwrap() + ) + } + + #[test] + fn it_returns_error_on_b58_with_invalid_characters() { + let auth_token = AuthToken([42; AUTH_TOKEN_SIZE]); + let auth_token_string = auth_token.to_base58_string(); + + let mut chars = auth_token_string.chars(); + let _consumed_first_char = chars.next().unwrap(); + + let invalid_chars_token = "=".to_string() + chars.as_str(); + assert!(AuthToken::try_from_base58_string(invalid_chars_token).is_err()) + } + + #[test] + fn it_returns_error_on_too_long_b58_string() { + let auth_token = AuthToken([42; AUTH_TOKEN_SIZE]); + let mut auth_token_string = auth_token.to_base58_string(); + auth_token_string.push('f'); + + assert!(AuthToken::try_from_base58_string(auth_token_string).is_err()) + } + + #[test] + fn it_returns_error_on_too_short_b58_string() { + let auth_token = AuthToken([42; AUTH_TOKEN_SIZE]); + let auth_token_string = auth_token.to_base58_string(); + + let mut chars = auth_token_string.chars(); + let _consumed_first_char = chars.next().unwrap(); + let _consumed_second_char = chars.next().unwrap(); + let invalid_chars_token = chars.as_str(); + + assert!(AuthToken::try_from_base58_string(invalid_chars_token).is_err()) + } +} diff --git a/gateway/gateway-requests/src/requests/mod.rs b/gateway/gateway-requests/src/requests/mod.rs new file mode 100644 index 00000000000..40417ff81d7 --- /dev/null +++ b/gateway/gateway-requests/src/requests/mod.rs @@ -0,0 +1,19 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::auth_token::{AuthToken, AUTH_TOKEN_SIZE}; +use nymsphinx::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH}; +use std::convert::TryFrom; +use std::io; +use std::io::Error; diff --git a/gateway/gateway-requests/src/responses/mod.rs b/gateway/gateway-requests/src/responses/mod.rs new file mode 100644 index 00000000000..89ff71848dd --- /dev/null +++ b/gateway/gateway-requests/src/responses/mod.rs @@ -0,0 +1,18 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::auth_token::{AuthToken, AUTH_TOKEN_SIZE}; +use std::convert::{TryFrom, TryInto}; +use std::io; +use std::io::Error; From 52e3ca84da6e47e4155968d382bebbaa7d39d779 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Thu, 23 Apr 2020 11:31:38 +0100 Subject: [PATCH 22/70] ibid. --- gateway/gateway-requests/src/lib.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 gateway/gateway-requests/src/lib.rs diff --git a/gateway/gateway-requests/src/lib.rs b/gateway/gateway-requests/src/lib.rs new file mode 100644 index 00000000000..c00bc8c4b7c --- /dev/null +++ b/gateway/gateway-requests/src/lib.rs @@ -0,0 +1,20 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod auth_token; +pub mod requests; +pub mod responses; + +pub const DUMMY_MESSAGE_CONTENT: &[u8] = + b"[DUMMY MESSAGE] Wanting something does not give you the right to have it."; From 2d52390502e8ee7ce3cdb2094d2145b81d929838 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Thu, 23 Apr 2020 11:31:52 +0100 Subject: [PATCH 23/70] Restored most of ledger's functionalities --- gateway/src/client_handling/ledger.rs | 263 +++++++++++++------------- 1 file changed, 134 insertions(+), 129 deletions(-) diff --git a/gateway/src/client_handling/ledger.rs b/gateway/src/client_handling/ledger.rs index dd19b429c65..3ac409c6cab 100644 --- a/gateway/src/client_handling/ledger.rs +++ b/gateway/src/client_handling/ledger.rs @@ -18,6 +18,11 @@ //use sfw_provider_requests::auth_token::{AuthToken, AUTH_TOKEN_SIZE}; //use std::path::PathBuf; +use gateway_requests::auth_token::{AuthToken, AUTH_TOKEN_SIZE}; +use log::*; +use nymsphinx::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH}; +use std::path::PathBuf; + #[derive(Debug)] pub(crate) enum ClientLedgerError { DbReadError(sled::Error), @@ -31,132 +36,132 @@ pub(crate) enum ClientLedgerError { pub(crate) struct ClientLedger { db: sled::Db, } -// -//impl ClientLedger { -// pub(crate) fn load(file: PathBuf) -> Result { -// let db = match sled::open(file) { -// Err(e) => return Err(ClientLedgerError::DbOpenError(e)), -// Ok(db) => db, -// }; -// -// let ledger = ClientLedger { db }; -// -// ledger.db.iter().keys().for_each(|key| { -// println!( -// "key: {:?}", -// ledger -// .read_destination_address_bytes(key.unwrap()) -// .to_base58_string() -// ); -// }); -// -// Ok(ledger) -// } -// -// fn read_auth_token(&self, raw_token: sled::IVec) -> AuthToken { -// let token_bytes_ref = raw_token.as_ref(); -// // if this fails it means we have some database corruption and we -// // absolutely can't continue -// if token_bytes_ref.len() != AUTH_TOKEN_SIZE { -// error!("CLIENT LEDGER DATA CORRUPTION - TOKEN HAS INVALID LENGTH"); -// panic!("CLIENT LEDGER DATA CORRUPTION - TOKEN HAS INVALID LENGTH"); -// } -// -// let mut token_bytes = [0u8; AUTH_TOKEN_SIZE]; -// token_bytes.copy_from_slice(token_bytes_ref); -// AuthToken::from_bytes(token_bytes) -// } -// -// fn read_destination_address_bytes( -// &self, -// raw_destination: sled::IVec, -// ) -> DestinationAddressBytes { -// let destination_ref = raw_destination.as_ref(); -// // if this fails it means we have some database corruption and we -// // absolutely can't continue -// if destination_ref.len() != DESTINATION_ADDRESS_LENGTH { -// error!("CLIENT LEDGER DATA CORRUPTION - CLIENT ADDRESS HAS INVALID LENGTH"); -// panic!("CLIENT LEDGER DATA CORRUPTION - CLIENT ADDRESS HAS INVALID LENGTH"); -// } -// -// let mut destination_bytes = [0u8; DESTINATION_ADDRESS_LENGTH]; -// destination_bytes.copy_from_slice(destination_ref); -// DestinationAddressBytes::from_bytes(destination_bytes) -// } -// -// pub(crate) fn verify_token( -// &self, -// auth_token: &AuthToken, -// client_address: &DestinationAddressBytes, -// ) -> Result { -// match self.db.get(&client_address.to_bytes()) { -// Err(e) => Err(ClientLedgerError::DbReadError(e)), -// Ok(token) => match token { -// Some(token_ivec) => Ok(&self.read_auth_token(token_ivec) == auth_token), -// None => Ok(false), -// }, -// } -// } -// -// pub(crate) fn insert_token( -// &mut self, -// auth_token: AuthToken, -// client_address: DestinationAddressBytes, -// ) -> Result, ClientLedgerError> { -// let insertion_result = match self -// .db -// .insert(&client_address.to_bytes(), &auth_token.to_bytes()) -// { -// Err(e) => Err(ClientLedgerError::DbWriteError(e)), -// Ok(existing_token) => { -// Ok(existing_token.map(|existing_token| self.read_auth_token(existing_token))) -// } -// }; -// -// // registration doesn't happen that often so might as well flush it to the disk to be sure -// self.db.flush().unwrap(); -// insertion_result -// } -// -// pub(crate) fn remove_token( -// &mut self, -// client_address: &DestinationAddressBytes, -// ) -> Result, ClientLedgerError> { -// let removal_result = match self.db.remove(&client_address.to_bytes()) { -// Err(e) => Err(ClientLedgerError::DbWriteError(e)), -// Ok(existing_token) => { -// Ok(existing_token.map(|existing_token| self.read_auth_token(existing_token))) -// } -// }; -// -// // removing of tokens happens extremely rarely, so flush is also fine here -// self.db.flush().unwrap(); -// removal_result -// } -// -// pub(crate) fn current_clients(&self) -> Result, ClientLedgerError> { -// let clients = self.db.iter().keys(); -// -// let mut client_vec = Vec::new(); -// for client in clients { -// match client { -// Err(e) => return Err(ClientLedgerError::DbWriteError(e)), -// Ok(client_entry) => client_vec.push(MixProviderClient { -// pub_key: self -// .read_destination_address_bytes(client_entry) -// .to_base58_string(), -// }), -// } -// } -// -// Ok(client_vec) -// } -// -// #[cfg(test)] -// pub(crate) fn create_temporary() -> Self { -// let cfg = sled::Config::new().temporary(true); -// ClientLedger { -// db: cfg.open().unwrap(), -// } -// } -//} + +impl ClientLedger { + pub(crate) fn load(file: PathBuf) -> Result { + let db = match sled::open(file) { + Err(e) => return Err(ClientLedgerError::DbOpenError(e)), + Ok(db) => db, + }; + + let ledger = ClientLedger { db }; + + ledger.db.iter().keys().for_each(|key| { + println!( + "key: {:?}", + ledger + .read_destination_address_bytes(key.unwrap()) + .to_base58_string() + ); + }); + + Ok(ledger) + } + + fn read_auth_token(&self, raw_token: sled::IVec) -> AuthToken { + let token_bytes_ref = raw_token.as_ref(); + // if this fails it means we have some database corruption and we + // absolutely can't continue + if token_bytes_ref.len() != AUTH_TOKEN_SIZE { + error!("CLIENT LEDGER DATA CORRUPTION - TOKEN HAS INVALID LENGTH"); + panic!("CLIENT LEDGER DATA CORRUPTION - TOKEN HAS INVALID LENGTH"); + } + + let mut token_bytes = [0u8; AUTH_TOKEN_SIZE]; + token_bytes.copy_from_slice(token_bytes_ref); + AuthToken::from_bytes(token_bytes) + } + + fn read_destination_address_bytes( + &self, + raw_destination: sled::IVec, + ) -> DestinationAddressBytes { + let destination_ref = raw_destination.as_ref(); + // if this fails it means we have some database corruption and we + // absolutely can't continue + if destination_ref.len() != DESTINATION_ADDRESS_LENGTH { + error!("CLIENT LEDGER DATA CORRUPTION - CLIENT ADDRESS HAS INVALID LENGTH"); + panic!("CLIENT LEDGER DATA CORRUPTION - CLIENT ADDRESS HAS INVALID LENGTH"); + } + + let mut destination_bytes = [0u8; DESTINATION_ADDRESS_LENGTH]; + destination_bytes.copy_from_slice(destination_ref); + DestinationAddressBytes::from_bytes(destination_bytes) + } + + pub(crate) fn verify_token( + &self, + auth_token: &AuthToken, + client_address: &DestinationAddressBytes, + ) -> Result { + match self.db.get(&client_address.to_bytes()) { + Err(e) => Err(ClientLedgerError::DbReadError(e)), + Ok(token) => match token { + Some(token_ivec) => Ok(&self.read_auth_token(token_ivec) == auth_token), + None => Ok(false), + }, + } + } + + pub(crate) fn insert_token( + &mut self, + auth_token: AuthToken, + client_address: DestinationAddressBytes, + ) -> Result, ClientLedgerError> { + let insertion_result = match self + .db + .insert(&client_address.to_bytes(), &auth_token.to_bytes()) + { + Err(e) => Err(ClientLedgerError::DbWriteError(e)), + Ok(existing_token) => { + Ok(existing_token.map(|existing_token| self.read_auth_token(existing_token))) + } + }; + + // registration doesn't happen that often so might as well flush it to the disk to be sure + self.db.flush().unwrap(); + insertion_result + } + + pub(crate) fn remove_token( + &mut self, + client_address: &DestinationAddressBytes, + ) -> Result, ClientLedgerError> { + let removal_result = match self.db.remove(&client_address.to_bytes()) { + Err(e) => Err(ClientLedgerError::DbWriteError(e)), + Ok(existing_token) => { + Ok(existing_token.map(|existing_token| self.read_auth_token(existing_token))) + } + }; + + // removing of tokens happens extremely rarely, so flush is also fine here + self.db.flush().unwrap(); + removal_result + } + + // pub(crate) fn current_clients(&self) -> Result, ClientLedgerError> { + // let clients = self.db.iter().keys(); + // + // let mut client_vec = Vec::new(); + // for client in clients { + // match client { + // Err(e) => return Err(ClientLedgerError::DbWriteError(e)), + // Ok(client_entry) => client_vec.push(MixProviderClient { + // pub_key: self + // .read_destination_address_bytes(client_entry) + // .to_base58_string(), + // }), + // } + // } + // + // Ok(client_vec) + // } + + #[cfg(test)] + pub(crate) fn create_temporary() -> Self { + let cfg = sled::Config::new().temporary(true); + ClientLedger { + db: cfg.open().unwrap(), + } + } +} From 01be25fcd4cb838fa6ce45bec46b030bce55a3c9 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Thu, 23 Apr 2020 15:32:34 +0100 Subject: [PATCH 24/70] Ability to retrieve all messages regardless of rate limit --- gateway/src/storage/mod.rs | 41 +++++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/gateway/src/storage/mod.rs b/gateway/src/storage/mod.rs index b488dea5c83..77b08d36cca 100644 --- a/gateway/src/storage/mod.rs +++ b/gateway/src/storage/mod.rs @@ -14,10 +14,10 @@ use futures::lock::Mutex; use futures::StreamExt; +use gateway_requests::DUMMY_MESSAGE_CONTENT; use log::*; -use nymsphinx::{DestinationAddressBytes, SURBIdentifier}; +use nymsphinx::DestinationAddressBytes; use rand::Rng; -//use sfw_provider_requests::DUMMY_MESSAGE_CONTENT; use std::io; use std::path::PathBuf; use std::sync::Arc; @@ -25,8 +25,6 @@ use tokio::fs; use tokio::fs::File; use tokio::prelude::*; -const DUMMY_MESSAGE_CONTENT: &'static [u8] = b"foomp"; - fn dummy_message() -> ClientFile { ClientFile { content: DUMMY_MESSAGE_CONTENT.to_vec(), @@ -131,7 +129,40 @@ impl ClientStorage { file.write_all(store_data.message.as_ref()).await } - pub(crate) async fn retrieve_client_files( + pub(crate) async fn retrieve_all_client_messages( + &self, + client_address: DestinationAddressBytes, + ) -> io::Result> { + let inner_data = self.inner.lock().await; + + let client_dir_name = client_address.to_base58_string(); + let full_store_dir = inner_data.main_store_path_dir.join(client_dir_name); + + trace!("going to lookup: {:?}!", full_store_dir); + if !full_store_dir.exists() { + return Err(io::Error::new( + io::ErrorKind::NotFound, + "Target client does not exist", + )); + } + + let mut msgs = Vec::new(); + let mut read_dir = fs::read_dir(full_store_dir).await?; + + while let Some(dir_entry) = read_dir.next().await { + if let Ok(dir_entry) = dir_entry { + if !Self::is_valid_file(&dir_entry).await { + continue; + } + let client_file = + ClientFile::new(fs::read(dir_entry.path()).await?, dir_entry.path()); + msgs.push(client_file) + } + } + Ok(msgs) + } + + pub(crate) async fn retrieve_client_files_with_constant_rate( &self, client_address: DestinationAddressBytes, ) -> io::Result> { From 6432758266852daede824869986de56f23fa26dd Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Thu, 23 Apr 2020 15:32:52 +0100 Subject: [PATCH 25/70] ClientsHandler request handling logic --- .../src/client_handling/clients_handler.rs | 241 +++++++++++++++++- 1 file changed, 234 insertions(+), 7 deletions(-) diff --git a/gateway/src/client_handling/clients_handler.rs b/gateway/src/client_handling/clients_handler.rs index 91bd1aba0ea..e1ea459a63f 100644 --- a/gateway/src/client_handling/clients_handler.rs +++ b/gateway/src/client_handling/clients_handler.rs @@ -1,20 +1,25 @@ use crate::client_handling::ledger::ClientLedger; use crate::client_handling::websocket::message_receiver::MixMessageSender; +use crate::storage::ClientStorage; +use crypto::encryption; use futures::channel::{mpsc, oneshot}; use futures::StreamExt; +use gateway_requests::auth_token::AuthToken; +use hmac::{Hmac, Mac}; use log::*; use nymsphinx::DestinationAddressBytes; +use sha2::Sha256; use std::collections::HashMap; +use std::sync::Arc; use tokio::task::JoinHandle; -type temp_AuthToken = String; - pub(crate) type ClientsHandlerRequestSender = mpsc::UnboundedSender; pub(crate) type ClientsHandlerRequestReceiver = mpsc::UnboundedReceiver; pub(crate) type ClientsHandlerResponseSender = oneshot::Sender; pub(crate) type ClientsHandlerResponseReceiver = oneshot::Receiver; +#[derive(Debug)] pub(crate) enum ClientsHandlerRequest { // client Register( @@ -23,40 +28,262 @@ pub(crate) enum ClientsHandlerRequest { ClientsHandlerResponseSender, ), Authenticate( - temp_AuthToken, + DestinationAddressBytes, + AuthToken, MixMessageSender, ClientsHandlerResponseSender, ), + Disconnect(DestinationAddressBytes), // mix + // EmptyInbox(DestinationAddressBytes), IsOnline(DestinationAddressBytes, ClientsHandlerResponseSender), } +#[derive(Debug)] pub(crate) enum ClientsHandlerResponse { - Register(Option), + Register(AuthToken), Authenticate(bool), IsOnline(Option), + Error(Box), } pub(crate) struct ClientsHandler { - open_connections: HashMap, // clients_ledger: unimplemented!(), + secret_key: Arc, + open_connections: HashMap, clients_ledger: ClientLedger, + clients_inbox_storage: ClientStorage, request_receiver_channel: ClientsHandlerRequestReceiver, } impl ClientsHandler { - pub(crate) fn new(request_receiver_channel: ClientsHandlerRequestReceiver) -> Self { + pub(crate) fn new( + request_receiver_channel: ClientsHandlerRequestReceiver, + secret_key: Arc, + ) -> Self { ClientsHandler { + secret_key, open_connections: HashMap::new(), request_receiver_channel, clients_ledger: unimplemented!(), + clients_inbox_storage: unimplemented!(), + } + } + + fn make_error_response(&self, err: E) -> ClientsHandlerResponse + where + E: Into>, + { + ClientsHandlerResponse::Error(err.into()) + } + + // best effort sending error responses + fn send_error_response(&self, err: E, mut res_channel: ClientsHandlerResponseSender) + where + E: Into>, + { + if let Err(_) = res_channel.send(self.make_error_response(err)) { + error!("Somehow we failed to send response back to websocket handler - there seem to be a weird bug present!"); + } + } + + fn generate_new_auth_token(&self, client_address: DestinationAddressBytes) -> AuthToken { + type HmacSha256 = Hmac; + + // note that `new_varkey` doesn't even have an execution branch returning an error + // (true as of hmac 0.7.1) + let mut auth_token_raw = HmacSha256::new_varkey(&self.secret_key.to_bytes()).unwrap(); + auth_token_raw.input(client_address.as_bytes()); + let mut auth_token = [0u8; 32]; + auth_token.copy_from_slice(auth_token_raw.result().code().as_slice()); + AuthToken::from_bytes(auth_token) + } + + async fn push_stored_messages_to_client_and_save_channel( + &mut self, + client_address: DestinationAddressBytes, + comm_channel: MixMessageSender, + ) { + // TODO: it is possible that during a small window some of client messages will be "lost", + // i.e. be stored on the disk rather than pushed to the client, reason for this is as follows: + // now we push all stored messages from client's inbox to its websocket connection + // however, say, at the same time there's new message to the client - it gets stored on the disk! + // And only after this methods exits, mix receivers will become aware of the client + // connection going online and being able to forward traffic there. + // + // possible solution: spawn a future to empty inbox in X seconds rather than immediately + // JS: I will most likely do that (with including entries to config, etc.) once the + // basic version is up and running as not to waste time on it now + + // NOTE: THIS IGNORES MESSAGE RETRIEVAL LIMIT AND TAKES EVERYTHING! + let all_stored_messages = match self + .clients_inbox_storage + .retrieve_all_client_messages(client_address.clone()) + .await + { + Ok(msgs) => msgs, + Err(e) => { + error!( + "failed to retrieve client messages. {:?} inbox might be corrupted now - {:?}", + client_address.to_base58_string(), + e + ); + return; + } + }; + + let (messages, paths): (Vec<_>, Vec<_>) = all_stored_messages + .into_iter() + .map(|c| c.into_tuple()) + .unzip(); + + if let Err(_) = comm_channel.unbounded_send(messages) { + error!("Somehow we failed to stored messages to a fresh client channel - there seem to be a weird bug present!"); + } else { + // but if all went well, we can now delete it + if let Err(e) = self.clients_inbox_storage.delete_files(paths).await { + error!( + "Failed to remove client ({:?}) files - {:?}", + client_address.to_base58_string(), + e + ); + } else { + // finally, everything was fine - we retrieved everything, we deleted everything, + // we assume we can now safely delegate client message pushing + self.open_connections.insert(client_address, comm_channel); + } + } + } + + async fn handle_register_request( + &mut self, + address: DestinationAddressBytes, + comm_channel: MixMessageSender, + mut res_channel: ClientsHandlerResponseSender, + ) { + debug!( + "Processing register new client request: {:?}", + address.to_base58_string() + ); + + if self.open_connections.get(&address).is_some() { + warn!( + "Tried to process register request for a client with an already opened connection!" + ); + self.send_error_response("duplicate connection detected", res_channel); + return; + } + + // I presume some additional checks will go here: + // ... + let auth_token = self.generate_new_auth_token(address.clone()); + if self + .clients_ledger + .insert_token(auth_token.clone(), address.clone()) + .unwrap() + .is_some() + { + info!( + "Client {:?} was already registered before!", + address.to_base58_string() + ) + } else if let Err(e) = self + .clients_inbox_storage + .create_storage_dir(address.clone()) + .await + { + error!("We failed to create inbox directory for the client -{:?}\nReverting issued token...", e); + // we must revert our changes if this operation failed + self.clients_ledger.remove_token(&address).unwrap(); + self.send_error_response("failed to issue an auth token", res_channel); + return; + } + + self.push_stored_messages_to_client_and_save_channel(address, comm_channel) + .await; + + if let Err(_) = res_channel.send(ClientsHandlerResponse::Register(auth_token)) { + error!("Somehow we failed to send response back to websocket handler - there seem to be a weird bug present!"); + } + } + + async fn handle_authenticate_request( + &mut self, + address: DestinationAddressBytes, + token: AuthToken, + comm_channel: MixMessageSender, + mut res_channel: ClientsHandlerResponseSender, + ) { + debug!( + "Processing authenticate client request: {:?}", + address.to_base58_string() + ); + + if self.open_connections.get(&address).is_some() { + warn!("Tried to process authenticate request for a client with an already opened connection!"); + self.send_error_response("duplicate connection detected", res_channel); + return; } + + if self.clients_ledger.verify_token(&token, &address).unwrap() { + self.push_stored_messages_to_client_and_save_channel(address, comm_channel) + .await; + if let Err(_) = res_channel.send(ClientsHandlerResponse::Authenticate(true)) { + error!("Somehow we failed to send response back to websocket handler - there seem to be a weird bug present!"); + } + } else { + if let Err(_) = res_channel.send(ClientsHandlerResponse::Authenticate(false)) { + error!("Somehow we failed to send response back to websocket handler - there seem to be a weird bug present!"); + } + } + } + + fn handle_disconnect(&mut self, address: DestinationAddressBytes) { + debug!( + "Processing disconnect client request: {:?}", + address.to_base58_string() + ); + self.open_connections.remove(&address); + } + + fn handle_is_online_request( + &self, + address: DestinationAddressBytes, + mut res_channel: ClientsHandlerResponseSender, + ) { + debug!( + "Processing is online request for: {:?}", + address.to_base58_string() + ); + + let response_value = self + .open_connections + .get(&address) + .map(|channel| channel.clone()); + // if this fails, it's a critical failure, because mix handlers should ALWAYS be online + res_channel + .send(ClientsHandlerResponse::IsOnline(response_value)) + .unwrap(); } pub(crate) async fn run(&mut self) { while let Some(request) = self.request_receiver_channel.next().await { - // handle request + match request { + ClientsHandlerRequest::Register(address, comm_channel, res_channel) => { + self.handle_register_request(address, comm_channel, res_channel) + .await + } + ClientsHandlerRequest::Authenticate(address, token, comm_channel, res_channel) => { + self.handle_authenticate_request(address, token, comm_channel, res_channel) + .await + } + ClientsHandlerRequest::Disconnect(address) => self.handle_disconnect(address), + ClientsHandlerRequest::IsOnline(address, res_channel) => { + self.handle_is_online_request(address, res_channel) + } + }; } + error!("Something bad has happened and we stopped listening for requests!"); } pub(crate) fn start(mut self) -> JoinHandle<()> { From a82608ba7b99745a8a2447a579395175c75b0b2b Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Thu, 23 Apr 2020 15:33:05 +0100 Subject: [PATCH 26/70] Required 'new' dependencies --- gateway/Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index 0fc5e0c9cb7..e997d5db4ed 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -14,12 +14,15 @@ pretty_env_logger = "0.3" rand = "0.7.2" serde = { version = "1.0.104", features = ["derive"] } #serde_json = "1.0.44" +sha2 = "0.8.1" sled = "0.31" tokio = { version = "0.2", features = ["full"] } tokio-tungstenite = "0.10.1" +hmac = "0.7.1" # internal crypto = { path = "../common/crypto" } +gateway-requests = { path = "gateway-requests" } mix-client = { path = "../common/client-libs/mix-client" } # to be removed very soon multi-tcp-client = { path = "../common/client-libs/multi-tcp-client" } nymsphinx = { path = "../common/nymsphinx" } From c9975e58eb2427cef00b6567a23c53b0597ed25f Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Thu, 23 Apr 2020 15:33:22 +0100 Subject: [PATCH 27/70] Main changes required for compilation --- gateway/src/main.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gateway/src/main.rs b/gateway/src/main.rs index dff9e54de77..68310f3f1a3 100644 --- a/gateway/src/main.rs +++ b/gateway/src/main.rs @@ -12,10 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::client_handling::clients_handler::ClientsHandler; use crate::client_handling::websocket; use crate::mixnet_handling::receiver::packet_processing::PacketProcessor; use crate::storage::ClientStorage; use log::*; +use std::sync::Arc; mod client_handling; mod mixnet_client; @@ -29,15 +31,17 @@ async fn main() { let addr = "127.0.0.1:1793".parse().unwrap(); info!("Listening on: {}", addr); - let (dummy_clients_handler_tx, _) = futures::channel::mpsc::unbounded(); + let (dummy_clients_handler_tx, dummy_clients_handler_rx) = futures::channel::mpsc::unbounded(); let client_storage = ClientStorage::new(42, 42, "foomp".into()); let dummy_keypair = crypto::encryption::KeyPair::new(); + let arced_sk = Arc::new(dummy_keypair.private_key().to_owned()); let dummy_mix_packet_processor = PacketProcessor::new( - dummy_keypair.private_key().to_owned(), + Arc::clone(&arced_sk), dummy_clients_handler_tx.clone(), client_storage, ); + ClientsHandler::new(dummy_clients_handler_rx, arced_sk).start(); websocket::Listener::new(addr, dummy_clients_handler_tx.clone()).start(); mixnet_handling::Listener::new(addr).start(dummy_mix_packet_processor); From 0da7d04d67390ef0d8af78478f85ccd8feaddbcf Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Thu, 23 Apr 2020 15:33:40 +0100 Subject: [PATCH 28/70] PacketProcessor getting private key pointer --- gateway/src/mixnet_handling/receiver/packet_processing.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gateway/src/mixnet_handling/receiver/packet_processing.rs b/gateway/src/mixnet_handling/receiver/packet_processing.rs index 64a2092ec2e..8b50418f45f 100644 --- a/gateway/src/mixnet_handling/receiver/packet_processing.rs +++ b/gateway/src/mixnet_handling/receiver/packet_processing.rs @@ -22,7 +22,7 @@ use futures::channel::oneshot; use futures::lock::Mutex; use log::*; use mix_client::packet::LOOP_COVER_MESSAGE_PAYLOAD; -use nymsphinx::{DestinationAddressBytes, Payload, ProcessedPacket, SURBIdentifier, SphinxPacket}; +use nymsphinx::{DestinationAddressBytes, ProcessedPacket, SURBIdentifier, SphinxPacket}; use std::collections::HashMap; use std::io; use std::ops::Deref; @@ -73,7 +73,7 @@ pub struct PacketProcessor { impl PacketProcessor { pub(crate) fn new( - secret_key: encryption::PrivateKey, + secret_key: Arc, clients_handler_sender: ClientsHandlerRequestSender, client_store: ClientStorage, ) -> Self { @@ -81,7 +81,7 @@ impl PacketProcessor { available_socket_senders_cache: Arc::new(Mutex::new(HashMap::new())), clients_handler_sender, client_store, - secret_key: Arc::new(secret_key), + secret_key, } } From 5247b48d13d09a64746d189bdc70432f94d5811e Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Thu, 23 Apr 2020 15:55:26 +0100 Subject: [PATCH 29/70] "moved" types into gateway requests crate --- gateway/gateway-requests/Cargo.toml | 2 + gateway/gateway-requests/src/requests/mod.rs | 9 ++++ gateway/gateway-requests/src/responses/mod.rs | 10 +++++ gateway/src/client_handling/websocket/mod.rs | 1 - .../src/client_handling/websocket/types.rs | 42 ------------------- 5 files changed, 21 insertions(+), 43 deletions(-) delete mode 100644 gateway/src/client_handling/websocket/types.rs diff --git a/gateway/gateway-requests/Cargo.toml b/gateway/gateway-requests/Cargo.toml index e312b4fdb30..b0e325bfde7 100644 --- a/gateway/gateway-requests/Cargo.toml +++ b/gateway/gateway-requests/Cargo.toml @@ -9,3 +9,5 @@ edition = "2018" [dependencies] bs58 = "0.3" nymsphinx = { path = "../../common/nymsphinx" } +serde = { version = "1.0.104", features = ["derive"] } +tokio-tungstenite = "0.10.1" diff --git a/gateway/gateway-requests/src/requests/mod.rs b/gateway/gateway-requests/src/requests/mod.rs index 40417ff81d7..132cd8c24b8 100644 --- a/gateway/gateway-requests/src/requests/mod.rs +++ b/gateway/gateway-requests/src/requests/mod.rs @@ -14,6 +14,15 @@ use crate::auth_token::{AuthToken, AUTH_TOKEN_SIZE}; use nymsphinx::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH}; +use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use std::io; use std::io::Error; +use tokio_tungstenite::tungstenite::protocol::Message; + +#[derive(Serialize, Deserialize, Debug)] +pub(crate) enum Request { + Send, + Register { address: String }, + Authenticate { token: String }, +} diff --git a/gateway/gateway-requests/src/responses/mod.rs b/gateway/gateway-requests/src/responses/mod.rs index 89ff71848dd..d7cd557a089 100644 --- a/gateway/gateway-requests/src/responses/mod.rs +++ b/gateway/gateway-requests/src/responses/mod.rs @@ -13,6 +13,16 @@ // limitations under the License. use crate::auth_token::{AuthToken, AUTH_TOKEN_SIZE}; +use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; use std::io; use std::io::Error; +use tokio_tungstenite::tungstenite::protocol::Message; + +#[derive(Serialize, Deserialize, Debug)] +pub(crate) enum Response { + Send, + Register { token: String }, + Authenticate { status: bool }, + Error { message: String }, +} diff --git a/gateway/src/client_handling/websocket/mod.rs b/gateway/src/client_handling/websocket/mod.rs index 4ff6f227a06..40cf4a58716 100644 --- a/gateway/src/client_handling/websocket/mod.rs +++ b/gateway/src/client_handling/websocket/mod.rs @@ -1,6 +1,5 @@ pub(crate) mod connection_handler; pub(crate) mod listener; -pub(crate) mod types; pub(crate) mod message_receiver; pub(crate) use listener::Listener; diff --git a/gateway/src/client_handling/websocket/types.rs b/gateway/src/client_handling/websocket/types.rs deleted file mode 100644 index 1fcf2d135cd..00000000000 --- a/gateway/src/client_handling/websocket/types.rs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use serde::{Deserialize, Serialize}; -use std::convert::TryFrom; -use tokio_tungstenite::tungstenite::protocol::Message; - -#[derive(Serialize, Deserialize, Debug)] -pub(crate) enum Request { - Send, - Register { - address: String - }, - Authenticate { - token: String - }, -} - -#[derive(Serialize, Deserialize, Debug)] -pub(crate) enum Response { - Send, - Register { - token: String - }, - Authenticate { - status: bool - }, - Error { - message: String - }, -} \ No newline at end of file From da51be9d5a2eb6ace1726f1f58598fad546bbedc Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Thu, 23 Apr 2020 17:51:06 +0100 Subject: [PATCH 30/70] Moved and renamed types --- gateway/gateway-requests/src/requests/mod.rs | 28 ---------- gateway/gateway-requests/src/responses/mod.rs | 28 ---------- gateway/gateway-requests/src/types.rs | 56 +++++++++++++++++++ 3 files changed, 56 insertions(+), 56 deletions(-) delete mode 100644 gateway/gateway-requests/src/requests/mod.rs delete mode 100644 gateway/gateway-requests/src/responses/mod.rs create mode 100644 gateway/gateway-requests/src/types.rs diff --git a/gateway/gateway-requests/src/requests/mod.rs b/gateway/gateway-requests/src/requests/mod.rs deleted file mode 100644 index 132cd8c24b8..00000000000 --- a/gateway/gateway-requests/src/requests/mod.rs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::auth_token::{AuthToken, AUTH_TOKEN_SIZE}; -use nymsphinx::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH}; -use serde::{Deserialize, Serialize}; -use std::convert::TryFrom; -use std::io; -use std::io::Error; -use tokio_tungstenite::tungstenite::protocol::Message; - -#[derive(Serialize, Deserialize, Debug)] -pub(crate) enum Request { - Send, - Register { address: String }, - Authenticate { token: String }, -} diff --git a/gateway/gateway-requests/src/responses/mod.rs b/gateway/gateway-requests/src/responses/mod.rs deleted file mode 100644 index d7cd557a089..00000000000 --- a/gateway/gateway-requests/src/responses/mod.rs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::auth_token::{AuthToken, AUTH_TOKEN_SIZE}; -use serde::{Deserialize, Serialize}; -use std::convert::{TryFrom, TryInto}; -use std::io; -use std::io::Error; -use tokio_tungstenite::tungstenite::protocol::Message; - -#[derive(Serialize, Deserialize, Debug)] -pub(crate) enum Response { - Send, - Register { token: String }, - Authenticate { status: bool }, - Error { message: String }, -} diff --git a/gateway/gateway-requests/src/types.rs b/gateway/gateway-requests/src/types.rs new file mode 100644 index 00000000000..133f740bd0f --- /dev/null +++ b/gateway/gateway-requests/src/types.rs @@ -0,0 +1,56 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use serde::{Deserialize, Serialize}; +use std::convert::TryFrom; +use tokio_tungstenite::tungstenite::protocol::Message; + +#[derive(Serialize, Deserialize, Debug)] +#[serde(tag = "type", rename_all = "camelCase")] +pub enum ClientRequest { + Authenticate { address: String, token: String }, + Register { address: String }, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(tag = "type", rename_all = "camelCase")] +pub enum ServerResponse { + Authenticate { status: bool }, + Register { token: String }, + Send { status: bool }, + Error { message: String }, +} + +impl ServerResponse { + pub fn new_error>(msg: S) -> Self { + ServerResponse::Error {message: msg.into()} + } +} + +impl TryFrom for ClientRequest { + type Error = serde_json::Error; + + fn try_from(msg: String) -> Result { + serde_json::from_str(&msg) + } +} + +impl Into for ServerResponse { + fn into(self) -> Message { + // it should be safe to call `unwrap` here as the message is generated by the server + // so if it fails (and consequently panics) it's a bug that should be resolved + let str_res = serde_json::to_string(&self).unwrap(); + Message::Text(str_res) + } +} From 9ae85640cc207867656bbe2dcd85d0f93e5a98a1 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Thu, 23 Apr 2020 17:51:12 +0100 Subject: [PATCH 31/70] ibid. --- gateway/gateway-requests/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gateway/gateway-requests/src/lib.rs b/gateway/gateway-requests/src/lib.rs index c00bc8c4b7c..10976d815fc 100644 --- a/gateway/gateway-requests/src/lib.rs +++ b/gateway/gateway-requests/src/lib.rs @@ -13,8 +13,7 @@ // limitations under the License. pub mod auth_token; -pub mod requests; -pub mod responses; +pub mod types; pub const DUMMY_MESSAGE_CONTENT: &[u8] = b"[DUMMY MESSAGE] Wanting something does not give you the right to have it."; From 2e29021704fe2d944a0171c592469b498bb5894e Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Thu, 23 Apr 2020 17:51:26 +0100 Subject: [PATCH 32/70] Added required serde_json dependency --- Cargo.lock | 14 ++++++++++++++ gateway/gateway-requests/Cargo.toml | 1 + 2 files changed, 15 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index e29462985da..09e322e04b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -902,6 +902,8 @@ dependencies = [ "crypto", "dotenv", "futures 0.3.4", + "gateway-requests", + "hmac", "log 0.4.8", "mix-client", "multi-tcp-client", @@ -909,12 +911,24 @@ dependencies = [ "pretty_env_logger", "rand 0.7.3", "serde", + "sha2", "sled", "tokio 0.2.16", "tokio-tungstenite", "tungstenite", ] +[[package]] +name = "gateway-requests" +version = "0.1.0" +dependencies = [ + "bs58", + "nymsphinx", + "serde", + "serde_json", + "tokio-tungstenite", +] + [[package]] name = "generic-array" version = "0.12.3" diff --git a/gateway/gateway-requests/Cargo.toml b/gateway/gateway-requests/Cargo.toml index b0e325bfde7..9948964040b 100644 --- a/gateway/gateway-requests/Cargo.toml +++ b/gateway/gateway-requests/Cargo.toml @@ -10,4 +10,5 @@ edition = "2018" bs58 = "0.3" nymsphinx = { path = "../../common/nymsphinx" } serde = { version = "1.0.104", features = ["derive"] } +serde_json = "1.0.44" tokio-tungstenite = "0.10.1" From 82d9fa5d9df5600bbbe643709c1ec095c445dd8f Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Thu, 23 Apr 2020 17:51:59 +0100 Subject: [PATCH 33/70] Skeleton for websocket request handling --- .../websocket/connection_handler.rs | 199 +++++++++++++++++- 1 file changed, 190 insertions(+), 9 deletions(-) diff --git a/gateway/src/client_handling/websocket/connection_handler.rs b/gateway/src/client_handling/websocket/connection_handler.rs index fb83a1073d2..a63d80fe05d 100644 --- a/gateway/src/client_handling/websocket/connection_handler.rs +++ b/gateway/src/client_handling/websocket/connection_handler.rs @@ -1,11 +1,72 @@ -use crate::client_handling::clients_handler::ClientsHandlerRequestSender; +use crate::client_handling::clients_handler::{ClientsHandlerRequest, ClientsHandlerRequestSender}; +use crate::client_handling::websocket::message_receiver::{MixMessageReceiver, MixMessageSender}; +use futures::channel::{mpsc, oneshot}; +use futures::SinkExt; +use gateway_requests::auth_token::AuthToken; +use gateway_requests::types::{ClientRequest, ServerResponse}; use log::*; -use tokio::{prelude::*, stream::StreamExt}; +use nymsphinx::DestinationAddressBytes; +use std::convert::TryFrom; +use std::sync::Arc; +use tokio::{prelude::*, stream::StreamExt, sync::Notify}; use tokio_tungstenite::{ tungstenite::{protocol::Message, Error as WsError}, WebSocketStream, }; +// EXPERIMENT: +struct MixMessagesHandle { + sender: MixMessageSender, + receiver: MixMessageReceiver, + + shutdown: Arc, +} + +impl MixMessagesHandle { + fn new(shutdown: Notify) -> Self { + let (sender, receiver) = mpsc::unbounded(); + MixMessagesHandle { + sender, + receiver, + shutdown: Arc::new(Notify::new()), + } + } + + fn shutdown(&self) { + self.shutdown.notify() + } + + fn get_sender(&self) -> MixMessageSender { + self.sender.clone() + } + + fn start_accepting(&self) { + let shutdown_signal = Arc::clone(&self.shutdown); + + // note to the graceful pull request reviewer: this is by no means how we'd be handling + // proper shutdown signals, this is more of an experiment that happened to do exactly + // what I needed in here (basically to not leak memory) + tokio::spawn(async move { + tokio::select! { + // TODO: solve borrow issue and figure out how to push result to socket + // msg = self.receiver.next() => { + // + // } + + _ = shutdown_signal.notified() => { + info!("received shutdown notification") + } + } + }); + } +} + +// TODO: note for my future self to consider the following idea: +// split the socket connection into sink and stream +// stream will be for reading explicit requests +// and sink for pumping responses AND mix traffic +// but as byproduct this might (or might not) break the clean "SocketStream" enum here + enum SocketStream { RawTCP(S), UpgradedWebSocket(WebSocketStream), @@ -13,8 +74,10 @@ enum SocketStream { } pub(crate) struct Handle { - socket_connection: SocketStream, + address: Option, + authenticated: bool, clients_handler_sender: ClientsHandlerRequestSender, + socket_connection: SocketStream, } impl Handle @@ -25,8 +88,10 @@ where // if we decide we want to change it, that's not too difficult pub(crate) fn new(conn: S, clients_handler_sender: ClientsHandlerRequestSender) -> Self { Handle { - socket_connection: SocketStream::RawTCP(conn), + address: None, + authenticated: false, clients_handler_sender, + socket_connection: SocketStream::RawTCP(conn), } } @@ -52,17 +117,133 @@ where } } + async fn send_websocket_response(&mut self, msg: Message) -> Result<(), WsError> { + match self.socket_connection { + // TODO: more closely investigate difference between `Sink::send` and `Sink::send_all` + // it got something to do with batching and flushing - it might be important if it + // turns out somehow we've got a bottleneck here + SocketStream::UpgradedWebSocket(ref mut ws_stream) => ws_stream.send(msg).await, + _ => panic!("impossible state - websocket handshake was somehow reverted"), + } + } + + fn disconnect(&self) { + // if we never established what is the address of the client, its connection was never + // announced hence we do not need to send 'disconnect' message + self.address.as_ref().map(|addr| { + self.clients_handler_sender + .unbounded_send(ClientsHandlerRequest::Disconnect(addr.clone())) + .unwrap(); + }); + } + + async fn handle_binary(&self, bin_msg: Vec) -> Message { + trace!("Handling binary message (presumably sphinx packet)"); + + // if it's binary, it MUST BE a sphinx packet. We can't look into it, but let's at least + // validate its size. + if bin_msg.len() != nymsphinx::PACKET_SIZE {} + unimplemented!() + } + + async fn handle_authenticate(&mut self, address: String, token: String) -> ServerResponse { + // TODO: https://github.com/nymtech/sphinx/issues/57 to resolve possible panics + // because we do **NOT** trust whatever garbage client just sent. + let address = DestinationAddressBytes::from_base58_string(address); + let token = match AuthToken::try_from_base58_string(token) { + Ok(token) => token, + Err(e) => { + trace!("failed to parse received AuthToken: {:?}", e); + return ServerResponse::new_error("malformed authentication token").into(); + } + }; + + // TODO: how to deal with the mix sender channel? + + // let (res_sender, res_receiver) = oneshot::channel(); + // let clients_handler_request = + // ClientsHandlerRequest::Authenticate(address, token, res_sender); + // self.clients_handler_sender + // .unbounded_send(clients_handler_request) + // .unwrap(); // the receiver MUST BE alive + // + // let client_sender = match res_receiver.await.unwrap() { + // ClientsHandlerResponse::IsOnline(client_sender) => client_sender, + // _ => panic!("received response to wrong query!"), // again, this should NEVER happen + // }; + + unimplemented!() + } + + async fn handle_register(&mut self, address: String) -> ServerResponse { + // TODO: https://github.com/nymtech/sphinx/issues/57 to resolve possible panics + // because we do **NOT** trust whatever garbage client just sent. + let address = DestinationAddressBytes::from_base58_string(address); + + // TODO: how to deal with the mix sender channel? + + unimplemented!() + } + + async fn handle_text(&mut self, text_msg: String) -> Message { + trace!("Handling text message (presumably control message)"); + + match ClientRequest::try_from(text_msg) { + Err(e) => ServerResponse::Error { + message: format!("received invalid request. err: {:?}", e), + }, + Ok(req) => match req { + ClientRequest::Authenticate { address, token } => { + self.handle_authenticate(address, token).await + } + ClientRequest::Register { address } => self.handle_register(address).await, + }, + } + .into() + } + + async fn handle_request(&mut self, raw_request: Message) -> Option { + // apparently tungstenite auto-handles ping/pong/close messages so for now let's ignore + // them and let's test that claim. If that's not the case, just copy code from + // desktop nym-client websocket as I've manually handled everything there + match raw_request { + Message::Binary(bin_msg) => Some(self.handle_binary(bin_msg).await), + Message::Text(text_msg) => Some(self.handle_text(text_msg).await), + _ => None, + } + } + + // TODO: FUTURE SELF, START HERE TOMORROW: + // change into "wait for authentication" and then listen for either client request + // or mix message channel message! async fn listen_for_requests(&mut self) { trace!("Started listening for incoming requests..."); while let Some(msg) = self.next_websocket_request().await { - // start handling here - // let msg = msg?; - // if msg.is_binary() { - // mixnet_client::forward_to_mixnode(msg.into_data(), Arc::clone(&client_ref)).await; - // } + let msg = match msg { + Ok(msg) => msg, + Err(err) => { + error!("failed to obtain message from websocket stream! stopping connection handler: {}", err); + break; + } + }; + + if msg.is_close() { + break; + } + + if let Some(response) = self.handle_request(msg).await { + if let Err(err) = self.send_websocket_response(response).await { + warn!( + "Failed to send message over websocket: {}. Assuming the connection is dead.", + err + ); + break; + } + } } + self.disconnect(); trace!("The stream was closed!"); } From 2bcd56927486aacfec9f1b7e571f2295d2e3ecc3 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Fri, 24 Apr 2020 12:31:53 +0100 Subject: [PATCH 34/70] helper methods on ServerResponse --- gateway/gateway-requests/src/types.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/gateway/gateway-requests/src/types.rs b/gateway/gateway-requests/src/types.rs index 133f740bd0f..caf90423ec4 100644 --- a/gateway/gateway-requests/src/types.rs +++ b/gateway/gateway-requests/src/types.rs @@ -34,7 +34,24 @@ pub enum ServerResponse { impl ServerResponse { pub fn new_error>(msg: S) -> Self { - ServerResponse::Error {message: msg.into()} + ServerResponse::Error { + message: msg.into(), + } + } + + pub fn is_error(&self) -> bool { + match self { + ServerResponse::Error { .. } => true, + _ => false, + } + } + + pub fn implies_successful_authentication(&self) -> bool { + match self { + ServerResponse::Authenticate { status } => *status, + ServerResponse::Register { .. } => true, + _ => false, + } } } From 8a04e0afc33fc598a15f4275390d120e359a96ef Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Fri, 24 Apr 2020 12:32:32 +0100 Subject: [PATCH 35/70] WebSocket Handler pushing received mix messages directly to client --- .../websocket/connection_handler.rs | 342 ++++++++++++------ 1 file changed, 237 insertions(+), 105 deletions(-) diff --git a/gateway/src/client_handling/websocket/connection_handler.rs b/gateway/src/client_handling/websocket/connection_handler.rs index a63d80fe05d..81f5569b394 100644 --- a/gateway/src/client_handling/websocket/connection_handler.rs +++ b/gateway/src/client_handling/websocket/connection_handler.rs @@ -1,71 +1,79 @@ -use crate::client_handling::clients_handler::{ClientsHandlerRequest, ClientsHandlerRequestSender}; -use crate::client_handling::websocket::message_receiver::{MixMessageReceiver, MixMessageSender}; -use futures::channel::{mpsc, oneshot}; -use futures::SinkExt; -use gateway_requests::auth_token::AuthToken; -use gateway_requests::types::{ClientRequest, ServerResponse}; -use log::*; -use nymsphinx::DestinationAddressBytes; use std::convert::TryFrom; use std::sync::Arc; + +use futures::{ + channel::{mpsc, oneshot}, + SinkExt, TryStream, TryStreamExt, +}; +use log::*; use tokio::{prelude::*, stream::StreamExt, sync::Notify}; use tokio_tungstenite::{ tungstenite::{protocol::Message, Error as WsError}, WebSocketStream, }; -// EXPERIMENT: -struct MixMessagesHandle { - sender: MixMessageSender, - receiver: MixMessageReceiver, - - shutdown: Arc, -} - -impl MixMessagesHandle { - fn new(shutdown: Notify) -> Self { - let (sender, receiver) = mpsc::unbounded(); - MixMessagesHandle { - sender, - receiver, - shutdown: Arc::new(Notify::new()), - } - } - - fn shutdown(&self) { - self.shutdown.notify() - } - - fn get_sender(&self) -> MixMessageSender { - self.sender.clone() - } - - fn start_accepting(&self) { - let shutdown_signal = Arc::clone(&self.shutdown); - - // note to the graceful pull request reviewer: this is by no means how we'd be handling - // proper shutdown signals, this is more of an experiment that happened to do exactly - // what I needed in here (basically to not leak memory) - tokio::spawn(async move { - tokio::select! { - // TODO: solve borrow issue and figure out how to push result to socket - // msg = self.receiver.next() => { - // - // } +use gateway_requests::auth_token::AuthToken; +use gateway_requests::types::{ClientRequest, ServerResponse}; +use nymsphinx::DestinationAddressBytes; - _ = shutdown_signal.notified() => { - info!("received shutdown notification") - } - } - }); - } -} +use crate::client_handling::clients_handler::{ + ClientsHandlerRequest, ClientsHandlerRequestSender, ClientsHandlerResponse, +}; +use crate::client_handling::websocket::message_receiver::{MixMessageReceiver, MixMessageSender}; -// TODO: note for my future self to consider the following idea: -// split the socket connection into sink and stream -// stream will be for reading explicit requests -// and sink for pumping responses AND mix traffic -// but as byproduct this might (or might not) break the clean "SocketStream" enum here +// +//// EXPERIMENT: +//struct MixMessagesHandle { +// sender: MixMessageSender, +// receiver: MixMessageReceiver, +// +// shutdown: Arc, +//} +// +//impl MixMessagesHandle { +// fn new(shutdown: Notify) -> Self { +// let (sender, receiver) = mpsc::unbounded(); +// MixMessagesHandle { +// sender, +// receiver, +// shutdown: Arc::new(Notify::new()), +// } +// } +// +// fn shutdown(&self) { +// self.shutdown.notify() +// } +// +// fn get_sender(&self) -> MixMessageSender { +// self.sender.clone() +// } +// +// fn start_accepting(&self) { +// let shutdown_signal = Arc::clone(&self.shutdown); +// +// // note to the graceful pull request reviewer: this is by no means how we'd be handling +// // proper shutdown signals, this is more of an experiment that happened to do exactly +// // what I needed in here (basically to not leak memory) +// tokio::spawn(async move { +// tokio::select! { +// // TODO: solve borrow issue and figure out how to push result to socket +// // msg = self.receiver.next() => { +// // +// // } +// +// _ = shutdown_signal.notified() => { +// info!("received shutdown notification") +// } +// } +// }); +// } +//} +// +//// TODO: note for my future self to consider the following idea: +//// split the socket connection into sink and stream +//// stream will be for reading explicit requests +//// and sink for pumping responses AND mix traffic +//// but as byproduct this might (or might not) break the clean "SocketStream" enum here enum SocketStream { RawTCP(S), @@ -75,7 +83,6 @@ enum SocketStream { pub(crate) struct Handle { address: Option, - authenticated: bool, clients_handler_sender: ClientsHandlerRequestSender, socket_connection: SocketStream, } @@ -89,7 +96,6 @@ where pub(crate) fn new(conn: S, clients_handler_sender: ClientsHandlerRequestSender) -> Self { Handle { address: None, - authenticated: false, clients_handler_sender, socket_connection: SocketStream::RawTCP(conn), } @@ -127,6 +133,23 @@ where } } + async fn send_websocket_sphinx_packets( + &mut self, + packets: Vec>, + ) -> Result<(), WsError> { + let messages: Vec> = packets + .into_iter() + .map(|packet| Ok(Message::Binary(packet))) + .collect(); + let mut send_stream = futures::stream::iter(messages); + match self.socket_connection { + SocketStream::UpgradedWebSocket(ref mut ws_stream) => { + ws_stream.send_all(&mut send_stream).await + } + _ => panic!("impossible state - websocket handshake was somehow reverted"), + } + } + fn disconnect(&self) { // if we never established what is the address of the client, its connection was never // announced hence we do not need to send 'disconnect' message @@ -146,7 +169,12 @@ where unimplemented!() } - async fn handle_authenticate(&mut self, address: String, token: String) -> ServerResponse { + async fn handle_authenticate( + &mut self, + address: String, + token: String, + mix_sender: MixMessageSender, + ) -> ServerResponse { // TODO: https://github.com/nymtech/sphinx/issues/57 to resolve possible panics // because we do **NOT** trust whatever garbage client just sent. let address = DestinationAddressBytes::from_base58_string(address); @@ -158,48 +186,72 @@ where } }; - // TODO: how to deal with the mix sender channel? - - // let (res_sender, res_receiver) = oneshot::channel(); - // let clients_handler_request = - // ClientsHandlerRequest::Authenticate(address, token, res_sender); - // self.clients_handler_sender - // .unbounded_send(clients_handler_request) - // .unwrap(); // the receiver MUST BE alive - // - // let client_sender = match res_receiver.await.unwrap() { - // ClientsHandlerResponse::IsOnline(client_sender) => client_sender, - // _ => panic!("received response to wrong query!"), // again, this should NEVER happen - // }; - - unimplemented!() + let (res_sender, res_receiver) = oneshot::channel(); + let clients_handler_request = + ClientsHandlerRequest::Authenticate(address.clone(), token, mix_sender, res_sender); + self.clients_handler_sender + .unbounded_send(clients_handler_request) + .unwrap(); // the receiver MUST BE alive + + match res_receiver.await.unwrap() { + ClientsHandlerResponse::Authenticate(authenticated) => { + if authenticated { + self.address = Some(address); + } + ServerResponse::Authenticate { + status: authenticated, + } + } + ClientsHandlerResponse::Error(e) => { + error!("Authentication unexpectedly failed - {}", e); + ServerResponse::Error { + message: "unexpected failure".into(), + } + } + _ => panic!("received response to wrong query!"), // this should NEVER happen + } } - async fn handle_register(&mut self, address: String) -> ServerResponse { + async fn handle_register( + &mut self, + address: String, + mix_sender: MixMessageSender, + ) -> ServerResponse { // TODO: https://github.com/nymtech/sphinx/issues/57 to resolve possible panics // because we do **NOT** trust whatever garbage client just sent. let address = DestinationAddressBytes::from_base58_string(address); - // TODO: how to deal with the mix sender channel? - - unimplemented!() + let (res_sender, res_receiver) = oneshot::channel(); + let clients_handler_request = + ClientsHandlerRequest::Register(address.clone(), mix_sender, res_sender); + self.clients_handler_sender + .unbounded_send(clients_handler_request) + .unwrap(); // the receiver MUST BE alive + + match res_receiver.await.unwrap() { + // currently register can't fail (as in if all machines are working correctly and you + // send valid address, you will receive a valid token) + ClientsHandlerResponse::Register(token) => { + self.address = Some(address); + ServerResponse::Register { + token: token.to_base58_string(), + } + } + ClientsHandlerResponse::Error(e) => { + error!("Authentication unexpectedly failed - {}", e); + ServerResponse::Error { + message: "unexpected failure".into(), + } + } + _ => panic!("received response to wrong query!"), // this should NEVER happen + } } async fn handle_text(&mut self, text_msg: String) -> Message { trace!("Handling text message (presumably control message)"); - match ClientRequest::try_from(text_msg) { - Err(e) => ServerResponse::Error { - message: format!("received invalid request. err: {:?}", e), - }, - Ok(req) => match req { - ClientRequest::Authenticate { address, token } => { - self.handle_authenticate(address, token).await - } - ClientRequest::Register { address } => self.handle_register(address).await, - }, - } - .into() + error!("Currently there are no text messages besides 'Authenticate' and 'Register' and they were already dealt with!"); + ServerResponse::new_error("invalid request").into() } async fn handle_request(&mut self, raw_request: Message) -> Option { @@ -213,11 +265,8 @@ where } } - // TODO: FUTURE SELF, START HERE TOMORROW: - // change into "wait for authentication" and then listen for either client request - // or mix message channel message! - async fn listen_for_requests(&mut self) { - trace!("Started listening for incoming requests..."); + async fn wait_for_initial_authentication(&mut self) -> Option { + trace!("Started waiting for authenticate/register request..."); while let Some(msg) = self.next_websocket_request().await { let msg = match msg { @@ -232,13 +281,90 @@ where break; } - if let Some(response) = self.handle_request(msg).await { - if let Err(err) = self.send_websocket_response(response).await { - warn!( - "Failed to send message over websocket: {}. Assuming the connection is dead.", - err - ); - break; + let (mix_sender, mix_receiver) = mpsc::unbounded(); + + // ONLY handle 'Authenticate' or 'Register' requests, ignore everything else + let response = match msg { + Message::Close(_) => break, + Message::Text(text_msg) => { + if let Ok(request) = ClientRequest::try_from(text_msg) { + match request { + ClientRequest::Authenticate { address, token } => { + self.handle_authenticate(address, token, mix_sender).await + } + ClientRequest::Register { address } => { + self.handle_register(address, mix_sender).await + } + } + } else { + ServerResponse::new_error("malformed request") + } + } + Message::Binary(_) => { + // perhaps logging level should be reduced here, let's leave it for now and see what happens + warn!("possibly received a sphinx packet without prior authentication. Request is going to be ignored"); + ServerResponse::new_error("binary request without prior authentication") + } + + _ => continue, + }; + + let is_done = response.implies_successful_authentication(); + + if let Err(err) = self.send_websocket_response(response.into()).await { + warn!( + "Failed to send message over websocket: {}. Assuming the connection is dead.", + err + ); + break; + } + + // it means we successfully managed to perform authentication and announce our + // presence to ClientsHandler + if is_done { + return Some(mix_receiver); + } + } + None + } + + async fn listen_for_requests(&mut self, mut mix_receiver: MixMessageReceiver) { + trace!("Started listening for ALL incoming requests..."); + + loop { + tokio::select! { + socket_msg = self.next_websocket_request() => { + if socket_msg.is_none() { + break; + } + let socket_msg = match socket_msg.unwrap() { + Ok(socket_msg) => socket_msg, + Err(err) => { + error!("failed to obtain message from websocket stream! stopping connection handler: {}", err); + break; + } + }; + + if socket_msg.is_close() { + break; + } + + if let Some(response) = self.handle_request(socket_msg).await { + if let Err(err) = self.send_websocket_response(response).await { + warn!( + "Failed to send message over websocket: {}. Assuming the connection is dead.", + err + ); + break; + } + } + }, + mix_messages = mix_receiver.next() => { + let mix_messages = mix_messages.expect("sender was unexpectedly closed! this shouldn't have ever happened!"); + if let Err(e) = self.send_websocket_sphinx_packets(mix_messages).await { + warn!("failed to send sphinx packets back to the client, assuming the connection is dead"); + break; + } } } } @@ -250,6 +376,12 @@ where pub(crate) async fn start_handling(&mut self) { self.perform_websocket_handshake().await; trace!("Managed to perform websocket handshake!"); - self.listen_for_requests().await; + let mix_receiver = self.wait_for_initial_authentication().await; + trace!("Performed initial authentication"); + match mix_receiver { + Some(receiver) => self.listen_for_requests(receiver).await, + None => trace!("But connection was closed during the process"), + } + trace!("The handler is done!"); } } From 2a17b52e6f8047d44c3710d30996cb46e31a1507 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Fri, 24 Apr 2020 12:51:45 +0100 Subject: [PATCH 36/70] PacketForwarder returning JoinHandle alongside the channel --- gateway/src/mixnet_handling/sender/mod.rs | 31 ++++++++++++++--------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/gateway/src/mixnet_handling/sender/mod.rs b/gateway/src/mixnet_handling/sender/mod.rs index e6dc32e0f02..d85f68042c2 100644 --- a/gateway/src/mixnet_handling/sender/mod.rs +++ b/gateway/src/mixnet_handling/sender/mod.rs @@ -19,11 +19,15 @@ use futures::StreamExt; use log::*; use std::net::SocketAddr; use std::time::Duration; +use tokio::task::JoinHandle; + +pub(crate) type OutboundMixMessageSender = mpsc::UnboundedSender<(SocketAddr, Vec)>; +pub(crate) type OutboundMixMessageReceiver = mpsc::UnboundedReceiver<(SocketAddr, Vec)>; pub(crate) struct PacketForwarder { tcp_client: multi_tcp_client::Client, - conn_tx: mpsc::UnboundedSender<(SocketAddr, Vec)>, - conn_rx: mpsc::UnboundedReceiver<(SocketAddr, Vec)>, + conn_tx: OutboundMixMessageSender, + conn_rx: OutboundMixMessageReceiver, } impl PacketForwarder { @@ -47,17 +51,20 @@ impl PacketForwarder { } } - pub(crate) fn start(mut self) -> mpsc::UnboundedSender<(SocketAddr, Vec)> { + pub(crate) fn start(mut self) -> (JoinHandle<()>, OutboundMixMessageSender) { // TODO: what to do with the lost JoinHandle - do we even care? let sender_channel = self.conn_tx.clone(); - tokio::spawn(async move { - while let Some((address, packet)) = self.conn_rx.next().await { - trace!("Going to forward packet to {:?}", address); - // as a mix node we don't care about responses, we just want to fire packets - // as quickly as possible - self.tcp_client.send(address, packet, false).await.unwrap(); // if we're not waiting for response, we MUST get an Ok - } - }); - sender_channel + ( + tokio::spawn(async move { + while let Some((address, packet)) = self.conn_rx.next().await { + trace!("Going to forward packet to {:?}", address); + // as a mix node we don't care about responses, we just want to fire packets + // as quickly as possible + self.tcp_client.send(address, packet, false).await.unwrap(); + // if we're not waiting for response, we MUST get an Ok + } + }), + sender_channel, + ) } } From d6885250b57a37d3b6ce4f2d06e38fe50149e427 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Fri, 24 Apr 2020 12:56:06 +0100 Subject: [PATCH 37/70] ClientsHandler following the same pattern --- gateway/src/client_handling/clients_handler.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/gateway/src/client_handling/clients_handler.rs b/gateway/src/client_handling/clients_handler.rs index e1ea459a63f..398f9d48b07 100644 --- a/gateway/src/client_handling/clients_handler.rs +++ b/gateway/src/client_handling/clients_handler.rs @@ -53,7 +53,6 @@ pub(crate) struct ClientsHandler { open_connections: HashMap, clients_ledger: ClientLedger, clients_inbox_storage: ClientStorage, - request_receiver_channel: ClientsHandlerRequestReceiver, } impl ClientsHandler { @@ -64,7 +63,6 @@ impl ClientsHandler { ClientsHandler { secret_key, open_connections: HashMap::new(), - request_receiver_channel, clients_ledger: unimplemented!(), clients_inbox_storage: unimplemented!(), } @@ -266,8 +264,8 @@ impl ClientsHandler { .unwrap(); } - pub(crate) async fn run(&mut self) { - while let Some(request) = self.request_receiver_channel.next().await { + pub(crate) async fn run(&mut self, mut request_receiver_channel: ClientsHandlerRequestReceiver) { + while let Some(request) = request_receiver_channel.next().await { match request { ClientsHandlerRequest::Register(address, comm_channel, res_channel) => { self.handle_register_request(address, comm_channel, res_channel) @@ -286,7 +284,8 @@ impl ClientsHandler { error!("Something bad has happened and we stopped listening for requests!"); } - pub(crate) fn start(mut self) -> JoinHandle<()> { - tokio::spawn(async move { self.run().await }) + pub(crate) fn start(mut self) -> (JoinHandle<()>, ClientsHandlerRequestSender) { + let (sender, receiver) = mpsc::unbounded(), + (tokio::spawn(async move { self.run(receiver).await }), sender) } } From be87f8636fba3aa0a076f33fe7114346255c0ea2 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Fri, 24 Apr 2020 13:00:46 +0100 Subject: [PATCH 38/70] Made websocket listener start method consistent with mix listener --- .../src/client_handling/websocket/listener.rs | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/gateway/src/client_handling/websocket/listener.rs b/gateway/src/client_handling/websocket/listener.rs index e7cef58a97a..42a6d44cc05 100644 --- a/gateway/src/client_handling/websocket/listener.rs +++ b/gateway/src/client_handling/websocket/listener.rs @@ -20,21 +20,14 @@ use tokio::task::JoinHandle; pub(crate) struct Listener { address: SocketAddr, - clients_handler_sender: ClientsHandlerRequestSender, } impl Listener { - pub(crate) fn new( - address: SocketAddr, - clients_handler_sender: ClientsHandlerRequestSender, - ) -> Self { - Listener { - address, - clients_handler_sender, - } + pub(crate) fn new(address: SocketAddr) -> Self { + Listener { address } } - pub(crate) async fn run(&mut self) { + pub(crate) async fn run(&mut self, clients_handler_sender: ClientsHandlerRequestSender) { info!("Starting websocket listener at {}", self.address); let mut tcp_listener = tokio::net::TcpListener::bind(self.address) .await @@ -44,7 +37,7 @@ impl Listener { match tcp_listener.accept().await { Ok((socket, remote_addr)) => { trace!("received a socket connection from {}", remote_addr); - let mut handle = Handle::new(socket, self.clients_handler_sender.clone()); + let mut handle = Handle::new(socket, clients_handler_sender.clone()); tokio::spawn(async move { handle.start_handling().await }); } Err(e) => warn!("failed to get client: {:?}", e), @@ -52,7 +45,10 @@ impl Listener { } } - pub(crate) fn start(mut self) -> JoinHandle<()> { - tokio::spawn(async move { self.run().await }) + pub(crate) fn start( + mut self, + clients_handler_sender: ClientsHandlerRequestSender, + ) -> JoinHandle<()> { + tokio::spawn(async move { self.run(clients_handler_sender).await }) } } From e946a01527b41bf1103cf5003608c6cba86a3762 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Fri, 24 Apr 2020 13:03:19 +0100 Subject: [PATCH 39/70] Syntax error + formatting --- gateway/src/client_handling/clients_handler.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/gateway/src/client_handling/clients_handler.rs b/gateway/src/client_handling/clients_handler.rs index 398f9d48b07..e49c3746e39 100644 --- a/gateway/src/client_handling/clients_handler.rs +++ b/gateway/src/client_handling/clients_handler.rs @@ -264,7 +264,10 @@ impl ClientsHandler { .unwrap(); } - pub(crate) async fn run(&mut self, mut request_receiver_channel: ClientsHandlerRequestReceiver) { + pub(crate) async fn run( + &mut self, + mut request_receiver_channel: ClientsHandlerRequestReceiver, + ) { while let Some(request) = request_receiver_channel.next().await { match request { ClientsHandlerRequest::Register(address, comm_channel, res_channel) => { @@ -285,7 +288,10 @@ impl ClientsHandler { } pub(crate) fn start(mut self) -> (JoinHandle<()>, ClientsHandlerRequestSender) { - let (sender, receiver) = mpsc::unbounded(), - (tokio::spawn(async move { self.run(receiver).await }), sender) + let (sender, receiver) = mpsc::unbounded(); + ( + tokio::spawn(async move { self.run(receiver).await }), + sender, + ) } } From 26c1e152ca72b904eab6ff455eb01478023a11e1 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Fri, 24 Apr 2020 13:08:38 +0100 Subject: [PATCH 40/70] Websocket handler having access to mix forwarder --- .../websocket/connection_handler.rs | 65 ++++--------------- .../src/client_handling/websocket/listener.rs | 16 ++++- 2 files changed, 24 insertions(+), 57 deletions(-) diff --git a/gateway/src/client_handling/websocket/connection_handler.rs b/gateway/src/client_handling/websocket/connection_handler.rs index 81f5569b394..b680c40a52b 100644 --- a/gateway/src/client_handling/websocket/connection_handler.rs +++ b/gateway/src/client_handling/websocket/connection_handler.rs @@ -1,12 +1,10 @@ -use std::convert::TryFrom; -use std::sync::Arc; - use futures::{ channel::{mpsc, oneshot}, - SinkExt, TryStream, TryStreamExt, + SinkExt, }; use log::*; -use tokio::{prelude::*, stream::StreamExt, sync::Notify}; +use std::convert::TryFrom; +use tokio::{prelude::*, stream::StreamExt}; use tokio_tungstenite::{ tungstenite::{protocol::Message, Error as WsError}, WebSocketStream, @@ -20,55 +18,8 @@ use crate::client_handling::clients_handler::{ ClientsHandlerRequest, ClientsHandlerRequestSender, ClientsHandlerResponse, }; use crate::client_handling::websocket::message_receiver::{MixMessageReceiver, MixMessageSender}; +use crate::mixnet_handling::sender::OutboundMixMessageSender; -// -//// EXPERIMENT: -//struct MixMessagesHandle { -// sender: MixMessageSender, -// receiver: MixMessageReceiver, -// -// shutdown: Arc, -//} -// -//impl MixMessagesHandle { -// fn new(shutdown: Notify) -> Self { -// let (sender, receiver) = mpsc::unbounded(); -// MixMessagesHandle { -// sender, -// receiver, -// shutdown: Arc::new(Notify::new()), -// } -// } -// -// fn shutdown(&self) { -// self.shutdown.notify() -// } -// -// fn get_sender(&self) -> MixMessageSender { -// self.sender.clone() -// } -// -// fn start_accepting(&self) { -// let shutdown_signal = Arc::clone(&self.shutdown); -// -// // note to the graceful pull request reviewer: this is by no means how we'd be handling -// // proper shutdown signals, this is more of an experiment that happened to do exactly -// // what I needed in here (basically to not leak memory) -// tokio::spawn(async move { -// tokio::select! { -// // TODO: solve borrow issue and figure out how to push result to socket -// // msg = self.receiver.next() => { -// // -// // } -// -// _ = shutdown_signal.notified() => { -// info!("received shutdown notification") -// } -// } -// }); -// } -//} -// //// TODO: note for my future self to consider the following idea: //// split the socket connection into sink and stream //// stream will be for reading explicit requests @@ -84,6 +35,7 @@ enum SocketStream { pub(crate) struct Handle { address: Option, clients_handler_sender: ClientsHandlerRequestSender, + outbound_mix_sender: OutboundMixMessageSender, socket_connection: SocketStream, } @@ -93,10 +45,15 @@ where { // for time being we assume handle is always constructed from raw socket. // if we decide we want to change it, that's not too difficult - pub(crate) fn new(conn: S, clients_handler_sender: ClientsHandlerRequestSender) -> Self { + pub(crate) fn new( + conn: S, + clients_handler_sender: ClientsHandlerRequestSender, + outbound_mix_sender: OutboundMixMessageSender, + ) -> Self { Handle { address: None, clients_handler_sender, + outbound_mix_sender, socket_connection: SocketStream::RawTCP(conn), } } diff --git a/gateway/src/client_handling/websocket/listener.rs b/gateway/src/client_handling/websocket/listener.rs index 42a6d44cc05..4d57469fc39 100644 --- a/gateway/src/client_handling/websocket/listener.rs +++ b/gateway/src/client_handling/websocket/listener.rs @@ -14,6 +14,7 @@ use crate::client_handling::clients_handler::ClientsHandlerRequestSender; use crate::client_handling::websocket::connection_handler::Handle; +use crate::mixnet_handling::sender::OutboundMixMessageSender; use log::*; use std::net::SocketAddr; use tokio::task::JoinHandle; @@ -27,7 +28,11 @@ impl Listener { Listener { address } } - pub(crate) async fn run(&mut self, clients_handler_sender: ClientsHandlerRequestSender) { + pub(crate) async fn run( + &mut self, + clients_handler_sender: ClientsHandlerRequestSender, + outbound_mix_sender: OutboundMixMessageSender, + ) { info!("Starting websocket listener at {}", self.address); let mut tcp_listener = tokio::net::TcpListener::bind(self.address) .await @@ -37,7 +42,11 @@ impl Listener { match tcp_listener.accept().await { Ok((socket, remote_addr)) => { trace!("received a socket connection from {}", remote_addr); - let mut handle = Handle::new(socket, clients_handler_sender.clone()); + let mut handle = Handle::new( + socket, + clients_handler_sender.clone(), + outbound_mix_sender.clone(), + ); tokio::spawn(async move { handle.start_handling().await }); } Err(e) => warn!("failed to get client: {:?}", e), @@ -48,7 +57,8 @@ impl Listener { pub(crate) fn start( mut self, clients_handler_sender: ClientsHandlerRequestSender, + outbound_mix_sender: OutboundMixMessageSender, ) -> JoinHandle<()> { - tokio::spawn(async move { self.run(clients_handler_sender).await }) + tokio::spawn(async move { self.run(clients_handler_sender, outbound_mix_sender).await }) } } From 59b529a928257c58d960a712b1a36da20b168f8b Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Fri, 24 Apr 2020 14:37:35 +0100 Subject: [PATCH 41/70] Minimal binary request parsing --- gateway/gateway-requests/src/types.rs | 64 ++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/gateway/gateway-requests/src/types.rs b/gateway/gateway-requests/src/types.rs index caf90423ec4..f1c60bebebf 100644 --- a/gateway/gateway-requests/src/types.rs +++ b/gateway/gateway-requests/src/types.rs @@ -12,13 +12,28 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::types::BinaryRequest::ForwardSphinx; +use nymsphinx::addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError}; use serde::{Deserialize, Serialize}; use std::convert::TryFrom; +use std::net::SocketAddr; use tokio_tungstenite::tungstenite::protocol::Message; +#[derive(Debug)] +pub enum GatewayRequestsError { + IncorrectlyEncodedAddress, + RequestOfInvalidSize(usize, usize), +} + +impl From for GatewayRequestsError { + fn from(_: NymNodeRoutingAddressError) -> Self { + GatewayRequestsError::IncorrectlyEncodedAddress + } +} + #[derive(Serialize, Deserialize, Debug)] #[serde(tag = "type", rename_all = "camelCase")] -pub enum ClientRequest { +pub enum ClientControlRequest { Authenticate { address: String, token: String }, Register { address: String }, } @@ -32,6 +47,51 @@ pub enum ServerResponse { Error { message: String }, } +pub enum BinaryRequest { + ForwardSphinx { address: SocketAddr, data: Vec }, +} + +impl BinaryRequest { + pub fn try_from_bytes(raw_req: &[u8]) -> Result { + // right now there's only a single option possible which significantly simplifies the logic + // if we decided to allow for more 'binary' messages, the API wouldn't need to change + let address = NymNodeRoutingAddress::try_from_bytes(&raw_req)?; + let offset = address.bytes_min_len(); + + if raw_req[offset..].len() != nymsphinx::PACKET_SIZE { + Err(GatewayRequestsError::RequestOfInvalidSize( + raw_req[offset..].len(), + nymsphinx::PACKET_SIZE, + )) + } else { + Ok(ForwardSphinx { + address: address.into(), + data: raw_req[offset..].into(), + }) + } + } + + pub fn into_bytes(self) -> Vec { + match self { + BinaryRequest::ForwardSphinx { address, data } => { + // TODO: using intermediate `NymNodeRoutingAddress` here is just temporary, because + // it happens to do exactly what we needed, but we don't really want to be + // dependant on what it does + let wrapped_address = NymNodeRoutingAddress::from(address); + wrapped_address + .as_bytes() + .into_iter() + .chain(data.into_iter()) + .collect() + } + } + } + + pub fn new_forward_request(address: SocketAddr, data: Vec) -> BinaryRequest { + BinaryRequest::ForwardSphinx { address, data } + } +} + impl ServerResponse { pub fn new_error>(msg: S) -> Self { ServerResponse::Error { @@ -55,7 +115,7 @@ impl ServerResponse { } } -impl TryFrom for ClientRequest { +impl TryFrom for ClientControlRequest { type Error = serde_json::Error; fn try_from(msg: String) -> Result { From 9b7acabbe1e72a1500b987bc0a34f4aead1c25cb Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Fri, 24 Apr 2020 15:20:08 +0100 Subject: [PATCH 42/70] Implicitly derived std::error::Error on GatewayRequestsError --- gateway/gateway-requests/src/types.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/gateway/gateway-requests/src/types.rs b/gateway/gateway-requests/src/types.rs index f1c60bebebf..cf67d84cb03 100644 --- a/gateway/gateway-requests/src/types.rs +++ b/gateway/gateway-requests/src/types.rs @@ -14,8 +14,11 @@ use crate::types::BinaryRequest::ForwardSphinx; use nymsphinx::addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError}; +use serde::export::fmt::Error; +use serde::export::Formatter; use serde::{Deserialize, Serialize}; use std::convert::TryFrom; +use std::fmt; use std::net::SocketAddr; use tokio_tungstenite::tungstenite::protocol::Message; @@ -25,6 +28,22 @@ pub enum GatewayRequestsError { RequestOfInvalidSize(usize, usize), } +// to use it as `std::error::Error`, and we don't want to just derive is because we want +// the message to convey meanings of the usize tuple in RequestOfInvalidSize. +impl fmt::Display for GatewayRequestsError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + use GatewayRequestsError::*; + match self { + IncorrectlyEncodedAddress => write!(f, "address field was incorrectly encoded"), + RequestOfInvalidSize(actual, expected) => write!( + f, + "received request had invalid size. (actual: {}, expected: {})", + actual, expected + ), + } + } +} + impl From for GatewayRequestsError { fn from(_: NymNodeRoutingAddressError) -> Self { GatewayRequestsError::IncorrectlyEncodedAddress From e55019fb161d3af1cfaf33c5c14c3bd85239c19c Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Fri, 24 Apr 2020 15:21:16 +0100 Subject: [PATCH 43/70] Handling of all websocket requests --- .../websocket/connection_handler.rs | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/gateway/src/client_handling/websocket/connection_handler.rs b/gateway/src/client_handling/websocket/connection_handler.rs index b680c40a52b..f55a21fc48e 100644 --- a/gateway/src/client_handling/websocket/connection_handler.rs +++ b/gateway/src/client_handling/websocket/connection_handler.rs @@ -11,7 +11,7 @@ use tokio_tungstenite::{ }; use gateway_requests::auth_token::AuthToken; -use gateway_requests::types::{ClientRequest, ServerResponse}; +use gateway_requests::types::{BinaryRequest, ClientControlRequest, ServerResponse}; use nymsphinx::DestinationAddressBytes; use crate::client_handling::clients_handler::{ @@ -120,10 +120,20 @@ where async fn handle_binary(&self, bin_msg: Vec) -> Message { trace!("Handling binary message (presumably sphinx packet)"); - // if it's binary, it MUST BE a sphinx packet. We can't look into it, but let's at least - // validate its size. - if bin_msg.len() != nymsphinx::PACKET_SIZE {} - unimplemented!() + match BinaryRequest::try_from_bytes(&bin_msg) { + Err(e) => ServerResponse::new_error(e.to_string()), + Ok(request) => match request { + // currently only a single type exists + BinaryRequest::ForwardSphinx { address, data } => { + // we know data has correct size (but nothing else besides of it) + self.outbound_mix_sender + .unbounded_send((address, data)) + .unwrap(); + ServerResponse::Send { status: true } + } + }, + } + .into() } async fn handle_authenticate( @@ -204,7 +214,8 @@ where } } - async fn handle_text(&mut self, text_msg: String) -> Message { + // currently there are no valid control messages you can send after authentication + async fn handle_text(&mut self, _: String) -> Message { trace!("Handling text message (presumably control message)"); error!("Currently there are no text messages besides 'Authenticate' and 'Register' and they were already dealt with!"); @@ -244,12 +255,12 @@ where let response = match msg { Message::Close(_) => break, Message::Text(text_msg) => { - if let Ok(request) = ClientRequest::try_from(text_msg) { + if let Ok(request) = ClientControlRequest::try_from(text_msg) { match request { - ClientRequest::Authenticate { address, token } => { + ClientControlRequest::Authenticate { address, token } => { self.handle_authenticate(address, token, mix_sender).await } - ClientRequest::Register { address } => { + ClientControlRequest::Register { address } => { self.handle_register(address, mix_sender).await } } @@ -319,7 +330,7 @@ where mix_messages = mix_receiver.next() => { let mix_messages = mix_messages.expect("sender was unexpectedly closed! this shouldn't have ever happened!"); if let Err(e) = self.send_websocket_sphinx_packets(mix_messages).await { - warn!("failed to send sphinx packets back to the client, assuming the connection is dead"); + warn!("failed to send sphinx packets back to the client - {:?}, assuming the connection is dead", e); break; } } From 24616a7db93bda4ec8f4f0b5b792e28a2cf10e04 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Fri, 24 Apr 2020 15:21:49 +0100 Subject: [PATCH 44/70] Types import cleanup --- gateway/gateway-requests/src/types.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gateway/gateway-requests/src/types.rs b/gateway/gateway-requests/src/types.rs index cf67d84cb03..75bc711f4bb 100644 --- a/gateway/gateway-requests/src/types.rs +++ b/gateway/gateway-requests/src/types.rs @@ -14,12 +14,12 @@ use crate::types::BinaryRequest::ForwardSphinx; use nymsphinx::addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError}; -use serde::export::fmt::Error; -use serde::export::Formatter; use serde::{Deserialize, Serialize}; -use std::convert::TryFrom; -use std::fmt; -use std::net::SocketAddr; +use std::{ + convert::TryFrom, + fmt::{self, Error, Formatter}, + net::SocketAddr, +}; use tokio_tungstenite::tungstenite::protocol::Message; #[derive(Debug)] From 65c56c7b71f833be931f29404bf1ee116697c747 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Fri, 24 Apr 2020 15:30:24 +0100 Subject: [PATCH 45/70] Updated placeholder fields --- .../src/client_handling/clients_handler.rs | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/gateway/src/client_handling/clients_handler.rs b/gateway/src/client_handling/clients_handler.rs index e49c3746e39..3f84e883f7b 100644 --- a/gateway/src/client_handling/clients_handler.rs +++ b/gateway/src/client_handling/clients_handler.rs @@ -1,18 +1,22 @@ -use crate::client_handling::ledger::ClientLedger; -use crate::client_handling::websocket::message_receiver::MixMessageSender; -use crate::storage::ClientStorage; -use crypto::encryption; +use std::collections::HashMap; +use std::sync::Arc; + use futures::channel::{mpsc, oneshot}; use futures::StreamExt; -use gateway_requests::auth_token::AuthToken; use hmac::{Hmac, Mac}; use log::*; -use nymsphinx::DestinationAddressBytes; use sha2::Sha256; -use std::collections::HashMap; -use std::sync::Arc; use tokio::task::JoinHandle; +use crypto::encryption; +use gateway_requests::auth_token::AuthToken; +use nymsphinx::DestinationAddressBytes; + +use crate::client_handling::ledger::ClientLedger; +use crate::client_handling::websocket::message_receiver::MixMessageSender; +use crate::storage::ClientStorage; +use std::path::PathBuf; + pub(crate) type ClientsHandlerRequestSender = mpsc::UnboundedSender; pub(crate) type ClientsHandlerRequestReceiver = mpsc::UnboundedReceiver; @@ -57,14 +61,15 @@ pub(crate) struct ClientsHandler { impl ClientsHandler { pub(crate) fn new( - request_receiver_channel: ClientsHandlerRequestReceiver, secret_key: Arc, + ledger_path: PathBuf, + clients_inbox_storage: ClientStorage, ) -> Self { ClientsHandler { secret_key, open_connections: HashMap::new(), - clients_ledger: unimplemented!(), - clients_inbox_storage: unimplemented!(), + clients_ledger: ClientLedger::load(ledger_path).unwrap(), + clients_inbox_storage, } } From 72b8b7acd302e166f32de53633caa4f82690d004 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Fri, 24 Apr 2020 15:30:31 +0100 Subject: [PATCH 46/70] Everything put into main --- gateway/src/main.rs | 54 ++++++++++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 15 deletions(-) diff --git a/gateway/src/main.rs b/gateway/src/main.rs index 68310f3f1a3..df822e19da9 100644 --- a/gateway/src/main.rs +++ b/gateway/src/main.rs @@ -15,9 +15,12 @@ use crate::client_handling::clients_handler::ClientsHandler; use crate::client_handling::websocket; use crate::mixnet_handling::receiver::packet_processing::PacketProcessor; +use crate::mixnet_handling::sender::PacketForwarder; use crate::storage::ClientStorage; use log::*; +use std::path::PathBuf; use std::sync::Arc; +use std::time::Duration; mod client_handling; mod mixnet_client; @@ -28,22 +31,43 @@ pub(crate) mod storage; async fn main() { dotenv::dotenv().ok(); setup_logging(); - let addr = "127.0.0.1:1793".parse().unwrap(); - info!("Listening on: {}", addr); + // TODO: assume config is parsed here, keys are loaded, etc + // ALL OF BELOW WILL BE DONE VIA CONFIG + let keypair = crypto::encryption::KeyPair::new(); + let clients_addr = "127.0.0.1:9000".parse().unwrap(); + let mix_addr = "127.0.0.1:1789".parse().unwrap(); + let inbox_store_dir: PathBuf = "foomp".into(); + let ledger_path: PathBuf = "foomp2".into(); + let message_retrieval_limit = 1000; + let filename_len = 16; + let initial_reconnection_backoff = Duration::from_millis(10_000); + let maximum_reconnection_backoff = Duration::from_millis(300_000); + let initial_connection_timeout = Duration::from_millis(1500); + // ALL OF ABOVE WILL HAVE BEEN DONE VIA CONFIG - let (dummy_clients_handler_tx, dummy_clients_handler_rx) = futures::channel::mpsc::unbounded(); - let client_storage = ClientStorage::new(42, 42, "foomp".into()); - let dummy_keypair = crypto::encryption::KeyPair::new(); - let arced_sk = Arc::new(dummy_keypair.private_key().to_owned()); - let dummy_mix_packet_processor = PacketProcessor::new( - Arc::clone(&arced_sk), - dummy_clients_handler_tx.clone(), - client_storage, - ); + let arced_sk = Arc::new(keypair.private_key().to_owned()); + + // TODO: this should really be a proper DB, right now it will be most likely a bottleneck, + // due to possible frequent independent writes + let client_storage = ClientStorage::new(message_retrieval_limit, filename_len, inbox_store_dir); + + let (_, forwarding_channel) = PacketForwarder::new( + initial_reconnection_backoff, + maximum_reconnection_backoff, + initial_connection_timeout, + ) + .start(); + + let (_, clients_handler_sender) = + ClientsHandler::new(Arc::clone(&arced_sk), ledger_path, client_storage.clone()).start(); + + let packet_processor = + PacketProcessor::new(arced_sk, clients_handler_sender.clone(), client_storage); + + websocket::Listener::new(clients_addr).start(clients_handler_sender, forwarding_channel); + mixnet_handling::Listener::new(mix_addr).start(packet_processor); - ClientsHandler::new(dummy_clients_handler_rx, arced_sk).start(); - websocket::Listener::new(addr, dummy_clients_handler_tx.clone()).start(); - mixnet_handling::Listener::new(addr).start(dummy_mix_packet_processor); + info!("All up and running!"); if let Err(e) = tokio::signal::ctrl_c().await { error!( @@ -53,7 +77,7 @@ async fn main() { } println!( - "Received SIGINT - the provider will terminate now (threads are not YET nicely stopped)" + "Received SIGINT - the gateway will terminate now (threads are not YET nicely stopped)" ); } From 27db08b08135eb7151c656e35e0abc8ce501f7aa Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Fri, 24 Apr 2020 15:35:16 +0100 Subject: [PATCH 47/70] Missing license notices --- .../src/client_handling/clients_handler.rs | 14 +++++++++++++ gateway/src/client_handling/mod.rs | 14 +++++++++++++ .../websocket/connection_handler.rs | 14 +++++++++++++ .../websocket/message_receiver.rs | 14 +++++++++++++ gateway/src/client_handling/websocket/mod.rs | 14 +++++++++++++ gateway/src/mixnet_handling/mod.rs | 14 +++++++++++++ .../receiver/connection_handler.rs | 21 ++++++++++++------- 7 files changed, 98 insertions(+), 7 deletions(-) diff --git a/gateway/src/client_handling/clients_handler.rs b/gateway/src/client_handling/clients_handler.rs index 3f84e883f7b..8d45e0f3e23 100644 --- a/gateway/src/client_handling/clients_handler.rs +++ b/gateway/src/client_handling/clients_handler.rs @@ -1,3 +1,17 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use std::collections::HashMap; use std::sync::Arc; diff --git a/gateway/src/client_handling/mod.rs b/gateway/src/client_handling/mod.rs index 15706c869da..ef1497b2149 100644 --- a/gateway/src/client_handling/mod.rs +++ b/gateway/src/client_handling/mod.rs @@ -1,3 +1,17 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + pub(crate) mod clients_handler; pub(crate) mod ledger; pub(crate) mod websocket; diff --git a/gateway/src/client_handling/websocket/connection_handler.rs b/gateway/src/client_handling/websocket/connection_handler.rs index f55a21fc48e..02608f5af84 100644 --- a/gateway/src/client_handling/websocket/connection_handler.rs +++ b/gateway/src/client_handling/websocket/connection_handler.rs @@ -1,3 +1,17 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use futures::{ channel::{mpsc, oneshot}, SinkExt, diff --git a/gateway/src/client_handling/websocket/message_receiver.rs b/gateway/src/client_handling/websocket/message_receiver.rs index 568b0d367d1..ad693302708 100644 --- a/gateway/src/client_handling/websocket/message_receiver.rs +++ b/gateway/src/client_handling/websocket/message_receiver.rs @@ -1,3 +1,17 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use futures::channel::mpsc; pub(crate) type MixMessageSender = mpsc::UnboundedSender>>; diff --git a/gateway/src/client_handling/websocket/mod.rs b/gateway/src/client_handling/websocket/mod.rs index 40cf4a58716..babcbc7d805 100644 --- a/gateway/src/client_handling/websocket/mod.rs +++ b/gateway/src/client_handling/websocket/mod.rs @@ -1,3 +1,17 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + pub(crate) mod connection_handler; pub(crate) mod listener; pub(crate) mod message_receiver; diff --git a/gateway/src/mixnet_handling/mod.rs b/gateway/src/mixnet_handling/mod.rs index 22092565613..a2b89ab065f 100644 --- a/gateway/src/mixnet_handling/mod.rs +++ b/gateway/src/mixnet_handling/mod.rs @@ -1,3 +1,17 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + pub(crate) mod receiver; pub(crate) mod sender; diff --git a/gateway/src/mixnet_handling/receiver/connection_handler.rs b/gateway/src/mixnet_handling/receiver/connection_handler.rs index 4255f9beffc..80a820b08c9 100644 --- a/gateway/src/mixnet_handling/receiver/connection_handler.rs +++ b/gateway/src/mixnet_handling/receiver/connection_handler.rs @@ -1,12 +1,19 @@ -use crate::client_handling::clients_handler::{ - ClientsHandlerRequest, ClientsHandlerRequestSender, ClientsHandlerResponse, -}; -use crate::client_handling::websocket::message_receiver::MixMessageSender; +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use crate::mixnet_handling::receiver::packet_processing::PacketProcessor; -use futures::channel::{mpsc, oneshot}; use log::*; -use nymsphinx::DestinationAddressBytes; -use std::collections::HashMap; use std::net::SocketAddr; use tokio::{io::AsyncReadExt, prelude::*}; From 0b37a0821e491b93a7016db6c7609212f7bfda21 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Fri, 24 Apr 2020 15:39:14 +0100 Subject: [PATCH 48/70] Cleaned up unused code --- .../src/client_handling/clients_handler.rs | 35 +++++++-------- gateway/src/main.rs | 1 - gateway/src/mixnet_client.rs | 43 ------------------- .../receiver/packet_processing.rs | 10 +---- 4 files changed, 18 insertions(+), 71 deletions(-) delete mode 100644 gateway/src/mixnet_client.rs diff --git a/gateway/src/client_handling/clients_handler.rs b/gateway/src/client_handling/clients_handler.rs index 8d45e0f3e23..5bbfe16348b 100644 --- a/gateway/src/client_handling/clients_handler.rs +++ b/gateway/src/client_handling/clients_handler.rs @@ -12,30 +12,27 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; -use std::sync::Arc; - -use futures::channel::{mpsc, oneshot}; -use futures::StreamExt; -use hmac::{Hmac, Mac}; -use log::*; -use sha2::Sha256; -use tokio::task::JoinHandle; - +use crate::client_handling::{ledger::ClientLedger, websocket::message_receiver::MixMessageSender}; +use crate::storage::ClientStorage; use crypto::encryption; +use futures::{ + channel::{mpsc, oneshot}, + StreamExt, +}; use gateway_requests::auth_token::AuthToken; +use hmac::{Hmac, Mac}; +use log::*; use nymsphinx::DestinationAddressBytes; - -use crate::client_handling::ledger::ClientLedger; -use crate::client_handling::websocket::message_receiver::MixMessageSender; -use crate::storage::ClientStorage; +use sha2::Sha256; +use std::collections::HashMap; use std::path::PathBuf; +use std::sync::Arc; +use tokio::task::JoinHandle; pub(crate) type ClientsHandlerRequestSender = mpsc::UnboundedSender; pub(crate) type ClientsHandlerRequestReceiver = mpsc::UnboundedReceiver; pub(crate) type ClientsHandlerResponseSender = oneshot::Sender; -pub(crate) type ClientsHandlerResponseReceiver = oneshot::Receiver; #[derive(Debug)] pub(crate) enum ClientsHandlerRequest { @@ -95,7 +92,7 @@ impl ClientsHandler { } // best effort sending error responses - fn send_error_response(&self, err: E, mut res_channel: ClientsHandlerResponseSender) + fn send_error_response(&self, err: E, res_channel: ClientsHandlerResponseSender) where E: Into>, { @@ -176,7 +173,7 @@ impl ClientsHandler { &mut self, address: DestinationAddressBytes, comm_channel: MixMessageSender, - mut res_channel: ClientsHandlerResponseSender, + res_channel: ClientsHandlerResponseSender, ) { debug!( "Processing register new client request: {:?}", @@ -229,7 +226,7 @@ impl ClientsHandler { address: DestinationAddressBytes, token: AuthToken, comm_channel: MixMessageSender, - mut res_channel: ClientsHandlerResponseSender, + res_channel: ClientsHandlerResponseSender, ) { debug!( "Processing authenticate client request: {:?}", @@ -266,7 +263,7 @@ impl ClientsHandler { fn handle_is_online_request( &self, address: DestinationAddressBytes, - mut res_channel: ClientsHandlerResponseSender, + res_channel: ClientsHandlerResponseSender, ) { debug!( "Processing is online request for: {:?}", diff --git a/gateway/src/main.rs b/gateway/src/main.rs index df822e19da9..e53afdd65f9 100644 --- a/gateway/src/main.rs +++ b/gateway/src/main.rs @@ -23,7 +23,6 @@ use std::sync::Arc; use std::time::Duration; mod client_handling; -mod mixnet_client; mod mixnet_handling; pub(crate) mod storage; diff --git a/gateway/src/mixnet_client.rs b/gateway/src/mixnet_client.rs deleted file mode 100644 index 3094d6c4bd7..00000000000 --- a/gateway/src/mixnet_client.rs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// use log::*; -use futures::lock::Mutex; -use multi_tcp_client::Client as MultiClient; -use nymsphinx::addressing::nodes::NymNodeRoutingAddress; -use nymsphinx::NODE_ADDRESS_LENGTH; -use std::sync::Arc; -use std::time::Duration; - -pub fn new() -> Arc> { - let config = multi_tcp_client::Config::new( - Duration::from_millis(200), - Duration::from_secs(86400), - Duration::from_secs(2), - ); - let client = multi_tcp_client::Client::new(config); - Arc::new(Mutex::new(client)) -} - -pub async fn forward_to_mixnode(mut payload: Vec, client_ref: Arc>) { - let mut address_buffer = [0; NODE_ADDRESS_LENGTH]; - let packet = payload.split_off(NODE_ADDRESS_LENGTH); - address_buffer.copy_from_slice(payload.as_slice()); - let address = NymNodeRoutingAddress::try_from_bytes(&address_buffer) - .unwrap() - .into(); - - let mut client = client_ref.lock().await; - client.send(address, packet, false).await.unwrap(); -} diff --git a/gateway/src/mixnet_handling/receiver/packet_processing.rs b/gateway/src/mixnet_handling/receiver/packet_processing.rs index 8b50418f45f..424411f3689 100644 --- a/gateway/src/mixnet_handling/receiver/packet_processing.rs +++ b/gateway/src/mixnet_handling/receiver/packet_processing.rs @@ -22,7 +22,7 @@ use futures::channel::oneshot; use futures::lock::Mutex; use log::*; use mix_client::packet::LOOP_COVER_MESSAGE_PAYLOAD; -use nymsphinx::{DestinationAddressBytes, ProcessedPacket, SURBIdentifier, SphinxPacket}; +use nymsphinx::{DestinationAddressBytes, ProcessedPacket, SphinxPacket}; use std::collections::HashMap; use std::io; use std::ops::Deref; @@ -37,12 +37,6 @@ pub enum MixProcessingError { IOError(String), } -pub enum MixProcessingResult { - #[allow(dead_code)] - ForwardHop, - FinalHop, -} - impl From for MixProcessingError { // for time being just have a single error instance for all possible results of nymsphinx::ProcessingError fn from(_: nymsphinx::ProcessingError) -> Self { @@ -172,7 +166,7 @@ impl PacketProcessor { warn!("Received a forward hop message - those are not implemented for providers"); Err(MixProcessingError::ReceivedForwardHopError) } - Ok(ProcessedPacket::ProcessedPacketFinalHop(client_address, surb_id, payload)) => { + Ok(ProcessedPacket::ProcessedPacketFinalHop(client_address, _surb_id, payload)) => { // in our current design, we do not care about the 'surb_id' in the header // as it will always be empty anyway let (payload_destination, message) = payload From 9a90b0bed686c02935fcf328bbb1ffe0a906b344 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Fri, 24 Apr 2020 16:40:08 +0100 Subject: [PATCH 49/70] Copied and did initial minor changes to commands and config --- Cargo.lock | 52 +- gateway/Cargo.toml | 32 +- gateway/build.rs | 19 + gateway/src/built_info.rs | 16 + gateway/src/commands/init.rs | 131 +++++ gateway/src/commands/mod.rs | 105 ++++ gateway/src/commands/run.rs | 268 +++++++++ gateway/src/config/mod.rs | 553 +++++++++++++++++++ gateway/src/config/persistence/mod.rs | 15 + gateway/src/config/persistence/pathfinder.rs | 58 ++ gateway/src/config/template.rs | 92 +++ gateway/src/main.rs | 89 ++- sfw-provider/src/commands/run.rs | 2 +- 13 files changed, 1350 insertions(+), 82 deletions(-) create mode 100644 gateway/build.rs create mode 100644 gateway/src/built_info.rs create mode 100644 gateway/src/commands/init.rs create mode 100644 gateway/src/commands/mod.rs create mode 100644 gateway/src/commands/run.rs create mode 100644 gateway/src/config/mod.rs create mode 100644 gateway/src/config/persistence/mod.rs create mode 100644 gateway/src/config/persistence/pathfinder.rs create mode 100644 gateway/src/config/template.rs diff --git a/Cargo.lock b/Cargo.lock index d67503143e9..6b7dc1552dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -895,29 +895,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "gateway" -version = "0.1.0" -dependencies = [ - "crypto", - "dotenv", - "futures 0.3.4", - "gateway-requests", - "hmac", - "log 0.4.8", - "mix-client", - "multi-tcp-client", - "nymsphinx", - "pretty_env_logger", - "rand 0.7.3", - "serde", - "sha2", - "sled", - "tokio 0.2.16", - "tokio-tungstenite", - "tungstenite", -] - [[package]] name = "gateway-requests" version = "0.1.0" @@ -1654,6 +1631,35 @@ dependencies = [ "topology", ] +[[package]] +name = "nym-gateway" +version = "0.7.0-dev" +dependencies = [ + "built", + "clap", + "config", + "crypto", + "dirs", + "dotenv", + "futures 0.3.4", + "gateway-requests", + "hmac", + "log 0.4.8", + "mix-client", + "multi-tcp-client", + "nymsphinx", + "pemstore", + "pretty_env_logger", + "rand 0.7.3", + "serde", + "sha2", + "sled", + "tempfile", + "tokio 0.2.16", + "tokio-tungstenite", + "tungstenite", +] + [[package]] name = "nym-mixnode" version = "0.7.0-dev" diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index e997d5db4ed..470061f7450 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -1,33 +1,51 @@ [package] -name = "gateway" -version = "0.1.0" -authors = ["Dave Hrycyszyn "] +build = "build.rs" +name = "nym-gateway" +version = "0.7.0-dev" +authors = ["Dave Hrycyszyn ", "JÄ™drzej StuczyÅ„ski "] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +clap = "2.33.0" +dirs = "2.0.2" dotenv = "0.15.0" futures = "0.3" +hmac = "0.7.1" log = "0.4" pretty_env_logger = "0.3" rand = "0.7.2" serde = { version = "1.0.104", features = ["derive"] } -#serde_json = "1.0.44" sha2 = "0.8.1" sled = "0.31" tokio = { version = "0.2", features = ["full"] } tokio-tungstenite = "0.10.1" -hmac = "0.7.1" # internal +config = { path = "../common/config" } crypto = { path = "../common/crypto" } gateway-requests = { path = "gateway-requests" } -mix-client = { path = "../common/client-libs/mix-client" } # to be removed very soon multi-tcp-client = { path = "../common/client-libs/multi-tcp-client" } nymsphinx = { path = "../common/nymsphinx" } +pemstore = { path = "../common/pemstore" } + +# this dependency is due to requiring to know content of loop message. however, mix-client module itself +# is going to be removed or renamed at some point as it no longer servers its purpose of sending traffic to mix network +# and only provides utility functions +mix-client = { path = "../common/client-libs/mix-client" } [dependencies.tungstenite] version = "0.10.0" -default-features = false \ No newline at end of file +default-features = false + +[build-dependencies] +built = "0.3.2" + +[dev-dependencies] +tempfile = "3.1.0" + +[features] +qa = [] +local = [] \ No newline at end of file diff --git a/gateway/build.rs b/gateway/build.rs new file mode 100644 index 00000000000..56d753472cf --- /dev/null +++ b/gateway/build.rs @@ -0,0 +1,19 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use built; + +fn main() { + built::write_built_file().expect("Failed to acquire build-time information"); +} diff --git a/gateway/src/built_info.rs b/gateway/src/built_info.rs new file mode 100644 index 00000000000..38b46d59e64 --- /dev/null +++ b/gateway/src/built_info.rs @@ -0,0 +1,16 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// The file has been placed there by the build script. +include!(concat!(env!("OUT_DIR"), "/built.rs")); diff --git a/gateway/src/commands/init.rs b/gateway/src/commands/init.rs new file mode 100644 index 00000000000..be63e54d770 --- /dev/null +++ b/gateway/src/commands/init.rs @@ -0,0 +1,131 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::commands::override_config; +use crate::config::persistence::pathfinder::GatewayPathfinder; +use clap::{App, Arg, ArgMatches}; +use config::NymConfig; +use crypto::encryption; +use pemstore::pemstore::PemStore; + +pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { + App::new("init") + .about("Initialise the gateway") + .arg( + Arg::with_name("id") + .long("id") + .help("Id of the gateway we want to create config for.") + .takes_value(true) + .required(true), + ) + .arg( + Arg::with_name("location") + .long("location") + .help("Optional geographical location of this provider") + .takes_value(true), + ) + .arg( + Arg::with_name("mix-host") + .long("mix-host") + .help("The custom host on which the gateway will be running for receiving sphinx packets") + .takes_value(true) + .required(true), + ) + .arg( + Arg::with_name("mix-port") + .long("mix-port") + .help("The port on which the gateway will be listening for sphinx packets") + .takes_value(true) + ) + .arg( + Arg::with_name("clients-host") + .long("clients-host") + .help("The custom host on which the gateway will be running for receiving clients gateway-requests") + .takes_value(true) + .required(true), + ) + .arg( + Arg::with_name("clients-port") + .long("clients-port") + .help("The port on which the gateway will be listening for clients gateway-requests") + .takes_value(true) + ) + .arg( + Arg::with_name("mix-announce-host") + .long("mix-announce-host") + .help("The host that will be reported to the directory server") + .takes_value(true), + ) + .arg( + Arg::with_name("mix-announce-port") + .long("mix-announce-port") + .help("The port that will be reported to the directory server") + .takes_value(true), + ) + .arg( + Arg::with_name("clients-announce-host") + .long("clients-announce-host") + .help("The host that will be reported to the directory server") + .takes_value(true), + ) + .arg( + Arg::with_name("clients-announce-port") + .long("clients-announce-port") + .help("The port that will be reported to the directory server") + .takes_value(true), + ) + .arg( + Arg::with_name("inboxes") + .long("inboxes") + .help("Directory with inboxes where all packets for the clients are stored") + .takes_value(true) + ) + .arg( + Arg::with_name("clients-ledger") + .long("clients-ledger") + .help("Ledger directory containing registered clients") + .takes_value(true) + ) + .arg( + Arg::with_name("directory") + .long("directory") + .help("Address of the directory server the node is sending presence data to") + .takes_value(true), + ) +} + +pub fn execute(matches: &ArgMatches) { + let id = matches.value_of("id").unwrap(); + println!("Initialising gateway {}...", id); + + let mut config = crate::config::Config::new(id); + + config = override_config(config, matches); + + let sphinx_keys = encryption::KeyPair::new(); + let pathfinder = GatewayPathfinder::new_from_config(&config); + let pem_store = PemStore::new(pathfinder); + pem_store + .write_encryption_keys(sphinx_keys) + .expect("Failed to save sphinx keys"); + println!("Saved mixnet sphinx keypair"); + + let config_save_location = config.get_config_file_save_location(); + config + .save_to_file(None) + .expect("Failed to save the config file"); + println!("Saved configuration file to {:?}", config_save_location); + + println!("Service provider configuration completed.\n\n\n") +} diff --git a/gateway/src/commands/mod.rs b/gateway/src/commands/mod.rs new file mode 100644 index 00000000000..4a8c3eeff81 --- /dev/null +++ b/gateway/src/commands/mod.rs @@ -0,0 +1,105 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::config::Config; +use clap::ArgMatches; + +pub mod init; +pub mod run; + +pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Config { + let mut was_mix_host_overridden = false; + if let Some(mix_host) = matches.value_of("mix-host") { + config = config.with_mix_listening_host(mix_host); + was_mix_host_overridden = true; + } + + if let Some(mix_port) = matches.value_of("mix-port").map(|port| port.parse::()) { + if let Err(err) = mix_port { + // if port was overridden, it must be parsable + panic!("Invalid port value provided - {:?}", err); + } + config = config.with_mix_listening_port(mix_port.unwrap()); + } + let mut was_clients_host_overridden = false; + if let Some(clients_host) = matches.value_of("clients-host") { + config = config.with_clients_listening_host(clients_host); + was_clients_host_overridden = true; + } + + if let Some(clients_port) = matches + .value_of("clients-port") + .map(|port| port.parse::()) + { + if let Err(err) = clients_port { + // if port was overridden, it must be parsable + panic!("Invalid port value provided - {:?}", err); + } + config = config.with_clients_listening_port(clients_port.unwrap()); + } + + if let Some(mix_announce_host) = matches.value_of("mix-announce-host") { + config = config.with_mix_announce_host(mix_announce_host); + } else if was_mix_host_overridden { + // make sure our 'mix-announce-host' always defaults to 'mix-host' + config = config.mix_announce_host_from_listening_host() + } + + if let Some(mix_announce_port) = matches + .value_of("mix-announce-port") + .map(|port| port.parse::()) + { + if let Err(err) = mix_announce_port { + // if port was overridden, it must be parsable + panic!("Invalid port value provided - {:?}", err); + } + config = config.with_mix_announce_port(mix_announce_port.unwrap()); + } + + if let Some(clients_announce_host) = matches.value_of("clients-announce-host") { + config = config.with_clients_announce_host(clients_announce_host); + } else if was_clients_host_overridden { + // make sure our 'clients-announce-host' always defaults to 'clients-host' + config = config.clients_announce_host_from_listening_host() + } + + if let Some(clients_announce_port) = matches + .value_of("clients-announce-port") + .map(|port| port.parse::()) + { + if let Err(err) = clients_announce_port { + // if port was overridden, it must be parsable + panic!("Invalid port value provided - {:?}", err); + } + config = config.with_clients_announce_port(clients_announce_port.unwrap()); + } + + if let Some(directory) = matches.value_of("directory") { + config = config.with_custom_directory(directory); + } + + if let Some(inboxes_dir) = matches.value_of("inboxes") { + config = config.with_custom_clients_inboxes(inboxes_dir); + } + + if let Some(clients_ledger) = matches.value_of("clients-ledger") { + config = config.with_custom_clients_ledger(clients_ledger); + } + + if let Some(location) = matches.value_of("location") { + config = config.with_location(location); + } + + config +} diff --git a/gateway/src/commands/run.rs b/gateway/src/commands/run.rs new file mode 100644 index 00000000000..002cd74b068 --- /dev/null +++ b/gateway/src/commands/run.rs @@ -0,0 +1,268 @@ +use crate::client_handling::clients_handler::ClientsHandler; +use crate::client_handling::websocket; +use crate::commands::override_config; +use crate::config::persistence::pathfinder::GatewayPathfinder; +use crate::config::Config; +use crate::mixnet_handling; +use crate::mixnet_handling::receiver::packet_processing::PacketProcessor; +use crate::mixnet_handling::sender::PacketForwarder; +use crate::storage::ClientStorage; +use clap::{App, Arg, ArgMatches}; +use config::NymConfig; +use crypto::encryption; +use log::*; +use pemstore::pemstore::PemStore; +use std::path::PathBuf; +use std::sync::Arc; +use std::time::Duration; + +pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { + App::new("run") + .about("Starts the gateway") + .arg( + Arg::with_name("id") + .long("id") + .help("Id of the gateway we want to run") + .takes_value(true) + .required(true), + ) + // the rest of arguments are optional, they are used to override settings in config file + .arg( + Arg::with_name("location") + .long("location") + .help("Optional geographical location of this gateway") + .takes_value(true), + ) + .arg( + Arg::with_name("config") + .long("config") + .help("Custom path to the nym gateway configuration file") + .takes_value(true), + ) + .arg( + Arg::with_name("mix-host") + .long("mix-host") + .help("The custom host on which the gateway will be running for receiving sphinx packets") + .takes_value(true) + ) + .arg( + Arg::with_name("mix-port") + .long("mix-port") + .help("The port on which the gateway will be listening for sphinx packets") + .takes_value(true) + ) + .arg( + Arg::with_name("clients-host") + .long("clients-host") + .help("The custom host on which the gateway will be running for receiving clients gateway-requests") + .takes_value(true) + ) + .arg( + Arg::with_name("clients-port") + .long("clients-port") + .help("The port on which the gateway will be listening for clients gateway-requests") + .takes_value(true) + ) + .arg( + Arg::with_name("mix-announce-host") + .long("mix-announce-host") + .help("The host that will be reported to the directory server") + .takes_value(true), + ) + .arg( + Arg::with_name("mix-announce-port") + .long("mix-announce-port") + .help("The port that will be reported to the directory server") + .takes_value(true), + ) + .arg( + Arg::with_name("clients-announce-host") + .long("clients-announce-host") + .help("The host that will be reported to the directory server") + .takes_value(true), + ) + .arg( + Arg::with_name("clients-announce-port") + .long("clients-announce-port") + .help("The port that will be reported to the directory server") + .takes_value(true), + ) + .arg( + Arg::with_name("inboxes") + .long("inboxes") + .help("Directory with inboxes where all packets for the clients are stored") + .takes_value(true) + ) + .arg( + Arg::with_name("clients-ledger") + .long("clients-ledger") + .help("Ledger file containing registered clients") + .takes_value(true) + ) + .arg( + Arg::with_name("directory") + .long("directory") + .help("Address of the directory server the gateway is sending presence data to") + .takes_value(true), + ) +} + +fn show_binding_warning(address: String) { + println!("\n##### NOTE #####"); + println!( + "\nYou are trying to bind to {} - you might not be accessible to other nodes\n\ + You can ignore this warning if you're running setup on a local network \n\ + or have set a custom 'announce-host'", + address + ); + println!("\n\n"); +} + +fn special_addresses() -> Vec<&'static str> { + vec!["localhost", "127.0.0.1", "0.0.0.0", "::1", "[::1]"] +} + +fn load_sphinx_keys(config_file: &Config) -> encryption::KeyPair { + let sphinx_keypair = PemStore::new(GatewayPathfinder::new_from_config(&config_file)) + .read_encryption() + .expect("Failed to read stored sphinx key files"); + println!( + "Public key: {}\n", + sphinx_keypair.public_key().to_base58_string() + ); + sphinx_keypair +} + +pub fn execute(matches: &ArgMatches) { + let id = matches.value_of("id").unwrap(); + + println!("Starting gateway {}...", id); + + let mut config = + Config::load_from_file(matches.value_of("config").map(|path| path.into()), Some(id)) + .expect("Failed to load config file"); + + config = override_config(config, matches); + + let mix_listening_ip_string = config.get_mix_listening_address().ip().to_string(); + if special_addresses().contains(&mix_listening_ip_string.as_ref()) { + show_binding_warning(mix_listening_ip_string); + } + + let clients_listening_ip_string = config.get_clients_listening_address().ip().to_string(); + if special_addresses().contains(&clients_listening_ip_string.as_ref()) { + show_binding_warning(clients_listening_ip_string); + } + + // TODO: define them in config + let initial_reconnection_backoff = Duration::from_millis(10_000); + let maximum_reconnection_backoff = Duration::from_millis(300_000); + let initial_connection_timeout = Duration::from_millis(1500); + + // very temporary will be moved into 'Gateway' struct within few next commits + // this is literally what #[tokio::main] is doing anyway (well, not 'literally', it's + // a bit of simplification from my side, but the end result is the same) + tokio::runtime::Runtime::new().unwrap().block_on(async { + let keypair = load_sphinx_keys(&config); + + let arced_sk = Arc::new(keypair.private_key().to_owned()); + + // TODO: this should really be a proper DB, right now it will be most likely a bottleneck, + // due to possible frequent independent writes + let client_storage = ClientStorage::new( + config.get_message_retrieval_limit() as usize, + config.get_stored_messages_filename_length(), + config.get_clients_inboxes_dir(), + ); + + let (_, forwarding_channel) = PacketForwarder::new( + initial_reconnection_backoff, + maximum_reconnection_backoff, + initial_connection_timeout, + ) + .start(); + + let (_, clients_handler_sender) = ClientsHandler::new( + Arc::clone(&arced_sk), + config.get_clients_ledger_path(), + client_storage.clone(), + ) + .start(); + + let packet_processor = + PacketProcessor::new(arced_sk, clients_handler_sender.clone(), client_storage); + + websocket::Listener::new(config.get_clients_listening_address()) + .start(clients_handler_sender, forwarding_channel); + mixnet_handling::Listener::new(config.get_mix_listening_address()).start(packet_processor); + + info!("All up and running!"); + + if let Err(e) = tokio::signal::ctrl_c().await { + error!( + "There was an error while capturing SIGINT - {:?}. We will terminate regardless", + e + ); + } + + println!( + "Received SIGINT - the gateway will terminate now (threads are not YET nicely stopped)" + ); + }); +} + +// +//#[tokio::main] +//async fn main() { +// dotenv::dotenv().ok(); +// setup_logging(); +// // TODO: assume config is parsed here, keys are loaded, etc +// // ALL OF BELOW WILL BE DONE VIA CONFIG +// let keypair = crypto::encryption::KeyPair::new(); +// let clients_addr = "127.0.0.1:9000".parse().unwrap(); +// let mix_addr = "127.0.0.1:1789".parse().unwrap(); +// let inbox_store_dir: PathBuf = "foomp".into(); +// let ledger_path: PathBuf = "foomp2".into(); +// let message_retrieval_limit = 1000; +// let filename_len = 16; +// let initial_reconnection_backoff = Duration::from_millis(10_000); +// let maximum_reconnection_backoff = Duration::from_millis(300_000); +// let initial_connection_timeout = Duration::from_millis(1500); +// // ALL OF ABOVE WILL HAVE BEEN DONE VIA CONFIG +// +// let arced_sk = Arc::new(keypair.private_key().to_owned()); +// +// // TODO: this should really be a proper DB, right now it will be most likely a bottleneck, +// // due to possible frequent independent writes +// let client_storage = ClientStorage::new(message_retrieval_limit, filename_len, inbox_store_dir); +// +// let (_, forwarding_channel) = PacketForwarder::new( +// initial_reconnection_backoff, +// maximum_reconnection_backoff, +// initial_connection_timeout, +// ) +// .start(); +// +// let (_, clients_handler_sender) = +// ClientsHandler::new(Arc::clone(&arced_sk), ledger_path, client_storage.clone()).start(); +// +// let packet_processor = +// PacketProcessor::new(arced_sk, clients_handler_sender.clone(), client_storage); +// +// websocket::Listener::new(clients_addr).start(clients_handler_sender, forwarding_channel); +// mixnet_handling::Listener::new(mix_addr).start(packet_processor); +// +// info!("All up and running!"); +// +// if let Err(e) = tokio::signal::ctrl_c().await { +// error!( +// "There was an error while capturing SIGINT - {:?}. We will terminate regardless", +// e +// ); +// } +// +// println!( +// "Received SIGINT - the gateway will terminate now (threads are not YET nicely stopped)" +// ); +//} +// diff --git a/gateway/src/config/mod.rs b/gateway/src/config/mod.rs new file mode 100644 index 00000000000..564beb535e0 --- /dev/null +++ b/gateway/src/config/mod.rs @@ -0,0 +1,553 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::config::template::config_template; +use config::NymConfig; +use log::*; +use serde::{Deserialize, Serialize}; +use std::net::{IpAddr, SocketAddr}; +use std::path::PathBuf; +use std::str::FromStr; +use std::time; + +pub mod persistence; +mod template; + +// 'GATEWAY' +const DEFAULT_MIX_LISTENING_PORT: u16 = 1789; +const DEFAULT_CLIENT_LISTENING_PORT: u16 = 9000; +// 'DEBUG' +// where applicable, the below are defined in milliseconds +const DEFAULT_PRESENCE_SENDING_DELAY: u64 = 1500; + +const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16; +const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: u16 = 5; +const DEFAULT_MAX_REQUEST_SIZE: u32 = 16 * 1024; + +#[derive(Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Config { + gateway: Gateway, + + mixnet_endpoint: MixnetEndpoint, + + clients_endpoint: ClientsEndpoint, + + #[serde(default)] + logging: Logging, + #[serde(default)] + debug: Debug, +} + +impl NymConfig for Config { + fn template() -> &'static str { + config_template() + } + + fn config_file_name() -> String { + "config.toml".to_string() + } + + fn default_root_directory() -> PathBuf { + dirs::home_dir() + .expect("Failed to evaluate $HOME value") + .join(".nym") + .join("gateways") + } + + fn root_directory(&self) -> PathBuf { + self.gateway.nym_root_directory.clone() + } + + fn config_directory(&self) -> PathBuf { + self.gateway + .nym_root_directory + .join(&self.gateway.id) + .join("config") + } + + fn data_directory(&self) -> PathBuf { + self.gateway + .nym_root_directory + .join(&self.gateway.id) + .join("data") + } +} + +impl Config { + pub fn new>(id: S) -> Self { + Config::default().with_id(id) + } + + // builder methods + pub fn with_id>(mut self, id: S) -> Self { + let id = id.into(); + if self.gateway.private_sphinx_key_file.as_os_str().is_empty() { + self.gateway.private_sphinx_key_file = + self::Gateway::default_private_sphinx_key_file(&id); + } + if self.gateway.public_sphinx_key_file.as_os_str().is_empty() { + self.gateway.public_sphinx_key_file = + self::Gateway::default_public_sphinx_key_file(&id); + } + if self + .clients_endpoint + .inboxes_directory + .as_os_str() + .is_empty() + { + self.clients_endpoint.inboxes_directory = + self::ClientsEndpoint::default_inboxes_directory(&id); + } + if self.clients_endpoint.ledger_path.as_os_str().is_empty() { + self.clients_endpoint.ledger_path = self::ClientsEndpoint::default_ledger_path(&id); + } + + self.gateway.id = id; + self + } + + pub fn with_custom_directory>(mut self, directory_server: S) -> Self { + self.debug.presence_directory_server = directory_server.into(); + self + } + + pub fn with_location>(mut self, location: S) -> Self { + self.gateway.location = location.into(); + self + } + + pub fn with_mix_listening_host>(mut self, host: S) -> Self { + // see if the provided `host` is just an ip address or ip:port + let host = host.into(); + + // is it ip:port? + match SocketAddr::from_str(host.as_ref()) { + Ok(socket_addr) => { + self.mixnet_endpoint.listening_address = socket_addr; + self + } + // try just for ip + Err(_) => match IpAddr::from_str(host.as_ref()) { + Ok(ip_addr) => { + self.mixnet_endpoint.listening_address.set_ip(ip_addr); + self + } + Err(_) => { + error!( + "failed to make any changes to config - invalid host {}", + host + ); + self + } + }, + } + } + + pub fn with_mix_listening_port(mut self, port: u16) -> Self { + self.mixnet_endpoint.listening_address.set_port(port); + self + } + + pub fn with_mix_announce_host>(mut self, host: S) -> Self { + // this is slightly more complicated as we store announce information as String, + // since it might not necessarily be a valid SocketAddr (say `nymtech.net:8080` is a valid + // announce address, yet invalid SocketAddr` + + // first lets see if we received host:port or just host part of an address + let host = host.into(); + let split_host: Vec<_> = host.split(':').collect(); + match split_host.len() { + 1 => { + // we provided only 'host' part so we are going to reuse existing port + self.mixnet_endpoint.announce_address = + format!("{}:{}", host, self.mixnet_endpoint.listening_address.port()); + self + } + 2 => { + // we provided 'host:port' so just put the whole thing there + self.mixnet_endpoint.announce_address = host; + self + } + _ => { + // we provided something completely invalid, so don't try to parse it + error!( + "failed to make any changes to config - invalid announce host {}", + host + ); + self + } + } + } + + pub fn mix_announce_host_from_listening_host(mut self) -> Self { + self.mixnet_endpoint.announce_address = self.mixnet_endpoint.listening_address.to_string(); + self + } + + pub fn with_mix_announce_port(mut self, port: u16) -> Self { + let current_host: Vec<_> = self.mixnet_endpoint.announce_address.split(':').collect(); + debug_assert_eq!(current_host.len(), 2); + self.mixnet_endpoint.announce_address = format!("{}:{}", current_host[0], port); + self + } + + pub fn with_clients_listening_host>(mut self, host: S) -> Self { + // see if the provided `host` is just an ip address or ip:port + let host = host.into(); + + // is it ip:port? + match SocketAddr::from_str(host.as_ref()) { + Ok(socket_addr) => { + self.clients_endpoint.listening_address = socket_addr; + self + } + // try just for ip + Err(_) => match IpAddr::from_str(host.as_ref()) { + Ok(ip_addr) => { + self.clients_endpoint.listening_address.set_ip(ip_addr); + self + } + Err(_) => { + error!( + "failed to make any changes to config - invalid host {}", + host + ); + self + } + }, + } + } + + pub fn clients_announce_host_from_listening_host(mut self) -> Self { + self.clients_endpoint.announce_address = + self.clients_endpoint.listening_address.to_string(); + self + } + + pub fn with_clients_listening_port(mut self, port: u16) -> Self { + self.clients_endpoint.listening_address.set_port(port); + self + } + + pub fn with_clients_announce_host>(mut self, host: S) -> Self { + // this is slightly more complicated as we store announce information as String, + // since it might not necessarily be a valid SocketAddr (say `nymtech.net:8080` is a valid + // announce address, yet invalid SocketAddr` + + // first lets see if we received host:port or just host part of an address + let host = host.into(); + let split_host: Vec<_> = host.split(':').collect(); + match split_host.len() { + 1 => { + // we provided only 'host' part so we are going to reuse existing port + self.clients_endpoint.announce_address = format!( + "{}:{}", + host, + self.clients_endpoint.listening_address.port() + ); + self + } + 2 => { + // we provided 'host:port' so just put the whole thing there + self.clients_endpoint.announce_address = host; + self + } + _ => { + // we provided something completely invalid, so don't try to parse it + error!( + "failed to make any changes to config - invalid announce host {}", + host + ); + self + } + } + } + + pub fn with_clients_announce_port(mut self, port: u16) -> Self { + let current_host: Vec<_> = self.clients_endpoint.announce_address.split(':').collect(); + debug_assert_eq!(current_host.len(), 2); + self.clients_endpoint.announce_address = format!("{}:{}", current_host[0], port); + self + } + + pub fn with_custom_clients_inboxes>(mut self, inboxes_dir: S) -> Self { + self.clients_endpoint.inboxes_directory = PathBuf::from(inboxes_dir.into()); + self + } + + pub fn with_custom_clients_ledger>(mut self, ledger_path: S) -> Self { + self.clients_endpoint.ledger_path = PathBuf::from(ledger_path.into()); + self + } + + // getters + pub fn get_config_file_save_location(&self) -> PathBuf { + self.config_directory().join(Self::config_file_name()) + } + + pub fn get_location(&self) -> String { + self.gateway.location.clone() + } + + pub fn get_private_sphinx_key_file(&self) -> PathBuf { + self.gateway.private_sphinx_key_file.clone() + } + + pub fn get_public_sphinx_key_file(&self) -> PathBuf { + self.gateway.public_sphinx_key_file.clone() + } + + pub fn get_presence_directory_server(&self) -> String { + self.debug.presence_directory_server.clone() + } + + pub fn get_presence_sending_delay(&self) -> time::Duration { + time::Duration::from_millis(self.debug.presence_sending_delay) + } + + pub fn get_mix_listening_address(&self) -> SocketAddr { + self.mixnet_endpoint.listening_address + } + + pub fn get_mix_announce_address(&self) -> String { + self.mixnet_endpoint.announce_address.clone() + } + + pub fn get_clients_listening_address(&self) -> SocketAddr { + self.clients_endpoint.listening_address + } + + pub fn get_clients_announce_address(&self) -> String { + self.clients_endpoint.announce_address.clone() + } + + pub fn get_clients_inboxes_dir(&self) -> PathBuf { + self.clients_endpoint.inboxes_directory.clone() + } + + pub fn get_clients_ledger_path(&self) -> PathBuf { + self.clients_endpoint.ledger_path.clone() + } + + pub fn get_message_retrieval_limit(&self) -> u16 { + self.debug.message_retrieval_limit + } + + pub fn get_stored_messages_filename_length(&self) -> u16 { + self.debug.stored_messages_filename_length + } + + pub fn get_max_request_size(&self) -> usize { + self.debug.max_request_size as usize + } +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Gateway { + /// ID specifies the human readable ID of this particular gateway. + id: String, + + /// Completely optional value specifying geographical location of this particular gateway. + /// Currently it's used entirely for debug purposes, as there are no mechanisms implemented + /// to verify correctness of the information provided. However, feel free to fill in + /// this field with as much accuracy as you wish to share. + location: String, + + /// Path to file containing private sphinx key. + private_sphinx_key_file: PathBuf, + + /// Path to file containing public sphinx key. + public_sphinx_key_file: PathBuf, + + /// nym_home_directory specifies absolute path to the home nym gateways directory. + /// It is expected to use default value and hence .toml file should not redefine this field. + nym_root_directory: PathBuf, +} + +impl Gateway { + fn default_private_sphinx_key_file(id: &str) -> PathBuf { + Config::default_data_directory(Some(id)).join("private_sphinx.pem") + } + + fn default_public_sphinx_key_file(id: &str) -> PathBuf { + Config::default_data_directory(Some(id)).join("public_sphinx.pem") + } + + fn default_location() -> String { + "unknown".into() + } +} + +impl Default for Gateway { + fn default() -> Self { + Gateway { + id: "".to_string(), + location: Self::default_location(), + private_sphinx_key_file: Default::default(), + public_sphinx_key_file: Default::default(), + nym_root_directory: Config::default_root_directory(), + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct MixnetEndpoint { + /// Socket address to which this gateway will bind to + /// and will be listening for sphinx packets coming from the mixnet. + listening_address: SocketAddr, + + /// Optional address announced to the directory server for the clients to connect to. + /// It is useful, say, in NAT scenarios or wanting to more easily update actual IP address + /// later on by using name resolvable with a DNS query, such as `nymtech.net:8080`. + /// Additionally a custom port can be provided, so both `nymtech.net:8080` and `nymtech.net` + /// are valid announce addresses, while the later will default to whatever port is used for + /// `listening_address`. + announce_address: String, +} + +impl Default for MixnetEndpoint { + fn default() -> Self { + MixnetEndpoint { + listening_address: format!("0.0.0.0:{}", DEFAULT_MIX_LISTENING_PORT) + .parse() + .unwrap(), + announce_address: format!("127.0.0.1:{}", DEFAULT_MIX_LISTENING_PORT), + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ClientsEndpoint { + /// Socket address to which this gateway will bind to + /// and will be listening for data packets coming from the clients. + listening_address: SocketAddr, + + /// Optional address announced to the directory server for the clients to connect to. + /// It is useful, say, in NAT scenarios or wanting to more easily update actual IP address + /// later on by using name resolvable with a DNS query, such as `nymtech.net:8080`. + /// Additionally a custom port can be provided, so both `nymtech.net:8080` and `nymtech.net` + /// are valid announce addresses, while the later will default to whatever port is used for + /// `listening_address`. + announce_address: String, + + /// Path to the directory with clients inboxes containing messages stored for them. + inboxes_directory: PathBuf, + + /// [TODO: implement its storage] Full path to a file containing mapping of + /// client addresses to their access tokens. + ledger_path: PathBuf, +} + +impl ClientsEndpoint { + fn default_inboxes_directory(id: &str) -> PathBuf { + Config::default_data_directory(Some(id)).join("inboxes") + } + + fn default_ledger_path(id: &str) -> PathBuf { + Config::default_data_directory(Some(id)).join("client_ledger.sled") + } +} + +impl Default for ClientsEndpoint { + fn default() -> Self { + ClientsEndpoint { + listening_address: format!("0.0.0.0:{}", DEFAULT_CLIENT_LISTENING_PORT) + .parse() + .unwrap(), + announce_address: format!("127.0.0.1:{}", DEFAULT_CLIENT_LISTENING_PORT), + inboxes_directory: Default::default(), + ledger_path: Default::default(), + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Logging {} + +impl Default for Logging { + fn default() -> Self { + Logging {} + } +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(default, deny_unknown_fields)] +pub struct Debug { + /// Directory server to which the server will be reporting their presence data. + presence_directory_server: String, + + /// Delay between each subsequent presence data being sent. + presence_sending_delay: u64, + + /// Length of filenames for new client messages. + stored_messages_filename_length: u16, + + /// Number of messages client gets on each request + /// if there are no real messages, dummy ones are create to always return + /// `message_retrieval_limit` total messages + message_retrieval_limit: u16, + + /// Maximum allowed length for requests received. + /// Anything declaring bigger size than that will be regarded as an error and + /// is going to be rejected. + max_request_size: u32, +} + +impl Debug { + fn default_directory_server() -> String { + #[cfg(feature = "qa")] + return "https://qa-directory.nymtech.net".to_string(); + #[cfg(feature = "local")] + return "http://localhost:8080".to_string(); + + "https://directory.nymtech.net".to_string() + } +} + +impl Default for Debug { + fn default() -> Self { + Debug { + presence_directory_server: Self::default_directory_server(), + presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY, + stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH, + message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT, + max_request_size: DEFAULT_MAX_REQUEST_SIZE, + } + } +} + +#[cfg(test)] +mod gateway_config { + use super::*; + + #[test] + fn after_saving_default_config_the_loaded_one_is_identical() { + // need to figure out how to do something similar but without touching the disk + // or the file system at all... + let temp_location = tempfile::tempdir().unwrap().path().join("config.toml"); + let default_config = Config::default().with_id("foomp".to_string()); + default_config + .save_to_file(Some(temp_location.clone())) + .unwrap(); + + let loaded_config = Config::load_from_file(Some(temp_location), None).unwrap(); + + assert_eq!(default_config, loaded_config); + } +} diff --git a/gateway/src/config/persistence/mod.rs b/gateway/src/config/persistence/mod.rs new file mode 100644 index 00000000000..97f4c000a4d --- /dev/null +++ b/gateway/src/config/persistence/mod.rs @@ -0,0 +1,15 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod pathfinder; diff --git a/gateway/src/config/persistence/pathfinder.rs b/gateway/src/config/persistence/pathfinder.rs new file mode 100644 index 00000000000..81d3d2d69bd --- /dev/null +++ b/gateway/src/config/persistence/pathfinder.rs @@ -0,0 +1,58 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::config::Config; +use pemstore::pathfinder::PathFinder; +use std::path::PathBuf; + +#[derive(Debug)] +pub struct GatewayPathfinder { + pub config_dir: PathBuf, + pub private_sphinx_key: PathBuf, + pub public_sphinx_key: PathBuf, +} + +impl GatewayPathfinder { + pub fn new_from_config(config: &Config) -> Self { + GatewayPathfinder { + config_dir: config.get_config_file_save_location(), + private_sphinx_key: config.get_private_sphinx_key_file(), + public_sphinx_key: config.get_public_sphinx_key_file(), + } + } +} + +impl PathFinder for GatewayPathfinder { + fn config_dir(&self) -> PathBuf { + self.config_dir.clone() + } + + fn private_identity_key(&self) -> PathBuf { + // TEMPORARILY USE SAME KEYS AS ENCRYPTION + self.private_sphinx_key.clone() + } + + fn public_identity_key(&self) -> PathBuf { + // TEMPORARILY USE SAME KEYS AS ENCRYPTION + self.public_sphinx_key.clone() + } + + fn private_encryption_key(&self) -> Option { + Some(self.private_sphinx_key.clone()) + } + + fn public_encryption_key(&self) -> Option { + Some(self.public_sphinx_key.clone()) + } +} diff --git a/gateway/src/config/template.rs b/gateway/src/config/template.rs new file mode 100644 index 00000000000..9e7f71c3c38 --- /dev/null +++ b/gateway/src/config/template.rs @@ -0,0 +1,92 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub(crate) fn config_template() -> &'static str { + // While using normal toml marshalling would have been way simpler with less overhead, + // I think it's useful to have comments attached to the saved config file to explain behaviour of + // particular fields. + // Note: any changes to the template must be reflected in the appropriate structs in mod.rs. + r#" +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +##### main base mixnode config options ##### + +[gateway] +# Human readable ID of this particular gateway. +id = '{{ gateway.id }}' + +# Completely optional value specifying geographical location of this particular node. +# Currently it's used entirely for debug purposes, as there are no mechanisms implemented +# to verify correctness of the information provided. However, feel free to fill in +# this field with as much accuracy as you wish to share. +location = '{{ gateway.location }}' + +# Path to file containing private sphinx key. +private_sphinx_key_file = '{{ gateway.private_sphinx_key_file }}' + +# Path to file containing public sphinx key. +public_sphinx_key_file = '{{ gateway.public_sphinx_key_file }}' + +# nym_home_directory specifies absolute path to the home nym gateway directory. +# It is expected to use default value and hence .toml file should not redefine this field. +nym_root_directory = '{{ gateway.nym_root_directory }}' + + +##### Mixnet endpoint config options ##### + +[mixnet_endpoint] +# Socket address to which this gateway will bind to +# and will be listening for sphinx packets coming from the mixnet. +listening_address = '{{ mixnet_endpoint.listening_address }}' + +# Optional address announced to the directory server for the clients to connect to. +# It is useful, say, in NAT scenarios or wanting to more easily update actual IP address +# later on by using name resolvable with a DNS query, such as `nymtech.net:8080`. +# Additionally a custom port can be provided, so both `nymtech.net:8080` and `nymtech.net` +# are valid announce addresses, while the later will default to whatever port is used for +# `listening_address`. +announce_address = '{{ mixnet_endpoint.announce_address }}' + + +#### Clients endpoint config options ##### + +[clients_endpoint] +# Socket address to which this gateway will bind to +# and will be listening for sphinx packets coming from the mixnet. +listening_address = '{{ clients_endpoint.listening_address }}' + +# Optional address announced to the directory server for the clients to connect to. +# It is useful, say, in NAT scenarios or wanting to more easily update actual IP address +# later on by using name resolvable with a DNS query, such as `nymtech.net:8080`. +# Additionally a custom port can be provided, so both `nymtech.net:8080` and `nymtech.net` +# are valid announce addresses, while the later will default to whatever port is used for +# `listening_address`. +announce_address = '{{ clients_endpoint.announce_address }}' + +# Path to the directory with clients inboxes containing messages stored for them. +inboxes_directory = '{{ clients_endpoint.inboxes_directory }}' + +# Full path to a file containing mapping of client addresses to their access tokens. +ledger_path = '{{ clients_endpoint.ledger_path }}' + + +##### logging configuration options ##### + +[logging] + +# TODO + +"# +} diff --git a/gateway/src/main.rs b/gateway/src/main.rs index e53afdd65f9..a6336e2b477 100644 --- a/gateway/src/main.rs +++ b/gateway/src/main.rs @@ -12,72 +12,58 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::client_handling::clients_handler::ClientsHandler; -use crate::client_handling::websocket; -use crate::mixnet_handling::receiver::packet_processing::PacketProcessor; -use crate::mixnet_handling::sender::PacketForwarder; -use crate::storage::ClientStorage; -use log::*; -use std::path::PathBuf; -use std::sync::Arc; -use std::time::Duration; +use clap::{App, ArgMatches}; +pub mod built_info; mod client_handling; +mod commands; +mod config; mod mixnet_handling; pub(crate) mod storage; -#[tokio::main] -async fn main() { +fn main() { dotenv::dotenv().ok(); setup_logging(); - // TODO: assume config is parsed here, keys are loaded, etc - // ALL OF BELOW WILL BE DONE VIA CONFIG - let keypair = crypto::encryption::KeyPair::new(); - let clients_addr = "127.0.0.1:9000".parse().unwrap(); - let mix_addr = "127.0.0.1:1789".parse().unwrap(); - let inbox_store_dir: PathBuf = "foomp".into(); - let ledger_path: PathBuf = "foomp2".into(); - let message_retrieval_limit = 1000; - let filename_len = 16; - let initial_reconnection_backoff = Duration::from_millis(10_000); - let maximum_reconnection_backoff = Duration::from_millis(300_000); - let initial_connection_timeout = Duration::from_millis(1500); - // ALL OF ABOVE WILL HAVE BEEN DONE VIA CONFIG + println!("{}", banner()); - let arced_sk = Arc::new(keypair.private_key().to_owned()); + let arg_matches = App::new("Nym Mixnet Gateway") + .version(built_info::PKG_VERSION) + .author("Nymtech") + .about("Implementation of the Nym Mixnet Gateway") + .subcommand(commands::init::command_args()) + .subcommand(commands::run::command_args()) + .get_matches(); - // TODO: this should really be a proper DB, right now it will be most likely a bottleneck, - // due to possible frequent independent writes - let client_storage = ClientStorage::new(message_retrieval_limit, filename_len, inbox_store_dir); - - let (_, forwarding_channel) = PacketForwarder::new( - initial_reconnection_backoff, - maximum_reconnection_backoff, - initial_connection_timeout, - ) - .start(); + execute(arg_matches); +} - let (_, clients_handler_sender) = - ClientsHandler::new(Arc::clone(&arced_sk), ledger_path, client_storage.clone()).start(); +fn execute(matches: ArgMatches) { + match matches.subcommand() { + ("init", Some(m)) => commands::init::execute(m), + ("run", Some(m)) => commands::run::execute(m), + _ => println!("{}", usage()), + } +} - let packet_processor = - PacketProcessor::new(arced_sk, clients_handler_sender.clone(), client_storage); +fn usage() -> &'static str { + "usage: --help to see available options.\n\n" +} - websocket::Listener::new(clients_addr).start(clients_handler_sender, forwarding_channel); - mixnet_handling::Listener::new(mix_addr).start(packet_processor); +fn banner() -> String { + format!( + r#" - info!("All up and running!"); + _ __ _ _ _ __ ___ + | '_ \| | | | '_ \ _ \ + | | | | |_| | | | | | | + |_| |_|\__, |_| |_| |_| + |___/ - if let Err(e) = tokio::signal::ctrl_c().await { - error!( - "There was an error while capturing SIGINT - {:?}. We will terminate regardless", - e - ); - } + (gateway - version {:}) - println!( - "Received SIGINT - the gateway will terminate now (threads are not YET nicely stopped)" - ); + "#, + built_info::PKG_VERSION + ) } fn setup_logging() { @@ -95,5 +81,6 @@ fn setup_logging() { .filter_module("reqwest", log::LevelFilter::Warn) .filter_module("mio", log::LevelFilter::Warn) .filter_module("want", log::LevelFilter::Warn) + .filter_module("sled", log::LevelFilter::Warn) .init(); } diff --git a/sfw-provider/src/commands/run.rs b/sfw-provider/src/commands/run.rs index 4e864516c95..4941d228e8a 100644 --- a/sfw-provider/src/commands/run.rs +++ b/sfw-provider/src/commands/run.rs @@ -24,7 +24,7 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .arg( Arg::with_name("id") .long("id") - .help("Id of the nym-mixnode we want to run") + .help("Id of the nym-provider we want to run") .takes_value(true) .required(true), ) From d841b4ffb5aced801ce61549c896f62c9b45313e Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Fri, 24 Apr 2020 16:43:24 +0100 Subject: [PATCH 50/70] It's actually gateway --- gateway/src/commands/init.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gateway/src/commands/init.rs b/gateway/src/commands/init.rs index be63e54d770..54752d5f63e 100644 --- a/gateway/src/commands/init.rs +++ b/gateway/src/commands/init.rs @@ -127,5 +127,5 @@ pub fn execute(matches: &ArgMatches) { .expect("Failed to save the config file"); println!("Saved configuration file to {:?}", config_save_location); - println!("Service provider configuration completed.\n\n\n") + println!("Gateway configuration completed.\n\n\n") } From 8d3b4400fe310481b856b7a4840f936f4ace13e8 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Mon, 27 Apr 2020 09:19:06 +0100 Subject: [PATCH 51/70] Gateway sending its regular presence to directory server --- Cargo.lock | 1 + .../client-libs/directory-client/src/lib.rs | 6 + .../directory-client/src/presence/gateways.rs | 86 +++++++++++++ .../directory-client/src/presence/mod.rs | 18 ++- .../directory-client/src/requests/mod.rs | 1 + .../src/requests/presence_gateways_post.rs | 103 +++++++++++++++ common/topology/src/gateway.rs | 61 +++++++++ common/topology/src/lib.rs | 22 +++- gateway/Cargo.toml | 1 + .../src/client_handling/clients_handler.rs | 4 +- gateway/src/client_handling/ledger.rs | 40 +++--- gateway/src/commands/run.rs | 20 ++- gateway/src/main.rs | 3 +- gateway/src/presence.rs | 121 ++++++++++++++++++ 14 files changed, 455 insertions(+), 32 deletions(-) create mode 100644 common/client-libs/directory-client/src/presence/gateways.rs create mode 100644 common/client-libs/directory-client/src/requests/presence_gateways_post.rs create mode 100644 common/topology/src/gateway.rs create mode 100644 gateway/src/presence.rs diff --git a/Cargo.lock b/Cargo.lock index 6b7dc1552dd..25ea3d34616 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1639,6 +1639,7 @@ dependencies = [ "clap", "config", "crypto", + "directory-client", "dirs", "dotenv", "futures 0.3.4", diff --git a/common/client-libs/directory-client/src/lib.rs b/common/client-libs/directory-client/src/lib.rs index be0f49c85aa..4141ac9aa71 100644 --- a/common/client-libs/directory-client/src/lib.rs +++ b/common/client-libs/directory-client/src/lib.rs @@ -18,6 +18,9 @@ use crate::requests::metrics_mixes_post::{MetricsMixPoster, Request as MetricsMi use crate::requests::presence_coconodes_post::{ PresenceCocoNodesPoster, Request as PresenceCocoNodesPost, }; +use crate::requests::presence_gateways_post:: { + PresenceGatewayPoster, Request as PresenceGatewayPost, +}; use crate::requests::presence_mixnodes_post::{ PresenceMixNodesPoster, Request as PresenceMixNodesPost, }; @@ -51,6 +54,7 @@ pub struct Client { pub metrics_mixes: MetricsMixRequest, pub metrics_post: MetricsMixPost, pub presence_coconodes_post: PresenceCocoNodesPost, + pub presence_gateway_post: PresenceGatewayPost, pub presence_mix_nodes_post: PresenceMixNodesPost, pub presence_providers_post: PresenceProvidersPost, pub presence_topology: PresenceTopologyRequest, @@ -63,6 +67,7 @@ impl DirectoryClient for Client { let metrics_post = MetricsMixPost::new(config.base_url.clone()); let presence_topology = PresenceTopologyRequest::new(config.base_url.clone()); let presence_coconodes_post = PresenceCocoNodesPost::new(config.base_url.clone()); + let presence_gateway_post = PresenceGatewayPost::new(config.base_url.clone()); let presence_mix_nodes_post = PresenceMixNodesPost::new(config.base_url.clone()); let presence_providers_post = PresenceProvidersPost::new(config.base_url); Client { @@ -70,6 +75,7 @@ impl DirectoryClient for Client { metrics_mixes, metrics_post, presence_coconodes_post, + presence_gateway_post, presence_mix_nodes_post, presence_providers_post, presence_topology, diff --git a/common/client-libs/directory-client/src/presence/gateways.rs b/common/client-libs/directory-client/src/presence/gateways.rs new file mode 100644 index 00000000000..78ffa78f444 --- /dev/null +++ b/common/client-libs/directory-client/src/presence/gateways.rs @@ -0,0 +1,86 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use serde::{Deserialize, Serialize}; +use topology::gateway; + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GatewayPresence { + pub location: String, + pub client_listener: String, + pub mixnet_listener: String, + pub pub_key: String, + pub registered_clients: Vec, + pub last_seen: u64, + pub version: String, +} + +impl Into for GatewayPresence { + fn into(self) -> topology::gateway::Node { + topology::gateway::Node { + location: self.location, + client_listener: self.client_listener.parse().unwrap(), + mixnet_listener: self.mixnet_listener.parse().unwrap(), + pub_key: self.pub_key, + registered_clients: self + .registered_clients + .into_iter() + .map(|c| c.into()) + .collect(), + last_seen: self.last_seen, + version: self.version, + } + } +} + +impl From for GatewayPresence { + fn from(mpn: gateway::Node) -> Self { + GatewayPresence { + location: mpn.location, + client_listener: mpn.client_listener.to_string(), + mixnet_listener: mpn.mixnet_listener.to_string(), + pub_key: mpn.pub_key, + registered_clients: mpn + .registered_clients + .into_iter() + .map(|c| c.into()) + .collect(), + last_seen: mpn.last_seen, + version: mpn.version, + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GatewayClient { + pub pub_key: String, +} + +impl Into for GatewayClient { + fn into(self) -> topology::gateway::Client { + topology::gateway::Client { + pub_key: self.pub_key, + } + } +} + +impl From for GatewayClient { + fn from(mpc: topology::gateway::Client) -> Self { + GatewayClient { + pub_key: mpc.pub_key, + } + } +} diff --git a/common/client-libs/directory-client/src/presence/mod.rs b/common/client-libs/directory-client/src/presence/mod.rs index fa7bb93df70..0e28bde6111 100644 --- a/common/client-libs/directory-client/src/presence/mod.rs +++ b/common/client-libs/directory-client/src/presence/mod.rs @@ -17,12 +17,11 @@ use crate::{Client, Config, DirectoryClient}; use log::*; use serde::{Deserialize, Serialize}; use std::convert::TryInto; -use topology::coco; -use topology::mix; -use topology::provider; -use topology::NymTopology; +use topology::gateway::Node; +use topology::{coco, gateway, mix, provider, NymTopology}; pub mod coconodes; +pub mod gateways; pub mod mixnodes; pub mod providers; @@ -33,9 +32,11 @@ pub struct Topology { pub coco_nodes: Vec, pub mix_nodes: Vec, pub mix_provider_nodes: Vec, + pub gateway_nodes: Vec, } impl NymTopology for Topology { + // TODO: this will need some changes to not imply having to make an HTTP request in constructor fn new(directory_server: String) -> Self { debug!("Using directory server: {:?}", directory_server); let directory_config = Config { @@ -53,6 +54,7 @@ impl NymTopology for Topology { mix_nodes: Vec, mix_provider_nodes: Vec, coco_nodes: Vec, + gateway_nodes: Vec, ) -> Self { Topology { coco_nodes: coco_nodes.into_iter().map(|node| node.into()).collect(), @@ -61,6 +63,7 @@ impl NymTopology for Topology { .into_iter() .map(|node| node.into()) .collect(), + gateway_nodes: gateway_nodes.into_iter().map(|node| node.into()).collect(), } } @@ -78,6 +81,13 @@ impl NymTopology for Topology { .collect() } + fn gateways(&self) -> Vec { + self.gateway_nodes + .iter() + .map(|x| x.clone().into()) + .collect() + } + fn coco_nodes(&self) -> Vec { self.coco_nodes.iter().map(|x| x.clone().into()).collect() } diff --git a/common/client-libs/directory-client/src/requests/mod.rs b/common/client-libs/directory-client/src/requests/mod.rs index 6490770e73c..3ac0f648fa9 100644 --- a/common/client-libs/directory-client/src/requests/mod.rs +++ b/common/client-libs/directory-client/src/requests/mod.rs @@ -16,6 +16,7 @@ pub mod health_check_get; pub mod metrics_mixes_get; pub mod metrics_mixes_post; pub mod presence_coconodes_post; +pub mod presence_gateways_post; pub mod presence_mixnodes_post; pub mod presence_providers_post; pub mod presence_topology_get; diff --git a/common/client-libs/directory-client/src/requests/presence_gateways_post.rs b/common/client-libs/directory-client/src/requests/presence_gateways_post.rs new file mode 100644 index 00000000000..1a0115dbc63 --- /dev/null +++ b/common/client-libs/directory-client/src/requests/presence_gateways_post.rs @@ -0,0 +1,103 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::presence::gateways::GatewayPresence; +use reqwest::Response; + +pub struct Request { + base_url: String, + path: String, +} + +pub trait PresenceGatewayPoster { + fn new(base_url: String) -> Self; + fn post(&self, presence: &GatewayPresence) -> Result; +} + +impl PresenceGatewayPoster for Request { + fn new(base_url: String) -> Self { + Request { + base_url, + path: "/api/presence/gateways".to_string(), + } + } + + fn post(&self, presence: &GatewayPresence) -> Result { + let url = format!("{}{}", self.base_url, self.path); + let client = reqwest::Client::new(); + let p = client.post(&url).json(&presence).send()?; + Ok(p) + } +} + +#[cfg(test)] +mod metrics_get_request { + use super::*; + + #[cfg(test)] + use mockito::mock; + + #[cfg(test)] + mod on_a_400_status { + use super::*; + + #[test] + fn it_returns_an_error() { + let _m = mock("POST", "/api/presence/mixproviders") + .with_status(400) + .create(); + let req = Request::new(mockito::server_url()); + let presence = fixtures::new_presence(); + let result = req.post(&presence); + assert_eq!(400, result.unwrap().status()); + _m.assert(); + } + } + + #[cfg(test)] + mod on_a_200 { + use super::*; + #[test] + fn it_returns_a_response_with_201() { + let json = r#"{ + "ok": true + }"#; + let _m = mock("POST", "/api/presence/mixproviders") + .with_status(201) + .with_body(json) + .create(); + let req = Request::new(mockito::server_url()); + let presence = fixtures::new_presence(); + let result = req.post(&presence); + assert_eq!(true, result.is_ok()); + _m.assert(); + } + } + #[cfg(test)] + mod fixtures { + use crate::presence::gateways::GatewayPresence; + + pub fn new_presence() -> GatewayPresence { + GatewayPresence { + location: "foomp".to_string(), + client_listener: "foo.com".to_string(), + mixnet_listener: "foo.com".to_string(), + pub_key: "abc".to_string(), + registered_clients: vec![], + last_seen: 0, + version: "0.1.0".to_string(), + } + } + } +} diff --git a/common/topology/src/gateway.rs b/common/topology/src/gateway.rs new file mode 100644 index 00000000000..c869a886370 --- /dev/null +++ b/common/topology/src/gateway.rs @@ -0,0 +1,61 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::filter; +use nymsphinx::addressing::nodes::NymNodeRoutingAddress; +use nymsphinx::Node as SphinxNode; +use std::convert::TryInto; +use std::net::SocketAddr; + +#[derive(Debug, Clone)] +pub struct Client { + pub pub_key: String, +} + +#[derive(Debug, Clone)] +pub struct Node { + pub location: String, + pub client_listener: SocketAddr, + pub mixnet_listener: SocketAddr, + pub pub_key: String, + pub registered_clients: Vec, + pub last_seen: u64, + pub version: String, +} + +impl Node { + pub fn get_pub_key_bytes(&self) -> [u8; 32] { + let mut key_bytes = [0; 32]; + bs58::decode(&self.pub_key).into(&mut key_bytes).unwrap(); + key_bytes + } +} + +impl filter::Versioned for Node { + fn version(&self) -> String { + self.version.clone() + } +} + +impl Into for Node { + fn into(self) -> SphinxNode { + let node_address_bytes = NymNodeRoutingAddress::from(self.mixnet_listener) + .try_into() + .unwrap(); + let key_bytes = self.get_pub_key_bytes(); + let key = nymsphinx::key::new(key_bytes); + + SphinxNode::new(node_address_bytes, key) + } +} diff --git a/common/topology/src/lib.rs b/common/topology/src/lib.rs index f77a4654c8a..790d683f27a 100644 --- a/common/topology/src/lib.rs +++ b/common/topology/src/lib.rs @@ -21,6 +21,7 @@ use std::collections::HashMap; pub mod coco; mod filter; +pub mod gateway; pub mod mix; pub mod provider; @@ -32,9 +33,11 @@ pub trait NymTopology: Sized + std::fmt::Debug + Send + Sync + Clone { mix_nodes: Vec, mix_provider_nodes: Vec, coco_nodes: Vec, + gateway_nodes: Vec, ) -> Self; fn mix_nodes(&self) -> Vec; fn providers(&self) -> Vec; + fn gateways(&self) -> Vec; fn coco_nodes(&self) -> Vec; fn make_layered_topology(&self) -> Result>, NymTopologyError> { let mut layered_topology: HashMap> = HashMap::new(); @@ -94,14 +97,14 @@ pub trait NymTopology: Sized + std::fmt::Debug + Send + Sync + Clone { fn all_paths(&self) -> Result>, NymTopologyError> { let mut layered_topology = self.make_layered_topology()?; - let providers = self.providers(); + let gateways = self.gateways(); let sorted_layers: Vec> = (1..=layered_topology.len() as u64) .map(|layer| layered_topology.remove(&layer).unwrap()) // get all nodes per layer .map(|layer_nodes| layer_nodes.into_iter().map(|node| node.into()).collect()) // convert them into 'proper' sphinx nodes .chain(std::iter::once( - providers.into_iter().map(|node| node.into()).collect(), - )) // append all providers to the end + gateways.into_iter().map(|node| node.into()).collect(), + )) // append all gateways to the end .collect(); let all_paths = sorted_layers @@ -112,19 +115,30 @@ pub trait NymTopology: Sized + std::fmt::Debug + Send + Sync + Clone { Ok(all_paths) } + fn filter_system_version(&self, expected_version: &str) -> Self { + self.filter_node_versions( + expected_version, + expected_version, + expected_version, + expected_version, + ) + } + fn filter_node_versions( &self, expected_mix_version: &str, expected_provider_version: &str, + expected_gateway_version: &str, expected_coco_version: &str, ) -> Self { let mixes = self.mix_nodes().filter_by_version(expected_mix_version); let providers = self .providers() .filter_by_version(expected_provider_version); + let gateways = self.gateways().filter_by_version(expected_gateway_version); let cocos = self.coco_nodes().filter_by_version(expected_coco_version); - Self::new_from_nodes(mixes, providers, cocos) + Self::new_from_nodes(mixes, providers, cocos, gateways) } fn can_construct_path_through(&self) -> bool { diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index 470061f7450..27fed8288c0 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -25,6 +25,7 @@ tokio-tungstenite = "0.10.1" # internal config = { path = "../common/config" } crypto = { path = "../common/crypto" } +directory-client = { path = "../common/client-libs/directory-client" } gateway-requests = { path = "gateway-requests" } multi-tcp-client = { path = "../common/client-libs/multi-tcp-client" } nymsphinx = { path = "../common/nymsphinx" } diff --git a/gateway/src/client_handling/clients_handler.rs b/gateway/src/client_handling/clients_handler.rs index 5bbfe16348b..1efab3fa596 100644 --- a/gateway/src/client_handling/clients_handler.rs +++ b/gateway/src/client_handling/clients_handler.rs @@ -73,13 +73,13 @@ pub(crate) struct ClientsHandler { impl ClientsHandler { pub(crate) fn new( secret_key: Arc, - ledger_path: PathBuf, + clients_ledger: ClientLedger, clients_inbox_storage: ClientStorage, ) -> Self { ClientsHandler { secret_key, open_connections: HashMap::new(), - clients_ledger: ClientLedger::load(ledger_path).unwrap(), + clients_ledger, clients_inbox_storage, } } diff --git a/gateway/src/client_handling/ledger.rs b/gateway/src/client_handling/ledger.rs index 3ac409c6cab..df9d79d9d22 100644 --- a/gateway/src/client_handling/ledger.rs +++ b/gateway/src/client_handling/ledger.rs @@ -18,10 +18,12 @@ //use sfw_provider_requests::auth_token::{AuthToken, AUTH_TOKEN_SIZE}; //use std::path::PathBuf; -use gateway_requests::auth_token::{AuthToken, AUTH_TOKEN_SIZE}; +use std::path::PathBuf; + use log::*; + +use gateway_requests::auth_token::{AuthToken, AUTH_TOKEN_SIZE}; use nymsphinx::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH}; -use std::path::PathBuf; #[derive(Debug)] pub(crate) enum ClientLedgerError { @@ -139,23 +141,23 @@ impl ClientLedger { removal_result } - // pub(crate) fn current_clients(&self) -> Result, ClientLedgerError> { - // let clients = self.db.iter().keys(); - // - // let mut client_vec = Vec::new(); - // for client in clients { - // match client { - // Err(e) => return Err(ClientLedgerError::DbWriteError(e)), - // Ok(client_entry) => client_vec.push(MixProviderClient { - // pub_key: self - // .read_destination_address_bytes(client_entry) - // .to_base58_string(), - // }), - // } - // } - // - // Ok(client_vec) - // } + pub(crate) fn current_clients( + &self, + ) -> Result, ClientLedgerError> { + let clients = self.db.iter().keys(); + + let mut client_vec = Vec::new(); + for client in clients { + match client { + Err(e) => return Err(ClientLedgerError::DbWriteError(e)), + Ok(client_entry) => { + client_vec.push(self.read_destination_address_bytes(client_entry)) + } + } + } + + Ok(client_vec) + } #[cfg(test)] pub(crate) fn create_temporary() -> Self { diff --git a/gateway/src/commands/run.rs b/gateway/src/commands/run.rs index 002cd74b068..eba443a3ca2 100644 --- a/gateway/src/commands/run.rs +++ b/gateway/src/commands/run.rs @@ -1,12 +1,14 @@ use crate::client_handling::clients_handler::ClientsHandler; +use crate::client_handling::ledger::ClientLedger; use crate::client_handling::websocket; use crate::commands::override_config; use crate::config::persistence::pathfinder::GatewayPathfinder; use crate::config::Config; -use crate::mixnet_handling; use crate::mixnet_handling::receiver::packet_processing::PacketProcessor; use crate::mixnet_handling::sender::PacketForwarder; +use crate::presence::NotifierConfig; use crate::storage::ClientStorage; +use crate::{mixnet_handling, presence}; use clap::{App, Arg, ArgMatches}; use config::NymConfig; use crypto::encryption; @@ -182,9 +184,23 @@ pub fn execute(matches: &ArgMatches) { ) .start(); + // literally for the time of a single commit as to see if presence is sent correctly + // then ledger will be moved into 'Gateway' struct + let clients_ledger = ClientLedger::load(config.get_clients_ledger_path()).unwrap(); + + let notifier_config = presence::NotifierConfig::new( + "foomplandia".parse().unwrap(), + config.get_presence_directory_server(), + config.get_mix_announce_address(), + config.get_clients_announce_address(), + keypair.public_key().to_base58_string(), + config.get_presence_sending_delay(), + ); + presence::Notifier::new(notifier_config, clients_ledger.clone()).start(); + let (_, clients_handler_sender) = ClientsHandler::new( Arc::clone(&arced_sk), - config.get_clients_ledger_path(), + clients_ledger, client_storage.clone(), ) .start(); diff --git a/gateway/src/main.rs b/gateway/src/main.rs index a6336e2b477..90de90b887f 100644 --- a/gateway/src/main.rs +++ b/gateway/src/main.rs @@ -19,7 +19,8 @@ mod client_handling; mod commands; mod config; mod mixnet_handling; -pub(crate) mod storage; +mod presence; +mod storage; fn main() { dotenv::dotenv().ok(); diff --git a/gateway/src/presence.rs b/gateway/src/presence.rs new file mode 100644 index 00000000000..9971e7b506b --- /dev/null +++ b/gateway/src/presence.rs @@ -0,0 +1,121 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::built_info; +use crate::client_handling::ledger::ClientLedger; +use directory_client::presence::gateways::{GatewayClient, GatewayPresence}; +use directory_client::requests::presence_gateways_post::PresenceGatewayPoster; +use directory_client::DirectoryClient; +use log::{error, trace}; +use std::time::Duration; +use tokio::runtime::Handle; +use tokio::task::JoinHandle; + +pub(crate) struct NotifierConfig { + location: String, + directory_server: String, + mix_announce_host: String, + clients_announce_host: String, + pub_key_string: String, + sending_delay: Duration, +} + +impl NotifierConfig { + pub(crate) fn new( + location: String, + directory_server: String, + mix_announce_host: String, + clients_announce_host: String, + pub_key_string: String, + sending_delay: Duration, + ) -> Self { + NotifierConfig { + location, + directory_server, + mix_announce_host, + clients_announce_host, + pub_key_string, + sending_delay, + } + } +} + +pub(crate) struct Notifier { + location: String, + net_client: directory_client::Client, + client_ledger: ClientLedger, + sending_delay: Duration, + client_listener: String, + mixnet_listener: String, + pub_key_string: String, +} + +impl Notifier { + pub(crate) fn new(config: NotifierConfig, client_ledger: ClientLedger) -> Notifier { + let directory_client_cfg = directory_client::Config { + base_url: config.directory_server, + }; + let net_client = directory_client::Client::new(directory_client_cfg); + + Notifier { + client_ledger, + net_client, + location: config.location, + client_listener: config.clients_announce_host, + mixnet_listener: config.mix_announce_host, + pub_key_string: config.pub_key_string, + sending_delay: config.sending_delay, + } + } + + async fn make_presence(&self) -> GatewayPresence { + let client_keys = self.client_ledger.current_clients().unwrap(); + let registered_clients = client_keys + .into_iter() + .map(|key_bytes| GatewayClient { + pub_key: key_bytes.to_base58_string(), + }) + .collect(); + + GatewayPresence { + location: self.location.clone(), + client_listener: self.client_listener.clone(), + mixnet_listener: self.mixnet_listener.clone(), + pub_key: self.pub_key_string.clone(), + registered_clients, + last_seen: 0, + version: built_info::PKG_VERSION.to_string(), + } + } + + pub(crate) fn notify(&self, presence: GatewayPresence) { + match self.net_client.presence_gateway_post.post(&presence) { + Err(err) => error!("failed to send presence - {:?}", err), + Ok(_) => trace!("sent presence information"), + } + } + + pub fn start(self) -> JoinHandle<()> { + tokio::spawn(async move { + loop { + // set the deadline in the future + let sending_delay = tokio::time::delay_for(self.sending_delay); + let presence = self.make_presence().await; + self.notify(presence); + // wait for however much is left + sending_delay.await; + } + }) + } +} From c4df48d449386b31354126252d84b3d92be359bc Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Mon, 27 Apr 2020 12:22:54 +0100 Subject: [PATCH 52/70] Re-organisation of gateway code + Gateway struct --- .../src/client_handling/websocket/types.rs | 32 --- gateway/src/commands/run.rs | 206 +++++++----------- gateway/src/config/mod.rs | 13 +- gateway/src/main.rs | 5 +- .../client_handling/clients_handler.rs | 8 +- gateway/src/{ => node}/client_handling/mod.rs | 1 - .../websocket/connection_handler.rs | 8 +- .../client_handling/websocket/listener.rs | 6 +- .../websocket/message_receiver.rs | 0 .../client_handling/websocket/mod.rs | 0 gateway/src/{ => node}/mixnet_handling/mod.rs | 1 + .../receiver/connection_handler.rs | 2 +- .../mixnet_handling/receiver/listener.rs | 5 +- .../mixnet_handling/receiver/mod.rs | 0 .../receiver/packet_processing.rs | 6 +- .../{ => node}/mixnet_handling/sender/mod.rs | 0 gateway/src/node/mod.rs | 167 ++++++++++++++ .../src/{presence.rs => node/presence/mod.rs} | 3 +- .../mod.rs => node/storage/inboxes.rs} | 0 .../storage}/ledger.rs | 0 gateway/src/node/storage/mod.rs | 4 + 21 files changed, 268 insertions(+), 199 deletions(-) delete mode 100644 gateway/src/client_handling/websocket/types.rs rename gateway/src/{ => node}/client_handling/clients_handler.rs (98%) rename gateway/src/{ => node}/client_handling/mod.rs (96%) rename gateway/src/{ => node}/client_handling/websocket/connection_handler.rs (98%) rename gateway/src/{ => node}/client_handling/websocket/listener.rs (90%) rename gateway/src/{ => node}/client_handling/websocket/message_receiver.rs (100%) rename gateway/src/{ => node}/client_handling/websocket/mod.rs (100%) rename gateway/src/{ => node}/mixnet_handling/mod.rs (91%) rename gateway/src/{ => node}/mixnet_handling/receiver/connection_handler.rs (97%) rename gateway/src/{ => node}/mixnet_handling/receiver/listener.rs (92%) rename gateway/src/{ => node}/mixnet_handling/receiver/mod.rs (100%) rename gateway/src/{ => node}/mixnet_handling/receiver/packet_processing.rs (97%) rename gateway/src/{ => node}/mixnet_handling/sender/mod.rs (100%) create mode 100644 gateway/src/node/mod.rs rename gateway/src/{presence.rs => node/presence/mod.rs} (98%) rename gateway/src/{storage/mod.rs => node/storage/inboxes.rs} (100%) rename gateway/src/{client_handling => node/storage}/ledger.rs (100%) create mode 100644 gateway/src/node/storage/mod.rs diff --git a/gateway/src/client_handling/websocket/types.rs b/gateway/src/client_handling/websocket/types.rs deleted file mode 100644 index 4f6867b2d1d..00000000000 --- a/gateway/src/client_handling/websocket/types.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use serde::{Deserialize, Serialize}; -use std::convert::TryFrom; -use tokio_tungstenite::tungstenite::protocol::Message; - -#[derive(Serialize, Deserialize, Debug)] -pub(crate) enum Request { - Send, - Register { address: String }, - Authenticate { token: String }, -} - -#[derive(Serialize, Deserialize, Debug)] -pub(crate) enum Response { - Send, - Register { token: String }, - Authenticate { status: bool }, - Error { message: String }, -} diff --git a/gateway/src/commands/run.rs b/gateway/src/commands/run.rs index eba443a3ca2..ba90f903bf4 100644 --- a/gateway/src/commands/run.rs +++ b/gateway/src/commands/run.rs @@ -1,22 +1,25 @@ -use crate::client_handling::clients_handler::ClientsHandler; -use crate::client_handling::ledger::ClientLedger; -use crate::client_handling::websocket; +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use crate::commands::override_config; use crate::config::persistence::pathfinder::GatewayPathfinder; use crate::config::Config; -use crate::mixnet_handling::receiver::packet_processing::PacketProcessor; -use crate::mixnet_handling::sender::PacketForwarder; -use crate::presence::NotifierConfig; -use crate::storage::ClientStorage; -use crate::{mixnet_handling, presence}; +use crate::node::Gateway; use clap::{App, Arg, ArgMatches}; use config::NymConfig; use crypto::encryption; -use log::*; use pemstore::pemstore::PemStore; -use std::path::PathBuf; -use std::sync::Arc; -use std::time::Duration; pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { App::new("run") @@ -135,6 +138,23 @@ fn load_sphinx_keys(config_file: &Config) -> encryption::KeyPair { sphinx_keypair } +fn check_if_same_ip_gateway_exists( + announced_mix_host: String, + announced_clients_host: String, +) -> Option { + unimplemented!() + // // TODO: once we change to graph topology this here will need to be updated! + // let topology = Topology::new(self.config.get_presence_directory_server()); + // let existing_gateways = topology.gateway_nodes; + // existing_gateways + // .iter() + // .find(|node| { + // node.mixnet_listener == announced_mix_host + // || node.client_listener == announced_clients_host + // }) + // .map(|node| node.pub_key.clone()) +} + pub fn execute(matches: &ArgMatches) { let id = matches.value_of("id").unwrap(); @@ -146,6 +166,19 @@ pub fn execute(matches: &ArgMatches) { config = override_config(config, matches); + // if let Some(duplicate_gateway_key) = check_if_same_ip_gateway_exists( + // config.get_mix_announce_address(), + // config.get_clients_announce_address(), + // ) { + // println!( + // "Our announce-host is identical to an existing node's announce-host! (its key is {:?}", + // duplicate_gateway_key + // ); + // return; + // } + + let sphinx_keypair = load_sphinx_keys(&config); + let mix_listening_ip_string = config.get_mix_listening_address().ip().to_string(); if special_addresses().contains(&mix_listening_ip_string.as_ref()) { show_binding_warning(mix_listening_ip_string); @@ -156,129 +189,38 @@ pub fn execute(matches: &ArgMatches) { show_binding_warning(clients_listening_ip_string); } - // TODO: define them in config - let initial_reconnection_backoff = Duration::from_millis(10_000); - let maximum_reconnection_backoff = Duration::from_millis(300_000); - let initial_connection_timeout = Duration::from_millis(1500); - - // very temporary will be moved into 'Gateway' struct within few next commits - // this is literally what #[tokio::main] is doing anyway (well, not 'literally', it's - // a bit of simplification from my side, but the end result is the same) - tokio::runtime::Runtime::new().unwrap().block_on(async { - let keypair = load_sphinx_keys(&config); - - let arced_sk = Arc::new(keypair.private_key().to_owned()); - - // TODO: this should really be a proper DB, right now it will be most likely a bottleneck, - // due to possible frequent independent writes - let client_storage = ClientStorage::new( - config.get_message_retrieval_limit() as usize, - config.get_stored_messages_filename_length(), - config.get_clients_inboxes_dir(), - ); - - let (_, forwarding_channel) = PacketForwarder::new( - initial_reconnection_backoff, - maximum_reconnection_backoff, - initial_connection_timeout, - ) - .start(); - - // literally for the time of a single commit as to see if presence is sent correctly - // then ledger will be moved into 'Gateway' struct - let clients_ledger = ClientLedger::load(config.get_clients_ledger_path()).unwrap(); - - let notifier_config = presence::NotifierConfig::new( - "foomplandia".parse().unwrap(), - config.get_presence_directory_server(), - config.get_mix_announce_address(), - config.get_clients_announce_address(), - keypair.public_key().to_base58_string(), - config.get_presence_sending_delay(), - ); - presence::Notifier::new(notifier_config, clients_ledger.clone()).start(); - - let (_, clients_handler_sender) = ClientsHandler::new( - Arc::clone(&arced_sk), - clients_ledger, - client_storage.clone(), - ) - .start(); + println!( + "Directory server [presence]: {}", + config.get_presence_directory_server() + ); - let packet_processor = - PacketProcessor::new(arced_sk, clients_handler_sender.clone(), client_storage); + println!( + "Listening for incoming sphinx packets on {}", + config.get_mix_listening_address() + ); + println!( + "Announcing the following socket address for sphinx packets: {}", + config.get_mix_announce_address() + ); - websocket::Listener::new(config.get_clients_listening_address()) - .start(clients_handler_sender, forwarding_channel); - mixnet_handling::Listener::new(config.get_mix_listening_address()).start(packet_processor); + println!( + "Listening for incoming clients packets on {}", + config.get_clients_listening_address() + ); + println!( + "Announcing the following socket address for clients packets: {}", + config.get_clients_announce_address() + ); - info!("All up and running!"); + println!( + "Inboxes directory is: {:?}", + config.get_clients_inboxes_dir() + ); - if let Err(e) = tokio::signal::ctrl_c().await { - error!( - "There was an error while capturing SIGINT - {:?}. We will terminate regardless", - e - ); - } + println!( + "Clients ledger is stored at: {:?}", + config.get_clients_ledger_path() + ); - println!( - "Received SIGINT - the gateway will terminate now (threads are not YET nicely stopped)" - ); - }); + Gateway::new(config, sphinx_keypair).run(); } - -// -//#[tokio::main] -//async fn main() { -// dotenv::dotenv().ok(); -// setup_logging(); -// // TODO: assume config is parsed here, keys are loaded, etc -// // ALL OF BELOW WILL BE DONE VIA CONFIG -// let keypair = crypto::encryption::KeyPair::new(); -// let clients_addr = "127.0.0.1:9000".parse().unwrap(); -// let mix_addr = "127.0.0.1:1789".parse().unwrap(); -// let inbox_store_dir: PathBuf = "foomp".into(); -// let ledger_path: PathBuf = "foomp2".into(); -// let message_retrieval_limit = 1000; -// let filename_len = 16; -// let initial_reconnection_backoff = Duration::from_millis(10_000); -// let maximum_reconnection_backoff = Duration::from_millis(300_000); -// let initial_connection_timeout = Duration::from_millis(1500); -// // ALL OF ABOVE WILL HAVE BEEN DONE VIA CONFIG -// -// let arced_sk = Arc::new(keypair.private_key().to_owned()); -// -// // TODO: this should really be a proper DB, right now it will be most likely a bottleneck, -// // due to possible frequent independent writes -// let client_storage = ClientStorage::new(message_retrieval_limit, filename_len, inbox_store_dir); -// -// let (_, forwarding_channel) = PacketForwarder::new( -// initial_reconnection_backoff, -// maximum_reconnection_backoff, -// initial_connection_timeout, -// ) -// .start(); -// -// let (_, clients_handler_sender) = -// ClientsHandler::new(Arc::clone(&arced_sk), ledger_path, client_storage.clone()).start(); -// -// let packet_processor = -// PacketProcessor::new(arced_sk, clients_handler_sender.clone(), client_storage); -// -// websocket::Listener::new(clients_addr).start(clients_handler_sender, forwarding_channel); -// mixnet_handling::Listener::new(mix_addr).start(packet_processor); -// -// info!("All up and running!"); -// -// if let Err(e) = tokio::signal::ctrl_c().await { -// error!( -// "There was an error while capturing SIGINT - {:?}. We will terminate regardless", -// e -// ); -// } -// -// println!( -// "Received SIGINT - the gateway will terminate now (threads are not YET nicely stopped)" -// ); -//} -// diff --git a/gateway/src/config/mod.rs b/gateway/src/config/mod.rs index 564beb535e0..6d30c17fe12 100644 --- a/gateway/src/config/mod.rs +++ b/gateway/src/config/mod.rs @@ -33,7 +33,6 @@ const DEFAULT_PRESENCE_SENDING_DELAY: u64 = 1500; const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16; const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: u16 = 5; -const DEFAULT_MAX_REQUEST_SIZE: u32 = 16 * 1024; #[derive(Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] @@ -348,10 +347,6 @@ impl Config { pub fn get_stored_messages_filename_length(&self) -> u16 { self.debug.stored_messages_filename_length } - - pub fn get_max_request_size(&self) -> usize { - self.debug.max_request_size as usize - } } #[derive(Debug, Deserialize, PartialEq, Serialize)] @@ -448,7 +443,7 @@ pub struct ClientsEndpoint { /// Path to the directory with clients inboxes containing messages stored for them. inboxes_directory: PathBuf, - /// [TODO: implement its storage] Full path to a file containing mapping of + /// Full path to a file containing mapping of /// client addresses to their access tokens. ledger_path: PathBuf, } @@ -502,11 +497,6 @@ pub struct Debug { /// if there are no real messages, dummy ones are create to always return /// `message_retrieval_limit` total messages message_retrieval_limit: u16, - - /// Maximum allowed length for requests received. - /// Anything declaring bigger size than that will be regarded as an error and - /// is going to be rejected. - max_request_size: u32, } impl Debug { @@ -527,7 +517,6 @@ impl Default for Debug { presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY, stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH, message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT, - max_request_size: DEFAULT_MAX_REQUEST_SIZE, } } } diff --git a/gateway/src/main.rs b/gateway/src/main.rs index 90de90b887f..3499f2a6851 100644 --- a/gateway/src/main.rs +++ b/gateway/src/main.rs @@ -15,12 +15,9 @@ use clap::{App, ArgMatches}; pub mod built_info; -mod client_handling; mod commands; mod config; -mod mixnet_handling; -mod presence; -mod storage; +mod node; fn main() { dotenv::dotenv().ok(); diff --git a/gateway/src/client_handling/clients_handler.rs b/gateway/src/node/client_handling/clients_handler.rs similarity index 98% rename from gateway/src/client_handling/clients_handler.rs rename to gateway/src/node/client_handling/clients_handler.rs index 1efab3fa596..273d6b70b2f 100644 --- a/gateway/src/client_handling/clients_handler.rs +++ b/gateway/src/node/client_handling/clients_handler.rs @@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::client_handling::{ledger::ClientLedger, websocket::message_receiver::MixMessageSender}; -use crate::storage::ClientStorage; +use crate::node::{ + client_handling::websocket::message_receiver::MixMessageSender, + storage::{inboxes::ClientStorage, ClientLedger}, +}; use crypto::encryption; use futures::{ channel::{mpsc, oneshot}, @@ -25,7 +27,6 @@ use log::*; use nymsphinx::DestinationAddressBytes; use sha2::Sha256; use std::collections::HashMap; -use std::path::PathBuf; use std::sync::Arc; use tokio::task::JoinHandle; @@ -51,7 +52,6 @@ pub(crate) enum ClientsHandlerRequest { Disconnect(DestinationAddressBytes), // mix - // EmptyInbox(DestinationAddressBytes), IsOnline(DestinationAddressBytes, ClientsHandlerResponseSender), } diff --git a/gateway/src/client_handling/mod.rs b/gateway/src/node/client_handling/mod.rs similarity index 96% rename from gateway/src/client_handling/mod.rs rename to gateway/src/node/client_handling/mod.rs index ef1497b2149..1256cb2691f 100644 --- a/gateway/src/client_handling/mod.rs +++ b/gateway/src/node/client_handling/mod.rs @@ -13,5 +13,4 @@ // limitations under the License. pub(crate) mod clients_handler; -pub(crate) mod ledger; pub(crate) mod websocket; diff --git a/gateway/src/client_handling/websocket/connection_handler.rs b/gateway/src/node/client_handling/websocket/connection_handler.rs similarity index 98% rename from gateway/src/client_handling/websocket/connection_handler.rs rename to gateway/src/node/client_handling/websocket/connection_handler.rs index 02608f5af84..69995d1c5f2 100644 --- a/gateway/src/client_handling/websocket/connection_handler.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler.rs @@ -28,11 +28,13 @@ use gateway_requests::auth_token::AuthToken; use gateway_requests::types::{BinaryRequest, ClientControlRequest, ServerResponse}; use nymsphinx::DestinationAddressBytes; -use crate::client_handling::clients_handler::{ +use crate::node::client_handling::clients_handler::{ ClientsHandlerRequest, ClientsHandlerRequestSender, ClientsHandlerResponse, }; -use crate::client_handling::websocket::message_receiver::{MixMessageReceiver, MixMessageSender}; -use crate::mixnet_handling::sender::OutboundMixMessageSender; +use crate::node::client_handling::websocket::message_receiver::{ + MixMessageReceiver, MixMessageSender, +}; +use crate::node::mixnet_handling::sender::OutboundMixMessageSender; //// TODO: note for my future self to consider the following idea: //// split the socket connection into sink and stream diff --git a/gateway/src/client_handling/websocket/listener.rs b/gateway/src/node/client_handling/websocket/listener.rs similarity index 90% rename from gateway/src/client_handling/websocket/listener.rs rename to gateway/src/node/client_handling/websocket/listener.rs index 4d57469fc39..9560e8fb8a9 100644 --- a/gateway/src/client_handling/websocket/listener.rs +++ b/gateway/src/node/client_handling/websocket/listener.rs @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::client_handling::clients_handler::ClientsHandlerRequestSender; -use crate::client_handling::websocket::connection_handler::Handle; -use crate::mixnet_handling::sender::OutboundMixMessageSender; +use crate::node::client_handling::clients_handler::ClientsHandlerRequestSender; +use crate::node::client_handling::websocket::connection_handler::Handle; +use crate::node::mixnet_handling::sender::OutboundMixMessageSender; use log::*; use std::net::SocketAddr; use tokio::task::JoinHandle; diff --git a/gateway/src/client_handling/websocket/message_receiver.rs b/gateway/src/node/client_handling/websocket/message_receiver.rs similarity index 100% rename from gateway/src/client_handling/websocket/message_receiver.rs rename to gateway/src/node/client_handling/websocket/message_receiver.rs diff --git a/gateway/src/client_handling/websocket/mod.rs b/gateway/src/node/client_handling/websocket/mod.rs similarity index 100% rename from gateway/src/client_handling/websocket/mod.rs rename to gateway/src/node/client_handling/websocket/mod.rs diff --git a/gateway/src/mixnet_handling/mod.rs b/gateway/src/node/mixnet_handling/mod.rs similarity index 91% rename from gateway/src/mixnet_handling/mod.rs rename to gateway/src/node/mixnet_handling/mod.rs index a2b89ab065f..c4d03e42fad 100644 --- a/gateway/src/mixnet_handling/mod.rs +++ b/gateway/src/node/mixnet_handling/mod.rs @@ -16,3 +16,4 @@ pub(crate) mod receiver; pub(crate) mod sender; pub(crate) use receiver::listener::Listener; +pub(crate) use receiver::packet_processing::PacketProcessor; diff --git a/gateway/src/mixnet_handling/receiver/connection_handler.rs b/gateway/src/node/mixnet_handling/receiver/connection_handler.rs similarity index 97% rename from gateway/src/mixnet_handling/receiver/connection_handler.rs rename to gateway/src/node/mixnet_handling/receiver/connection_handler.rs index 80a820b08c9..2eb1107a545 100644 --- a/gateway/src/mixnet_handling/receiver/connection_handler.rs +++ b/gateway/src/node/mixnet_handling/receiver/connection_handler.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::mixnet_handling::receiver::packet_processing::PacketProcessor; +use crate::node::mixnet_handling::receiver::packet_processing::PacketProcessor; use log::*; use std::net::SocketAddr; use tokio::{io::AsyncReadExt, prelude::*}; diff --git a/gateway/src/mixnet_handling/receiver/listener.rs b/gateway/src/node/mixnet_handling/receiver/listener.rs similarity index 92% rename from gateway/src/mixnet_handling/receiver/listener.rs rename to gateway/src/node/mixnet_handling/receiver/listener.rs index 52a5c13cb7b..9532a37af5a 100644 --- a/gateway/src/mixnet_handling/receiver/listener.rs +++ b/gateway/src/node/mixnet_handling/receiver/listener.rs @@ -12,8 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::mixnet_handling::receiver::connection_handler::Handle; -use crate::mixnet_handling::receiver::packet_processing::PacketProcessor; +use crate::node::mixnet_handling::receiver::{ + connection_handler::Handle, packet_processing::PacketProcessor, +}; use log::*; use std::net::SocketAddr; use tokio::task::JoinHandle; diff --git a/gateway/src/mixnet_handling/receiver/mod.rs b/gateway/src/node/mixnet_handling/receiver/mod.rs similarity index 100% rename from gateway/src/mixnet_handling/receiver/mod.rs rename to gateway/src/node/mixnet_handling/receiver/mod.rs diff --git a/gateway/src/mixnet_handling/receiver/packet_processing.rs b/gateway/src/node/mixnet_handling/receiver/packet_processing.rs similarity index 97% rename from gateway/src/mixnet_handling/receiver/packet_processing.rs rename to gateway/src/node/mixnet_handling/receiver/packet_processing.rs index 424411f3689..075e7fb815b 100644 --- a/gateway/src/mixnet_handling/receiver/packet_processing.rs +++ b/gateway/src/node/mixnet_handling/receiver/packet_processing.rs @@ -12,11 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::client_handling::clients_handler::{ +use crate::node::client_handling::clients_handler::{ ClientsHandlerRequest, ClientsHandlerRequestSender, ClientsHandlerResponse, }; -use crate::client_handling::websocket::message_receiver::MixMessageSender; -use crate::storage::{ClientStorage, StoreData}; +use crate::node::client_handling::websocket::message_receiver::MixMessageSender; +use crate::node::storage::inboxes::{ClientStorage, StoreData}; use crypto::encryption; use futures::channel::oneshot; use futures::lock::Mutex; diff --git a/gateway/src/mixnet_handling/sender/mod.rs b/gateway/src/node/mixnet_handling/sender/mod.rs similarity index 100% rename from gateway/src/mixnet_handling/sender/mod.rs rename to gateway/src/node/mixnet_handling/sender/mod.rs diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs new file mode 100644 index 00000000000..7fc73073b27 --- /dev/null +++ b/gateway/src/node/mod.rs @@ -0,0 +1,167 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::config::Config; +use crate::node::client_handling::clients_handler::{ClientsHandler, ClientsHandlerRequestSender}; +use crate::node::client_handling::websocket; +use crate::node::mixnet_handling::sender::{OutboundMixMessageSender, PacketForwarder}; +use crate::node::storage::{inboxes, ClientLedger}; +use crypto::encryption; +use log::*; +use std::sync::Arc; +use std::time::Duration; +use tokio::runtime::Runtime; + +pub(crate) mod client_handling; +pub(crate) mod mixnet_handling; +mod presence; +pub(crate) mod storage; + +// current issues in this file: +// - two calls to `Arc::new(self.sphinx_keypair.private_key().clone()),` - basically 2 separate +// Arcs to the same underlying data (well, after a clone), so what it ends up resulting in is +// private key being in 3 different places in memory rather than in a single location. +// Does it affect performance? No, not really. Is it *SUPER* insecure? Also, not as much, because +// if somebody could read memory of the machine, they probably got better attack vectors. +// Should it get fixed? Probably. But it's very low priority for time being. + +pub struct Gateway { + config: Config, + sphinx_keypair: encryption::KeyPair, + registered_clients_ledger: ClientLedger, + client_inbox_storage: inboxes::ClientStorage, +} + +impl Gateway { + // the constructor differs from mixnodes and providers in that it takes keys directly + // as opposed to having `Self::load_sphinx_keys(cfg: &Config)` method. Let's see + // how it works out, because I'm not sure which one would be "better", but when I think about it, + // I kinda prefer to delegate having to load the keys to outside the gateway + pub fn new(config: Config, sphinx_keypair: encryption::KeyPair) -> Self { + let registered_clients_ledger = match ClientLedger::load(config.get_clients_ledger_path()) { + Err(e) => panic!(format!("Failed to load the ledger - {:?}", e)), + Ok(ledger) => ledger, + }; + let client_inbox_storage = inboxes::ClientStorage::new( + config.get_message_retrieval_limit() as usize, + config.get_stored_messages_filename_length(), + config.get_clients_inboxes_dir(), + ); + Gateway { + config, + sphinx_keypair, + client_inbox_storage, + registered_clients_ledger, + } + } + + fn start_mix_socket_listener(&self, clients_handler_sender: ClientsHandlerRequestSender) { + info!("Starting mix socket listener..."); + + let packet_processor = mixnet_handling::PacketProcessor::new( + Arc::new(self.sphinx_keypair.private_key().clone()), + clients_handler_sender, + self.client_inbox_storage.clone(), + ); + + mixnet_handling::Listener::new(self.config.get_mix_listening_address()) + .start(packet_processor); + } + + fn start_client_websocket_listener( + &self, + forwarding_channel: OutboundMixMessageSender, + clients_handler_sender: ClientsHandlerRequestSender, + ) { + info!("Starting client [web]socket listener..."); + + websocket::Listener::new(self.config.get_clients_listening_address()) + .start(clients_handler_sender, forwarding_channel); + } + + fn start_packet_forwarder(&self) -> OutboundMixMessageSender { + // TODO: put those into configs + let initial_reconnection_backoff = Duration::from_millis(10_000); + let maximum_reconnection_backoff = Duration::from_millis(300_000); + let initial_connection_timeout = Duration::from_millis(1500); + + info!("Starting mix packet forwarder..."); + + let (_, forwarding_channel) = PacketForwarder::new( + initial_reconnection_backoff, + maximum_reconnection_backoff, + initial_connection_timeout, + ) + .start(); + forwarding_channel + } + + fn start_clients_handler(&self) -> ClientsHandlerRequestSender { + info!("Starting clients handler"); + let (_, clients_handler_sender) = ClientsHandler::new( + Arc::new(self.sphinx_keypair.private_key().clone()), + self.registered_clients_ledger.clone(), + self.client_inbox_storage.clone(), + ) + .start(); + clients_handler_sender + } + + fn start_presence_notifier(&self) { + info!("Starting presence notifier..."); + let notifier_config = presence::NotifierConfig::new( + self.config.get_location(), + self.config.get_presence_directory_server(), + self.config.get_mix_announce_address(), + self.config.get_clients_announce_address(), + self.sphinx_keypair.public_key().to_base58_string(), + self.config.get_presence_sending_delay(), + ); + presence::Notifier::new(notifier_config, self.registered_clients_ledger.clone()).start(); + } + + async fn wait_for_interrupt(&self) { + if let Err(e) = tokio::signal::ctrl_c().await { + error!( + "There was an error while capturing SIGINT - {:?}. We will terminate regardless", + e + ); + } + println!( + "Received SIGINT - the gateway will terminate now (threads are not YET nicely stopped)" + ); + } + + // Rather than starting all futures with explicit `&Handle` argument, let's see how it works + // out if we make it implicit using `tokio::spawn` inside Runtime context. + // Basically more or less equivalent of using #[tokio::main] attribute. + pub fn run(&mut self) { + info!("Starting nym gateway!"); + let mut runtime = Runtime::new().unwrap(); + + runtime.block_on(async { + let mix_forwarding_channel = self.start_packet_forwarder(); + let clients_handler_sender = self.start_clients_handler(); + + self.start_mix_socket_listener(clients_handler_sender.clone()); + self.start_client_websocket_listener(mix_forwarding_channel, clients_handler_sender); + + self.start_presence_notifier(); + + info!("Finished nym gateway startup procedure - it should now be able to receive mix and client traffic!"); + + self.wait_for_interrupt().await + }); + } +} diff --git a/gateway/src/presence.rs b/gateway/src/node/presence/mod.rs similarity index 98% rename from gateway/src/presence.rs rename to gateway/src/node/presence/mod.rs index 9971e7b506b..3ab06c487d5 100644 --- a/gateway/src/presence.rs +++ b/gateway/src/node/presence/mod.rs @@ -13,13 +13,12 @@ // limitations under the License. use crate::built_info; -use crate::client_handling::ledger::ClientLedger; +use crate::node::storage::ClientLedger; use directory_client::presence::gateways::{GatewayClient, GatewayPresence}; use directory_client::requests::presence_gateways_post::PresenceGatewayPoster; use directory_client::DirectoryClient; use log::{error, trace}; use std::time::Duration; -use tokio::runtime::Handle; use tokio::task::JoinHandle; pub(crate) struct NotifierConfig { diff --git a/gateway/src/storage/mod.rs b/gateway/src/node/storage/inboxes.rs similarity index 100% rename from gateway/src/storage/mod.rs rename to gateway/src/node/storage/inboxes.rs diff --git a/gateway/src/client_handling/ledger.rs b/gateway/src/node/storage/ledger.rs similarity index 100% rename from gateway/src/client_handling/ledger.rs rename to gateway/src/node/storage/ledger.rs diff --git a/gateway/src/node/storage/mod.rs b/gateway/src/node/storage/mod.rs new file mode 100644 index 00000000000..e62b1d562c8 --- /dev/null +++ b/gateway/src/node/storage/mod.rs @@ -0,0 +1,4 @@ +pub(crate) mod inboxes; +mod ledger; + +pub(crate) use ledger::ClientLedger; From a30bb912ce22cca024c8081062b494a0a7c415cc Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Mon, 27 Apr 2020 12:23:06 +0100 Subject: [PATCH 53/70] Updated provider argument description --- sfw-provider/src/commands/init.rs | 2 +- sfw-provider/src/commands/run.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sfw-provider/src/commands/init.rs b/sfw-provider/src/commands/init.rs index a2a56f79474..12d0f98dfa5 100644 --- a/sfw-provider/src/commands/init.rs +++ b/sfw-provider/src/commands/init.rs @@ -94,7 +94,7 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .arg( Arg::with_name("clients-ledger") .long("clients-ledger") - .help("[UNIMPLEMENTED] Ledger file containing registered clients") + .help("Ledger file containing registered clients") .takes_value(true) ) .arg( diff --git a/sfw-provider/src/commands/run.rs b/sfw-provider/src/commands/run.rs index 4941d228e8a..446ae73fa66 100644 --- a/sfw-provider/src/commands/run.rs +++ b/sfw-provider/src/commands/run.rs @@ -98,7 +98,7 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .arg( Arg::with_name("clients-ledger") .long("clients-ledger") - .help("[UNIMPLEMENTED] Ledger file containing registered clients") + .help("Ledger file containing registered clients") .takes_value(true) ) .arg( From 08f125db250fb088e807621d7264dabb1c5bbe0a Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Mon, 27 Apr 2020 12:27:38 +0100 Subject: [PATCH 54/70] Restored duplicate ip check without importing NymTopology trait --- gateway/src/commands/run.rs | 49 +++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/gateway/src/commands/run.rs b/gateway/src/commands/run.rs index ba90f903bf4..529a8b3dc58 100644 --- a/gateway/src/commands/run.rs +++ b/gateway/src/commands/run.rs @@ -19,6 +19,8 @@ use crate::node::Gateway; use clap::{App, Arg, ArgMatches}; use config::NymConfig; use crypto::encryption; +use directory_client::requests::presence_topology_get::PresenceTopologyGetRequester; +use directory_client::DirectoryClient; use pemstore::pemstore::PemStore; pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { @@ -139,20 +141,24 @@ fn load_sphinx_keys(config_file: &Config) -> encryption::KeyPair { } fn check_if_same_ip_gateway_exists( + directory_server: String, announced_mix_host: String, announced_clients_host: String, ) -> Option { - unimplemented!() - // // TODO: once we change to graph topology this here will need to be updated! - // let topology = Topology::new(self.config.get_presence_directory_server()); - // let existing_gateways = topology.gateway_nodes; - // existing_gateways - // .iter() - // .find(|node| { - // node.mixnet_listener == announced_mix_host - // || node.client_listener == announced_clients_host - // }) - // .map(|node| node.pub_key.clone()) + let directory_client_cfg = directory_client::Config::new(directory_server); + let topology = directory_client::Client::new(directory_client_cfg) + .presence_topology + .get() + .expect("Failed to retrieve network topology"); + + let existing_gateways = topology.gateway_nodes; + existing_gateways + .iter() + .find(|node| { + node.mixnet_listener == announced_mix_host + || node.client_listener == announced_clients_host + }) + .map(|node| node.pub_key.clone()) } pub fn execute(matches: &ArgMatches) { @@ -166,16 +172,17 @@ pub fn execute(matches: &ArgMatches) { config = override_config(config, matches); - // if let Some(duplicate_gateway_key) = check_if_same_ip_gateway_exists( - // config.get_mix_announce_address(), - // config.get_clients_announce_address(), - // ) { - // println!( - // "Our announce-host is identical to an existing node's announce-host! (its key is {:?}", - // duplicate_gateway_key - // ); - // return; - // } + if let Some(duplicate_gateway_key) = check_if_same_ip_gateway_exists( + config.get_presence_directory_server(), + config.get_mix_announce_address(), + config.get_clients_announce_address(), + ) { + println!( + "Our announce-host is identical to an existing node's announce-host! (its key is {:?}", + duplicate_gateway_key + ); + return; + } let sphinx_keypair = load_sphinx_keys(&config); From 20ec7f354ac2857886b39aa6d9b1ed6dd1f86574 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Mon, 27 Apr 2020 12:30:56 +0100 Subject: [PATCH 55/70] Moved hardcoded values into config --- gateway/src/config/mod.rs | 34 +++++++++++++++++++++++++++++++++- gateway/src/node/mod.rs | 11 +++-------- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/gateway/src/config/mod.rs b/gateway/src/config/mod.rs index 6d30c17fe12..b8567270977 100644 --- a/gateway/src/config/mod.rs +++ b/gateway/src/config/mod.rs @@ -29,7 +29,10 @@ const DEFAULT_MIX_LISTENING_PORT: u16 = 1789; const DEFAULT_CLIENT_LISTENING_PORT: u16 = 9000; // 'DEBUG' // where applicable, the below are defined in milliseconds -const DEFAULT_PRESENCE_SENDING_DELAY: u64 = 1500; +const DEFAULT_PRESENCE_SENDING_DELAY: u64 = 1500; // 1.5s +const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: u64 = 10_000; // 10s +const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: u64 = 300_000; // 5min +const DEFAULT_INITIAL_CONNECTION_TIMEOUT: u64 = 1_500; // 1.5s const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16; const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: u16 = 5; @@ -340,6 +343,18 @@ impl Config { self.clients_endpoint.ledger_path.clone() } + pub fn get_packet_forwarding_initial_backoff(&self) -> time::Duration { + time::Duration::from_millis(self.debug.packet_forwarding_initial_backoff) + } + + pub fn get_packet_forwarding_maximum_backoff(&self) -> time::Duration { + time::Duration::from_millis(self.debug.packet_forwarding_maximum_backoff) + } + + pub fn get_initial_connection_timeout(&self) -> time::Duration { + time::Duration::from_millis(self.debug.initial_connection_timeout) + } + pub fn get_message_retrieval_limit(&self) -> u16 { self.debug.message_retrieval_limit } @@ -487,6 +502,20 @@ pub struct Debug { /// Directory server to which the server will be reporting their presence data. presence_directory_server: String, + /// Initial value of an exponential backoff to reconnect to dropped TCP connection when + /// forwarding sphinx packets. + /// The provided value is interpreted as milliseconds. + packet_forwarding_initial_backoff: u64, + + /// Maximum value of an exponential backoff to reconnect to dropped TCP connection when + /// forwarding sphinx packets. + /// The provided value is interpreted as milliseconds. + packet_forwarding_maximum_backoff: u64, + + /// Timeout for establishing initial connection when trying to forward a sphinx packet. + /// The provider value is interpreted as milliseconds. + initial_connection_timeout: u64, + /// Delay between each subsequent presence data being sent. presence_sending_delay: u64, @@ -514,6 +543,9 @@ impl Default for Debug { fn default() -> Self { Debug { presence_directory_server: Self::default_directory_server(), + packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF, + packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF, + initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT, presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY, stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH, message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT, diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index 7fc73073b27..405f7276a18 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -91,17 +91,12 @@ impl Gateway { } fn start_packet_forwarder(&self) -> OutboundMixMessageSender { - // TODO: put those into configs - let initial_reconnection_backoff = Duration::from_millis(10_000); - let maximum_reconnection_backoff = Duration::from_millis(300_000); - let initial_connection_timeout = Duration::from_millis(1500); - info!("Starting mix packet forwarder..."); let (_, forwarding_channel) = PacketForwarder::new( - initial_reconnection_backoff, - maximum_reconnection_backoff, - initial_connection_timeout, + self.config.get_packet_forwarding_initial_backoff(), + self.config.get_packet_forwarding_maximum_backoff(), + self.config.get_initial_connection_timeout(), ) .start(); forwarding_channel From bc9aa841242b41207d1e696eb7b3067c6fdbe9ea Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Mon, 27 Apr 2020 12:32:23 +0100 Subject: [PATCH 56/70] Cargo fmt --- common/client-libs/directory-client/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/client-libs/directory-client/src/lib.rs b/common/client-libs/directory-client/src/lib.rs index 4141ac9aa71..8a4160ab485 100644 --- a/common/client-libs/directory-client/src/lib.rs +++ b/common/client-libs/directory-client/src/lib.rs @@ -18,7 +18,7 @@ use crate::requests::metrics_mixes_post::{MetricsMixPoster, Request as MetricsMi use crate::requests::presence_coconodes_post::{ PresenceCocoNodesPoster, Request as PresenceCocoNodesPost, }; -use crate::requests::presence_gateways_post:: { +use crate::requests::presence_gateways_post::{ PresenceGatewayPoster, Request as PresenceGatewayPost, }; use crate::requests::presence_mixnodes_post::{ From 91acc63f73559ab08d7217607654bd839e6ad68a Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Mon, 27 Apr 2020 12:36:42 +0100 Subject: [PATCH 57/70] Compilation errors in other crates due to topology adjustments --- clients/desktop/src/client/topology_control.rs | 7 ++----- clients/desktop/src/commands/init.rs | 6 +----- common/healthcheck/src/result.rs | 16 ++++++++++++++++ .../services/mixmining/health_check_runner.rs | 7 ++----- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/clients/desktop/src/client/topology_control.rs b/clients/desktop/src/client/topology_control.rs index 98bc254dea4..6a899f9e8a8 100644 --- a/clients/desktop/src/client/topology_control.rs +++ b/clients/desktop/src/client/topology_control.rs @@ -183,11 +183,8 @@ impl TopologyRefresher { async fn get_current_compatible_topology(&self) -> Result { let full_topology = T::new(self.directory_server.clone()); - let version_filtered_topology = full_topology.filter_node_versions( - built_info::PKG_VERSION, - built_info::PKG_VERSION, - built_info::PKG_VERSION, - ); + let version_filtered_topology = + full_topology.filter_system_version(built_info::PKG_VERSION); let healthcheck_result = self .health_checker diff --git a/clients/desktop/src/commands/init.rs b/clients/desktop/src/commands/init.rs index bae272ced52..e4ce291d49f 100644 --- a/clients/desktop/src/commands/init.rs +++ b/clients/desktop/src/commands/init.rs @@ -88,11 +88,7 @@ async fn choose_provider( ) -> (String, AuthToken) { // TODO: once we change to graph topology this here will need to be updated! let topology = Topology::new(directory_server.clone()); - let version_filtered_topology = topology.filter_node_versions( - built_info::PKG_VERSION, - built_info::PKG_VERSION, - built_info::PKG_VERSION, - ); + let version_filtered_topology = topology.filter_system_version(built_info::PKG_VERSION); // don't care about health of the networks as mixes can go up and down any time, // but DO care about providers let providers = version_filtered_topology.providers(); diff --git a/common/healthcheck/src/result.rs b/common/healthcheck/src/result.rs index 3b257910885..6b5f20d1609 100644 --- a/common/healthcheck/src/result.rs +++ b/common/healthcheck/src/result.rs @@ -95,6 +95,21 @@ impl HealthCheckResult { } }) .collect(); + + let filtered_gateway_nodes = topology + .gateways() + .into_iter() + .filter(|node| { + match self.node_score(NodeAddressBytes::from_base58_string(node.pub_key.clone())) { + None => { + error!("Unknown node in topology - {:?}", node); + false + } + Some(score) => score > score_threshold, + } + }) + .collect(); + // coco nodes remain unchanged as no healthcheck is being run on them or time being let filtered_coco_nodes = topology.coco_nodes(); @@ -102,6 +117,7 @@ impl HealthCheckResult { filtered_mix_nodes, filtered_provider_nodes, filtered_coco_nodes, + filtered_gateway_nodes, ) } diff --git a/validator/src/services/mixmining/health_check_runner.rs b/validator/src/services/mixmining/health_check_runner.rs index e3e929f3e9d..60d66a87baa 100644 --- a/validator/src/services/mixmining/health_check_runner.rs +++ b/validator/src/services/mixmining/health_check_runner.rs @@ -42,11 +42,8 @@ impl HealthCheckRunner { loop { let full_topology = directory_client::presence::Topology::new(self.directory_server.clone()); - let version_filtered_topology = full_topology.filter_node_versions( - crate::built_info::PKG_VERSION, - crate::built_info::PKG_VERSION, - crate::built_info::PKG_VERSION, - ); + let version_filtered_topology = + full_topology.filter_system_version(crate::built_info::PKG_VERSION); match self .health_checker .do_check(&version_filtered_topology) From 7261aa25745bbbca26a4f0e3dc6238211b5008a6 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Mon, 27 Apr 2020 12:53:11 +0100 Subject: [PATCH 58/70] Test fixes --- .../src/requests/presence_gateways_post.rs | 4 +- .../src/requests/presence_topology_get.rs | 415 +++++++----------- 2 files changed, 159 insertions(+), 260 deletions(-) diff --git a/common/client-libs/directory-client/src/requests/presence_gateways_post.rs b/common/client-libs/directory-client/src/requests/presence_gateways_post.rs index 1a0115dbc63..c15779f2a51 100644 --- a/common/client-libs/directory-client/src/requests/presence_gateways_post.rs +++ b/common/client-libs/directory-client/src/requests/presence_gateways_post.rs @@ -54,7 +54,7 @@ mod metrics_get_request { #[test] fn it_returns_an_error() { - let _m = mock("POST", "/api/presence/mixproviders") + let _m = mock("POST", "/api/presence/gateways") .with_status(400) .create(); let req = Request::new(mockito::server_url()); @@ -73,7 +73,7 @@ mod metrics_get_request { let json = r#"{ "ok": true }"#; - let _m = mock("POST", "/api/presence/mixproviders") + let _m = mock("POST", "/api/presence/gateways") .with_status(201) .with_body(json) .create(); diff --git a/common/client-libs/directory-client/src/requests/presence_topology_get.rs b/common/client-libs/directory-client/src/requests/presence_topology_get.rs index deffca83ab3..21b714ff4a4 100644 --- a/common/client-libs/directory-client/src/requests/presence_topology_get.rs +++ b/common/client-libs/directory-client/src/requests/presence_topology_get.rs @@ -71,7 +71,7 @@ mod topology_requests { .create(); let req = Request::new(mockito::server_url()); let result = req.get(); - assert_eq!(true, result.is_ok()); + assert!(result.is_ok()); assert_eq!( 1575915097085539300, result.unwrap().coco_nodes.first().unwrap().last_seen @@ -84,263 +84,162 @@ mod topology_requests { #[cfg(test)] pub fn topology_response_json() -> String { r#"{ - "cocoNodes": [ - { - "location": "unknown", - "host": "3.8.244.109:4000", - "pubKey": "AAAAAAAAAAEKwAECSqKy8I8KkSYIBSctxRBRxuR61PpAOwK0UQtkeuPRdwusAyaoBbvv1IBWyMEhvbgT4CtgUnGfYH2s06CIJ09lWWvQ0Jkgthq12mG73H9QSTNM8RITlF1X5ax9BV0EK34M5dUncn1uEYzJzcbaLjUarf2bqoy906dtQpppUWDRLJI6ycw7rKKJ4ZNUhgi4KAEGBsSgLqc0zDKs0rArwouZyz4ofoWnY68mdJKrVy6Zqz83DSdc7B2hqqkHX_Bfeb4SwAEFuhRpy4HfcuxwcRI9sIWMo_LVmbk19g1gfMRlBrmZqoEQL6rDApVLZ9eMp-5IQK8WLlZpWf4Zjy7kZolARAyp_rHUQkH4PrDjgoPrKbm6qK_iejYpL7qx28Q3VeInMpwMIMaSbbW9y36sEVtGc2I0Iu5vS0sp8ESiVlQ5NaBz72deZ8oKJJ4IEPPHP99-b0UQX80fVIrNM88mMzKy0bHri9NFlmIG-e0G1cqmw_ry3XWGQkcr1M5RuNa6oX50w5QawAEVxd5FP5bE8bS4x54Csof11sQWUTwMp6Q7_3H7ZCTSlKSqujlOhmfqSHfGPO2sDIYPHDhDzjakZpKAZWWhn_hiR6DfPpomQ01ZYUhVKKSMxz7_VPjsQplP0bZXA2gfnkADUN8UQ0N9g_usIw73r4aZsOviMsRM8oByvsjVfUWc4_HTLSdnQyImFkHz9CiCmrIYL2dYQRePRatWggvBAyeRzntxI4jDqLKiBdi54ZlAKgV6MCRaJ7Bu7BtmLXrtK4sawAED3QYxuvOSZrbZdUr4yG-U9yVvJ9Klkf-5Mo4EYp3qTL2KBB6_LrZepjAQqp486YkZ03mTIezcsZ48EboXVTWKBZ3QnTI5tX-j4gGxQb7klOJc97qJkDxsvpz4F0ChgCUIZhpIItWHia7_R3Gi-b5siLIdQdUho9isn3kiDGm6t0NED2Bgy3ZxxQwzqsBZm4kPr2_fPX4YyvIoP9895YcGjZyE5iiRC_TE41RJmB1GZYdxegTMq3lNDllKgiqaiPgawAEJASDkmZHTwlg9YOev5OWpQD-FnhPkqVNo_QcDyRu9eoGcWSGFp2sYqjG2SpmiXq0VNnAO7AcKxRzDFu7TjfhlU3Kt0uTKIcrWVU1zFNbJNMjYEq90pp50nowwx8INz20IXET2ZNX6kIXYFCsEvPLZFlG2OoL6xg3uQS1qMl3lIS_VxdO_JfVe0rT65WsJ_P4Nkc1jYiuNPHY6d_iFO0BVYqX0sOCX73GC_TT13BR0jnPwDAVw0rGtYHsXBb8TKOsawAEZIClauuT1V3qOZnb7uRZhFXO-PKTxgc1LCzJt2ChOrMZaBpjlkf3IPpJ2UF4JH4kGaDeBf2k_S-FLAs3drK21efbi5P6_a4QTxAiiRimXGoQIyvOg462s6kP_ZRFufo8YYQHS4olaOeqU4564dNskg_uBPsFMz_2GNOhmn_15cJqP1jfkyD49Z16GTS5YLHgVl9bJKqyvLuypsToLbt1BJzipEP0L2OohuRm-_MvqvwwWKyjNQsubgee1K728d9AawAEBkGggcNVCtXyhoSqi3_w0tVxtkAYeud8sBeAtZHGs06me_QL8co0MFLlO-zdkUb4ZBq08rFEbgLOma8_3whleM8NIPaHNISp1q3IsIhB5zdXcZoGsqLixODBFHtID3YEHAlr4f9T_yh11yJ95xGCl_6Y37hpwLQVGyrfSfccM24mVFqnV3TT5Wdq3ile-jesUx1Q2G1yK_xVqc6itmk-kDuBjyZgzYi1-jsIXAjnhM9G7t8J_Bv5yGGZhLK2dCzM=", - "type": "validator", - "lastSeen": 1575915097085539300, - "version": "0.1.0" - }, - { - "location": "unknown", - "host": "3.9.129.61:4000", - "pubKey": "AAAAAAAAAAMKwAECSqKy8I8KkSYIBSctxRBRxuR61PpAOwK0UQtkeuPRdwusAyaoBbvv1IBWyMEhvbgT4CtgUnGfYH2s06CIJ09lWWvQ0Jkgthq12mG73H9QSTNM8RITlF1X5ax9BV0EK34M5dUncn1uEYzJzcbaLjUarf2bqoy906dtQpppUWDRLJI6ycw7rKKJ4ZNUhgi4KAEGBsSgLqc0zDKs0rArwouZyz4ofoWnY68mdJKrVy6Zqz83DSdc7B2hqqkHX_Bfeb4SwAEEv6RMevAQmLGkeK0uJKnMPPAtm8GgXjWSQijYdnxlPh5SJSNeJUbPZKWFFWdk8yIFXKa8jnzETtdGFKgUUt5AVUDpTBmEdwaHCzlFhXrttshy0V5OhPUlV8cGABmxbagMYm0bFPg0r-snSkrB9YG6wqJYQVeIMOCGYCPbHmDA8R_0-h8VkRKWs1d9KvQOK4kShqgZtYN71KJW8uDE4q2jsGDVvxFt1AgmU9b93xsXF17KrpZy5WxlLZ73HtnTD_oawAED4vd_rK-Kx_n8x_OdDiiEOPUlYDlDCQUqenU9XHKH3B6ijfkJ368wd3LDDVStjDwNORrAyUSw_VlSNUpd1XLC8d17gTaIq5ZI2fWuwwZaoN1JCsYU8fQ6USgtIehQX7IPP8EkFuNmuCBCmpr4schtYniGe9J8Q4dsV-TYPr2uLJkdx1r7luzF--I22k7NfQQM14QDci_0kgrgmZ54CJGkjXyOhCppBXg3fqLC6aFvT3ZocfiiXBJt0huGgPMDtYsawAECLh8KUdNsDolERwJ8v04bS5jI_KKf7uUnCHWuCELwbJSUI3OK1ufS1qSpauvSzVQSbrhEzrEfwQn4VtxQxJlX4UdDU-R-hafiZvVC6DLLAbuORBAC3FScn9W58CnezH4DvCp_w7nftDfdxeuungbZT9XaxS3iNC6PnFsWF6WM3DxMwrzOrFe6wEEoTSPe1mcUDrtwM5UksIvJr6MBRAXrdl0IdBTQr7cLwKe_KYi4siwdjfJEJtOh7oxQBxBg2UkawAEJAPZK2Gg2MQwpxdDT24lNQHF7FVfkO_LuhJwn0RbwNDSVeA4P6-tWL5TkCpqr8xYHfwQ6Z3ILfpGCZr8PspwIoRzqZHQ16f8Pq9xnr0hLEI9BOQU0FS2EtuyPgju5iwsAJAfehUzu6kNLphuLGsXoIZdXDG5mbylwh9JzAVXTwgaR0hNqyXVJxgbt7jcYaSEBFcMGV-hjXyVVNzBleE-G9o_noI_KWU4Ce7K-qOMcewMKfy_VEw-gVaD6dHz6AMoawAEE9XuOLwRttvKybAssZ9gsK-_YRUwuFOeRDIr3NX___9bx6pCc18adCIlH_8EJWFwXZ05ZpNNE88mYx7ZQ3aqaArZJRoWeZeKhqH_s05V10xbzkYX71G5cqz--8vr9ZlQRb2BeETF_Tdq_PLk7qbT8WTGIoq7ZwyDRQTgzvkCgyzj_hBLh2o7sSVNgUo38SFUTMn7YtvVFYlSrTDE3WKE-T-nh5SWdDBxgDTc3Bw8JpzNH-WkoJ4Lim7sB4Op1gEUawAEW4-kenlffwsNr_3b3aV0YuusLpxB03sxPzQ5B0CWNiVtbja1Z4tWhKGUUrdq_eUgMV0y5Of-BqNi5FspAQnhJBFSSxtOzRGV1h3qyUTksfZyed9z8zPI-ZPP9XXm7hYgJgDz_kxte-NfS9UG9q5AZetHUN4kGxXutjjzfUQZ9yTvhBKgKgTI2Dp_R_jZrWQ8F1BoWzIJzjddT1K2MvCQEkARYw08isbOeFmCwgVUcjxYZO45WyOmLQA7QJRL9WvA=", - "type": "validator", - "lastSeen": 1575915097388409000, - "version": "0.1.0" - }, - { - "location": "unknown", - "host": "3.9.222.1:4000", - "pubKey": "AAAAAAAAAAQKwAECSqKy8I8KkSYIBSctxRBRxuR61PpAOwK0UQtkeuPRdwusAyaoBbvv1IBWyMEhvbgT4CtgUnGfYH2s06CIJ09lWWvQ0Jkgthq12mG73H9QSTNM8RITlF1X5ax9BV0EK34M5dUncn1uEYzJzcbaLjUarf2bqoy906dtQpppUWDRLJI6ycw7rKKJ4ZNUhgi4KAEGBsSgLqc0zDKs0rArwouZyz4ofoWnY68mdJKrVy6Zqz83DSdc7B2hqqkHX_Bfeb4SwAECh9xcxpjOp1r7kiNIgrI9GgAlvXwgHkTchOxUiyOzTq6FDWdGN64KiC3NDeyGTg8FmzvGzS3jREeJqOdr4G9ZGtWkauAITgLFiH62t-YntRslhr8_1shxlmzKiNKJN_QFflEq79pZIlWtp3N8LIHMvXRtl-zt2DMze4s02XDmEkviyVE4CkQUDtCc-2MfPT4JcmEFqtFIxjrXn18SbYg3c6XUQHsGIkuDrKuCTRlpC8kvmM0uVoIeWdmwDlZk4jUawAEJhRwK5ozjqIWRP1bFzBPS9VhaJnfKU9PeFYtN5beiAHrYr2ylIB3yDfmAQUdKDowDUm5nfJATejEjEnrTGxh70QtfoNV391rSns3F71tBwY62KLaNr8qnVfeSFHV3FcQTMHHF_8mDb5_11Rj6aiMvW0y6eetHo7CDPMdEyDPmok_U2ZM5BzOUnwjT21HtnvcKxKKwHJ_QGfnAHPyDIhNOMgxJCrVazOidLCHeYGpyCLw1ipeTyKOQX0_ByB8dH6AawAEGV1GuF5SSlT67B1ityPJK2ZwXjeeKB4gGdCG3qRtWxLTZfGhVm7YAYm2f5tw_wrsJAZ9FubVhateGg0ZN67NxZtsvOOejXz6743f7ijnQopPgd_8pH-iVf6BEcSO8ZdcHxNRUTayzjVLs99bwMo2zaPevW4X4G_bN4mh---aPkdGYHwaiklzUhqJ-eqycrYAFyjyEXaPBXLQm1rpczqluNvnKbd8Q9LZWukgm7_uWv_HxufIvdWgoq8bAt78UU3oawAEP9VDehhqrQG5-WHMB66XVxo1TgMM8aVV0SwAq3lCRkpiFBz_9kw8T1F9Hx2AiNrEGT1QLbdMkpms1cG_5gBBahQofdt_NmUs1jfTFXY9iyMy1Q7A6ZYaLP8Z6q-orc1cKqySY-BJZQ_CpGFfXS0OVniFDQ6v78ytPK7K-yRgT1PxFgm3rZqrG0Tjbrpsg2PUL5S5fuXfMhUosP0uoLj0D1guWAR9Y7kfFBIXaTSFMoa8fghVBUTRNhK9f72a8SxQawAEOiv71taLjKqaaWQ_QjcDhWbvjG1EnsCyI0toNjGkcF19x4Vk-5NC96_4ioUGz404IC0XN03roRnibRT_78D9vZFVCWCqve9EjdF5TcApx03zIP4JT2g2q0MKIGgGrwt4Pz6LO6yOfMm7B8Yraps8IV-nP1w7K1m9XKP_FvH8egl5GHJe-_omlC2YyL_b28jMLENbxDFD-3KPjZFBhSLrRukX2PlayYTwEiTtokA2R9_11vQvJgP8KFEjGHg6zsAMawAEBn2H_hz2knb8ltnpEA5YSKVcV3nUtojkCNi_WUz7xUKd7efw1oI_lbnKrS7HkyC0JkQUZ1pCWUlSXNmgjMEhsn823a1LFzpV7rOv4vayYvvFX61hB9R78VjpyxJiYpDwRZLiUY3AK4WY8NqFDbjXR7rT4CkFHEf-VhSQQ8ZNvlpod1nmeVQVizHH9e7Tq7wsWz-LWEk3Hx6LmcrgDsL79LZYG9JXU5IdvG8RvLNx9cSwEI8yxcchpISAaot7UoYQ=", - "type": "validator", - "lastSeen": 1575915094734973000, - "version": "0.1.0" - }, - { - "location": "unknown", - "host": "3.9.102.214:4000", - "pubKey": "AAAAAAAAAAIKwAECSqKy8I8KkSYIBSctxRBRxuR61PpAOwK0UQtkeuPRdwusAyaoBbvv1IBWyMEhvbgT4CtgUnGfYH2s06CIJ09lWWvQ0Jkgthq12mG73H9QSTNM8RITlF1X5ax9BV0EK34M5dUncn1uEYzJzcbaLjUarf2bqoy906dtQpppUWDRLJI6ycw7rKKJ4ZNUhgi4KAEGBsSgLqc0zDKs0rArwouZyz4ofoWnY68mdJKrVy6Zqz83DSdc7B2hqqkHX_Bfeb4SwAEOVCUN3EwiVroS5-TOq2o7hYSxphK9X0G23N-IBZ0Tr1Rl8XEiJ-OEy0rqnAKwmhAZJWnx3u8oXqbZtOWIZmzQSpcoxhgwfhdmTZJCqT2RVzZyeFItX4sVeilEP3z2xdsJs8-a1kg6UZnx1s1BNLBo7eZrreZygWojPCIDBn03fSAflXoVc5PpY2CGy5MA_IgWgSYBHDdoZEtigp_amjqK7Us44Db20XpLxMXfbahiqa7WKNnMgi6Ca2H67VtaaD8awAEF3zbE1nZRAa7a8vbU25c80YBYJBaW8P6FwXQI-K0Xk5MakwYeMMnIrm6w6IS_0XAO5YlD453GLqnxY8H1BEnRpfOnT7PE4el9mJ8MuYQMo6R2up0lGCmYM0YA9FORjroM3ng69SEPfJPCReG7LfJkERl_m2U403ertDRBYrlqCDagDfyI500srBcMrjSvV3oNouyyx3yZUrjLQfbHhDteQFsYdmakJs8Y-Q9-5MXCcrz6Qa4xwv522Euv0CCxkHcawAEYjfsU_zDhUZA1ey1aquWXlFOnx-iEALqxW1slDYHwQ1M2SILc-v_E6i1doa5e_bAZHVezBHFAlaNAVedNyHFFJxYAqAK3hbzbvl2glw3Q6h_rTXElymloqtaqVFIJ-oUWWOHsZBmu8EDA-HzvGCiBa_GbRaVfh2lE4ObeMXoJrEm_5dbxxeEic2l3IYeIz40N9ooQQOkQcOZdY4AXWYCavIAwWEJBjLtptJgCLu9a_zM1S5GsiyJHpdDs46WbP0EawAEWZ-95Sf0YAHujxRNLdXgpqe0ZF8loVwzZfvyMvqaxF1Ug274BqHuY_c5NdPAzuqoTwjfEn8NKEoaNqlumM75FUYbaTd7mXvk4WVYWjVnkO40dfQjRB7DYhvj0LBlbndAJ4wJIA2ilPYgjZsXVbNNh3e2j3u9eABd0VaFMbSb8Sz5_31r8HzoWmPJs3HiyuyANGFUA6CvAnMN6K3b-D8BhFZU_nPUTgu80o8_n6LQt-XWbaC_mTHzsnOjzBiPJxlYawAEW3bmOEtStH2T8q7vMkhchImp2-hg9MFYGBmEe9sSByTn3NUf8eksqXOC1dUjHkXoZm298FgUYLkNdnlxWpf993j5mEDoFxjcTB7scBD7k6nu6Nrs_wK0-seS8gsHrx9UK7GwAsi10q82Cm4PFyAtrWjmy_d9WLHuZt6VIOKunTs8cf0FwNUiMcvZsruqIFJcP7iWxdiFdUkh65P_iCz1ZEjJcj2GEZoq4v3a3by1aizGPaaiKc1jd_T-XJg_YpncawAEWnstu5b9WiZv0x8xfsiMk6YRlU0Cnj5svxLLXz_8drvwAa--GBY5yH0ke2EM6udMEi2EPeFcGTe6Sjs0YEhSbY7Uad_8suD2J4tIWJSWBbiyvh7rSqzv57m7BlsVcHfQJn_wNH-UlC9xkx8vg-LwfN8_FlxvHNPTc7XZG3lKYbwpUWlZxAziOYT1VQ-2K2bQQBBMdix-ht_SjccL1Dc2dP5kDazQ8yZV_8xnyeheazEedWe63uutfkHlZRg9YwP8=", - "type": "validator", - "lastSeen": 1575915094967382800, - "version": "0.1.0" - } - ], - "mixNodes": [ - { - "location": "unknown", - "host": "35.176.155.107:1789", - "pubKey": "zSob16499jT7C3S3ky4GihNOjlU6aLfSRkf1xAxOwV0=", - "layer": 3, - "lastSeen": 1575915096805374500, - "version": "0.1.0" - }, - { - "location": "unknown", - "host": "18.130.86.190:1789", - "pubKey": "vCdpFc0NvW0NSqsuTxtjFtiSY35aXesgT3JNA8sSIXk=", - "layer": 1, - "lastSeen": 1575915097370376000, - "version": "0.1.0" - }, - { - "location": "unknown", - "host": "3.10.22.152:1789", - "pubKey": "OwOqwWjh_IlnaWS2PxO6odnhNahOYpRCkju50beQCTA=", - "layer": 1, - "lastSeen": 1575915097639423500, - "version": "0.1.0" - }, - { - "location": "unknown", - "host": "35.178.213.77:1789", - "pubKey": "nkkrUjgL8UJk05QydvWvFSvtRB6nmeV8RMvH5540J3s=", - "layer": 2, - "lastSeen": 1575915097895166500, - "version": "0.1.0" - }, - { - "location": "unknown", - "host": "52.56.99.196:1789", - "pubKey": "whHuBuEc6zyOZOquKbuATaH4Crml61V_3Y-MztpWhF4=", - "layer": 2, - "lastSeen": 1575915096255174700, - "version": "0.1.0" - }, - { - "location": "unknown", - "host": "3.9.12.238:1789", - "pubKey": "vk5Sr-Xyi0cTbugACv8U42ZJ6hs6cGDox0rpmXY94Fc=", - "layer": 3, - "lastSeen": 1575915096497827600, - "version": "0.1.0" - } - ], - "mixProviderNodes": [ - { - "location": "unknown", - "clientListener": "3.8.176.11:8888", - "mixnetListener": "3.8.176.11:9999", - "pubKey": "54U6krAr-j9nQXFlsHk3io04_p0tctuqH71t7w_usgI=", - "registeredClients": [ - { - "pubKey": "zOqdJFH49HcgGSCRnmbXGzovnwRLEPN0YGN1SCafTyo=" - }, - { - "pubKey": "fy9xo69hZ2UJ9uxhIS1YzKHZsH8saV-02AiyCNXPNUc=" - }, - { - "pubKey": "6hFCz42d5AODAoMXqcBWtoOoZh-7hPMDXbLKKhS7x3I=" - }, - { - "pubKey": "pPZMDzw0FyZ-LBAhwCvjlPGj1p2bO_vaWbRBc7Ojq3Q=" - }, - { - "pubKey": "kjl5poXt2GdIrHLuMBjmTBSChV-zMqsXFBhKZoIREVI=" - }, - { - "pubKey": "T1jMGuk7-rcIUWnVopAsdGzhAJ7ZVymwON2LzjuOfnY=" - }, - { - "pubKey": "3X0eKPkflx5Vzok0Gk0jm5-YEfvPAuWP55ovyBUOtXA=" - }, - { - "pubKey": "5wC3i8rCZolLKbWJ9U6eLieXNGLKM21dtL6lR30u_hE=" - }, - { - "pubKey": "1P6p7fgjwjlEepD9JgbN1V-rk9n36-hCmPN5P6y62n4=" - }, - { - "pubKey": "aBjlLQKKFroqhX_kvYLnMm1uq3FJdQWqVy9Q35zzERA=" - }, - { - "pubKey": "I0gVOPj6lv9ha60xPYKeAgbeUU8pdyMD-Y7Nb1nS9EQ=" - }, - { - "pubKey": "WgdvQ74QH1uFDWDL2YeApvv7oniNGh9BQJ_HZam20QA=" - }, - { - "pubKey": "Mlw23KaSL2hyrIjEZM76bZStt2iMzxVAqXwO5clJfxg=" - }, - { - "pubKey": "F9xzbjnMQVN4ZidcqN2ip9kVnI9wbS39aVayZGiMihY=" - }, - { - "pubKey": "s6pfVkZrUG--RNjfzS55N2oPvFkMdvgb1LUut6gqRy4=" - }, - { - "pubKey": "bSi-9k0jJNKc8PGx8M3SWFaNpORFjYw-NkWXRZVRWGU=" - }, - { - "pubKey": "pz6ahQcGOQcZBFx1tGmzRngqk6BecXB_wFd4WVdOQDU=" - }, - { - "pubKey": "5sfwIMcG2zRCxhDh4D9Evw1WPI5bfKZAShM_6o9Pu1I=" - }, - { - "pubKey": "9fZnxXy9onGPpZ3Ygckqw0okqCw3di02sLr-NTBr4SE=" - }, - { - "pubKey": "Q0TbbggOwzZjalUdi5eEHVFi9VMv-rMm5mJPUZZs12A=" - }, - { - "pubKey": "aPNyox_qAIGFB2-wZ0lc9iAWDN6jzLojApSiWVFjCks=" - }, - { - "pubKey": "DUKLEsIGMw-ucs3DjS7Ag9qCb5-C_A84DuIsZuLkdwI=" - }, - { - "pubKey": "YV84vPoSrLf1p9Sw6FnnrcCpS3kvpJUfKyKpnwk8z0Q=" - }, - { - "pubKey": "_IYEzZQoBAYeTxqzpEe_ez1-7pn7aId8AKliazy0qlE=" - }, - { - "pubKey": "srkOAoVU-a02lnEsoH_wOLLw7HFx_xHIZUSbnhjFwDw=" - }, - { - "pubKey": "LxSCBn_OEQq-hI2xDi6bfGoioRO_lSTIq6AQ9l1k5jg=" - }, - { - "pubKey": "OC0OOqtGfAytgZjthpjoKeYNa0VrtzfgZ0iO5Fag5y8=" - }, - { - "pubKey": "ImVEch2focRhm1ial1gA6YJPr6WDyW3oh-OgYcO5Ll4=" - }, - { - "pubKey": "yTDnzLvEaSq0mC9xNrtyjpAKtsIU6yRuBepCuWQMBm4=" - }, - { - "pubKey": "-LCUUc46HuL7iUEOMkrlVAkvvulRiQ7dR1QYh7bkKxw=" - }, - { - "pubKey": "Bx1MGpISig25rqe7mhoX68EROUPPzmF7yGLYah9DPgM=" - }, - { - "pubKey": "Z4Nu6iwLmgJ93yoIFTbTEBeDAHRwS-vo1T_K2Kv1FQo=" - }, - { - "pubKey": "cKFAGxllwAmEXCtDxG_T1iEm3-lKWUVQxxpDBje6mQU=" - }, - { - "pubKey": "pQV40whlQWUSXtrNTTePzO6sdq3zr1JUIWZWvD443nY=" - }, - { - "pubKey": "6Bb5HwnVqJPy5wcNsaHY-0y__coZCE7XC80kUkesnRU=" - }, - { - "pubKey": "COGdpfhmzNGR6YX820GqJIkjOihL8mr6-h-d3JlTDFA=" - } - ], - "lastSeen": 1575915097358694100, - "version": "0.1.0" - }, - { - "location": "unknown", - "clientListener": "3.8.176.12:8888", - "mixnetListener": "3.8.176.12:9999", - "pubKey": "sA-sxi038pEbGy4lgZWG-RdHHDkA6kZzu44G0LUxFSc=", - "registeredClients": [ - { - "pubKey": "UE-7r6-bpw0b4T3GxOBVxlg02psx23DF2p5Tuf-OBSE=" - }, - { - "pubKey": "UnZuLpzq64_EPtIcr1Fd-5AESBCBLFnDMDsjUaOqrUA=" - }, - { - "pubKey": "4ExXPrW5w0nZIQ5ravBRT6H9r0RH0MXuOcGIF8HzUhg=" - }, - { - "pubKey": "wPsRpJPi1e2sjItyRKDkFACbxwu3Cw5GlYVPmdYxk2U=" - }, - { - "pubKey": "UfK5UvT3HUkT1SGbv1QGafy3in3uQ9a6NSy5EOT6k0k=" - }, - { - "pubKey": "5Giu-tJpGXU0S9Av75iAv5qDO0k6l8v_k9-UCcRUCl4=" - }, - { - "pubKey": "MnQxlmmKDybku4CfxsQQxfftilsaphF9Gq1w3MB1ZCE=" - }, - { - "pubKey": "GdP1fHVs2R65EkuWVWKZSz6WPDh0MgThyuBOv6_xsmQ=" - }, - { - "pubKey": "4JEtSrKsonmBuDvxJ9nITSu7iC4f8reutXRAVugPgS4=" - }, - { - "pubKey": "q4XyuUJbSGJaoRb3SmWzeX88V2dKB7sPTf72BAtQp3k=" - }, - { - "pubKey": "gTzKd1Ph5bpUw-JxTZiCe8RBfO-FsZiVYDYioQ-6dVg=" - }, - { - "pubKey": "NX4oaDLYEOmMUP_9pcEaZv5MJHJ4ZYAoxPQDxov7tRs=" - }, - { - "pubKey": "7fbk4oGQNlTW-tnWjVz8rWtKrtAicTsiNWgO98sqMyk=" - }, - { - "pubKey": "w1bfLpnd3rWu5JczB0nQfnE2S6nUCbx2AA7HDE48DQo=" - } - ], - "lastSeen": 1575915097869025000, - "version": "0.1.0" - } - ] - }"#.to_string() + "cocoNodes": [ + { + "location": "unknown", + "host": "3.8.244.109:4000", + "pubKey": "AAAAAAAAAAEKwAECSqKy8I8KkSYIBSctxRBRxuR61PpAOwK0UQtkeuPRdwusAyaoBbvv1IBWyMEhvbgT4CtgUnGfYH2s06CIJ09lWWvQ0Jkgthq12mG73H9QSTNM8RITlF1X5ax9BV0EK34M5dUncn1uEYzJzcbaLjUarf2bqoy906dtQpppUWDRLJI6ycw7rKKJ4ZNUhgi4KAEGBsSgLqc0zDKs0rArwouZyz4ofoWnY68mdJKrVy6Zqz83DSdc7B2hqqkHX_Bfeb4SwAEFuhRpy4HfcuxwcRI9sIWMo_LVmbk19g1gfMRlBrmZqoEQL6rDApVLZ9eMp-5IQK8WLlZpWf4Zjy7kZolARAyp_rHUQkH4PrDjgoPrKbm6qK_iejYpL7qx28Q3VeInMpwMIMaSbbW9y36sEVtGc2I0Iu5vS0sp8ESiVlQ5NaBz72deZ8oKJJ4IEPPHP99-b0UQX80fVIrNM88mMzKy0bHri9NFlmIG-e0G1cqmw_ry3XWGQkcr1M5RuNa6oX50w5QawAEVxd5FP5bE8bS4x54Csof11sQWUTwMp6Q7_3H7ZCTSlKSqujlOhmfqSHfGPO2sDIYPHDhDzjakZpKAZWWhn_hiR6DfPpomQ01ZYUhVKKSMxz7_VPjsQplP0bZXA2gfnkADUN8UQ0N9g_usIw73r4aZsOviMsRM8oByvsjVfUWc4_HTLSdnQyImFkHz9CiCmrIYL2dYQRePRatWggvBAyeRzntxI4jDqLKiBdi54ZlAKgV6MCRaJ7Bu7BtmLXrtK4sawAED3QYxuvOSZrbZdUr4yG-U9yVvJ9Klkf-5Mo4EYp3qTL2KBB6_LrZepjAQqp486YkZ03mTIezcsZ48EboXVTWKBZ3QnTI5tX-j4gGxQb7klOJc97qJkDxsvpz4F0ChgCUIZhpIItWHia7_R3Gi-b5siLIdQdUho9isn3kiDGm6t0NED2Bgy3ZxxQwzqsBZm4kPr2_fPX4YyvIoP9895YcGjZyE5iiRC_TE41RJmB1GZYdxegTMq3lNDllKgiqaiPgawAEJASDkmZHTwlg9YOev5OWpQD-FnhPkqVNo_QcDyRu9eoGcWSGFp2sYqjG2SpmiXq0VNnAO7AcKxRzDFu7TjfhlU3Kt0uTKIcrWVU1zFNbJNMjYEq90pp50nowwx8INz20IXET2ZNX6kIXYFCsEvPLZFlG2OoL6xg3uQS1qMl3lIS_VxdO_JfVe0rT65WsJ_P4Nkc1jYiuNPHY6d_iFO0BVYqX0sOCX73GC_TT13BR0jnPwDAVw0rGtYHsXBb8TKOsawAEZIClauuT1V3qOZnb7uRZhFXO-PKTxgc1LCzJt2ChOrMZaBpjlkf3IPpJ2UF4JH4kGaDeBf2k_S-FLAs3drK21efbi5P6_a4QTxAiiRimXGoQIyvOg462s6kP_ZRFufo8YYQHS4olaOeqU4564dNskg_uBPsFMz_2GNOhmn_15cJqP1jfkyD49Z16GTS5YLHgVl9bJKqyvLuypsToLbt1BJzipEP0L2OohuRm-_MvqvwwWKyjNQsubgee1K728d9AawAEBkGggcNVCtXyhoSqi3_w0tVxtkAYeud8sBeAtZHGs06me_QL8co0MFLlO-zdkUb4ZBq08rFEbgLOma8_3whleM8NIPaHNISp1q3IsIhB5zdXcZoGsqLixODBFHtID3YEHAlr4f9T_yh11yJ95xGCl_6Y37hpwLQVGyrfSfccM24mVFqnV3TT5Wdq3ile-jesUx1Q2G1yK_xVqc6itmk-kDuBjyZgzYi1-jsIXAjnhM9G7t8J_Bv5yGGZhLK2dCzM=", + "type": "validator", + "lastSeen": 1575915097085539300, + "version": "0.1.0" + }, + { + "location": "unknown", + "host": "3.9.129.61:4000", + "pubKey": "AAAAAAAAAAMKwAECSqKy8I8KkSYIBSctxRBRxuR61PpAOwK0UQtkeuPRdwusAyaoBbvv1IBWyMEhvbgT4CtgUnGfYH2s06CIJ09lWWvQ0Jkgthq12mG73H9QSTNM8RITlF1X5ax9BV0EK34M5dUncn1uEYzJzcbaLjUarf2bqoy906dtQpppUWDRLJI6ycw7rKKJ4ZNUhgi4KAEGBsSgLqc0zDKs0rArwouZyz4ofoWnY68mdJKrVy6Zqz83DSdc7B2hqqkHX_Bfeb4SwAEEv6RMevAQmLGkeK0uJKnMPPAtm8GgXjWSQijYdnxlPh5SJSNeJUbPZKWFFWdk8yIFXKa8jnzETtdGFKgUUt5AVUDpTBmEdwaHCzlFhXrttshy0V5OhPUlV8cGABmxbagMYm0bFPg0r-snSkrB9YG6wqJYQVeIMOCGYCPbHmDA8R_0-h8VkRKWs1d9KvQOK4kShqgZtYN71KJW8uDE4q2jsGDVvxFt1AgmU9b93xsXF17KrpZy5WxlLZ73HtnTD_oawAED4vd_rK-Kx_n8x_OdDiiEOPUlYDlDCQUqenU9XHKH3B6ijfkJ368wd3LDDVStjDwNORrAyUSw_VlSNUpd1XLC8d17gTaIq5ZI2fWuwwZaoN1JCsYU8fQ6USgtIehQX7IPP8EkFuNmuCBCmpr4schtYniGe9J8Q4dsV-TYPr2uLJkdx1r7luzF--I22k7NfQQM14QDci_0kgrgmZ54CJGkjXyOhCppBXg3fqLC6aFvT3ZocfiiXBJt0huGgPMDtYsawAECLh8KUdNsDolERwJ8v04bS5jI_KKf7uUnCHWuCELwbJSUI3OK1ufS1qSpauvSzVQSbrhEzrEfwQn4VtxQxJlX4UdDU-R-hafiZvVC6DLLAbuORBAC3FScn9W58CnezH4DvCp_w7nftDfdxeuungbZT9XaxS3iNC6PnFsWF6WM3DxMwrzOrFe6wEEoTSPe1mcUDrtwM5UksIvJr6MBRAXrdl0IdBTQr7cLwKe_KYi4siwdjfJEJtOh7oxQBxBg2UkawAEJAPZK2Gg2MQwpxdDT24lNQHF7FVfkO_LuhJwn0RbwNDSVeA4P6-tWL5TkCpqr8xYHfwQ6Z3ILfpGCZr8PspwIoRzqZHQ16f8Pq9xnr0hLEI9BOQU0FS2EtuyPgju5iwsAJAfehUzu6kNLphuLGsXoIZdXDG5mbylwh9JzAVXTwgaR0hNqyXVJxgbt7jcYaSEBFcMGV-hjXyVVNzBleE-G9o_noI_KWU4Ce7K-qOMcewMKfy_VEw-gVaD6dHz6AMoawAEE9XuOLwRttvKybAssZ9gsK-_YRUwuFOeRDIr3NX___9bx6pCc18adCIlH_8EJWFwXZ05ZpNNE88mYx7ZQ3aqaArZJRoWeZeKhqH_s05V10xbzkYX71G5cqz--8vr9ZlQRb2BeETF_Tdq_PLk7qbT8WTGIoq7ZwyDRQTgzvkCgyzj_hBLh2o7sSVNgUo38SFUTMn7YtvVFYlSrTDE3WKE-T-nh5SWdDBxgDTc3Bw8JpzNH-WkoJ4Lim7sB4Op1gEUawAEW4-kenlffwsNr_3b3aV0YuusLpxB03sxPzQ5B0CWNiVtbja1Z4tWhKGUUrdq_eUgMV0y5Of-BqNi5FspAQnhJBFSSxtOzRGV1h3qyUTksfZyed9z8zPI-ZPP9XXm7hYgJgDz_kxte-NfS9UG9q5AZetHUN4kGxXutjjzfUQZ9yTvhBKgKgTI2Dp_R_jZrWQ8F1BoWzIJzjddT1K2MvCQEkARYw08isbOeFmCwgVUcjxYZO45WyOmLQA7QJRL9WvA=", + "type": "validator", + "lastSeen": 1575915097388409000, + "version": "0.1.0" + }, + { + "location": "unknown", + "host": "3.9.222.1:4000", + "pubKey": "AAAAAAAAAAQKwAECSqKy8I8KkSYIBSctxRBRxuR61PpAOwK0UQtkeuPRdwusAyaoBbvv1IBWyMEhvbgT4CtgUnGfYH2s06CIJ09lWWvQ0Jkgthq12mG73H9QSTNM8RITlF1X5ax9BV0EK34M5dUncn1uEYzJzcbaLjUarf2bqoy906dtQpppUWDRLJI6ycw7rKKJ4ZNUhgi4KAEGBsSgLqc0zDKs0rArwouZyz4ofoWnY68mdJKrVy6Zqz83DSdc7B2hqqkHX_Bfeb4SwAECh9xcxpjOp1r7kiNIgrI9GgAlvXwgHkTchOxUiyOzTq6FDWdGN64KiC3NDeyGTg8FmzvGzS3jREeJqOdr4G9ZGtWkauAITgLFiH62t-YntRslhr8_1shxlmzKiNKJN_QFflEq79pZIlWtp3N8LIHMvXRtl-zt2DMze4s02XDmEkviyVE4CkQUDtCc-2MfPT4JcmEFqtFIxjrXn18SbYg3c6XUQHsGIkuDrKuCTRlpC8kvmM0uVoIeWdmwDlZk4jUawAEJhRwK5ozjqIWRP1bFzBPS9VhaJnfKU9PeFYtN5beiAHrYr2ylIB3yDfmAQUdKDowDUm5nfJATejEjEnrTGxh70QtfoNV391rSns3F71tBwY62KLaNr8qnVfeSFHV3FcQTMHHF_8mDb5_11Rj6aiMvW0y6eetHo7CDPMdEyDPmok_U2ZM5BzOUnwjT21HtnvcKxKKwHJ_QGfnAHPyDIhNOMgxJCrVazOidLCHeYGpyCLw1ipeTyKOQX0_ByB8dH6AawAEGV1GuF5SSlT67B1ityPJK2ZwXjeeKB4gGdCG3qRtWxLTZfGhVm7YAYm2f5tw_wrsJAZ9FubVhateGg0ZN67NxZtsvOOejXz6743f7ijnQopPgd_8pH-iVf6BEcSO8ZdcHxNRUTayzjVLs99bwMo2zaPevW4X4G_bN4mh---aPkdGYHwaiklzUhqJ-eqycrYAFyjyEXaPBXLQm1rpczqluNvnKbd8Q9LZWukgm7_uWv_HxufIvdWgoq8bAt78UU3oawAEP9VDehhqrQG5-WHMB66XVxo1TgMM8aVV0SwAq3lCRkpiFBz_9kw8T1F9Hx2AiNrEGT1QLbdMkpms1cG_5gBBahQofdt_NmUs1jfTFXY9iyMy1Q7A6ZYaLP8Z6q-orc1cKqySY-BJZQ_CpGFfXS0OVniFDQ6v78ytPK7K-yRgT1PxFgm3rZqrG0Tjbrpsg2PUL5S5fuXfMhUosP0uoLj0D1guWAR9Y7kfFBIXaTSFMoa8fghVBUTRNhK9f72a8SxQawAEOiv71taLjKqaaWQ_QjcDhWbvjG1EnsCyI0toNjGkcF19x4Vk-5NC96_4ioUGz404IC0XN03roRnibRT_78D9vZFVCWCqve9EjdF5TcApx03zIP4JT2g2q0MKIGgGrwt4Pz6LO6yOfMm7B8Yraps8IV-nP1w7K1m9XKP_FvH8egl5GHJe-_omlC2YyL_b28jMLENbxDFD-3KPjZFBhSLrRukX2PlayYTwEiTtokA2R9_11vQvJgP8KFEjGHg6zsAMawAEBn2H_hz2knb8ltnpEA5YSKVcV3nUtojkCNi_WUz7xUKd7efw1oI_lbnKrS7HkyC0JkQUZ1pCWUlSXNmgjMEhsn823a1LFzpV7rOv4vayYvvFX61hB9R78VjpyxJiYpDwRZLiUY3AK4WY8NqFDbjXR7rT4CkFHEf-VhSQQ8ZNvlpod1nmeVQVizHH9e7Tq7wsWz-LWEk3Hx6LmcrgDsL79LZYG9JXU5IdvG8RvLNx9cSwEI8yxcchpISAaot7UoYQ=", + "type": "validator", + "lastSeen": 1575915094734973000, + "version": "0.1.0" + }, + { + "location": "unknown", + "host": "3.9.102.214:4000", + "pubKey": "AAAAAAAAAAIKwAECSqKy8I8KkSYIBSctxRBRxuR61PpAOwK0UQtkeuPRdwusAyaoBbvv1IBWyMEhvbgT4CtgUnGfYH2s06CIJ09lWWvQ0Jkgthq12mG73H9QSTNM8RITlF1X5ax9BV0EK34M5dUncn1uEYzJzcbaLjUarf2bqoy906dtQpppUWDRLJI6ycw7rKKJ4ZNUhgi4KAEGBsSgLqc0zDKs0rArwouZyz4ofoWnY68mdJKrVy6Zqz83DSdc7B2hqqkHX_Bfeb4SwAEOVCUN3EwiVroS5-TOq2o7hYSxphK9X0G23N-IBZ0Tr1Rl8XEiJ-OEy0rqnAKwmhAZJWnx3u8oXqbZtOWIZmzQSpcoxhgwfhdmTZJCqT2RVzZyeFItX4sVeilEP3z2xdsJs8-a1kg6UZnx1s1BNLBo7eZrreZygWojPCIDBn03fSAflXoVc5PpY2CGy5MA_IgWgSYBHDdoZEtigp_amjqK7Us44Db20XpLxMXfbahiqa7WKNnMgi6Ca2H67VtaaD8awAEF3zbE1nZRAa7a8vbU25c80YBYJBaW8P6FwXQI-K0Xk5MakwYeMMnIrm6w6IS_0XAO5YlD453GLqnxY8H1BEnRpfOnT7PE4el9mJ8MuYQMo6R2up0lGCmYM0YA9FORjroM3ng69SEPfJPCReG7LfJkERl_m2U403ertDRBYrlqCDagDfyI500srBcMrjSvV3oNouyyx3yZUrjLQfbHhDteQFsYdmakJs8Y-Q9-5MXCcrz6Qa4xwv522Euv0CCxkHcawAEYjfsU_zDhUZA1ey1aquWXlFOnx-iEALqxW1slDYHwQ1M2SILc-v_E6i1doa5e_bAZHVezBHFAlaNAVedNyHFFJxYAqAK3hbzbvl2glw3Q6h_rTXElymloqtaqVFIJ-oUWWOHsZBmu8EDA-HzvGCiBa_GbRaVfh2lE4ObeMXoJrEm_5dbxxeEic2l3IYeIz40N9ooQQOkQcOZdY4AXWYCavIAwWEJBjLtptJgCLu9a_zM1S5GsiyJHpdDs46WbP0EawAEWZ-95Sf0YAHujxRNLdXgpqe0ZF8loVwzZfvyMvqaxF1Ug274BqHuY_c5NdPAzuqoTwjfEn8NKEoaNqlumM75FUYbaTd7mXvk4WVYWjVnkO40dfQjRB7DYhvj0LBlbndAJ4wJIA2ilPYgjZsXVbNNh3e2j3u9eABd0VaFMbSb8Sz5_31r8HzoWmPJs3HiyuyANGFUA6CvAnMN6K3b-D8BhFZU_nPUTgu80o8_n6LQt-XWbaC_mTHzsnOjzBiPJxlYawAEW3bmOEtStH2T8q7vMkhchImp2-hg9MFYGBmEe9sSByTn3NUf8eksqXOC1dUjHkXoZm298FgUYLkNdnlxWpf993j5mEDoFxjcTB7scBD7k6nu6Nrs_wK0-seS8gsHrx9UK7GwAsi10q82Cm4PFyAtrWjmy_d9WLHuZt6VIOKunTs8cf0FwNUiMcvZsruqIFJcP7iWxdiFdUkh65P_iCz1ZEjJcj2GEZoq4v3a3by1aizGPaaiKc1jd_T-XJg_YpncawAEWnstu5b9WiZv0x8xfsiMk6YRlU0Cnj5svxLLXz_8drvwAa--GBY5yH0ke2EM6udMEi2EPeFcGTe6Sjs0YEhSbY7Uad_8suD2J4tIWJSWBbiyvh7rSqzv57m7BlsVcHfQJn_wNH-UlC9xkx8vg-LwfN8_FlxvHNPTc7XZG3lKYbwpUWlZxAziOYT1VQ-2K2bQQBBMdix-ht_SjccL1Dc2dP5kDazQ8yZV_8xnyeheazEedWe63uutfkHlZRg9YwP8=", + "type": "validator", + "lastSeen": 1575915094967382800, + "version": "0.1.0" + } + ], + "mixNodes": [ + { + "location": "unknown", + "host": "35.176.155.107:1789", + "pubKey": "zSob16499jT7C3S3ky4GihNOjlU6aLfSRkf1xAxOwV0=", + "layer": 3, + "lastSeen": 1575915096805374500, + "version": "0.1.0" + }, + { + "location": "unknown", + "host": "18.130.86.190:1789", + "pubKey": "vCdpFc0NvW0NSqsuTxtjFtiSY35aXesgT3JNA8sSIXk=", + "layer": 1, + "lastSeen": 1575915097370376000, + "version": "0.1.0" + }, + { + "location": "unknown", + "host": "3.10.22.152:1789", + "pubKey": "OwOqwWjh_IlnaWS2PxO6odnhNahOYpRCkju50beQCTA=", + "layer": 1, + "lastSeen": 1575915097639423500, + "version": "0.1.0" + }, + { + "location": "unknown", + "host": "35.178.213.77:1789", + "pubKey": "nkkrUjgL8UJk05QydvWvFSvtRB6nmeV8RMvH5540J3s=", + "layer": 2, + "lastSeen": 1575915097895166500, + "version": "0.1.0" + }, + { + "location": "unknown", + "host": "52.56.99.196:1789", + "pubKey": "whHuBuEc6zyOZOquKbuATaH4Crml61V_3Y-MztpWhF4=", + "layer": 2, + "lastSeen": 1575915096255174700, + "version": "0.1.0" + }, + { + "location": "unknown", + "host": "3.9.12.238:1789", + "pubKey": "vk5Sr-Xyi0cTbugACv8U42ZJ6hs6cGDox0rpmXY94Fc=", + "layer": 3, + "lastSeen": 1575915096497827600, + "version": "0.1.0" + } + ], + "mixProviderNodes": [ + { + "location": "unknown", + "clientListener": "3.8.176.11:8888", + "mixnetListener": "3.8.176.11:9999", + "pubKey": "54U6krAr-j9nQXFlsHk3io04_p0tctuqH71t7w_usgI=", + "registeredClients": [ + { + "pubKey": "zOqdJFH49HcgGSCRnmbXGzovnwRLEPN0YGN1SCafTyo=" + }, + { + "pubKey": "fy9xo69hZ2UJ9uxhIS1YzKHZsH8saV-02AiyCNXPNUc=" + }, + { + "pubKey": "COGdpfhmzNGR6YX820GqJIkjOihL8mr6-h-d3JlTDFA=" + } + ], + "lastSeen": 1575915097358694100, + "version": "0.1.0" + }, + { + "location": "unknown", + "clientListener": "3.8.176.12:8888", + "mixnetListener": "3.8.176.12:9999", + "pubKey": "sA-sxi038pEbGy4lgZWG-RdHHDkA6kZzu44G0LUxFSc=", + "registeredClients": [ + { + "pubKey": "7fbk4oGQNlTW-tnWjVz8rWtKrtAicTsiNWgO98sqMyk=" + }, + { + "pubKey": "w1bfLpnd3rWu5JczB0nQfnE2S6nUCbx2AA7HDE48DQo=" + } + ], + "lastSeen": 1575915097869025000, + "version": "0.1.0" + } + ], + "gatewayNodes": [ + { + "location": "unknown", + "clientListener": "3.8.176.11:8888", + "mixnetListener": "3.8.176.11:9999", + "pubKey": "54U6krAr-j9nQXFlsHk3io04_p0tctuqH71t7w_usgI=", + "registeredClients": [ + { + "pubKey": "zOqdJFH49HcgGSCRnmbXGzovnwRLEPN0YGN1SCafTyo=" + }, + { + "pubKey": "fy9xo69hZ2UJ9uxhIS1YzKHZsH8saV-02AiyCNXPNUc=" + } + ], + "lastSeen": 1575915097358694100, + "version": "0.1.0" + }, + { + "location": "unknown", + "clientListener": "3.8.176.12:8888", + "mixnetListener": "3.8.176.12:9999", + "pubKey": "sA-sxi038pEbGy4lgZWG-RdHHDkA6kZzu44G0LUxFSc=", + "registeredClients": [ + { + "pubKey": "UE-7r6-bpw0b4T3GxOBVxlg02psx23DF2p5Tuf-OBSE=" + }, + { + "pubKey": "UnZuLpzq64_EPtIcr1Fd-5AESBCBLFnDMDsjUaOqrUA=" + } + ], + "lastSeen": 1575915097869025000, + "version": "0.1.0" + } + ] + }"#.to_string() } } } From a8f5bd1ad8c7eeb960723e94e2129bdcbd7ecd77 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Tue, 28 Apr 2020 16:23:42 +0100 Subject: [PATCH 59/70] Initial gateway-client --- Cargo.lock | 12 + Cargo.toml | 1 + common/client-libs/gateway-client/Cargo.toml | 22 + common/client-libs/gateway-client/src/lib.rs | 405 +++++++++++++++++++ gateway/gateway-requests/src/lib.rs | 2 + gateway/gateway-requests/src/types.rs | 123 ++++-- 6 files changed, 525 insertions(+), 40 deletions(-) create mode 100644 common/client-libs/gateway-client/Cargo.toml create mode 100644 common/client-libs/gateway-client/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 25ea3d34616..08b4ca7f178 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -895,6 +895,17 @@ dependencies = [ "byteorder", ] +[[package]] +name = "gateway-client" +version = "0.1.0" +dependencies = [ + "futures 0.3.4", + "gateway-requests", + "nymsphinx", + "tokio 0.2.16", + "tokio-tungstenite", +] + [[package]] name = "gateway-requests" version = "0.1.0" @@ -1612,6 +1623,7 @@ dependencies = [ "dirs", "dotenv", "futures 0.3.4", + "gateway-requests", "healthcheck", "log 0.4.8", "mix-client", diff --git a/Cargo.toml b/Cargo.toml index 271cc6de666..d780fecd751 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "clients/desktop", "clients/webassembly", "common/client-libs/directory-client", + "common/client-libs/gateway-client", "common/client-libs/mix-client", "common/client-libs/multi-tcp-client", "common/client-libs/provider-client", diff --git a/common/client-libs/gateway-client/Cargo.toml b/common/client-libs/gateway-client/Cargo.toml new file mode 100644 index 00000000000..3077250306e --- /dev/null +++ b/common/client-libs/gateway-client/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "gateway-client" +version = "0.1.0" +authors = ["JÄ™drzej StuczyÅ„ski "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# TODO: (for this and other crates), similarly to 'tokio', import only required "futures" modules rather than +# the entire crate +futures = "0.3" +tokio = { version = "0.2", features = ["macros", "rt-core", "stream", "sync", "time"] } +tokio-tungstenite = "0.10" + +# internal +gateway-requests = { path = "../../../gateway/gateway-requests" } +nymsphinx = { path = "../../nymsphinx" } + +[dev-dependencies] +# for tests +#url = "2.1" diff --git a/common/client-libs/gateway-client/src/lib.rs b/common/client-libs/gateway-client/src/lib.rs new file mode 100644 index 00000000000..85a0fe3e4f4 --- /dev/null +++ b/common/client-libs/gateway-client/src/lib.rs @@ -0,0 +1,405 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use futures::{channel::mpsc, future::LocalBoxFuture, FutureExt, SinkExt, StreamExt}; +use gateway_requests::auth_token::{AuthToken, AuthTokenConversionError}; +use gateway_requests::{BinaryRequest, ClientControlRequest, ServerResponse}; +use nymsphinx::DestinationAddressBytes; +use std::convert::TryFrom; +use std::fmt::{self, Error, Formatter}; +use std::net::SocketAddr; +use std::sync::Arc; +use std::time::Duration; +use tokio::net::TcpStream; +use tokio::sync::Notify; +use tokio_tungstenite::{ + connect_async, + tungstenite::{client::IntoClientRequest, protocol::Message, Error as WsError}, + WebSocketStream, +}; + +// TODO: combine the duplicate reading procedure, i.e. +/* +msg read from conn.next().await: + if msg.is_none() { + res = Some(Err(GatewayClientError::ConnectionAbruptlyClosed)); + break; + } + let msg = match msg.unwrap() { + Ok(msg) => msg, + Err(err) => { + res = Some(Err(GatewayClientError::NetworkError(err))); + break; + } + }; + match msg { + // specific handling + } +*/ + +// to be moved to different crate perhaps? We'll see +type SphinxPacketSender = mpsc::UnboundedSender>; + +// type alias for not having to type the whole thing every single time +type WsConn = WebSocketStream; + +#[derive(Debug)] +pub enum GatewayClientError { + ConnectionNotEstablished, + GatewayError(String), + NetworkError(WsError), + NoAuthTokenAvailable, + ConnectionAbruptlyClosed, + MalformedResponse, + NotAuthenticated, + ConnectionInInvalidState, + Timeout, +} + +impl From for GatewayClientError { + fn from(err: WsError) -> Self { + GatewayClientError::NetworkError(err) + } +} + +impl From for GatewayClientError { + fn from(_err: AuthTokenConversionError) -> Self { + GatewayClientError::MalformedResponse + } +} + +impl fmt::Display for GatewayClientError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + match self { + GatewayClientError::ConnectionNotEstablished => { + write!(f, "connection to the gateway is not established") + } + GatewayClientError::NoAuthTokenAvailable => { + write!(f, "no AuthToken was provided or obtained") + } + GatewayClientError::NotAuthenticated => write!(f, "client is not authenticated"), + GatewayClientError::NetworkError(err) => { + write!(f, "there was a network error - {}", err) + } + GatewayClientError::ConnectionAbruptlyClosed => { + write!(f, "connection was abruptly closed") + } + GatewayClientError::Timeout => write!(f, "timed out"), + GatewayClientError::MalformedResponse => write!(f, "received response was malformed"), + GatewayClientError::ConnectionInInvalidState => write!( + f, + "connection is in an invalid state - please send a bug report" + ), + GatewayClientError::GatewayError(err) => { + write!(f, "gateway returned an error response - {}", err) + } + } + } +} + +// we can either have the stream itself or an option to re-obtain it +// by notifying the future owning it to finish the execution and awaiting the result +// which should be almost immediate (or an invalid state which should never, ever happen) +enum SocketState<'a> { + Available(WsConn), + Delegated( + LocalBoxFuture<'a, Result>, + Arc, + ), + NotConnected, + Invalid, +} + +impl<'a> SocketState<'a> { + fn is_available(&self) -> bool { + match self { + SocketState::Available(_) => true, + _ => false, + } + } + + fn is_delegated(&self) -> bool { + match self { + SocketState::Delegated(_, _) => true, + _ => false, + } + } + + fn is_established(&self) -> bool { + match self { + SocketState::Available(_) | SocketState::Delegated(_, _) => true, + _ => false, + } + } +} + +pub struct GatewayClient<'a, R: IntoClientRequest + Unpin + Clone> { + authenticated: bool, + // can be String, string slices, `url::Url`, `http::Uri`, etc. + gateway_address: R, + our_address: DestinationAddressBytes, + auth_token: Option, + connection: SocketState<'a>, + sphinx_packet_sender: SphinxPacketSender, + response_timeout_duration: Duration, +} + +impl<'a, R> GatewayClient<'static, R> +where + R: IntoClientRequest + Unpin + Clone, +{ + pub fn new( + gateway_address: R, + our_address: DestinationAddressBytes, + auth_token: Option, + sphinx_packet_sender: SphinxPacketSender, + response_timeout_duration: Duration, + ) -> Self { + GatewayClient { + authenticated: false, + gateway_address, + our_address, + auth_token, + connection: SocketState::NotConnected, + sphinx_packet_sender, + response_timeout_duration, + } + } + + pub async fn establish_connection(&mut self) -> Result<(), GatewayClientError> { + let ws_stream = match connect_async(self.gateway_address.clone()).await { + Ok((ws_stream, _)) => ws_stream, + Err(e) => return Err(GatewayClientError::NetworkError(e)), + }; + + self.connection = SocketState::Available(ws_stream); + Ok(()) + } + + async fn read_control_response(&mut self) -> Result { + // we use the fact that all request responses are Message::Text and only pushed + // sphinx packets are Message::Binary + + let conn = match self.connection { + SocketState::Available(ref mut conn) => conn, + _ => return Err(GatewayClientError::ConnectionInInvalidState), + }; + + let mut timeout = tokio::time::delay_for(self.response_timeout_duration); + + let mut res = None; + while res.is_none() { + tokio::select! { + _ = &mut timeout => { + res = Some(Err(GatewayClientError::Timeout)) + } + // just keep getting through socket buffer until we get to what we want... + // (or we time out) + msg = conn.next() => { + if msg.is_none() { + res = Some(Err(GatewayClientError::ConnectionAbruptlyClosed)); + break; + } + let msg = match msg.unwrap() { + Ok(msg) => msg, + Err(err) => { + res = Some(Err(GatewayClientError::NetworkError(err))); + break; + } + }; + match msg { + Message::Binary(bin_msg) => { + self.sphinx_packet_sender.unbounded_send(bin_msg).unwrap() + } + Message::Text(txt_msg) => { + res = Some(ServerResponse::try_from(txt_msg).map_err(|_| GatewayClientError::MalformedResponse)); + } + _ => (), + } + } + } + } + + res.expect("response value should have been written in one of the branches!. If you see this error, please report a bug!") + } + + // If we want to send a message, we need to have a full control over the socket, + // as we need to be able to write the request and read the subsequent response + async fn send_websocket_message( + &mut self, + msg: Message, + ) -> Result { + let mut should_restart_sphinx_listener = false; + if self.connection.is_delegated() { + self.recover_socket_connection().await?; + should_restart_sphinx_listener = true; + } + + let conn = match self.connection { + SocketState::Available(ref mut conn) => conn, + SocketState::Delegated(_, _) => { + return Err(GatewayClientError::ConnectionInInvalidState) + } + SocketState::Invalid => return Err(GatewayClientError::ConnectionInInvalidState), + SocketState::NotConnected => return Err(GatewayClientError::ConnectionNotEstablished), + }; + conn.send(msg).await?; + let response = self.read_control_response().await; + + if should_restart_sphinx_listener { + self.start_listening_for_sphinx_packets()?; + } + response + } + + pub async fn register(&mut self) -> Result { + if !self.connection.is_established() { + return Err(GatewayClientError::ConnectionNotEstablished); + } + let msg = ClientControlRequest::new_register(self.our_address.clone()).into(); + let token = match self.send_websocket_message(msg).await? { + ServerResponse::Register { token } => { + self.authenticated = true; + Ok(AuthToken::try_from_base58_string(token)?) + } + ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)), + _ => unreachable!(), + }?; + self.start_listening_for_sphinx_packets()?; + Ok(token) + } + + pub async fn authenticate( + &mut self, + auth_token: Option, + ) -> Result { + if auth_token.is_none() && self.auth_token.is_none() { + return Err(GatewayClientError::NoAuthTokenAvailable); + } + if !self.connection.is_established() { + return Err(GatewayClientError::ConnectionNotEstablished); + } + // because of the previous check one of the unwraps MUST succeed + let auth_token = auth_token.unwrap_or_else(|| self.auth_token.unwrap()); + + let msg = + ClientControlRequest::new_authenticate(self.our_address.clone(), auth_token).into(); + let authenticated = match self.send_websocket_message(msg).await? { + ServerResponse::Authenticate { status } => { + self.authenticated = status; + Ok(status) + } + ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)), + _ => unreachable!(), + }?; + self.start_listening_for_sphinx_packets()?; + Ok(authenticated) + } + + pub async fn send_sphinx_packet( + &mut self, + address: SocketAddr, + packet: Vec, + ) -> Result { + if !self.authenticated { + return Err(GatewayClientError::NotAuthenticated); + } + if !self.connection.is_established() { + return Err(GatewayClientError::ConnectionNotEstablished); + } + let msg = BinaryRequest::new_forward_request(address, packet).into(); + match self.send_websocket_message(msg).await? { + ServerResponse::Send { status } => Ok(status), + ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)), + _ => unreachable!(), + } + } + + async fn recover_socket_connection(&mut self) -> Result<(), GatewayClientError> { + if self.connection.is_available() { + return Ok(()); + } + if !self.connection.is_delegated() { + return Err(GatewayClientError::ConnectionInInvalidState); + } + + let (conn_fut, notify) = match std::mem::replace(&mut self.connection, SocketState::Invalid) + { + SocketState::Delegated(conn_fut, notify) => (conn_fut, notify), + _ => unreachable!(), + }; + + // tell the future to wrap up whatever it's doing now + notify.notify(); + self.connection = SocketState::Available(conn_fut.await?); + Ok(()) + } + + // TODO: this can be potentially bad as we have no direct restrictions of ensuring it's called + // within tokio runtime. Perhaps we should use the "old" way of passing explicit + // runtime handle to the constructor and using that instead? + fn start_listening_for_sphinx_packets(&mut self) -> Result<(), GatewayClientError> { + if !self.connection.is_available() { + return Err(GatewayClientError::ConnectionInInvalidState); + } + + // when called for, it NEEDS TO yield back the stream so that we could merge it and + // read control request responses. + let notify = Arc::new(Notify::new()); + let notify_clone = Arc::clone(¬ify); + + let mut extracted_connection = + match std::mem::replace(&mut self.connection, SocketState::Invalid) { + SocketState::Available(conn) => conn, + _ => unreachable!(), // impossible due to initial check + }; + + let sphinx_packet_sender = self.sphinx_packet_sender.clone(); + let sphinx_receiver_future = async move { + let mut should_return = false; + while !should_return { + tokio::select! { + _ = notify_clone.notified() => { + should_return = true; + } + msg = extracted_connection.next() => { + if msg.is_none() { + return Err(GatewayClientError::ConnectionAbruptlyClosed); + } + let msg = match msg.unwrap() { + Ok(msg) => msg, + Err(err) => { + return Err(GatewayClientError::NetworkError(err)); + } + }; + match msg { + Message::Binary(bin_msg) => { + sphinx_packet_sender.unbounded_send(bin_msg).unwrap() + } + _ => (), + }; + } + }; + } + Ok(extracted_connection) + }; + + let spawned_boxed_task = tokio::spawn(sphinx_receiver_future) + .map(|join_handle| join_handle.expect("task must have not failed to finish execution!")) + .boxed(); + + self.connection = SocketState::Delegated(spawned_boxed_task, notify); + Ok(()) + } +} diff --git a/gateway/gateway-requests/src/lib.rs b/gateway/gateway-requests/src/lib.rs index 10976d815fc..667e9f83721 100644 --- a/gateway/gateway-requests/src/lib.rs +++ b/gateway/gateway-requests/src/lib.rs @@ -17,3 +17,5 @@ pub mod types; pub const DUMMY_MESSAGE_CONTENT: &[u8] = b"[DUMMY MESSAGE] Wanting something does not give you the right to have it."; + +pub use types::*; diff --git a/gateway/gateway-requests/src/types.rs b/gateway/gateway-requests/src/types.rs index 75bc711f4bb..e0544cd0f53 100644 --- a/gateway/gateway-requests/src/types.rs +++ b/gateway/gateway-requests/src/types.rs @@ -12,9 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::auth_token::AuthToken; use crate::types::BinaryRequest::ForwardSphinx; use nymsphinx::addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError}; +use nymsphinx::DestinationAddressBytes; use serde::{Deserialize, Serialize}; +use std::convert::TryInto; use std::{ convert::TryFrom, fmt::{self, Error, Formatter}, @@ -57,6 +60,38 @@ pub enum ClientControlRequest { Register { address: String }, } +impl ClientControlRequest { + pub fn new_authenticate(address: DestinationAddressBytes, token: AuthToken) -> Self { + ClientControlRequest::Authenticate { + address: address.to_base58_string(), + token: token.to_base58_string(), + } + } + + pub fn new_register(address: DestinationAddressBytes) -> Self { + ClientControlRequest::Register { + address: address.to_base58_string(), + } + } +} + +impl Into for ClientControlRequest { + fn into(self) -> Message { + // it should be safe to call `unwrap` here as the message is generated by the server + // so if it fails (and consequently panics) it's a bug that should be resolved + let str_req = serde_json::to_string(&self).unwrap(); + Message::Text(str_req) + } +} + +impl TryFrom for ClientControlRequest { + type Error = serde_json::Error; + + fn try_from(msg: String) -> Result { + serde_json::from_str(&msg) + } +} + #[derive(Serialize, Deserialize, Debug)] #[serde(tag = "type", rename_all = "camelCase")] pub enum ServerResponse { @@ -66,6 +101,46 @@ pub enum ServerResponse { Error { message: String }, } +impl ServerResponse { + pub fn new_error>(msg: S) -> Self { + ServerResponse::Error { + message: msg.into(), + } + } + + pub fn is_error(&self) -> bool { + match self { + ServerResponse::Error { .. } => true, + _ => false, + } + } + + pub fn implies_successful_authentication(&self) -> bool { + match self { + ServerResponse::Authenticate { status, .. } => *status, + ServerResponse::Register { .. } => true, + _ => false, + } + } +} + +impl Into for ServerResponse { + fn into(self) -> Message { + // it should be safe to call `unwrap` here as the message is generated by the server + // so if it fails (and consequently panics) it's a bug that should be resolved + let str_res = serde_json::to_string(&self).unwrap(); + Message::Text(str_res) + } +} + +impl TryFrom for ServerResponse { + type Error = serde_json::Error; + + fn try_from(msg: String) -> Result { + serde_json::from_str(&msg) + } +} + pub enum BinaryRequest { ForwardSphinx { address: SocketAddr, data: Vec }, } @@ -75,17 +150,17 @@ impl BinaryRequest { // right now there's only a single option possible which significantly simplifies the logic // if we decided to allow for more 'binary' messages, the API wouldn't need to change let address = NymNodeRoutingAddress::try_from_bytes(&raw_req)?; - let offset = address.bytes_min_len(); + let addr_offset = address.bytes_min_len(); - if raw_req[offset..].len() != nymsphinx::PACKET_SIZE { + if raw_req[addr_offset..].len() != nymsphinx::PACKET_SIZE { Err(GatewayRequestsError::RequestOfInvalidSize( - raw_req[offset..].len(), + raw_req[addr_offset..].len(), nymsphinx::PACKET_SIZE, )) } else { Ok(ForwardSphinx { address: address.into(), - data: raw_req[offset..].into(), + data: raw_req[addr_offset..].into(), }) } } @@ -111,42 +186,10 @@ impl BinaryRequest { } } -impl ServerResponse { - pub fn new_error>(msg: S) -> Self { - ServerResponse::Error { - message: msg.into(), - } - } - - pub fn is_error(&self) -> bool { - match self { - ServerResponse::Error { .. } => true, - _ => false, - } - } - - pub fn implies_successful_authentication(&self) -> bool { - match self { - ServerResponse::Authenticate { status } => *status, - ServerResponse::Register { .. } => true, - _ => false, - } - } -} - -impl TryFrom for ClientControlRequest { - type Error = serde_json::Error; - - fn try_from(msg: String) -> Result { - serde_json::from_str(&msg) - } -} - -impl Into for ServerResponse { +impl Into for BinaryRequest { fn into(self) -> Message { - // it should be safe to call `unwrap` here as the message is generated by the server - // so if it fails (and consequently panics) it's a bug that should be resolved - let str_res = serde_json::to_string(&self).unwrap(); - Message::Text(str_res) + Message::Binary(self.into_bytes()) } } + +// TODO: tests... From 96a98e0f6b3268eee4b4b32384176d30fa74eb43 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 29 Apr 2020 13:03:16 +0100 Subject: [PATCH 60/70] Seemingly working version with bunch of hardcoded and temporary values --- Cargo.lock | 3 +- clients/desktop/Cargo.toml | 4 +- clients/desktop/src/client/mix_traffic.rs | 40 ++-- clients/desktop/src/client/mod.rs | 183 ++++++++++++------ clients/desktop/src/client/received_buffer.rs | 17 +- .../desktop/src/client/topology_control.rs | 55 +++--- clients/desktop/src/commands/init.rs | 106 +++++++--- .../directory-client/src/presence/mod.rs | 2 +- common/client-libs/gateway-client/src/lib.rs | 38 +++- .../nymsphinx/src/chunking/reconstruction.rs | 4 + gateway/gateway-requests/src/lib.rs | 1 + 11 files changed, 308 insertions(+), 145 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 08b4ca7f178..aa5d216b62b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1623,6 +1623,7 @@ dependencies = [ "dirs", "dotenv", "futures 0.3.4", + "gateway-client", "gateway-requests", "healthcheck", "log 0.4.8", @@ -1636,11 +1637,11 @@ dependencies = [ "reqwest", "serde", "serde_json", - "sfw-provider-requests", "tempfile", "tokio 0.2.16", "tokio-tungstenite", "topology", + "url 2.1.1", ] [[package]] diff --git a/clients/desktop/Cargo.toml b/clients/desktop/Cargo.toml index e9009eefab5..56d05ef0701 100644 --- a/clients/desktop/Cargo.toml +++ b/clients/desktop/Cargo.toml @@ -26,18 +26,20 @@ serde = { version = "1.0.104", features = ["derive"] } serde_json = "1.0.44" tokio = { version = "0.2", features = ["full"] } tokio-tungstenite = "0.10.1" +url = "2.1" ## internal config = {path = "../../common/config"} crypto = {path = "../../common/crypto"} directory-client = { path = "../../common/client-libs/directory-client" } +gateway-client = { path = "../../common/client-libs/gateway-client" } +gateway-requests = { path = "../../gateway/gateway-requests" } healthcheck = { path = "../../common/healthcheck" } mix-client = { path = "../../common/client-libs/mix-client" } multi-tcp-client = { path = "../../common/client-libs/multi-tcp-client" } nymsphinx = { path = "../../common/nymsphinx" } pemstore = {path = "../../common/pemstore"} provider-client = { path = "../../common/client-libs/provider-client" } -sfw-provider-requests = { path = "../../sfw-provider/sfw-provider-requests" } topology = {path = "../../common/topology" } [build-dependencies] diff --git a/clients/desktop/src/client/mix_traffic.rs b/clients/desktop/src/client/mix_traffic.rs index 67937b6cad3..38ce9b2b073 100644 --- a/clients/desktop/src/client/mix_traffic.rs +++ b/clients/desktop/src/client/mix_traffic.rs @@ -14,6 +14,7 @@ use futures::channel::mpsc; use futures::StreamExt; +use gateway_client::GatewayClient; use log::*; use nymsphinx::SphinxPacket; use std::net::SocketAddr; @@ -31,37 +32,40 @@ impl MixMessage { } } -pub(crate) struct MixTrafficController { - tcp_client: multi_tcp_client::Client, +pub(crate) struct MixTrafficController<'a> { + // TODO: most likely to be replaced by some higher level construct as + // later on gateway_client will need to be accessible by other entities + gateway_client: GatewayClient<'a, url::Url>, mix_rx: MixMessageReceiver, } -impl MixTrafficController { +impl<'a> MixTrafficController<'static> { pub(crate) fn new( - initial_reconnection_backoff: Duration, - maximum_reconnection_backoff: Duration, - initial_connection_timeout: Duration, mix_rx: MixMessageReceiver, - ) -> Self { - let tcp_client_config = multi_tcp_client::Config::new( - initial_reconnection_backoff, - maximum_reconnection_backoff, - initial_connection_timeout, - ); - + gateway_client: GatewayClient<'a, url::Url>, + ) -> MixTrafficController<'a> { MixTrafficController { - tcp_client: multi_tcp_client::Client::new(tcp_client_config), + gateway_client, mix_rx, } } async fn on_message(&mut self, mix_message: MixMessage) { debug!("Got a mix_message for {:?}", mix_message.0); - self.tcp_client - // TODO: possibly we might want to get an actual result here at some point - .send(mix_message.0, mix_message.1.to_bytes(), false) + match self + .gateway_client + .send_sphinx_packet(mix_message.0, mix_message.1.to_bytes()) .await - .unwrap(); // if we're not waiting for response, we MUST get an Ok + { + Err(e) => error!("Failed to send sphinx packet to the gateway! - {:?}", e), + Ok(was_successful) if !was_successful => { + warn!("Sent sphinx packet to the gateway but it failed to get processed!") + } + Ok(was_successful) if was_successful => { + trace!("Successfully forwarded sphinx packet to the gateway!") + } + Ok(_) => unreachable!("to shut up the compiler because all patterns ARE covered"), + } } pub(crate) async fn run(&mut self) { diff --git a/clients/desktop/src/client/mod.rs b/clients/desktop/src/client/mod.rs index 37ba5478a51..b7c37d3422d 100644 --- a/clients/desktop/src/client/mod.rs +++ b/clients/desktop/src/client/mod.rs @@ -14,7 +14,7 @@ use crate::client::cover_traffic_stream::LoopCoverTrafficStream; use crate::client::mix_traffic::{MixMessageReceiver, MixMessageSender, MixTrafficController}; -use crate::client::provider_poller::{PolledMessagesReceiver, PolledMessagesSender}; +//use crate::client::provider_poller::{PolledMessagesReceiver, PolledMessagesSender}; use crate::client::received_buffer::{ ReceivedBufferRequestReceiver, ReceivedBufferRequestSender, ReceivedMessagesBufferController, }; @@ -29,15 +29,19 @@ use directory_client::presence; use futures::channel::{mpsc, oneshot}; use log::*; use nymsphinx::chunking::split_and_prepare_payloads; -use nymsphinx::Destination; +use nymsphinx::{Destination, DestinationAddressBytes}; use pemstore::pemstore::PemStore; -use sfw_provider_requests::auth_token::AuthToken; +//use sfw_provider_requests::auth_token::AuthToken; +use gateway_client::{GatewayClient, SphinxPacketReceiver, SphinxPacketSender}; +use gateway_requests::auth_token::AuthToken; +use std::net::SocketAddr; +use std::time::Duration; use tokio::runtime::Runtime; use topology::NymTopology; mod cover_traffic_stream; mod mix_traffic; -mod provider_poller; +//mod provider_poller; mod real_traffic_stream; pub(crate) mod received_buffer; pub(crate) mod topology_control; @@ -85,6 +89,9 @@ impl NymClient { } } + #[deprecated( + note = "SURB_IDs are irrelevant in this system design and this method alongside everything using it, should be updated accordingly" + )] pub fn as_mix_destination(&self) -> Destination { Destination::new( self.identity_keypair.public_key().derive_address(), @@ -93,6 +100,10 @@ impl NymClient { ) } + pub fn as_mix_destination_address(&self) -> DestinationAddressBytes { + self.identity_keypair.public_key.derive_address() + } + // future constantly pumping loop cover traffic at some specified average rate // the pumped traffic goes to the MixTrafficController fn start_cover_traffic_stream( @@ -144,59 +155,101 @@ impl NymClient { fn start_received_messages_buffer_controller( &self, query_receiver: ReceivedBufferRequestReceiver, - poller_receiver: PolledMessagesReceiver, + sphinx_receiver: SphinxPacketReceiver, ) { info!("Starting 'received messages buffer controller'..."); - ReceivedMessagesBufferController::new(query_receiver, poller_receiver) + ReceivedMessagesBufferController::new(query_receiver, sphinx_receiver) .start(self.runtime.handle()) } - // future constantly trying to fetch any received messages from the provider - // the received messages are sent to ReceivedMessagesBuffer to be available to rest of the system - fn start_provider_poller( + fn start_gateway_client( &mut self, - mut topology_accessor: TopologyAccessor, - poller_input_tx: PolledMessagesSender, - ) { - info!("Starting provider poller..."); - // we already have our provider written in the config - let provider_id = self.config.get_provider_id(); - - // TODO: a slightly more graceful termination here - if !self.runtime.block_on(topology_accessor.is_routable()) { - panic!( - "The current network topology seem to be insufficient to route any packets through\ - - check if enough nodes and a sfw-provider are online" - ); - } - - // TODO: a slightly more graceful termination here - let provider_client_listener_address = self.runtime.block_on( - topology_accessor.get_provider_socket_addr(&provider_id) - ).unwrap_or_else(|| panic!("Could not find provider with id {:?}. It does not seem to be present in the current network topology.\ - Are you sure it is still online? Perhaps try to run `nym-client init` again to obtain a new provider", provider_id)); + sphinx_packet_sender: SphinxPacketSender, + gateway_address: url::Url, + ) -> GatewayClient<'static, url::Url> { + // TODO: put into config + let gateway_response_timeout = Duration::from_millis(1500); + + let auth_token = self + .config + .get_provider_auth_token() + .map(|str_token| AuthToken::try_from_base58_string(str_token).ok()) + .unwrap_or(None); + + let mut gateway_client = GatewayClient::new( + gateway_address, + self.as_mix_destination_address(), + auth_token, + sphinx_packet_sender, + gateway_response_timeout, + ); - let mut provider_poller = provider_poller::ProviderPoller::new( - poller_input_tx, - provider_client_listener_address, - self.identity_keypair.public_key().derive_address(), - self.config - .get_provider_auth_token() - .map(|str_token| AuthToken::try_from_base58_string(str_token).ok()) - .unwrap_or(None), - self.config.get_fetch_message_delay(), - self.config.get_max_response_size(), + let auth_token = self.runtime.block_on(async { + gateway_client + .establish_connection() + .await + .expect("could not establish initial connection with the gateway"); + gateway_client + .perform_initial_authentication() + .await + .expect("could not perform initial authentication with the gateway") + }); + + // TODO: if we didn't have an auth_token initially, save it to config or something? + info!( + "Performed initial authentication and our auth token is {:?}", + auth_token.to_base58_string() ); - if !provider_poller.is_registered() { - info!("Trying to perform initial provider registration..."); - self.runtime - .block_on(provider_poller.perform_initial_registration()) - .expect("Failed to perform initial provider registration"); - } - provider_poller.start(self.runtime.handle()); + gateway_client } + // // future constantly trying to fetch any received messages from the provider + // // the received messages are sent to ReceivedMessagesBuffer to be available to rest of the system + // fn start_provider_poller( + // &mut self, + // mut topology_accessor: TopologyAccessor, + // poller_input_tx: PolledMessagesSender, + // ) { + // info!("Starting provider poller..."); + // // we already have our provider written in the config + // let provider_id = self.config.get_provider_id(); + // + // // TODO: a slightly more graceful termination here + // if !self.runtime.block_on(topology_accessor.is_routable()) { + // panic!( + // "The current network topology seem to be insufficient to route any packets through\ + // - check if enough nodes and a sfw-provider are online" + // ); + // } + // + // // TODO: a slightly more graceful termination here + // let provider_client_listener_address = self.runtime.block_on( + // topology_accessor.get_provider_socket_addr(&provider_id) + // ).unwrap_or_else(|| panic!("Could not find provider with id {:?}. It does not seem to be present in the current network topology.\ + // Are you sure it is still online? Perhaps try to run `nym-client init` again to obtain a new provider", provider_id)); + // + // let mut provider_poller = provider_poller::ProviderPoller::new( + // poller_input_tx, + // provider_client_listener_address, + // self.identity_keypair.public_key().derive_address(), + // self.config + // .get_provider_auth_token() + // .map(|str_token| AuthToken::try_from_base58_string(str_token).ok()) + // .unwrap_or(None), + // self.config.get_fetch_message_delay(), + // self.config.get_max_response_size(), + // ); + // + // if !provider_poller.is_registered() { + // info!("Trying to perform initial provider registration..."); + // self.runtime + // .block_on(provider_poller.perform_initial_registration()) + // .expect("Failed to perform initial provider registration"); + // } + // provider_poller.start(self.runtime.handle()); + // } + // future responsible for periodically polling directory server and updating // the current global view of topology fn start_topology_refresher( @@ -228,18 +281,16 @@ impl NymClient { } // controller for sending sphinx packets to mixnet (either real traffic or cover traffic) - fn start_mix_traffic_controller(&mut self, mix_rx: MixMessageReceiver) { + // TODO: if we want to send control messages to gateway_client, this CAN'T take the ownership + // over it. Perhaps GatewayClient needs to be thread-shareable or have some channel for + // requests? + fn start_mix_traffic_controller( + &mut self, + mix_rx: MixMessageReceiver, + gateway_client: GatewayClient<'static, url::Url>, + ) { info!("Starting mix traffic controller..."); - self.runtime - .enter(|| { - MixTrafficController::new( - self.config.get_packet_forwarding_initial_backoff(), - self.config.get_packet_forwarding_maximum_backoff(), - self.config.get_initial_connection_timeout(), - mix_rx, - ) - }) - .start(self.runtime.handle()); + MixTrafficController::new(mix_rx, gateway_client).start(self.runtime.handle()); } fn start_socket_listener( @@ -322,6 +373,9 @@ impl NymClient { } pub fn start(&mut self) { + // TODO: put into config, etc. + let gateway_url = url::Url::parse("ws://localhost:9000").unwrap(); + info!("Starting nym client"); // channels for inter-component communication @@ -330,9 +384,9 @@ impl NymClient { // mix_rx is the receiver used by MixTrafficController that sends the actual traffic let (mix_tx, mix_rx) = mpsc::unbounded(); - // poller_input_tx is the transmitter of messages fetched from the provider - used by ProviderPoller - // poller_input_rx is the receiver for said messages - used by ReceivedMessagesBuffer - let (poller_input_tx, poller_input_rx) = mpsc::unbounded(); + // sphinx_packet_tx is the transmitter of sphinx messages received from the gateway + // sphinx_packet_rx is the receiver for said messages - used by ReceivedMessagesBuffer + let (sphinx_packet_tx, sphinx_packet_rx) = mpsc::unbounded(); // received_messages_buffer_output_tx is the transmitter for *REQUESTS* for messages contained in ReceivedMessagesBuffer - used by sockets // the requests contain a oneshot channel to send a reply on @@ -350,10 +404,13 @@ impl NymClient { self.start_topology_refresher(shared_topology_accessor.clone()); self.start_received_messages_buffer_controller( received_messages_buffer_output_rx, - poller_input_rx, + sphinx_packet_rx, ); - self.start_provider_poller(shared_topology_accessor.clone(), poller_input_tx); - self.start_mix_traffic_controller(mix_rx); + // self.start_provider_poller(shared_topology_accessor.clone(), sphinx_packet_tx); + + let gateway_client = self.start_gateway_client(sphinx_packet_tx, gateway_url); + + self.start_mix_traffic_controller(mix_rx, gateway_client); self.start_cover_traffic_stream(shared_topology_accessor.clone(), mix_tx.clone()); self.start_real_traffic_stream(shared_topology_accessor.clone(), mix_tx, input_rx); self.start_socket_listener( diff --git a/clients/desktop/src/client/received_buffer.rs b/clients/desktop/src/client/received_buffer.rs index f50f46d7b7f..13ab5df0d59 100644 --- a/clients/desktop/src/client/received_buffer.rs +++ b/clients/desktop/src/client/received_buffer.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::client::provider_poller::PolledMessagesReceiver; use futures::channel::{mpsc, oneshot}; use futures::lock::Mutex; use futures::StreamExt; @@ -21,6 +20,7 @@ use nymsphinx::chunking::reconstruction::MessageReconstructor; use std::sync::Arc; use tokio::runtime::Handle; use tokio::task::JoinHandle; +use gateway_client::SphinxPacketReceiver; pub(crate) type ReceivedBufferResponse = oneshot::Sender>>; pub(crate) type ReceivedBufferRequestSender = mpsc::UnboundedSender; @@ -117,22 +117,22 @@ impl RequestReceiver { struct MessageReceiver { received_buffer: ReceivedMessagesBuffer, - poller_receiver: PolledMessagesReceiver, + sphinx_packet_receiver: SphinxPacketReceiver, } impl MessageReceiver { fn new( received_buffer: ReceivedMessagesBuffer, - poller_receiver: PolledMessagesReceiver, + sphinx_packet_receiver: SphinxPacketReceiver, ) -> Self { MessageReceiver { received_buffer, - poller_receiver, + sphinx_packet_receiver, } } fn start(mut self, handle: &Handle) -> JoinHandle<()> { handle.spawn(async move { - while let Some(new_messages) = self.poller_receiver.next().await { + while let Some(new_messages) = self.sphinx_packet_receiver.next().await { self.received_buffer .add_new_message_fragments(new_messages) .await; @@ -149,12 +149,15 @@ pub(crate) struct ReceivedMessagesBufferController { impl ReceivedMessagesBufferController { pub(crate) fn new( query_receiver: ReceivedBufferRequestReceiver, - poller_receiver: PolledMessagesReceiver, + sphinx_packet_receiver: SphinxPacketReceiver, ) -> Self { let received_buffer = ReceivedMessagesBuffer::new(); ReceivedMessagesBufferController { - messsage_receiver: MessageReceiver::new(received_buffer.clone(), poller_receiver), + messsage_receiver: MessageReceiver::new( + received_buffer.clone(), + sphinx_packet_receiver, + ), request_receiver: RequestReceiver::new(received_buffer, query_receiver), } } diff --git a/clients/desktop/src/client/topology_control.rs b/clients/desktop/src/client/topology_control.rs index 6a899f9e8a8..f46eb8370f6 100644 --- a/clients/desktop/src/client/topology_control.rs +++ b/clients/desktop/src/client/topology_control.rs @@ -101,12 +101,12 @@ impl TopologyAccessor { match &self.inner.lock().await.0 { None => None, Some(ref topology) => { - let mut providers = topology.providers(); - if providers.is_empty() { + let mut gateways = topology.gateways(); + if gateways.is_empty() { return None; } // unwrap is fine here as we asserted there is at least single provider - let provider = providers.pop().unwrap().into(); + let provider = gateways.pop().unwrap().into(); topology.route_to(provider).ok() } } @@ -186,29 +186,32 @@ impl TopologyRefresher { let version_filtered_topology = full_topology.filter_system_version(built_info::PKG_VERSION); - let healthcheck_result = self - .health_checker - .do_check(&version_filtered_topology) - .await; - let healthcheck_scores = match healthcheck_result { - Err(err) => { - error!("Error while performing the healthcheck: {:?}", err); - return Err(TopologyError::HealthCheckError); - } - Ok(scores) => scores, - }; - - debug!("{}", healthcheck_scores); - - let healthy_topology = healthcheck_scores - .filter_topology_by_score(&version_filtered_topology, self.node_score_threshold); - - // make sure you can still send a packet through the network: - if !healthy_topology.can_construct_path_through() { - return Err(TopologyError::NoValidPathsError); - } - - Ok(healthy_topology) + // healthcheck needs some adjustments to work with gateways so for time being just dont run it + return Ok(version_filtered_topology); + + // let healthcheck_result = self + // .health_checker + // .do_check(&version_filtered_topology) + // .await; + // let healthcheck_scores = match healthcheck_result { + // Err(err) => { + // error!("Error while performing the healthcheck: {:?}", err); + // return Err(TopologyError::HealthCheckError); + // } + // Ok(scores) => scores, + // }; + // + // debug!("{}", healthcheck_scores); + // + // let healthy_topology = healthcheck_scores + // .filter_topology_by_score(&version_filtered_topology, self.node_score_threshold); + // + // // make sure you can still send a packet through the network: + // if !healthy_topology.can_construct_path_through() { + // return Err(TopologyError::NoValidPathsError); + // } + // + // Ok(healthy_topology) } pub(crate) async fn refresh(&mut self) { diff --git a/clients/desktop/src/commands/init.rs b/clients/desktop/src/commands/init.rs index e4ce291d49f..1f4552455e6 100644 --- a/clients/desktop/src/commands/init.rs +++ b/clients/desktop/src/commands/init.rs @@ -19,10 +19,13 @@ use clap::{App, Arg, ArgMatches}; use config::NymConfig; use crypto::identity::MixIdentityKeyPair; use directory_client::presence::Topology; +use futures::channel::mpsc; +use gateway_client::GatewayClient; +use gateway_requests::AuthToken; use nymsphinx::DestinationAddressBytes; use pemstore::pemstore::PemStore; -use sfw_provider_requests::auth_token::AuthToken; -use topology::provider::Node; +use std::time::Duration; +use topology::gateway::Node; use topology::NymTopology; pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { @@ -57,24 +60,51 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { ) } -async fn try_provider_registrations( - providers: Vec, +//async fn try_provider_registrations( +// providers: Vec, +// our_address: DestinationAddressBytes, +//) -> Option<(String, AuthToken)> { +// // we don't expect the response to be longer than AUTH_TOKEN_SIZE, but allow for more bytes +// // in case there was an error message +// let max_response_len = 16 * 1024; +// // since the order of providers is non-deterministic we can just try to get a first 'working' provider +// for provider in providers { +// let mut provider_client = provider_client::ProviderClient::new( +// provider.client_listener, +// our_address.clone(), +// None, +// max_response_len, +// ); +// let auth_token = provider_client.register().await; +// if let Ok(token) = auth_token { +// return Some((provider.pub_key, token)); +// } +// } +// None +//} + +async fn try_gateway_registration( + gateways: Vec, our_address: DestinationAddressBytes, ) -> Option<(String, AuthToken)> { - // we don't expect the response to be longer than AUTH_TOKEN_SIZE, but allow for more bytes - // in case there was an error message - let max_response_len = 16 * 1024; - // since the order of providers is non-deterministic we can just try to get a first 'working' provider - for provider in providers { - let mut provider_client = provider_client::ProviderClient::new( - provider.client_listener, + // TODO: having to do something like this suggests that perhaps GatewayClient's constructor + // could be improved + let (sphinx_tx, _) = mpsc::unbounded(); + let timeout = Duration::from_millis(1500); + for gateway in gateways { + let mut gateway_client = GatewayClient::new( + // TODO: can you construct urls this way? + url::Url::parse(format!("ws://{}", gateway.client_listener.to_string()).as_ref()) + .unwrap(), our_address.clone(), None, - max_response_len, + sphinx_tx.clone(), + timeout, ); - let auth_token = provider_client.register().await; - if let Ok(token) = auth_token { - return Some((provider.pub_key, token)); + if let Ok(_) = gateway_client.establish_connection().await { + if let Ok(token) = gateway_client.register().await { + return Some((gateway.pub_key, token)); + } } } None @@ -82,7 +112,36 @@ async fn try_provider_registrations( // in the long run this will be provider specific and only really applicable to a // relatively small subset of all providers -async fn choose_provider( +//async fn choose_provider( +// directory_server: String, +// our_address: DestinationAddressBytes, +//) -> (String, AuthToken) { +// // TODO: once we change to graph topology this here will need to be updated! +// let topology = Topology::new(directory_server.clone()); +// let version_filtered_topology = topology.filter_system_version(built_info::PKG_VERSION); +// // don't care about health of the networks as mixes can go up and down any time, +// // but DO care about providers +// let providers = version_filtered_topology.providers(); +// +// // try to perform registration so that we wouldn't need to do it at startup +// // + at the same time we'll know if we can actually talk with that provider +// let registration_result = try_provider_registrations(providers, our_address).await; +// match registration_result { +// None => { +// // while technically there's no issue client-side, it will be impossible to execute +// // `nym-client run` as no provider is available so it might be best to not finalize +// // the init and rely on users trying to init another time? +// panic!( +// "Currently there are no valid providers available on the network ({}). \ +// Please try to run `init` again at later time or change your directory server", +// directory_server +// ) +// } +// Some((provider_id, auth_token)) => (provider_id, auth_token), +// } +//} + +async fn choose_gateway( directory_server: String, our_address: DestinationAddressBytes, ) -> (String, AuthToken) { @@ -91,18 +150,18 @@ async fn choose_provider( let version_filtered_topology = topology.filter_system_version(built_info::PKG_VERSION); // don't care about health of the networks as mixes can go up and down any time, // but DO care about providers - let providers = version_filtered_topology.providers(); + let gateways = version_filtered_topology.gateways(); // try to perform registration so that we wouldn't need to do it at startup // + at the same time we'll know if we can actually talk with that provider - let registration_result = try_provider_registrations(providers, our_address).await; + let registration_result = try_gateway_registration(gateways, our_address).await; match registration_result { None => { // while technically there's no issue client-side, it will be impossible to execute // `nym-client run` as no provider is available so it might be best to not finalize // the init and rely on users trying to init another time? panic!( - "Currently there are no valid providers available on the network ({}). \ + "Currently there are no valid gateways available on the network ({}). \ Please try to run `init` again at later time or change your directory server", directory_server ) @@ -126,10 +185,12 @@ pub fn execute(matches: &ArgMatches) { let our_address = mix_identity_keys.public_key().derive_address(); // TODO: is there perhaps a way to make it work without having to spawn entire runtime? let mut rt = tokio::runtime::Runtime::new().unwrap(); - let (provider_id, auth_token) = - rt.block_on(choose_provider(config.get_directory_server(), our_address)); + let (gateway_id, auth_token) = + rt.block_on(choose_gateway(config.get_directory_server(), our_address)); + + // TODO: this isn't really a provider, but gateway, yet another change to make config = config - .with_provider_id(provider_id) + .with_provider_id(gateway_id) .with_provider_auth_token(auth_token); } @@ -146,6 +207,7 @@ pub fn execute(matches: &ArgMatches) { .expect("Failed to save the config file"); println!("Saved configuration file to {:?}", config_save_location); + // TODO: again change provider -> gateway println!( "Unless overridden in all `nym-client run` we will be talking to the following provider: {}...", config.get_provider_id(), diff --git a/common/client-libs/directory-client/src/presence/mod.rs b/common/client-libs/directory-client/src/presence/mod.rs index 0e28bde6111..b379d1f029b 100644 --- a/common/client-libs/directory-client/src/presence/mod.rs +++ b/common/client-libs/directory-client/src/presence/mod.rs @@ -81,7 +81,7 @@ impl NymTopology for Topology { .collect() } - fn gateways(&self) -> Vec { + fn gateways(&self) -> Vec { self.gateway_nodes .iter() .map(|x| x.clone().into()) diff --git a/common/client-libs/gateway-client/src/lib.rs b/common/client-libs/gateway-client/src/lib.rs index 85a0fe3e4f4..52dba6659f4 100644 --- a/common/client-libs/gateway-client/src/lib.rs +++ b/common/client-libs/gateway-client/src/lib.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use futures::{channel::mpsc, future::LocalBoxFuture, FutureExt, SinkExt, StreamExt}; +use futures::{channel::mpsc, future::BoxFuture, FutureExt, SinkExt, StreamExt}; use gateway_requests::auth_token::{AuthToken, AuthTokenConversionError}; use gateway_requests::{BinaryRequest, ClientControlRequest, ServerResponse}; use nymsphinx::DestinationAddressBytes; @@ -48,8 +48,9 @@ msg read from conn.next().await: } */ -// to be moved to different crate perhaps? We'll see -type SphinxPacketSender = mpsc::UnboundedSender>; +// TODO: some batching mechanism to allow reading and sending more than a single packet through +pub type SphinxPacketSender = mpsc::UnboundedSender>>; +pub type SphinxPacketReceiver = mpsc::UnboundedReceiver>>; // type alias for not having to type the whole thing every single time type WsConn = WebSocketStream; @@ -64,6 +65,7 @@ pub enum GatewayClientError { MalformedResponse, NotAuthenticated, ConnectionInInvalidState, + AuthenticationFailure, Timeout, } @@ -101,6 +103,7 @@ impl fmt::Display for GatewayClientError { f, "connection is in an invalid state - please send a bug report" ), + GatewayClientError::AuthenticationFailure => write!(f, "authentication failure"), GatewayClientError::GatewayError(err) => { write!(f, "gateway returned an error response - {}", err) } @@ -114,7 +117,7 @@ impl fmt::Display for GatewayClientError { enum SocketState<'a> { Available(WsConn), Delegated( - LocalBoxFuture<'a, Result>, + BoxFuture<'a, Result>, Arc, ), NotConnected, @@ -155,6 +158,12 @@ pub struct GatewayClient<'a, R: IntoClientRequest + Unpin + Clone> { response_timeout_duration: Duration, } +impl<'a, R: IntoClientRequest + Unpin + Clone> Drop for GatewayClient<'a, R> { + fn drop(&mut self) { + // TODO to fix forcibly closing connection + } +} + impl<'a, R> GatewayClient<'static, R> where R: IntoClientRequest + Unpin + Clone, @@ -220,7 +229,7 @@ where }; match msg { Message::Binary(bin_msg) => { - self.sphinx_packet_sender.unbounded_send(bin_msg).unwrap() + self.sphinx_packet_sender.unbounded_send(vec![bin_msg]).unwrap() } Message::Text(txt_msg) => { res = Some(ServerResponse::try_from(txt_msg).map_err(|_| GatewayClientError::MalformedResponse)); @@ -307,6 +316,23 @@ where Ok(authenticated) } + // just a helper method to either call register or authenticate based on self.auth_token value + pub async fn perform_initial_authentication( + &mut self, + ) -> Result { + if self.auth_token.is_some() { + self.authenticate(None).await?; + } else { + self.register().await?; + } + if self.authenticated { + // if we are authenticated it means we MUST have an associated auth_token + Ok(self.auth_token.clone().unwrap()) + } else { + Err(GatewayClientError::AuthenticationFailure) + } + } + pub async fn send_sphinx_packet( &mut self, address: SocketAddr, @@ -385,7 +411,7 @@ where }; match msg { Message::Binary(bin_msg) => { - sphinx_packet_sender.unbounded_send(bin_msg).unwrap() + sphinx_packet_sender.unbounded_send(vec![bin_msg]).unwrap() } _ => (), }; diff --git a/common/nymsphinx/src/chunking/reconstruction.rs b/common/nymsphinx/src/chunking/reconstruction.rs index bcfac943893..c9b9fdd109b 100644 --- a/common/nymsphinx/src/chunking/reconstruction.rs +++ b/common/nymsphinx/src/chunking/reconstruction.rs @@ -274,6 +274,10 @@ impl MessageReconstructor { let fragment_res = Fragment::try_from_bytes(&fragment_data); if let Err(e) = fragment_res { warn!("failed to recover fragment data: {:?}. The whole underlying message might be corrupted and unrecoverable!", e); + warn!( + "the string message: {:?}", + String::from_utf8(fragment_data).unwrap() + ); return None; } let fragment = fragment_res.unwrap(); diff --git a/gateway/gateway-requests/src/lib.rs b/gateway/gateway-requests/src/lib.rs index 667e9f83721..f437b47ddfa 100644 --- a/gateway/gateway-requests/src/lib.rs +++ b/gateway/gateway-requests/src/lib.rs @@ -18,4 +18,5 @@ pub mod types; pub const DUMMY_MESSAGE_CONTENT: &[u8] = b"[DUMMY MESSAGE] Wanting something does not give you the right to have it."; +pub use auth_token::AuthToken; pub use types::*; From 64ca290671e81b5a4de794cdc8c119c186a1fc9e Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 29 Apr 2020 13:04:09 +0100 Subject: [PATCH 61/70] cargo fmt --- clients/desktop/src/client/received_buffer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/desktop/src/client/received_buffer.rs b/clients/desktop/src/client/received_buffer.rs index 13ab5df0d59..c7fb7c2b0fc 100644 --- a/clients/desktop/src/client/received_buffer.rs +++ b/clients/desktop/src/client/received_buffer.rs @@ -15,12 +15,12 @@ use futures::channel::{mpsc, oneshot}; use futures::lock::Mutex; use futures::StreamExt; +use gateway_client::SphinxPacketReceiver; use log::*; use nymsphinx::chunking::reconstruction::MessageReconstructor; use std::sync::Arc; use tokio::runtime::Handle; use tokio::task::JoinHandle; -use gateway_client::SphinxPacketReceiver; pub(crate) type ReceivedBufferResponse = oneshot::Sender>>; pub(crate) type ReceivedBufferRequestSender = mpsc::UnboundedSender; From 56380987a68ac28d094e5b5ec616621a2429550a Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 29 Apr 2020 13:54:34 +0100 Subject: [PATCH 62/70] Removed provider poller --- clients/desktop/src/client/provider_poller.rs | 122 ------------------ 1 file changed, 122 deletions(-) delete mode 100644 clients/desktop/src/client/provider_poller.rs diff --git a/clients/desktop/src/client/provider_poller.rs b/clients/desktop/src/client/provider_poller.rs deleted file mode 100644 index da128fa08cf..00000000000 --- a/clients/desktop/src/client/provider_poller.rs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use futures::channel::mpsc; -use log::*; -use nymsphinx::DestinationAddressBytes; -use provider_client::ProviderClientError; -use sfw_provider_requests::auth_token::AuthToken; -use std::net::SocketAddr; -use std::time; -use tokio::runtime::Handle; -use tokio::task::JoinHandle; - -pub(crate) type PolledMessagesSender = mpsc::UnboundedSender>>; -pub(crate) type PolledMessagesReceiver = mpsc::UnboundedReceiver>>; - -pub(crate) struct ProviderPoller { - polling_rate: time::Duration, - provider_client: provider_client::ProviderClient, - poller_tx: mpsc::UnboundedSender>>, -} - -impl ProviderPoller { - pub(crate) fn new( - poller_tx: mpsc::UnboundedSender>>, - provider_client_listener_address: SocketAddr, - client_address: DestinationAddressBytes, - auth_token: Option, - polling_rate: time::Duration, - max_response_size: usize, - ) -> Self { - ProviderPoller { - provider_client: provider_client::ProviderClient::new( - provider_client_listener_address, - client_address, - auth_token, - max_response_size, - ), - poller_tx, - polling_rate, - } - } - - pub(crate) fn is_registered(&self) -> bool { - self.provider_client.is_registered() - } - - // This method is only temporary until registration is moved to `client init` - pub(crate) async fn perform_initial_registration(&mut self) -> Result<(), ProviderClientError> { - debug!("performing initial provider registration"); - - if !self.is_registered() { - let auth_token = match self.provider_client.register().await { - // in this particular case we can ignore this error - Err(ProviderClientError::ClientAlreadyRegisteredError) => return Ok(()), - Err(err) => return Err(err), - Ok(token) => token, - }; - - self.provider_client.update_token(auth_token) - } else { - warn!("did not perform provider registration - we were already registered") - } - - Ok(()) - } - - pub(crate) async fn start_provider_polling(&mut self) { - let loop_message = &mix_client::packet::LOOP_COVER_MESSAGE_PAYLOAD.to_vec(); - let dummy_message = &sfw_provider_requests::DUMMY_MESSAGE_CONTENT.to_vec(); - - let extended_delay_duration = self.polling_rate * 10; - - loop { - debug!("Polling provider..."); - - let messages = match self.provider_client.retrieve_messages().await { - Err(err) => { - error!("Failed to query the provider for messages: {:?}, ... Going to wait {:?} before retrying", err, extended_delay_duration); - tokio::time::delay_for(extended_delay_duration).await; - continue; - } - Ok(messages) => messages, - }; - - // todo: this will probably need to be updated at some point due to changed structure - // of nym-sphinx messages sent. However, for time being this is still compatible. - // Basically it is more of a personal note of if client keeps getting errors and is - // not getting messages, look at this filter here. - let good_messages: Vec<_> = messages - .into_iter() - .filter(|message| message != loop_message && message != dummy_message) - .collect(); - trace!("Obtained the following messages: {:?}", good_messages); - - // if this one fails, there's no retrying because it means that either: - // - we run out of memory - // - the receiver channel is closed - // in either case there's no recovery and we can only panic - if !good_messages.is_empty() { - self.poller_tx.unbounded_send(good_messages).unwrap(); - } - - tokio::time::delay_for(self.polling_rate).await; - } - } - - pub(crate) fn start(mut self, handle: &Handle) -> JoinHandle<()> { - handle.spawn(async move { self.start_provider_polling().await }) - } -} From 1b6cfd5318bfaf0234f8cf8143e134c1652cd5fd Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 29 Apr 2020 14:30:11 +0100 Subject: [PATCH 63/70] Updated config with gateway values instead of provider --- clients/desktop/src/client/mod.rs | 7 +- clients/desktop/src/commands/init.rs | 91 ++++----------------- clients/desktop/src/commands/mod.rs | 4 +- clients/desktop/src/commands/run.rs | 6 +- clients/desktop/src/config/mod.rs | 108 +++++++------------------ clients/desktop/src/config/template.rs | 11 ++- common/topology/src/gateway.rs | 2 +- 7 files changed, 57 insertions(+), 172 deletions(-) diff --git a/clients/desktop/src/client/mod.rs b/clients/desktop/src/client/mod.rs index b7c37d3422d..9ce858ade37 100644 --- a/clients/desktop/src/client/mod.rs +++ b/clients/desktop/src/client/mod.rs @@ -167,12 +167,9 @@ impl NymClient { sphinx_packet_sender: SphinxPacketSender, gateway_address: url::Url, ) -> GatewayClient<'static, url::Url> { - // TODO: put into config - let gateway_response_timeout = Duration::from_millis(1500); - let auth_token = self .config - .get_provider_auth_token() + .get_gateway_auth_token() .map(|str_token| AuthToken::try_from_base58_string(str_token).ok()) .unwrap_or(None); @@ -181,7 +178,7 @@ impl NymClient { self.as_mix_destination_address(), auth_token, sphinx_packet_sender, - gateway_response_timeout, + self.config.get_gateway_response_timeout(), ); let auth_token = self.runtime.block_on(async { diff --git a/clients/desktop/src/commands/init.rs b/clients/desktop/src/commands/init.rs index 1f4552455e6..2e57b4e8ea5 100644 --- a/clients/desktop/src/commands/init.rs +++ b/clients/desktop/src/commands/init.rs @@ -37,9 +37,9 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .takes_value(true) .required(true) ) - .arg(Arg::with_name("provider") - .long("provider") - .help("Id of the provider we have preference to connect to. If left empty, a random provider will be chosen.") + .arg(Arg::with_name("gateway") + .long("gateway") + .help("Id of the gateway we have preference to connect to. If left empty, a random gateway will be chosen.") .takes_value(true) ) .arg(Arg::with_name("directory") @@ -60,29 +60,6 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { ) } -//async fn try_provider_registrations( -// providers: Vec, -// our_address: DestinationAddressBytes, -//) -> Option<(String, AuthToken)> { -// // we don't expect the response to be longer than AUTH_TOKEN_SIZE, but allow for more bytes -// // in case there was an error message -// let max_response_len = 16 * 1024; -// // since the order of providers is non-deterministic we can just try to get a first 'working' provider -// for provider in providers { -// let mut provider_client = provider_client::ProviderClient::new( -// provider.client_listener, -// our_address.clone(), -// None, -// max_response_len, -// ); -// let auth_token = provider_client.register().await; -// if let Ok(token) = auth_token { -// return Some((provider.pub_key, token)); -// } -// } -// None -//} - async fn try_gateway_registration( gateways: Vec, our_address: DestinationAddressBytes, @@ -93,9 +70,7 @@ async fn try_gateway_registration( let timeout = Duration::from_millis(1500); for gateway in gateways { let mut gateway_client = GatewayClient::new( - // TODO: can you construct urls this way? - url::Url::parse(format!("ws://{}", gateway.client_listener.to_string()).as_ref()) - .unwrap(), + url::Url::parse(&gateway.client_listener).unwrap(), our_address.clone(), None, sphinx_tx.clone(), @@ -110,37 +85,6 @@ async fn try_gateway_registration( None } -// in the long run this will be provider specific and only really applicable to a -// relatively small subset of all providers -//async fn choose_provider( -// directory_server: String, -// our_address: DestinationAddressBytes, -//) -> (String, AuthToken) { -// // TODO: once we change to graph topology this here will need to be updated! -// let topology = Topology::new(directory_server.clone()); -// let version_filtered_topology = topology.filter_system_version(built_info::PKG_VERSION); -// // don't care about health of the networks as mixes can go up and down any time, -// // but DO care about providers -// let providers = version_filtered_topology.providers(); -// -// // try to perform registration so that we wouldn't need to do it at startup -// // + at the same time we'll know if we can actually talk with that provider -// let registration_result = try_provider_registrations(providers, our_address).await; -// match registration_result { -// None => { -// // while technically there's no issue client-side, it will be impossible to execute -// // `nym-client run` as no provider is available so it might be best to not finalize -// // the init and rely on users trying to init another time? -// panic!( -// "Currently there are no valid providers available on the network ({}). \ -// Please try to run `init` again at later time or change your directory server", -// directory_server -// ) -// } -// Some((provider_id, auth_token)) => (provider_id, auth_token), -// } -//} - async fn choose_gateway( directory_server: String, our_address: DestinationAddressBytes, @@ -149,16 +93,16 @@ async fn choose_gateway( let topology = Topology::new(directory_server.clone()); let version_filtered_topology = topology.filter_system_version(built_info::PKG_VERSION); // don't care about health of the networks as mixes can go up and down any time, - // but DO care about providers + // but DO care about gateways let gateways = version_filtered_topology.gateways(); // try to perform registration so that we wouldn't need to do it at startup - // + at the same time we'll know if we can actually talk with that provider + // + at the same time we'll know if we can actually talk with that gateway let registration_result = try_gateway_registration(gateways, our_address).await; match registration_result { None => { // while technically there's no issue client-side, it will be impossible to execute - // `nym-client run` as no provider is available so it might be best to not finalize + // `nym-client run` as no gateway is available so it might be best to not finalize // the init and rely on users trying to init another time? panic!( "Currently there are no valid gateways available on the network ({}). \ @@ -166,7 +110,7 @@ async fn choose_gateway( directory_server ) } - Some((provider_id, auth_token)) => (provider_id, auth_token), + Some((gateway_id, auth_token)) => (gateway_id, auth_token), } } @@ -180,18 +124,18 @@ pub fn execute(matches: &ArgMatches) { let mix_identity_keys = MixIdentityKeyPair::new(); - // if there is no provider chosen, get a random-ish one from the topology - if config.get_provider_id().is_empty() { + // if there is no gateway chosen, get a random-ish one from the topology + if config.get_gateway_id().is_empty() { let our_address = mix_identity_keys.public_key().derive_address(); // TODO: is there perhaps a way to make it work without having to spawn entire runtime? let mut rt = tokio::runtime::Runtime::new().unwrap(); let (gateway_id, auth_token) = rt.block_on(choose_gateway(config.get_directory_server(), our_address)); - // TODO: this isn't really a provider, but gateway, yet another change to make + // TODO: this isn't really a gateway, but gateway, yet another change to make config = config - .with_provider_id(gateway_id) - .with_provider_auth_token(auth_token); + .with_gateway_id(gateway_id) + .with_gateway_auth_token(auth_token); } let pathfinder = ClientPathfinder::new_from_config(&config); @@ -207,15 +151,14 @@ pub fn execute(matches: &ArgMatches) { .expect("Failed to save the config file"); println!("Saved configuration file to {:?}", config_save_location); - // TODO: again change provider -> gateway println!( - "Unless overridden in all `nym-client run` we will be talking to the following provider: {}...", - config.get_provider_id(), + "Unless overridden in all `nym-client run` we will be talking to the following gateway: {}...", + config.get_gateway_id(), ); - if config.get_provider_auth_token().is_some() { + if config.get_gateway_auth_token().is_some() { println!( "using optional AuthToken: {:?}", - config.get_provider_auth_token().unwrap() + config.get_gateway_auth_token().unwrap() ) } println!("Client configuration completed.\n\n\n") diff --git a/clients/desktop/src/commands/mod.rs b/clients/desktop/src/commands/mod.rs index 31041045955..fc1329caa54 100644 --- a/clients/desktop/src/commands/mod.rs +++ b/clients/desktop/src/commands/mod.rs @@ -23,8 +23,8 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi config = config.with_custom_directory(directory); } - if let Some(provider_id) = matches.value_of("provider") { - config = config.with_provider_id(provider_id); + if let Some(gateway_id) = matches.value_of("gateway") { + config = config.with_gateway_id(gateway_id); } if let Some(socket_type) = matches.value_of("socket-type") { diff --git a/clients/desktop/src/commands/run.rs b/clients/desktop/src/commands/run.rs index 9a6ee572a96..07048a071ab 100644 --- a/clients/desktop/src/commands/run.rs +++ b/clients/desktop/src/commands/run.rs @@ -38,9 +38,9 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .help("Address of the directory server the client is getting topology from") .takes_value(true), ) - .arg(Arg::with_name("provider") - .long("provider") - .help("Id of the provider we want to connect to. If overridden, it is user's responsibility to ensure prior registration happened") + .arg(Arg::with_name("gateway") + .long("gateway") + .help("Id of the gateway we want to connect to. If overridden, it is user's responsibility to ensure prior registration happened") .takes_value(true) ) .arg(Arg::with_name("socket-type") diff --git a/clients/desktop/src/config/mod.rs b/clients/desktop/src/config/mod.rs index 7fc1cd08d05..68e5d01b7dc 100644 --- a/clients/desktop/src/config/mod.rs +++ b/clients/desktop/src/config/mod.rs @@ -29,20 +29,15 @@ const DEFAULT_LISTENING_PORT: u16 = 9001; const DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY: u64 = 1000; // 1s const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: u64 = 500; // 0.5s const DEFAULT_AVERAGE_PACKET_DELAY: u64 = 200; // 0.2s -const DEFAULT_FETCH_MESSAGES_DELAY: u64 = 1000; // 1s const DEFAULT_TOPOLOGY_REFRESH_RATE: u64 = 30_000; // 30s const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: u64 = 5_000; // 5s -const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: u64 = 10_000; // 10s -const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: u64 = 300_000; // 5min -const DEFAULT_INITIAL_CONNECTION_TIMEOUT: u64 = 1_500; // 1.5s -const DEFAULT_HEALTHCHECK_CONNECTION_TIMEOUT: u64 = DEFAULT_INITIAL_CONNECTION_TIMEOUT; + +const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: u64 = 1_500; // 1.5s +const DEFAULT_HEALTHCHECK_CONNECTION_TIMEOUT: u64 = 1_500; // 1.5s const DEFAULT_NUMBER_OF_HEALTHCHECK_TEST_PACKETS: u64 = 2; const DEFAULT_NODE_SCORE_THRESHOLD: f64 = 0.0; -// for time being treat it as if there was no limit -const DEFAULT_MAX_RESPONSE_SIZE: u32 = u32::max_value(); - #[derive(Debug, Deserialize, PartialEq, Serialize, Clone, Copy)] #[serde(deny_unknown_fields)] pub enum SocketType { @@ -130,13 +125,13 @@ impl Config { self } - pub fn with_provider_id>(mut self, id: S) -> Self { - self.client.provider_id = id.into(); + pub fn with_gateway_id>(mut self, id: S) -> Self { + self.client.gateway_id = id.into(); self } - pub fn with_provider_auth_token>(mut self, token: S) -> Self { - self.client.provider_authtoken = Some(token.into()); + pub fn with_gateway_auth_token>(mut self, token: S) -> Self { + self.client.gateway_authtoken = Some(token.into()); self } @@ -172,12 +167,12 @@ impl Config { self.client.directory_server.clone() } - pub fn get_provider_id(&self) -> String { - self.client.provider_id.clone() + pub fn get_gateway_id(&self) -> String { + self.client.gateway_id.clone() } - pub fn get_provider_auth_token(&self) -> Option { - self.client.provider_authtoken.clone() + pub fn get_gateway_auth_token(&self) -> Option { + self.client.gateway_authtoken.clone() } pub fn get_socket_type(&self) -> SocketType { @@ -197,16 +192,12 @@ impl Config { time::Duration::from_millis(self.debug.loop_cover_traffic_average_delay) } - pub fn get_fetch_message_delay(&self) -> time::Duration { - time::Duration::from_millis(self.debug.fetch_message_delay) - } - pub fn get_message_sending_average_delay(&self) -> time::Duration { time::Duration::from_millis(self.debug.message_sending_average_delay) } - pub fn get_rate_compliant_cover_messages_disabled(&self) -> bool { - self.debug.rate_compliant_cover_messages_disabled + pub fn get_gateway_response_timeout(&self) -> time::Duration { + time::Duration::from_millis(self.debug.gateway_response_timeout) } pub fn get_topology_refresh_rate(&self) -> time::Duration { @@ -225,25 +216,9 @@ impl Config { self.debug.node_score_threshold } - pub fn get_packet_forwarding_initial_backoff(&self) -> time::Duration { - time::Duration::from_millis(self.debug.packet_forwarding_initial_backoff) - } - - pub fn get_packet_forwarding_maximum_backoff(&self) -> time::Duration { - time::Duration::from_millis(self.debug.packet_forwarding_maximum_backoff) - } - - pub fn get_initial_connection_timeout(&self) -> time::Duration { - time::Duration::from_millis(self.debug.initial_connection_timeout) - } - pub fn get_healthcheck_connection_timeout(&self) -> time::Duration { time::Duration::from_millis(self.debug.healthcheck_connection_timeout) } - - pub fn get_max_response_size(&self) -> usize { - self.debug.max_response_size as usize - } } fn de_option_string<'de, D>(deserializer: D) -> Result, D::Error> @@ -273,14 +248,14 @@ pub struct Client { /// Path to file containing public identity key. public_identity_key_file: PathBuf, - /// provider_id specifies ID of the provider to which the client should send messages. - /// If initially omitted, a random provider will be chosen from the available topology. - provider_id: String, + /// gateway_id specifies ID of the gateway to which the client should send messages. + /// If initially omitted, a random gateway will be chosen from the available topology. + gateway_id: String, - /// A provider specific, optional, base58 stringified authentication token used for - /// communication with particular provider. + /// A gateway specific, optional, base58 stringified authentication token used for + /// communication with particular gateway. #[serde(deserialize_with = "de_option_string")] - provider_authtoken: Option, + gateway_authtoken: Option, /// nym_home_directory specifies absolute path to the home nym Clients directory. /// It is expected to use default value and hence .toml file should not redefine this field. @@ -295,8 +270,8 @@ impl Default for Client { directory_server: Self::default_directory_server(), private_identity_key_file: Default::default(), public_identity_key_file: Default::default(), - provider_id: "".to_string(), - provider_authtoken: None, + gateway_id: "".to_string(), + gateway_authtoken: None, nym_root_directory: Config::default_root_directory(), } } @@ -362,10 +337,6 @@ pub struct Debug { /// The provided value is interpreted as milliseconds. loop_cover_traffic_average_delay: u64, - /// The uniform delay every which clients are querying the providers for received packets. - /// The provided value is interpreted as milliseconds. - fetch_message_delay: u64, - /// The parameter of Poisson distribution determining how long, on average, /// it is going to take another 'real traffic stream' message to be sent. /// If no real packets are available and cover traffic is enabled, @@ -373,11 +344,10 @@ pub struct Debug { /// The provided value is interpreted as milliseconds. message_sending_average_delay: u64, - /// Whether loop cover messages should be sent to respect message_sending_rate. - /// In the case of it being disabled and not having enough real traffic - /// waiting to be sent the actual sending rate is going be lower than the desired value - /// thus decreasing the anonymity. - rate_compliant_cover_messages_disabled: bool, + /// How long we're willing to wait for a response to a message sent to the gateway, + /// before giving up on it. + /// The provided value is interpreted as milliseconds. + gateway_response_timeout: u64, /// The uniform delay every which clients are querying the directory server /// to try to obtain a compatible network topology to send sphinx packets through. @@ -398,29 +368,10 @@ pub struct Debug { /// considered healthy. node_score_threshold: f64, - /// Initial value of an exponential backoff to reconnect to dropped TCP connection when - /// forwarding sphinx packets. - /// The provided value is interpreted as milliseconds. - packet_forwarding_initial_backoff: u64, - - /// Maximum value of an exponential backoff to reconnect to dropped TCP connection when - /// forwarding sphinx packets. - /// The provided value is interpreted as milliseconds. - packet_forwarding_maximum_backoff: u64, - - /// Timeout for establishing initial connection when trying to forward a sphinx packet. - /// The provider value is interpreted as milliseconds. - initial_connection_timeout: u64, - /// Timeout for establishing initial connection when trying to forward a sphinx packet /// during healthcheck. - /// The provider value is interpreted as milliseconds. + /// The provided value is interpreted as milliseconds. healthcheck_connection_timeout: u64, - - /// Maximum allowed length for sfw-provider responses received. - /// Anything declaring bigger size than that will be regarded as an error and - /// is going to be rejected. - max_response_size: u32, } impl Default for Debug { @@ -428,18 +379,13 @@ impl Default for Debug { Debug { average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY, loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY, - fetch_message_delay: DEFAULT_FETCH_MESSAGES_DELAY, message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY, - rate_compliant_cover_messages_disabled: false, + gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT, topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE, topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT, number_of_healthcheck_test_packets: DEFAULT_NUMBER_OF_HEALTHCHECK_TEST_PACKETS, node_score_threshold: DEFAULT_NODE_SCORE_THRESHOLD, - packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF, - packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF, - initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT, healthcheck_connection_timeout: DEFAULT_HEALTHCHECK_CONNECTION_TIMEOUT, - max_response_size: DEFAULT_MAX_RESPONSE_SIZE, } } } diff --git a/clients/desktop/src/config/template.rs b/clients/desktop/src/config/template.rs index 2b2e4c9a79d..f06acab0e1d 100644 --- a/clients/desktop/src/config/template.rs +++ b/clients/desktop/src/config/template.rs @@ -38,12 +38,12 @@ public_identity_key_file = '{{ client.public_identity_key_file }}' ##### additional client config options ##### -# ID of the provider from which the client should be fetching messages. -provider_id = '{{ client.provider_id }}' +# ID of the gateway from which the client should be fetching messages. +gateway_id = '{{ client.gateway_id }}' -# A provider specific, optional, base58 stringified authentication token used for -# communication with particular provider. -provider_authtoken = '{{ client.provider_authtoken }}' +# A gateway specific, optional, base58 stringified authentication token used for +# communication with particular gateway. +gateway_authtoken = '{{ client.gateway_authtoken }}' ##### advanced configuration options ##### @@ -78,7 +78,6 @@ listening_port = {{ socket.listening_port }} average_packet_delay = {{ debug.average_packet_delay }} loop_cover_traffic_average_delay = {{ debug.loop_cover_traffic_average_delay }} -fetch_message_delay = {{ debug.fetch_message_delay }} message_sending_average_delay = {{ debug.message_sending_average_delay }} "# diff --git a/common/topology/src/gateway.rs b/common/topology/src/gateway.rs index c869a886370..aca4da3fcb4 100644 --- a/common/topology/src/gateway.rs +++ b/common/topology/src/gateway.rs @@ -26,7 +26,7 @@ pub struct Client { #[derive(Debug, Clone)] pub struct Node { pub location: String, - pub client_listener: SocketAddr, + pub client_listener: String, pub mixnet_listener: SocketAddr, pub pub_key: String, pub registered_clients: Vec, From 6873057b77725e928b0e1afcf990f37e702b5da7 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 29 Apr 2020 14:56:47 +0100 Subject: [PATCH 64/70] Gateway address including ws --- gateway/src/config/mod.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/gateway/src/config/mod.rs b/gateway/src/config/mod.rs index b8567270977..110dba7a198 100644 --- a/gateway/src/config/mod.rs +++ b/gateway/src/config/mod.rs @@ -233,8 +233,10 @@ impl Config { } pub fn clients_announce_host_from_listening_host(mut self) -> Self { - self.clients_endpoint.announce_address = - self.clients_endpoint.listening_address.to_string(); + self.clients_endpoint.announce_address = format!( + "ws://{}", + self.clients_endpoint.listening_address.to_string() + ); self } @@ -259,11 +261,21 @@ impl Config { host, self.clients_endpoint.listening_address.port() ); + // make sure it has 'ws' prefix (by extension it also includes 'wss') + if !self.clients_endpoint.announce_address.starts_with("ws") { + self.clients_endpoint.announce_address = + format!("ws://{}", self.clients_endpoint.announce_address); + } self } 2 => { // we provided 'host:port' so just put the whole thing there self.clients_endpoint.announce_address = host; + // make sure it has 'ws' prefix (by extension it also includes 'wss') + if !self.clients_endpoint.announce_address.starts_with("ws") { + self.clients_endpoint.announce_address = + format!("ws://{}", self.clients_endpoint.announce_address); + } self } _ => { @@ -479,7 +491,7 @@ impl Default for ClientsEndpoint { listening_address: format!("0.0.0.0:{}", DEFAULT_CLIENT_LISTENING_PORT) .parse() .unwrap(), - announce_address: format!("127.0.0.1:{}", DEFAULT_CLIENT_LISTENING_PORT), + announce_address: format!("ws://127.0.0.1:{}", DEFAULT_CLIENT_LISTENING_PORT), inboxes_directory: Default::default(), ledger_path: Default::default(), } From 3f8f75a784a6620a5a89d13f2b4164fa57577180 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 29 Apr 2020 15:40:28 +0100 Subject: [PATCH 65/70] Removed hardcoded gateway address --- clients/desktop/src/client/mod.rs | 83 ++++++------------- .../desktop/src/client/topology_control.rs | 11 +++ 2 files changed, 38 insertions(+), 56 deletions(-) diff --git a/clients/desktop/src/client/mod.rs b/clients/desktop/src/client/mod.rs index 9ce858ade37..05b86772edb 100644 --- a/clients/desktop/src/client/mod.rs +++ b/clients/desktop/src/client/mod.rs @@ -14,7 +14,6 @@ use crate::client::cover_traffic_stream::LoopCoverTrafficStream; use crate::client::mix_traffic::{MixMessageReceiver, MixMessageSender, MixTrafficController}; -//use crate::client::provider_poller::{PolledMessagesReceiver, PolledMessagesSender}; use crate::client::received_buffer::{ ReceivedBufferRequestReceiver, ReceivedBufferRequestSender, ReceivedMessagesBufferController, }; @@ -27,21 +26,17 @@ use crate::sockets::{tcp, websocket}; use crypto::identity::MixIdentityKeyPair; use directory_client::presence; use futures::channel::{mpsc, oneshot}; +use gateway_client::{GatewayClient, SphinxPacketReceiver, SphinxPacketSender}; +use gateway_requests::auth_token::AuthToken; use log::*; use nymsphinx::chunking::split_and_prepare_payloads; use nymsphinx::{Destination, DestinationAddressBytes}; use pemstore::pemstore::PemStore; -//use sfw_provider_requests::auth_token::AuthToken; -use gateway_client::{GatewayClient, SphinxPacketReceiver, SphinxPacketSender}; -use gateway_requests::auth_token::AuthToken; -use std::net::SocketAddr; -use std::time::Duration; use tokio::runtime::Runtime; use topology::NymTopology; mod cover_traffic_stream; mod mix_traffic; -//mod provider_poller; mod real_traffic_stream; pub(crate) mod received_buffer; pub(crate) mod topology_control; @@ -201,51 +196,27 @@ impl NymClient { gateway_client } - // // future constantly trying to fetch any received messages from the provider - // // the received messages are sent to ReceivedMessagesBuffer to be available to rest of the system - // fn start_provider_poller( - // &mut self, - // mut topology_accessor: TopologyAccessor, - // poller_input_tx: PolledMessagesSender, - // ) { - // info!("Starting provider poller..."); - // // we already have our provider written in the config - // let provider_id = self.config.get_provider_id(); - // - // // TODO: a slightly more graceful termination here - // if !self.runtime.block_on(topology_accessor.is_routable()) { - // panic!( - // "The current network topology seem to be insufficient to route any packets through\ - // - check if enough nodes and a sfw-provider are online" - // ); - // } - // - // // TODO: a slightly more graceful termination here - // let provider_client_listener_address = self.runtime.block_on( - // topology_accessor.get_provider_socket_addr(&provider_id) - // ).unwrap_or_else(|| panic!("Could not find provider with id {:?}. It does not seem to be present in the current network topology.\ - // Are you sure it is still online? Perhaps try to run `nym-client init` again to obtain a new provider", provider_id)); - // - // let mut provider_poller = provider_poller::ProviderPoller::new( - // poller_input_tx, - // provider_client_listener_address, - // self.identity_keypair.public_key().derive_address(), - // self.config - // .get_provider_auth_token() - // .map(|str_token| AuthToken::try_from_base58_string(str_token).ok()) - // .unwrap_or(None), - // self.config.get_fetch_message_delay(), - // self.config.get_max_response_size(), - // ); - // - // if !provider_poller.is_registered() { - // info!("Trying to perform initial provider registration..."); - // self.runtime - // .block_on(provider_poller.perform_initial_registration()) - // .expect("Failed to perform initial provider registration"); - // } - // provider_poller.start(self.runtime.handle()); - // } + async fn get_gateway_address( + gateway_id: String, + mut topology_accessor: TopologyAccessor, + ) -> url::Url { + // we already have our gateway written in the config + let gateway_address = topology_accessor + .get_gateway_socket_url(&gateway_id) + .await + .expect( + format!( + "Could not find gateway with id {:?}.\ + It does not seem to be present in the current network topology.\ + Are you sure it is still online?\ + Perhaps try to run `nym-client init` again to obtain a new gateway", + gateway_id + ) + .as_ref(), + ); + + url::Url::parse(&gateway_address).expect("provided gateway address is invalid!") + } // future responsible for periodically polling directory server and updating // the current global view of topology @@ -370,9 +341,6 @@ impl NymClient { } pub fn start(&mut self) { - // TODO: put into config, etc. - let gateway_url = url::Url::parse("ws://localhost:9000").unwrap(); - info!("Starting nym client"); // channels for inter-component communication @@ -403,8 +371,11 @@ impl NymClient { received_messages_buffer_output_rx, sphinx_packet_rx, ); - // self.start_provider_poller(shared_topology_accessor.clone(), sphinx_packet_tx); + let gateway_url = self.runtime.block_on(Self::get_gateway_address( + self.config.get_gateway_id(), + shared_topology_accessor.clone(), + )); let gateway_client = self.start_gateway_client(sphinx_packet_tx, gateway_url); self.start_mix_traffic_controller(mix_rx, gateway_client); diff --git a/clients/desktop/src/client/topology_control.rs b/clients/desktop/src/client/topology_control.rs index f46eb8370f6..f7798f68e2c 100644 --- a/clients/desktop/src/client/topology_control.rs +++ b/clients/desktop/src/client/topology_control.rs @@ -67,6 +67,17 @@ impl TopologyAccessor { } } + pub(crate) async fn get_gateway_socket_url(&mut self, id: &str) -> Option { + match &self.inner.lock().await.0 { + None => None, + Some(ref topology) => topology + .gateways() + .iter() + .find(|gateway| gateway.pub_key == id) + .map(|gateway| gateway.client_listener.clone()), + } + } + // only used by the client at startup to get a slightly more reasonable error message pub(crate) async fn is_routable(&self) -> bool { match &self.inner.lock().await.0 { From 441c63cd2e3f875463c4f784a9fe2161b2316bb9 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 29 Apr 2020 15:46:20 +0100 Subject: [PATCH 66/70] Properly skipping loop cover messages --- clients/desktop/src/client/received_buffer.rs | 6 ++++++ common/nymsphinx/src/chunking/reconstruction.rs | 4 ---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/clients/desktop/src/client/received_buffer.rs b/clients/desktop/src/client/received_buffer.rs index c7fb7c2b0fc..4781ba46139 100644 --- a/clients/desktop/src/client/received_buffer.rs +++ b/clients/desktop/src/client/received_buffer.rs @@ -17,6 +17,7 @@ use futures::lock::Mutex; use futures::StreamExt; use gateway_client::SphinxPacketReceiver; use log::*; +use mix_client::packet::LOOP_COVER_MESSAGE_PAYLOAD; use nymsphinx::chunking::reconstruction::MessageReconstructor; use std::sync::Arc; use tokio::runtime::Handle; @@ -64,6 +65,11 @@ impl ReceivedMessagesBuffer { let mut completed_messages = Vec::new(); let mut inner_guard = self.inner.lock().await; for msg_fragment in msgs { + if msg_fragment == LOOP_COVER_MESSAGE_PAYLOAD { + trace!("The message was a loop cover message! Skipping it"); + continue; + } + if let Some(reconstructed_message) = inner_guard.message_reconstructor.new_fragment(msg_fragment) { diff --git a/common/nymsphinx/src/chunking/reconstruction.rs b/common/nymsphinx/src/chunking/reconstruction.rs index c9b9fdd109b..bcfac943893 100644 --- a/common/nymsphinx/src/chunking/reconstruction.rs +++ b/common/nymsphinx/src/chunking/reconstruction.rs @@ -274,10 +274,6 @@ impl MessageReconstructor { let fragment_res = Fragment::try_from_bytes(&fragment_data); if let Err(e) = fragment_res { warn!("failed to recover fragment data: {:?}. The whole underlying message might be corrupted and unrecoverable!", e); - warn!( - "the string message: {:?}", - String::from_utf8(fragment_data).unwrap() - ); return None; } let fragment = fragment_res.unwrap(); From e3ba13d51951fe2758481ff12eecb86d78b060f4 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 29 Apr 2020 16:24:22 +0100 Subject: [PATCH 67/70] Updated log filter with tokio tungstenite related modules --- clients/desktop/src/main.rs | 2 ++ gateway/src/main.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/clients/desktop/src/main.rs b/clients/desktop/src/main.rs index cb68443b0ea..7890cbeb481 100644 --- a/clients/desktop/src/main.rs +++ b/clients/desktop/src/main.rs @@ -80,5 +80,7 @@ fn setup_logging() { .filter_module("reqwest", log::LevelFilter::Warn) .filter_module("mio", log::LevelFilter::Warn) .filter_module("want", log::LevelFilter::Warn) + .filter_module("tungstenite", log::LevelFilter::Warn) + .filter_module("tokio_tungstenite", log::LevelFilter::Warn) .init(); } diff --git a/gateway/src/main.rs b/gateway/src/main.rs index 3499f2a6851..c921aaa1003 100644 --- a/gateway/src/main.rs +++ b/gateway/src/main.rs @@ -80,5 +80,7 @@ fn setup_logging() { .filter_module("mio", log::LevelFilter::Warn) .filter_module("want", log::LevelFilter::Warn) .filter_module("sled", log::LevelFilter::Warn) + .filter_module("tungstenite", log::LevelFilter::Warn) + .filter_module("tokio_tungstenite", log::LevelFilter::Warn) .init(); } From 7283400c05fd1019b94b1fe9fc4d43723f8f3d47 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 29 Apr 2020 17:20:58 +0100 Subject: [PATCH 68/70] Updated sphinx version used --- .../src/sockets/websocket/connection.rs | 12 ++++++++--- clients/webassembly/src/lib.rs | 5 +++-- common/client-libs/mix-client/src/packet.rs | 19 +++++++----------- common/healthcheck/src/path_check.rs | 1 + common/nymsphinx/Cargo.toml | 2 +- common/nymsphinx/src/lib.rs | 6 +++--- .../websocket/connection_handler.rs | 20 +++++++++++++------ .../receiver/packet_processing.rs | 14 ++++++------- mixnode/src/node/packet_processing.rs | 17 ++++++++-------- .../mix_handling/packet_processing.rs | 17 +++++++++------- 10 files changed, 64 insertions(+), 49 deletions(-) diff --git a/clients/desktop/src/sockets/websocket/connection.rs b/clients/desktop/src/sockets/websocket/connection.rs index 46950c29b08..43f4be21254 100644 --- a/clients/desktop/src/sockets/websocket/connection.rs +++ b/clients/desktop/src/sockets/websocket/connection.rs @@ -87,9 +87,15 @@ impl Connection { fn handle_text_send(&self, msg: String, recipient_address: String) -> ServerResponse { let message_bytes = msg.into_bytes(); - // TODO: the below can panic if recipient_address is malformed, but it should be - // resolved when refactoring sphinx code to make `from_base58_string` return a Result - let address = DestinationAddressBytes::from_base58_string(recipient_address); + let address = match DestinationAddressBytes::try_from_base58_string(recipient_address) { + Ok(address) => address, + Err(e) => { + trace!("failed to parse received DestinationAddress: {:?}", e); + return ServerResponse::Error { + message: "malformed destination address".to_string(), + }; + } + }; let dummy_surb = [0; 16]; // in case the message is too long and needs to be split into multiple packets: diff --git a/clients/webassembly/src/lib.rs b/clients/webassembly/src/lib.rs index 779c32c8b78..11a026fc0ed 100644 --- a/clients/webassembly/src/lib.rs +++ b/clients/webassembly/src/lib.rs @@ -62,10 +62,11 @@ pub fn create_sphinx_packet(topology_json: &str, msg: &str, destination: &str) - let average_delay = Duration::from_secs_f64(0.1); let delays = delays::generate_from_average_duration(route.len(), average_delay); - let dest_bytes = DestinationAddressBytes::from_base58_string(destination.to_owned()); + let dest_bytes = + DestinationAddressBytes::try_from_base58_string(destination.to_owned()).unwrap(); let dest = Destination::new(dest_bytes, [4u8; IDENTIFIER_LENGTH]); let message = split_and_prepare_payloads(&msg.as_bytes()).pop().unwrap(); - let sphinx_packet = match SphinxPacket::new(message, &route, &dest, &delays).unwrap() { + let sphinx_packet = match SphinxPacket::new(message, &route, &dest, &delays, None).unwrap() { SphinxPacket { header, payload } => SphinxPacket { header, payload }, }; diff --git a/common/client-libs/mix-client/src/packet.rs b/common/client-libs/mix-client/src/packet.rs index 633d7aced6a..cb26b8e5015 100644 --- a/common/client-libs/mix-client/src/packet.rs +++ b/common/client-libs/mix-client/src/packet.rs @@ -13,9 +13,7 @@ // limitations under the License. use nymsphinx::addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError}; -use nymsphinx::{ - delays, Destination, DestinationAddressBytes, SURBIdentifier, SphinxPacket, SphinxUnwrapError, -}; +use nymsphinx::{delays, Destination, DestinationAddressBytes, SURBIdentifier, SphinxPacket}; use std::convert::TryFrom; use std::net::SocketAddr; use std::time; @@ -27,7 +25,7 @@ pub const LOOP_COVER_MESSAGE_PAYLOAD: &[u8] = b"The cake is a lie!"; pub enum SphinxPacketEncapsulationError { NoValidProvidersError, InvalidTopologyError, - SphinxEncapsulationError(SphinxUnwrapError), + SphinxError(nymsphinx::Error), InvalidFirstMixAddress, } @@ -38,12 +36,9 @@ impl From for SphinxPacketEncapsulationError { } } -// it is correct error we're converting from, it just has an unfortunate name -// related issue: https://github.com/nymtech/sphinx/issues/40 -impl From for SphinxPacketEncapsulationError { - fn from(err: SphinxUnwrapError) -> Self { - use SphinxPacketEncapsulationError::*; - SphinxEncapsulationError(err) +impl From for SphinxPacketEncapsulationError { + fn from(err: nymsphinx::Error) -> Self { + SphinxPacketEncapsulationError::SphinxError(err) } } @@ -107,7 +102,7 @@ pub fn encapsulate_message( let delays = delays::generate_from_average_duration(route.len(), average_delay); // build the packet - let packet = SphinxPacket::new(message, &route[..], &recipient, &delays)?; + let packet = SphinxPacket::new(message, &route[..], &recipient, &delays, None)?; // we know the mix route must be valid otherwise we would have already returned an error let first_node_address = @@ -125,7 +120,7 @@ pub fn encapsulate_message_route( let delays = delays::generate_from_average_duration(route.len(), average_delay); // build the packet - let packet = SphinxPacket::new(message, &route[..], &recipient, &delays)?; + let packet = SphinxPacket::new(message, &route[..], &recipient, &delays, None)?; let first_node_address = NymNodeRoutingAddress::try_from(route.first().unwrap().address.clone())?; diff --git a/common/healthcheck/src/path_check.rs b/common/healthcheck/src/path_check.rs index d5dd7e5df05..9f0d54ed27e 100644 --- a/common/healthcheck/src/path_check.rs +++ b/common/healthcheck/src/path_check.rs @@ -259,6 +259,7 @@ impl PathChecker { &path[..], &self.our_destination, &delays, + None, ) .unwrap(); diff --git a/common/nymsphinx/Cargo.toml b/common/nymsphinx/Cargo.toml index 3649294f8a3..fb3be3da064 100644 --- a/common/nymsphinx/Cargo.toml +++ b/common/nymsphinx/Cargo.toml @@ -11,5 +11,5 @@ log = "0.4.8" rand = {version = "0.7.3", features = ["wasm-bindgen"]} ## will be moved to proper dependencies once released -sphinx = { git = "https://github.com/nymtech/sphinx", rev="298c7fda6a577daf6d9bb955fa52c2bffecf6926" } +sphinx = { git = "https://github.com/nymtech/sphinx", rev="72368ea048472823f7f9e78eed77a2c3d30a471f" } diff --git a/common/nymsphinx/src/lib.rs b/common/nymsphinx/src/lib.rs index d85d3b08ced..5488c2b08d8 100644 --- a/common/nymsphinx/src/lib.rs +++ b/common/nymsphinx/src/lib.rs @@ -27,10 +27,10 @@ pub use sphinx::{ constants::{ DESTINATION_ADDRESS_LENGTH, IDENTIFIER_LENGTH, MAX_PATH_LENGTH, NODE_ADDRESS_LENGTH, }, - header::{delays, delays::Delay, ProcessedHeader, SphinxHeader, SphinxUnwrapError}, - payload::{Payload, PayloadEncapsulationError}, + header::{delays, delays::Delay, ProcessedHeader, SphinxHeader}, + payload::Payload, route::{Destination, DestinationAddressBytes, Node, NodeAddressBytes, SURBIdentifier}, - ProcessedPacket, ProcessingError, SphinxPacket, PACKET_SIZE, + Error, ProcessedPacket, Result, SphinxPacket, PACKET_SIZE, }; // re-exporting this separately to remember to put special attention to below diff --git a/gateway/src/node/client_handling/websocket/connection_handler.rs b/gateway/src/node/client_handling/websocket/connection_handler.rs index 69995d1c5f2..3ecffe5f2eb 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler.rs @@ -158,9 +158,13 @@ where token: String, mix_sender: MixMessageSender, ) -> ServerResponse { - // TODO: https://github.com/nymtech/sphinx/issues/57 to resolve possible panics - // because we do **NOT** trust whatever garbage client just sent. - let address = DestinationAddressBytes::from_base58_string(address); + let address = match DestinationAddressBytes::try_from_base58_string(address) { + Ok(address) => address, + Err(e) => { + trace!("failed to parse received DestinationAddress: {:?}", e); + return ServerResponse::new_error("malformed destination address").into(); + } + }; let token = match AuthToken::try_from_base58_string(token) { Ok(token) => token, Err(e) => { @@ -200,9 +204,13 @@ where address: String, mix_sender: MixMessageSender, ) -> ServerResponse { - // TODO: https://github.com/nymtech/sphinx/issues/57 to resolve possible panics - // because we do **NOT** trust whatever garbage client just sent. - let address = DestinationAddressBytes::from_base58_string(address); + let address = match DestinationAddressBytes::try_from_base58_string(address) { + Ok(address) => address, + Err(e) => { + trace!("failed to parse received DestinationAddress: {:?}", e); + return ServerResponse::new_error("malformed destination address").into(); + } + }; let (res_sender, res_receiver) = oneshot::channel(); let clients_handler_request = diff --git a/gateway/src/node/mixnet_handling/receiver/packet_processing.rs b/gateway/src/node/mixnet_handling/receiver/packet_processing.rs index 075e7fb815b..33e7cce31bb 100644 --- a/gateway/src/node/mixnet_handling/receiver/packet_processing.rs +++ b/gateway/src/node/mixnet_handling/receiver/packet_processing.rs @@ -22,7 +22,7 @@ use futures::channel::oneshot; use futures::lock::Mutex; use log::*; use mix_client::packet::LOOP_COVER_MESSAGE_PAYLOAD; -use nymsphinx::{DestinationAddressBytes, ProcessedPacket, SphinxPacket}; +use nymsphinx::{DestinationAddressBytes, Error as SphinxError, ProcessedPacket, SphinxPacket}; use std::collections::HashMap; use std::io; use std::ops::Deref; @@ -33,16 +33,16 @@ pub enum MixProcessingError { ReceivedForwardHopError, NonMatchingRecipient, InvalidPayload, - SphinxProcessingError, + SphinxProcessingError(SphinxError), IOError(String), } -impl From for MixProcessingError { - // for time being just have a single error instance for all possible results of nymsphinx::ProcessingError - fn from(_: nymsphinx::ProcessingError) -> Self { +impl From for MixProcessingError { + // for time being just have a single error instance for all possible results of SphinxError + fn from(err: SphinxError) -> Self { use MixProcessingError::*; - SphinxProcessingError + SphinxProcessingError(err) } } @@ -179,7 +179,7 @@ impl PacketProcessor { } Err(e) => { warn!("Failed to unwrap Sphinx packet: {:?}", e); - Err(MixProcessingError::SphinxProcessingError) + Err(MixProcessingError::SphinxProcessingError(e)) } } } diff --git a/mixnode/src/node/packet_processing.rs b/mixnode/src/node/packet_processing.rs index eb514ca7f95..adba39dc64f 100644 --- a/mixnode/src/node/packet_processing.rs +++ b/mixnode/src/node/packet_processing.rs @@ -16,7 +16,9 @@ use crate::node::metrics; use crypto::encryption; use log::*; use nymsphinx::addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError}; -use nymsphinx::{Delay as SphinxDelay, NodeAddressBytes, ProcessedPacket, SphinxPacket}; +use nymsphinx::{ + Delay as SphinxDelay, Error as SphinxError, NodeAddressBytes, ProcessedPacket, SphinxPacket, +}; use std::convert::TryFrom; use std::net::SocketAddr; use std::ops::Deref; @@ -24,9 +26,8 @@ use std::sync::Arc; #[derive(Debug)] pub enum MixProcessingError { - SphinxRecoveryError, ReceivedFinalHopError, - SphinxProcessingError, + SphinxProcessingError(SphinxError), InvalidHopAddress, } @@ -36,12 +37,12 @@ pub enum MixProcessingResult { LoopMessage, } -impl From for MixProcessingError { - // for time being just have a single error instance for all possible results of nymsphinx::ProcessingError - fn from(_: nymsphinx::ProcessingError) -> Self { +impl From for MixProcessingError { + // for time being just have a single error instance for all possible results of SphinxError + fn from(err: SphinxError) -> Self { use MixProcessingError::*; - SphinxRecoveryError + SphinxProcessingError(err) } } @@ -111,7 +112,7 @@ impl PacketProcessor { } Err(e) => { warn!("Failed to unwrap Sphinx packet: {:?}", e); - Err(MixProcessingError::SphinxProcessingError) + Err(MixProcessingError::SphinxProcessingError(e)) } } } diff --git a/sfw-provider/src/provider/mix_handling/packet_processing.rs b/sfw-provider/src/provider/mix_handling/packet_processing.rs index 27597bcbad4..0ca79b4acdf 100644 --- a/sfw-provider/src/provider/mix_handling/packet_processing.rs +++ b/sfw-provider/src/provider/mix_handling/packet_processing.rs @@ -16,7 +16,10 @@ use crate::provider::storage::{ClientStorage, StoreData}; use crypto::encryption; use log::*; use mix_client::packet::LOOP_COVER_MESSAGE_PAYLOAD; -use nymsphinx::{DestinationAddressBytes, Payload, ProcessedPacket, SURBIdentifier, SphinxPacket}; +use nymsphinx::{ + DestinationAddressBytes, Error as SphinxError, Payload, ProcessedPacket, SURBIdentifier, + SphinxPacket, +}; use std::io; use std::ops::Deref; use std::sync::Arc; @@ -26,7 +29,7 @@ pub enum MixProcessingError { ReceivedForwardHopError, NonMatchingRecipient, InvalidPayload, - SphinxProcessingError, + SphinxProcessingError(SphinxError), IOError(String), } @@ -36,12 +39,12 @@ pub enum MixProcessingResult { FinalHop, } -impl From for MixProcessingError { - // for time being just have a single error instance for all possible results of nymsphinx::ProcessingError - fn from(_: nymsphinx::ProcessingError) -> Self { +impl From for MixProcessingError { + // for time being just have a single error instance for all possible results of SphinxError + fn from(err: SphinxError) -> Self { use MixProcessingError::*; - SphinxProcessingError + SphinxProcessingError(err) } } @@ -115,7 +118,7 @@ impl PacketProcessor { } Err(e) => { warn!("Failed to unwrap Sphinx packet: {:?}", e); - Err(MixProcessingError::SphinxProcessingError) + Err(MixProcessingError::SphinxProcessingError(e)) } } } From ec15f0d7a9293a802f3de3ca19a60e191def9891 Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 29 Apr 2020 17:26:57 +0100 Subject: [PATCH 69/70] Very minor cleanup --- Cargo.lock | 2 +- clients/desktop/src/client/mix_traffic.rs | 1 - .../desktop/src/client/topology_control.rs | 2 + common/client-libs/gateway-client/src/lib.rs | 22 +++++++- gateway/gateway-requests/src/types.rs | 1 - .../client_handling/websocket/listener.rs | 2 + .../src/node/mixnet_handling/sender/mod.rs | 1 - gateway/src/node/mod.rs | 1 - gateway/src/node/storage/inboxes.rs | 56 +------------------ 9 files changed, 27 insertions(+), 61 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aa5d216b62b..a91f02ff92d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2687,7 +2687,7 @@ dependencies = [ [[package]] name = "sphinx" version = "0.1.0" -source = "git+https://github.com/nymtech/sphinx?rev=298c7fda6a577daf6d9bb955fa52c2bffecf6926#298c7fda6a577daf6d9bb955fa52c2bffecf6926" +source = "git+https://github.com/nymtech/sphinx?rev=72368ea048472823f7f9e78eed77a2c3d30a471f#72368ea048472823f7f9e78eed77a2c3d30a471f" dependencies = [ "aes-ctr", "arrayref", diff --git a/clients/desktop/src/client/mix_traffic.rs b/clients/desktop/src/client/mix_traffic.rs index 38ce9b2b073..4886cbfe19e 100644 --- a/clients/desktop/src/client/mix_traffic.rs +++ b/clients/desktop/src/client/mix_traffic.rs @@ -18,7 +18,6 @@ use gateway_client::GatewayClient; use log::*; use nymsphinx::SphinxPacket; use std::net::SocketAddr; -use std::time::Duration; use tokio::runtime::Handle; use tokio::task::JoinHandle; diff --git a/clients/desktop/src/client/topology_control.rs b/clients/desktop/src/client/topology_control.rs index f7798f68e2c..21919edb33a 100644 --- a/clients/desktop/src/client/topology_control.rs +++ b/clients/desktop/src/client/topology_control.rs @@ -56,6 +56,7 @@ impl TopologyAccessor { self.inner.lock().await.update(new_topology); } + // not removed until healtchecker is not fully changed to use gateways instead of providers pub(crate) async fn get_provider_socket_addr(&mut self, id: &str) -> Option { match &self.inner.lock().await.0 { None => None, @@ -79,6 +80,7 @@ impl TopologyAccessor { } // only used by the client at startup to get a slightly more reasonable error message + // (currently displays as unused because healthchecker is disabled due to required changes) pub(crate) async fn is_routable(&self) -> bool { match &self.inner.lock().await.0 { None => false, diff --git a/common/client-libs/gateway-client/src/lib.rs b/common/client-libs/gateway-client/src/lib.rs index 52dba6659f4..ed024544e5f 100644 --- a/common/client-libs/gateway-client/src/lib.rs +++ b/common/client-libs/gateway-client/src/lib.rs @@ -81,6 +81,8 @@ impl From for GatewayClientError { } } +// better human readable representation of the error, mostly so that GatewayClientError +// would implement std::error::Error impl fmt::Display for GatewayClientError { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { match self { @@ -114,6 +116,9 @@ impl fmt::Display for GatewayClientError { // we can either have the stream itself or an option to re-obtain it // by notifying the future owning it to finish the execution and awaiting the result // which should be almost immediate (or an invalid state which should never, ever happen) +// TODO: perhaps restore the previous idea of Split(Stream, Sink) state to allow for +// sending messages without waiting for any responses and having no effect on rate of +// messages being pushed to us enum SocketState<'a> { Available(WsConn), Delegated( @@ -160,7 +165,8 @@ pub struct GatewayClient<'a, R: IntoClientRequest + Unpin + Clone> { impl<'a, R: IntoClientRequest + Unpin + Clone> Drop for GatewayClient<'a, R> { fn drop(&mut self) { - // TODO to fix forcibly closing connection + // TODO to fix forcibly closing connection (although now that I think about it, + // I'm not sure this would do it, as to fix the said issue we'd need graceful shutdowns) } } @@ -272,6 +278,15 @@ where response } + // next on TODO list: + // so that presumably we could increase our sending/receiving rate + async fn send_websocket_message_without_response( + &mut self, + msg: Message, + ) -> Result<(), GatewayClientError> { + unimplemented!() + } + pub async fn register(&mut self) -> Result { if !self.connection.is_established() { return Err(GatewayClientError::ConnectionNotEstablished); @@ -333,6 +348,7 @@ where } } + // TODO: make it optionally use `send_websocket_message_without_response` pub async fn send_sphinx_packet( &mut self, address: SocketAddr, @@ -422,7 +438,9 @@ where }; let spawned_boxed_task = tokio::spawn(sphinx_receiver_future) - .map(|join_handle| join_handle.expect("task must have not failed to finish execution!")) + .map(|join_handle| { + join_handle.expect("task must have not failed to finish its execution!") + }) .boxed(); self.connection = SocketState::Delegated(spawned_boxed_task, notify); diff --git a/gateway/gateway-requests/src/types.rs b/gateway/gateway-requests/src/types.rs index e0544cd0f53..9caad4d9370 100644 --- a/gateway/gateway-requests/src/types.rs +++ b/gateway/gateway-requests/src/types.rs @@ -17,7 +17,6 @@ use crate::types::BinaryRequest::ForwardSphinx; use nymsphinx::addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError}; use nymsphinx::DestinationAddressBytes; use serde::{Deserialize, Serialize}; -use std::convert::TryInto; use std::{ convert::TryFrom, fmt::{self, Error, Formatter}, diff --git a/gateway/src/node/client_handling/websocket/listener.rs b/gateway/src/node/client_handling/websocket/listener.rs index 9560e8fb8a9..970333ca878 100644 --- a/gateway/src/node/client_handling/websocket/listener.rs +++ b/gateway/src/node/client_handling/websocket/listener.rs @@ -42,6 +42,8 @@ impl Listener { match tcp_listener.accept().await { Ok((socket, remote_addr)) => { trace!("received a socket connection from {}", remote_addr); + // TODO: I think we need a mechanism for having a maximum number of connected + // clients or spawned tokio tasks -> perhaps a worker system? let mut handle = Handle::new( socket, clients_handler_sender.clone(), diff --git a/gateway/src/node/mixnet_handling/sender/mod.rs b/gateway/src/node/mixnet_handling/sender/mod.rs index d85f68042c2..7329f524b3b 100644 --- a/gateway/src/node/mixnet_handling/sender/mod.rs +++ b/gateway/src/node/mixnet_handling/sender/mod.rs @@ -52,7 +52,6 @@ impl PacketForwarder { } pub(crate) fn start(mut self) -> (JoinHandle<()>, OutboundMixMessageSender) { - // TODO: what to do with the lost JoinHandle - do we even care? let sender_channel = self.conn_tx.clone(); ( tokio::spawn(async move { diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index 405f7276a18..4eb912b2411 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -20,7 +20,6 @@ use crate::node::storage::{inboxes, ClientLedger}; use crypto::encryption; use log::*; use std::sync::Arc; -use std::time::Duration; use tokio::runtime::Runtime; pub(crate) mod client_handling; diff --git a/gateway/src/node/storage/inboxes.rs b/gateway/src/node/storage/inboxes.rs index 77b08d36cca..bd165e5a813 100644 --- a/gateway/src/node/storage/inboxes.rs +++ b/gateway/src/node/storage/inboxes.rs @@ -73,6 +73,8 @@ pub struct ClientStorage { // even though the data inside is extremely cheap to copy, we have to have a single mutex, // so might as well store the data behind it pub struct ClientStorageInner { + // basically part of rate limiting which does not exist anymore + #[allow(dead_code)] message_retrieval_limit: usize, filename_length: u16, main_store_path_dir: PathBuf, @@ -90,10 +92,6 @@ impl ClientStorage { } } - // TODO: does this method really require locking? - // The worst that can happen is client sending 2 requests: to pull messages and register - // if register does not lock, then under specific timing pull messages will fail, - // but can simply be retried with no issues pub(crate) async fn create_storage_dir( &self, client_address: DestinationAddressBytes, @@ -162,56 +160,6 @@ impl ClientStorage { Ok(msgs) } - pub(crate) async fn retrieve_client_files_with_constant_rate( - &self, - client_address: DestinationAddressBytes, - ) -> io::Result> { - let inner_data = self.inner.lock().await; - - let client_dir_name = client_address.to_base58_string(); - let full_store_dir = inner_data.main_store_path_dir.join(client_dir_name); - - trace!("going to lookup: {:?}!", full_store_dir); - if !full_store_dir.exists() { - return Err(io::Error::new( - io::ErrorKind::NotFound, - "Target client does not exist", - )); - } - - let mut msgs = Vec::new(); - let mut read_dir = fs::read_dir(full_store_dir).await?; - - while let Some(dir_entry) = read_dir.next().await { - if let Ok(dir_entry) = dir_entry { - if !Self::is_valid_file(&dir_entry).await { - continue; - } - // Do not delete the file itself here! - // Only do it after client has received it - let client_file = - ClientFile::new(fs::read(dir_entry.path()).await?, dir_entry.path()); - msgs.push(client_file) - } - if msgs.len() == inner_data.message_retrieval_limit { - break; - } - } - - let dummy_message = dummy_message(); - - // make sure we always return as many messages as we need - if msgs.len() != inner_data.message_retrieval_limit as usize { - msgs = msgs - .into_iter() - .chain(std::iter::repeat(dummy_message)) - .take(inner_data.message_retrieval_limit) - .collect(); - } - - Ok(msgs) - } - async fn is_valid_file(entry: &fs::DirEntry) -> bool { let metadata = match entry.metadata().await { Ok(meta) => meta, From 5aca68a28126f5d1f7866341edf9176159fb777a Mon Sep 17 00:00:00 2001 From: jstuczyn Date: Wed, 29 Apr 2020 17:28:15 +0100 Subject: [PATCH 70/70] unused import --- common/client-libs/directory-client/src/presence/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/common/client-libs/directory-client/src/presence/mod.rs b/common/client-libs/directory-client/src/presence/mod.rs index b379d1f029b..cea08e9efce 100644 --- a/common/client-libs/directory-client/src/presence/mod.rs +++ b/common/client-libs/directory-client/src/presence/mod.rs @@ -17,7 +17,6 @@ use crate::{Client, Config, DirectoryClient}; use log::*; use serde::{Deserialize, Serialize}; use std::convert::TryInto; -use topology::gateway::Node; use topology::{coco, gateway, mix, provider, NymTopology}; pub mod coconodes;