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:
committed by
GitHub
parent
903ef44328
commit
abbf7041a4
Generated
+47
-13
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
"#
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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(¬ify);
|
||||
|
||||
let mut extracted_connection =
|
||||
match std::mem::replace(&mut self.connection, SocketState::Invalid) {
|
||||
SocketState::Available(conn) => conn,
|
||||
_ => unreachable!(), // impossible due to initial check
|
||||
};
|
||||
|
||||
let sphinx_packet_sender = self.sphinx_packet_sender.clone();
|
||||
let sphinx_receiver_future = async move {
|
||||
let mut should_return = false;
|
||||
while !should_return {
|
||||
tokio::select! {
|
||||
_ = notify_clone.notified() => {
|
||||
should_return = true;
|
||||
}
|
||||
msg = extracted_connection.next() => {
|
||||
if msg.is_none() {
|
||||
return Err(GatewayClientError::ConnectionAbruptlyClosed);
|
||||
}
|
||||
let msg = match msg.unwrap() {
|
||||
Ok(msg) => msg,
|
||||
Err(err) => {
|
||||
return Err(GatewayClientError::NetworkError(err));
|
||||
}
|
||||
};
|
||||
match msg {
|
||||
Message::Binary(bin_msg) => {
|
||||
sphinx_packet_sender.unbounded_send(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(())
|
||||
}
|
||||
}
|
||||
@@ -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())?;
|
||||
|
||||
@@ -259,6 +259,7 @@ impl PathChecker {
|
||||
&path[..],
|
||||
&self.our_destination,
|
||||
&delays,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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" }
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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");
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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::*;
|
||||
@@ -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...
|
||||
@@ -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 })
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -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;
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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!");
|
||||
}
|
||||
}
|
||||
+23
-15
@@ -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;
|
||||
@@ -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;
|
||||
+15
-8
@@ -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::*};
|
||||
|
||||
+3
-2
@@ -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;
|
||||
+13
-19
@@ -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
-13
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub(crate) mod inboxes;
|
||||
mod ledger;
|
||||
|
||||
pub(crate) use ledger::ClientLedger;
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user