Feature/gateway provider merge (#208)

* Initial draft for ClientsHandler

* Created listener struct

* typo

* Stateful websocket connection handler

* Exposing modules

* Depdendencies updates

* Moved listener to correct file + made start consume listener

* Main starting new listener

* Catching sigint

* Copied client storage from provider into gateway

* Exposed websocket listener type for nicer import path

* Defined websocket message receiver concrete type

* Client ledger struct without implementation

* ClientsHandler using more concrete types

* Mixnet sender + receiver and exposed listener type

* Handling mix packets

* Ability to forward mix packets

* "starting" both listeners at main

* Depedencies updates

* Initial type definitions for client messages

* Initial "gateway-requests" with AuthToken

* ibid.

* Restored most of ledger's functionalities

* Ability to retrieve all messages regardless of rate limit

* ClientsHandler request handling logic

* Required 'new' dependencies

* Main changes required for compilation

* PacketProcessor getting private key pointer

* "moved" types into gateway requests crate

* Moved and renamed types

* ibid.

* Added required serde_json dependency

* Skeleton for websocket request handling

* helper methods on ServerResponse

* WebSocket Handler pushing received mix messages directly to client

* PacketForwarder returning JoinHandle alongside the channel

* ClientsHandler following the same pattern

* Made websocket listener start method consistent with mix listener

* Syntax error + formatting

* Websocket handler having access to mix forwarder

* Minimal binary request parsing

* Implicitly derived std::error::Error on GatewayRequestsError

* Handling of all websocket requests

* Types import cleanup

* Updated placeholder fields

* Everything put into main

* Missing license notices

* Cleaned up unused code

* Copied and did initial minor changes to commands and config

* It's actually gateway

* Gateway sending its regular presence to directory server

* Re-organisation of gateway code + Gateway struct

* Updated provider argument description

* Restored duplicate ip check without importing NymTopology trait

* Moved hardcoded values into config

* Cargo fmt

* Compilation errors in other crates due to topology adjustments

* Test fixes

* Initial gateway-client

* Seemingly working version with bunch of hardcoded and temporary values

* cargo fmt

* Removed provider poller

* Updated config with gateway values instead of provider

* Gateway address including ws

* Removed hardcoded gateway address

* Properly skipping loop cover messages

* Updated log filter with tokio tungstenite related modules

* Updated sphinx version used

* Very minor cleanup

* unused import
This commit is contained in:
Jędrzej Stuczyński
2020-04-30 10:14:21 +01:00
committed by GitHub
parent 903ef44328
commit abbf7041a4
76 changed files with 4246 additions and 1196 deletions
Generated
+47 -13
View File
@@ -896,23 +896,25 @@ dependencies = [
]
[[package]]
name = "gateway"
name = "gateway-client"
version = "0.1.0"
dependencies = [
"crypto",
"dotenv",
"futures 0.3.4",
"log 0.4.8",
"mix-client",
"multi-tcp-client",
"gateway-requests",
"nymsphinx",
"pretty_env_logger",
"rand 0.7.3",
"serde",
"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]]
@@ -1621,6 +1623,8 @@ dependencies = [
"dirs",
"dotenv",
"futures 0.3.4",
"gateway-client",
"gateway-requests",
"healthcheck",
"log 0.4.8",
"mix-client",
@@ -1633,11 +1637,41 @@ dependencies = [
"reqwest",
"serde",
"serde_json",
"sfw-provider-requests",
"tempfile",
"tokio 0.2.16",
"tokio-tungstenite",
"topology",
"url 2.1.1",
]
[[package]]
name = "nym-gateway"
version = "0.7.0-dev"
dependencies = [
"built",
"clap",
"config",
"crypto",
"directory-client",
"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]]
@@ -2653,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",
+2
View File
@@ -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",
@@ -19,6 +20,7 @@ members = [
"common/pemstore",
"common/topology",
"gateway",
"gateway/gateway-requests",
"mixnode",
"sfw-provider",
"sfw-provider/sfw-provider-requests",
+3 -1
View File
@@ -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]
+22 -19
View File
@@ -14,10 +14,10 @@
use futures::channel::mpsc;
use futures::StreamExt;
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;
@@ -31,37 +31,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) {
+88 -63
View File
@@ -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,17 +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;
use nymsphinx::{Destination, DestinationAddressBytes};
use pemstore::pemstore::PemStore;
use sfw_provider_requests::auth_token::AuthToken;
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;
@@ -85,6 +84,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 +95,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<T: 'static + NymTopology>(
@@ -144,57 +150,72 @@ 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<T: NymTopology>(
fn start_gateway_client(
&mut self,
mut topology_accessor: TopologyAccessor<T>,
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();
sphinx_packet_sender: SphinxPacketSender,
gateway_address: url::Url,
) -> GatewayClient<'static, url::Url> {
let auth_token = self
.config
.get_gateway_auth_token()
.map(|str_token| AuthToken::try_from_base58_string(str_token).ok())
.unwrap_or(None);
// 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(),
let mut gateway_client = GatewayClient::new(
gateway_address,
self.as_mix_destination_address(),
auth_token,
sphinx_packet_sender,
self.config.get_gateway_response_timeout(),
);
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());
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()
);
gateway_client
}
async fn get_gateway_address<T: NymTopology>(
gateway_id: String,
mut topology_accessor: TopologyAccessor<T>,
) -> 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
@@ -228,18 +249,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<T: 'static + NymTopology>(
@@ -330,9 +349,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 +369,16 @@ 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);
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);
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(
@@ -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<Vec<Vec<u8>>>;
pub(crate) type PolledMessagesReceiver = mpsc::UnboundedReceiver<Vec<Vec<u8>>>;
pub(crate) struct ProviderPoller {
polling_rate: time::Duration,
provider_client: provider_client::ProviderClient,
poller_tx: mpsc::UnboundedSender<Vec<Vec<u8>>>,
}
impl ProviderPoller {
pub(crate) fn new(
poller_tx: mpsc::UnboundedSender<Vec<Vec<u8>>>,
provider_client_listener_address: SocketAddr,
client_address: DestinationAddressBytes,
auth_token: Option<AuthToken>,
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 })
}
}
+16 -7
View File
@@ -12,11 +12,12 @@
// 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;
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)
{
@@ -117,22 +123,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 +155,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),
}
}
+43 -30
View File
@@ -56,6 +56,7 @@ impl<T: NymTopology> TopologyAccessor<T> {
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<SocketAddr> {
match &self.inner.lock().await.0 {
None => None,
@@ -67,7 +68,19 @@ impl<T: NymTopology> TopologyAccessor<T> {
}
}
pub(crate) async fn get_gateway_socket_url(&mut self, id: &str) -> Option<String> {
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
// (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,
@@ -101,12 +114,12 @@ impl<T: NymTopology> TopologyAccessor<T> {
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()
}
}
@@ -183,35 +196,35 @@ impl<T: 'static + NymTopology> TopologyRefresher<T> {
async fn get_current_compatible_topology(&self) -> Result<T, TopologyError> {
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
.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,
};
// healthcheck needs some adjustments to work with gateways so for time being just dont run it
return Ok(version_filtered_topology);
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)
// 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) {
+44 -43
View File
@@ -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> {
@@ -34,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")
@@ -57,61 +60,57 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
)
}
async fn try_provider_registrations(
providers: Vec<Node>,
async fn try_gateway_registration(
gateways: Vec<Node>,
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(
url::Url::parse(&gateway.client_listener).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
}
// 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_gateway(
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_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();
// 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
let registration_result = try_provider_registrations(providers, our_address).await;
// + 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 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
)
}
Some((provider_id, auth_token)) => (provider_id, auth_token),
Some((gateway_id, auth_token)) => (gateway_id, auth_token),
}
}
@@ -125,16 +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 (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 gateway, but gateway, yet another change to make
config = config
.with_provider_id(provider_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);
@@ -151,13 +152,13 @@ pub fn execute(matches: &ArgMatches) {
println!("Saved configuration file to {:?}", config_save_location);
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")
+2 -2
View File
@@ -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") {
+3 -3
View File
@@ -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")
+27 -81
View File
@@ -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<S: Into<String>>(mut self, id: S) -> Self {
self.client.provider_id = id.into();
pub fn with_gateway_id<S: Into<String>>(mut self, id: S) -> Self {
self.client.gateway_id = id.into();
self
}
pub fn with_provider_auth_token<S: Into<String>>(mut self, token: S) -> Self {
self.client.provider_authtoken = Some(token.into());
pub fn with_gateway_auth_token<S: Into<String>>(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<String> {
self.client.provider_authtoken.clone()
pub fn get_gateway_auth_token(&self) -> Option<String> {
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<Option<String>, 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<String>,
gateway_authtoken: Option<String>,
/// 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,
}
}
}
+5 -6
View File
@@ -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 }}
"#
+2
View File
@@ -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();
}
@@ -87,9 +87,15 @@ impl<T: NymTopology> Connection<T> {
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:
+3 -2
View File
@@ -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 },
};
@@ -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,
@@ -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<GatewayClient>,
pub last_seen: u64,
pub version: String,
}
impl Into<topology::gateway::Node> 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<topology::gateway::Node> 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<topology::gateway::Client> for GatewayClient {
fn into(self) -> topology::gateway::Client {
topology::gateway::Client {
pub_key: self.pub_key,
}
}
}
impl From<topology::gateway::Client> for GatewayClient {
fn from(mpc: topology::gateway::Client) -> Self {
GatewayClient {
pub_key: mpc.pub_key,
}
}
}
@@ -17,12 +17,10 @@ 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::{coco, gateway, mix, provider, NymTopology};
pub mod coconodes;
pub mod gateways;
pub mod mixnodes;
pub mod providers;
@@ -33,9 +31,11 @@ pub struct Topology {
pub coco_nodes: Vec<coconodes::CocoPresence>,
pub mix_nodes: Vec<mixnodes::MixNodePresence>,
pub mix_provider_nodes: Vec<providers::MixProviderPresence>,
pub gateway_nodes: Vec<gateways::GatewayPresence>,
}
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 +53,7 @@ impl NymTopology for Topology {
mix_nodes: Vec<mix::Node>,
mix_provider_nodes: Vec<provider::Node>,
coco_nodes: Vec<coco::Node>,
gateway_nodes: Vec<gateway::Node>,
) -> Self {
Topology {
coco_nodes: coco_nodes.into_iter().map(|node| node.into()).collect(),
@@ -61,6 +62,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 +80,13 @@ impl NymTopology for Topology {
.collect()
}
fn gateways(&self) -> Vec<gateway::Node> {
self.gateway_nodes
.iter()
.map(|x| x.clone().into())
.collect()
}
fn coco_nodes(&self) -> Vec<topology::coco::Node> {
self.coco_nodes.iter().map(|x| x.clone().into()).collect()
}
@@ -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;
@@ -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<Response, reqwest::Error>;
}
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<Response, reqwest::Error> {
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/gateways")
.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/gateways")
.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(),
}
}
}
}
@@ -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()
}
}
}
@@ -0,0 +1,22 @@
[package]
name = "gateway-client"
version = "0.1.0"
authors = ["Jędrzej Stuczyński <andrew@nymtech.net>"]
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"
@@ -0,0 +1,449 @@
// 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::BoxFuture, 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
}
*/
// TODO: some batching mechanism to allow reading and sending more than a single packet through
pub type SphinxPacketSender = mpsc::UnboundedSender<Vec<Vec<u8>>>;
pub type SphinxPacketReceiver = mpsc::UnboundedReceiver<Vec<Vec<u8>>>;
// type alias for not having to type the whole thing every single time
type WsConn = WebSocketStream<TcpStream>;
#[derive(Debug)]
pub enum GatewayClientError {
ConnectionNotEstablished,
GatewayError(String),
NetworkError(WsError),
NoAuthTokenAvailable,
ConnectionAbruptlyClosed,
MalformedResponse,
NotAuthenticated,
ConnectionInInvalidState,
AuthenticationFailure,
Timeout,
}
impl From<WsError> for GatewayClientError {
fn from(err: WsError) -> Self {
GatewayClientError::NetworkError(err)
}
}
impl From<AuthTokenConversionError> for GatewayClientError {
fn from(_err: AuthTokenConversionError) -> Self {
GatewayClientError::MalformedResponse
}
}
// 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 {
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::AuthenticationFailure => write!(f, "authentication failure"),
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)
// 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(
BoxFuture<'a, Result<WsConn, GatewayClientError>>,
Arc<Notify>,
),
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<AuthToken>,
connection: SocketState<'a>,
sphinx_packet_sender: SphinxPacketSender,
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 (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)
}
}
impl<'a, R> GatewayClient<'static, R>
where
R: IntoClientRequest + Unpin + Clone,
{
pub fn new(
gateway_address: R,
our_address: DestinationAddressBytes,
auth_token: Option<AuthToken>,
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<ServerResponse, GatewayClientError> {
// 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(vec![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<ServerResponse, GatewayClientError> {
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
}
// 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<AuthToken, GatewayClientError> {
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<AuthToken>,
) -> Result<bool, GatewayClientError> {
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)
}
// 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<AuthToken, GatewayClientError> {
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)
}
}
// TODO: make it optionally use `send_websocket_message_without_response`
pub async fn send_sphinx_packet(
&mut self,
address: SocketAddr,
packet: Vec<u8>,
) -> Result<bool, GatewayClientError> {
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(&notify);
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(vec![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 its execution!")
})
.boxed();
self.connection = SocketState::Delegated(spawned_boxed_task, notify);
Ok(())
}
}
+7 -12
View File
@@ -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<topology::NymTopologyError> 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<SphinxUnwrapError> for SphinxPacketEncapsulationError {
fn from(err: SphinxUnwrapError) -> Self {
use SphinxPacketEncapsulationError::*;
SphinxEncapsulationError(err)
impl From<nymsphinx::Error> for SphinxPacketEncapsulationError {
fn from(err: nymsphinx::Error) -> Self {
SphinxPacketEncapsulationError::SphinxError(err)
}
}
@@ -107,7 +102,7 @@ pub fn encapsulate_message<T: NymTopology>(
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())?;
+1
View File
@@ -259,6 +259,7 @@ impl PathChecker {
&path[..],
&self.our_destination,
&delays,
None,
)
.unwrap();
+16
View File
@@ -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,
)
}
+1 -1
View File
@@ -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" }
+3 -3
View File
@@ -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
+61
View File
@@ -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: String,
pub mixnet_listener: SocketAddr,
pub pub_key: String,
pub registered_clients: Vec<Client>,
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<SphinxNode> 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)
}
}
+18 -4
View File
@@ -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::Node>,
mix_provider_nodes: Vec<provider::Node>,
coco_nodes: Vec<coco::Node>,
gateway_nodes: Vec<gateway::Node>,
) -> Self;
fn mix_nodes(&self) -> Vec<mix::Node>;
fn providers(&self) -> Vec<provider::Node>;
fn gateways(&self) -> Vec<gateway::Node>;
fn coco_nodes(&self) -> Vec<coco::Node>;
fn make_layered_topology(&self) -> Result<HashMap<u64, Vec<mix::Node>>, NymTopologyError> {
let mut layered_topology: HashMap<u64, Vec<mix::Node>> = HashMap::new();
@@ -94,14 +97,14 @@ pub trait NymTopology: Sized + std::fmt::Debug + Send + Sync + Clone {
fn all_paths(&self) -> Result<Vec<Vec<SphinxNode>>, NymTopologyError> {
let mut layered_topology = self.make_layered_topology()?;
let providers = self.providers();
let gateways = self.gateways();
let sorted_layers: Vec<Vec<SphinxNode>> = (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 {
+28 -6
View File
@@ -1,30 +1,52 @@
[package]
name = "gateway"
version = "0.1.0"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
build = "build.rs"
name = "nym-gateway"
version = "0.7.0-dev"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
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"
# internal
config = { path = "../common/config" }
crypto = { path = "../common/crypto" }
mix-client = { path = "../common/client-libs/mix-client" } # to be removed very soon
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" }
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
default-features = false
[build-dependencies]
built = "0.3.2"
[dev-dependencies]
tempfile = "3.1.0"
[features]
qa = []
local = []
@@ -12,21 +12,8 @@
// 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;
use built;
#[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 },
fn main() {
built::write_built_file().expect("Failed to acquire build-time information");
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "gateway-requests"
version = "0.1.0"
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
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" }
serde = { version = "1.0.104", features = ["derive"] }
serde_json = "1.0.44"
tokio-tungstenite = "0.10.1"
+125
View File
@@ -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<S: Into<String>>(
val: S,
) -> Result<Self, AuthTokenConversionError> {
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<String> 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())
}
}
+22
View File
@@ -0,0 +1,22 @@
// 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 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::*;
+194
View File
@@ -0,0 +1,194 @@
// 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;
use crate::types::BinaryRequest::ForwardSphinx;
use nymsphinx::addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError};
use nymsphinx::DestinationAddressBytes;
use serde::{Deserialize, Serialize};
use std::{
convert::TryFrom,
fmt::{self, Error, Formatter},
net::SocketAddr,
};
use tokio_tungstenite::tungstenite::protocol::Message;
#[derive(Debug)]
pub enum GatewayRequestsError {
IncorrectlyEncodedAddress,
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<NymNodeRoutingAddressError> for GatewayRequestsError {
fn from(_: NymNodeRoutingAddressError) -> Self {
GatewayRequestsError::IncorrectlyEncodedAddress
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum ClientControlRequest {
Authenticate { address: String, token: String },
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<Message> 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<String> for ClientControlRequest {
type Error = serde_json::Error;
fn try_from(msg: String) -> Result<Self, Self::Error> {
serde_json::from_str(&msg)
}
}
#[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<S: Into<String>>(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<Message> 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<String> for ServerResponse {
type Error = serde_json::Error;
fn try_from(msg: String) -> Result<Self, serde_json::Error> {
serde_json::from_str(&msg)
}
}
pub enum BinaryRequest {
ForwardSphinx { address: SocketAddr, data: Vec<u8> },
}
impl BinaryRequest {
pub fn try_from_bytes(raw_req: &[u8]) -> Result<Self, GatewayRequestsError> {
// 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 addr_offset = address.bytes_min_len();
if raw_req[addr_offset..].len() != nymsphinx::PACKET_SIZE {
Err(GatewayRequestsError::RequestOfInvalidSize(
raw_req[addr_offset..].len(),
nymsphinx::PACKET_SIZE,
))
} else {
Ok(ForwardSphinx {
address: address.into(),
data: raw_req[addr_offset..].into(),
})
}
}
pub fn into_bytes(self) -> Vec<u8> {
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<u8>) -> BinaryRequest {
BinaryRequest::ForwardSphinx { address, data }
}
}
impl Into<Message> for BinaryRequest {
fn into(self) -> Message {
Message::Binary(self.into_bytes())
}
}
// TODO: tests...
+16
View File
@@ -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"));
@@ -1,65 +0,0 @@
use crate::client_handling::ledger::ClientLedger;
use crate::client_handling::websocket::message_receiver::MixMessageSender;
use futures::channel::{mpsc, oneshot};
use futures::StreamExt;
use log::*;
use nymsphinx::DestinationAddressBytes;
use std::collections::HashMap;
use tokio::task::JoinHandle;
type temp_AuthToken = String;
pub(crate) type ClientsHandlerRequestSender = mpsc::UnboundedSender<ClientsHandlerRequest>;
pub(crate) type ClientsHandlerRequestReceiver = mpsc::UnboundedReceiver<ClientsHandlerRequest>;
pub(crate) type ClientsHandlerResponseSender = oneshot::Sender<ClientsHandlerResponse>;
pub(crate) type ClientsHandlerResponseReceiver = oneshot::Receiver<ClientsHandlerResponse>;
pub(crate) enum ClientsHandlerRequest {
// client
Register(
DestinationAddressBytes,
MixMessageSender,
ClientsHandlerResponseSender,
),
Authenticate(
temp_AuthToken,
MixMessageSender,
ClientsHandlerResponseSender,
),
// mix
IsOnline(DestinationAddressBytes, ClientsHandlerResponseSender),
}
pub(crate) enum ClientsHandlerResponse {
Register(Option<temp_AuthToken>),
Authenticate(bool),
IsOnline(Option<MixMessageSender>),
}
pub(crate) struct ClientsHandler {
open_connections: HashMap<DestinationAddressBytes, MixMessageSender>, // clients_ledger: unimplemented!(),
clients_ledger: ClientLedger,
request_receiver_channel: ClientsHandlerRequestReceiver,
}
impl ClientsHandler {
pub(crate) fn new(request_receiver_channel: ClientsHandlerRequestReceiver) -> Self {
ClientsHandler {
open_connections: HashMap::new(),
request_receiver_channel,
clients_ledger: unimplemented!(),
}
}
pub(crate) async fn run(&mut self) {
while let Some(request) = self.request_receiver_channel.next().await {
// handle request
}
}
pub(crate) fn start(mut self) -> JoinHandle<()> {
tokio::spawn(async move { self.run().await })
}
}
-162
View File
@@ -1,162 +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 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<Self, ClientLedgerError> {
// 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<bool, ClientLedgerError> {
// 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<Option<AuthToken>, 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<Option<AuthToken>, 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<Vec<MixProviderClient>, 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(),
// }
// }
//}
-3
View File
@@ -1,3 +0,0 @@
pub(crate) mod clients_handler;
pub(crate) mod ledger;
pub(crate) mod websocket;
@@ -1,74 +0,0 @@
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<S: AsyncRead + AsyncWrite + Unpin> {
RawTCP(S),
UpgradedWebSocket(WebSocketStream<S>),
Invalid,
}
pub(crate) struct Handle<S: AsyncRead + AsyncWrite + Unpin> {
socket_connection: SocketStream<S>,
clients_handler_sender: ClientsHandlerRequestSender,
}
impl<S> Handle<S>
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<Result<Message, WsError>> {
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;
}
}
@@ -1,4 +0,0 @@
use futures::channel::mpsc;
pub(crate) type MixMessageSender = mpsc::UnboundedSender<Vec<Vec<u8>>>;
pub(crate) type MixMessageReceiver = mpsc::UnboundedReceiver<Vec<Vec<u8>>>;
@@ -1,6 +0,0 @@
pub(crate) mod connection_handler;
pub(crate) mod listener;
pub(crate) mod message_receiver;
pub(crate) mod types;
pub(crate) use listener::Listener;
+131
View File
@@ -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!("Gateway configuration completed.\n\n\n")
}
+105
View File
@@ -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::<u16>()) {
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::<u16>())
{
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::<u16>())
{
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::<u16>())
{
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
}
+233
View File
@@ -0,0 +1,233 @@
// 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::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> {
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
}
fn check_if_same_ip_gateway_exists(
directory_server: String,
announced_mix_host: String,
announced_clients_host: String,
) -> Option<String> {
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) {
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);
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);
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);
}
println!(
"Directory server [presence]: {}",
config.get_presence_directory_server()
);
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()
);
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()
);
println!(
"Inboxes directory is: {:?}",
config.get_clients_inboxes_dir()
);
println!(
"Clients ledger is stored at: {:?}",
config.get_clients_ledger_path()
);
Gateway::new(config, sphinx_keypair).run();
}
+586
View File
@@ -0,0 +1,586 @@
// 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; // 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;
#[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<S: Into<String>>(id: S) -> Self {
Config::default().with_id(id)
}
// builder methods
pub fn with_id<S: Into<String>>(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<S: Into<String>>(mut self, directory_server: S) -> Self {
self.debug.presence_directory_server = directory_server.into();
self
}
pub fn with_location<S: Into<String>>(mut self, location: S) -> Self {
self.gateway.location = location.into();
self
}
pub fn with_mix_listening_host<S: Into<String>>(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<S: Into<String>>(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<S: Into<String>>(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 = format!(
"ws://{}",
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<S: Into<String>>(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()
);
// 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
}
_ => {
// 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<S: Into<String>>(mut self, inboxes_dir: S) -> Self {
self.clients_endpoint.inboxes_directory = PathBuf::from(inboxes_dir.into());
self
}
pub fn with_custom_clients_ledger<S: Into<String>>(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_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
}
pub fn get_stored_messages_filename_length(&self) -> u16 {
self.debug.stored_messages_filename_length
}
}
#[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,
/// 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!("ws://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,
/// 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,
/// 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,
}
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(),
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,
}
}
}
#[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);
}
}
+15
View File
@@ -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;
@@ -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<PathBuf> {
Some(self.private_sphinx_key.clone())
}
fn public_encryption_key(&self) -> Option<PathBuf> {
Some(self.public_sphinx_key.clone())
}
}
+92
View File
@@ -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
"#
}
+44 -30
View File
@@ -12,45 +12,56 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::client_handling::websocket;
use crate::mixnet_handling::receiver::packet_processing::PacketProcessor;
use crate::storage::ClientStorage;
use log::*;
use clap::{App, ArgMatches};
mod client_handling;
mod mixnet_client;
mod mixnet_handling;
pub(crate) mod storage;
pub mod built_info;
mod commands;
mod config;
mod node;
#[tokio::main]
async fn main() {
fn main() {
dotenv::dotenv().ok();
setup_logging();
let addr = "127.0.0.1:1793".parse().unwrap();
info!("Listening on: {}", addr);
println!("{}", banner());
let (dummy_clients_handler_tx, _) = futures::channel::mpsc::unbounded();
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,
);
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();
websocket::Listener::new(addr, dummy_clients_handler_tx.clone()).start();
mixnet_handling::Listener::new(addr).start(dummy_mix_packet_processor);
execute(arg_matches);
}
if let Err(e) = tokio::signal::ctrl_c().await {
error!(
"There was an error while capturing SIGINT - {:?}. We will terminate regardless",
e
);
fn execute(matches: ArgMatches) {
match matches.subcommand() {
("init", Some(m)) => commands::init::execute(m),
("run", Some(m)) => commands::run::execute(m),
_ => println!("{}", usage()),
}
}
println!(
"Received SIGINT - the provider will terminate now (threads are not YET nicely stopped)"
);
fn usage() -> &'static str {
"usage: --help to see available options.\n\n"
}
fn banner() -> String {
format!(
r#"
_ __ _ _ _ __ ___
| '_ \| | | | '_ \ _ \
| | | | |_| | | | | | |
|_| |_|\__, |_| |_| |_|
|___/
(gateway - version {:})
"#,
built_info::PKG_VERSION
)
}
fn setup_logging() {
@@ -68,5 +79,8 @@ 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)
.filter_module("tungstenite", log::LevelFilter::Warn)
.filter_module("tokio_tungstenite", log::LevelFilter::Warn)
.init();
}
-43
View File
@@ -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<Mutex<multi_tcp_client::Client>> {
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<u8>, client_ref: Arc<Mutex<MultiClient>>) {
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();
}
-4
View File
@@ -1,4 +0,0 @@
pub(crate) mod receiver;
pub(crate) mod sender;
pub(crate) use receiver::listener::Listener;
@@ -0,0 +1,313 @@
// 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::node::{
client_handling::websocket::message_receiver::MixMessageSender,
storage::{inboxes::ClientStorage, ClientLedger},
};
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 sha2::Sha256;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::task::JoinHandle;
pub(crate) type ClientsHandlerRequestSender = mpsc::UnboundedSender<ClientsHandlerRequest>;
pub(crate) type ClientsHandlerRequestReceiver = mpsc::UnboundedReceiver<ClientsHandlerRequest>;
pub(crate) type ClientsHandlerResponseSender = oneshot::Sender<ClientsHandlerResponse>;
#[derive(Debug)]
pub(crate) enum ClientsHandlerRequest {
// client
Register(
DestinationAddressBytes,
MixMessageSender,
ClientsHandlerResponseSender,
),
Authenticate(
DestinationAddressBytes,
AuthToken,
MixMessageSender,
ClientsHandlerResponseSender,
),
Disconnect(DestinationAddressBytes),
// mix
IsOnline(DestinationAddressBytes, ClientsHandlerResponseSender),
}
#[derive(Debug)]
pub(crate) enum ClientsHandlerResponse {
Register(AuthToken),
Authenticate(bool),
IsOnline(Option<MixMessageSender>),
Error(Box<dyn std::error::Error + Send + Sync>),
}
pub(crate) struct ClientsHandler {
secret_key: Arc<encryption::PrivateKey>,
open_connections: HashMap<DestinationAddressBytes, MixMessageSender>,
clients_ledger: ClientLedger,
clients_inbox_storage: ClientStorage,
}
impl ClientsHandler {
pub(crate) fn new(
secret_key: Arc<encryption::PrivateKey>,
clients_ledger: ClientLedger,
clients_inbox_storage: ClientStorage,
) -> Self {
ClientsHandler {
secret_key,
open_connections: HashMap::new(),
clients_ledger,
clients_inbox_storage,
}
}
fn make_error_response<E>(&self, err: E) -> ClientsHandlerResponse
where
E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
ClientsHandlerResponse::Error(err.into())
}
// best effort sending error responses
fn send_error_response<E>(&self, err: E, res_channel: ClientsHandlerResponseSender)
where
E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
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<Sha256>;
// 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,
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,
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,
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,
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)
.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<()>, ClientsHandlerRequestSender) {
let (sender, receiver) = mpsc::unbounded();
(
tokio::spawn(async move { self.run(receiver).await }),
sender,
)
}
}
+16
View File
@@ -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.
pub(crate) mod clients_handler;
pub(crate) mod websocket;
@@ -0,0 +1,379 @@
// 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,
};
use log::*;
use std::convert::TryFrom;
use tokio::{prelude::*, stream::StreamExt};
use tokio_tungstenite::{
tungstenite::{protocol::Message, Error as WsError},
WebSocketStream,
};
use gateway_requests::auth_token::AuthToken;
use gateway_requests::types::{BinaryRequest, ClientControlRequest, ServerResponse};
use nymsphinx::DestinationAddressBytes;
use crate::node::client_handling::clients_handler::{
ClientsHandlerRequest, ClientsHandlerRequestSender, ClientsHandlerResponse,
};
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
//// 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<S: AsyncRead + AsyncWrite + Unpin> {
RawTCP(S),
UpgradedWebSocket(WebSocketStream<S>),
Invalid,
}
pub(crate) struct Handle<S: AsyncRead + AsyncWrite + Unpin> {
address: Option<DestinationAddressBytes>,
clients_handler_sender: ClientsHandlerRequestSender,
outbound_mix_sender: OutboundMixMessageSender,
socket_connection: SocketStream<S>,
}
impl<S> Handle<S>
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,
outbound_mix_sender: OutboundMixMessageSender,
) -> Self {
Handle {
address: None,
clients_handler_sender,
outbound_mix_sender,
socket_connection: SocketStream::RawTCP(conn),
}
}
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<Result<Message, WsError>> {
match self.socket_connection {
SocketStream::UpgradedWebSocket(ref mut ws_stream) => ws_stream.next().await,
_ => panic!("impossible state - websocket handshake was somehow reverted"),
}
}
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"),
}
}
async fn send_websocket_sphinx_packets(
&mut self,
packets: Vec<Vec<u8>>,
) -> Result<(), WsError> {
let messages: Vec<Result<Message, WsError>> = 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
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<u8>) -> Message {
trace!("Handling binary message (presumably sphinx packet)");
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(
&mut self,
address: String,
token: String,
mix_sender: MixMessageSender,
) -> ServerResponse {
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) => {
trace!("failed to parse received AuthToken: {:?}", e);
return ServerResponse::new_error("malformed authentication token").into();
}
};
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,
mix_sender: MixMessageSender,
) -> ServerResponse {
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 =
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
}
}
// 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!");
ServerResponse::new_error("invalid request").into()
}
async fn handle_request(&mut self, raw_request: Message) -> Option<Message> {
// 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,
}
}
async fn wait_for_initial_authentication(&mut self) -> Option<MixMessageReceiver> {
trace!("Started waiting for authenticate/register request...");
while let Some(msg) = self.next_websocket_request().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;
}
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) = ClientControlRequest::try_from(text_msg) {
match request {
ClientControlRequest::Authenticate { address, token } => {
self.handle_authenticate(address, token, mix_sender).await
}
ClientControlRequest::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", e);
break;
}
}
}
}
self.disconnect();
trace!("The stream was closed!");
}
pub(crate) async fn start_handling(&mut self) {
self.perform_websocket_handshake().await;
trace!("Managed to perform websocket handshake!");
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!");
}
}
@@ -12,29 +12,27 @@
// 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::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;
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,
outbound_mix_sender: OutboundMixMessageSender,
) {
info!("Starting websocket listener at {}", self.address);
let mut tcp_listener = tokio::net::TcpListener::bind(self.address)
.await
@@ -44,7 +42,13 @@ 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());
// 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(),
outbound_mix_sender.clone(),
);
tokio::spawn(async move { handle.start_handling().await });
}
Err(e) => warn!("failed to get client: {:?}", e),
@@ -52,7 +56,11 @@ 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,
outbound_mix_sender: OutboundMixMessageSender,
) -> JoinHandle<()> {
tokio::spawn(async move { self.run(clients_handler_sender, outbound_mix_sender).await })
}
}
@@ -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 futures::channel::mpsc;
pub(crate) type MixMessageSender = mpsc::UnboundedSender<Vec<Vec<u8>>>;
pub(crate) type MixMessageReceiver = mpsc::UnboundedReceiver<Vec<Vec<u8>>>;
@@ -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.
pub(crate) mod connection_handler;
pub(crate) mod listener;
pub(crate) mod message_receiver;
pub(crate) use listener::Listener;
+19
View File
@@ -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.
pub(crate) mod receiver;
pub(crate) mod sender;
pub(crate) use receiver::listener::Listener;
pub(crate) use receiver::packet_processing::PacketProcessor;
@@ -1,12 +1,19 @@
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};
// 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::node::mixnet_handling::receiver::packet_processing::PacketProcessor;
use log::*;
use nymsphinx::DestinationAddressBytes;
use std::collections::HashMap;
use std::net::SocketAddr;
use tokio::{io::AsyncReadExt, prelude::*};
@@ -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;
@@ -12,17 +12,17 @@
// 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;
use log::*;
use mix_client::packet::LOOP_COVER_MESSAGE_PAYLOAD;
use nymsphinx::{DestinationAddressBytes, Payload, ProcessedPacket, SURBIdentifier, SphinxPacket};
use nymsphinx::{DestinationAddressBytes, Error as SphinxError, ProcessedPacket, SphinxPacket};
use std::collections::HashMap;
use std::io;
use std::ops::Deref;
@@ -33,22 +33,16 @@ pub enum MixProcessingError {
ReceivedForwardHopError,
NonMatchingRecipient,
InvalidPayload,
SphinxProcessingError,
SphinxProcessingError(SphinxError),
IOError(String),
}
pub enum MixProcessingResult {
#[allow(dead_code)]
ForwardHop,
FinalHop,
}
impl From<nymsphinx::ProcessingError> 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<SphinxError> 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)
}
}
@@ -73,7 +67,7 @@ pub struct PacketProcessor {
impl PacketProcessor {
pub(crate) fn new(
secret_key: encryption::PrivateKey,
secret_key: Arc<encryption::PrivateKey>,
clients_handler_sender: ClientsHandlerRequestSender,
client_store: ClientStorage,
) -> Self {
@@ -81,7 +75,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,
}
}
@@ -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
@@ -185,7 +179,7 @@ impl PacketProcessor {
}
Err(e) => {
warn!("Failed to unwrap Sphinx packet: {:?}", e);
Err(MixProcessingError::SphinxProcessingError)
Err(MixProcessingError::SphinxProcessingError(e))
}
}
}
@@ -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<u8>)>;
pub(crate) type OutboundMixMessageReceiver = mpsc::UnboundedReceiver<(SocketAddr, Vec<u8>)>;
pub(crate) struct PacketForwarder {
tcp_client: multi_tcp_client::Client,
conn_tx: mpsc::UnboundedSender<(SocketAddr, Vec<u8>)>,
conn_rx: mpsc::UnboundedReceiver<(SocketAddr, Vec<u8>)>,
conn_tx: OutboundMixMessageSender,
conn_rx: OutboundMixMessageReceiver,
}
impl PacketForwarder {
@@ -47,17 +51,19 @@ impl PacketForwarder {
}
}
pub(crate) fn start(mut self) -> mpsc::UnboundedSender<(SocketAddr, Vec<u8>)> {
// TODO: what to do with the lost JoinHandle - do we even care?
pub(crate) fn start(mut self) -> (JoinHandle<()>, OutboundMixMessageSender) {
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,
)
}
}
+161
View File
@@ -0,0 +1,161 @@
// 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 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 {
info!("Starting mix packet forwarder...");
let (_, forwarding_channel) = PacketForwarder::new(
self.config.get_packet_forwarding_initial_backoff(),
self.config.get_packet_forwarding_maximum_backoff(),
self.config.get_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
});
}
}
+120
View File
@@ -0,0 +1,120 @@
// 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::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::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;
}
})
}
}
@@ -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(),
@@ -75,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,
@@ -92,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,
@@ -131,7 +127,7 @@ 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<Vec<ClientFile>> {
@@ -156,28 +152,11 @@ impl ClientStorage {
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)
}
+169
View File
@@ -0,0 +1,169 @@
// 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;
use std::path::PathBuf;
use log::*;
use gateway_requests::auth_token::{AuthToken, AUTH_TOKEN_SIZE};
use nymsphinx::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH};
#[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<Self, ClientLedgerError> {
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<bool, ClientLedgerError> {
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<Option<AuthToken>, 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<Option<AuthToken>, 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<Vec<DestinationAddressBytes>, 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 {
let cfg = sled::Config::new().temporary(true);
ClientLedger {
db: cfg.open().unwrap(),
}
}
}
+4
View File
@@ -0,0 +1,4 @@
pub(crate) mod inboxes;
mod ledger;
pub(crate) use ledger::ClientLedger;
+9 -8
View File
@@ -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<nymsphinx::ProcessingError> 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<SphinxError> 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))
}
}
}
+1 -1
View File
@@ -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(
+2 -2
View File
@@ -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),
)
@@ -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(
@@ -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<nymsphinx::ProcessingError> 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<SphinxError> 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))
}
}
}
@@ -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)