e5afd54ce0
* Basic storage stub * New models for new node status api * Route handling * Mounting new routes * Missing selective commit * Moved network monitor related files to separate package * Starting to see some sqlx action * Schema updates * Log statement upon finished migration * Removed old diesel related imports * Converted mixnode cache initialisation into a fairing * Moved cache related functionalities to separate package Also defined staging there * Created run method for validator cache + removed unwrap * Removed old node-status-api types and left bunch of todo placeholders in their place * Fixed managing validatorcache * Status reports are starting to get constructed * Submitting some dummy results to the database * Removing duplicate code for generating reports * Removed statuses older than 48h * Initial attempt at trying to obtain reports for all active nodes * Removed duplicates from the full report * Grabbing uptime history * Updating historical uptimes of active nodes * Updated sqlx-data.json * Removed all placeholder foomp owner values * Changed Layer serde behaviour for easier usage * Extended validator api config * Initial (seems working !) integration with network monitor * Added database path configuration to config * Using ValidatorCache in NetworkMonitor * Flag indicating whether validator cache has been initialised * Introduced a locla-only route for reward script to perform daily chores * Flag to save config to a file * Moved spawning of receiving future to run method rather than new * Removed arguments that dont make sense to be configured via CLI * Removed dead code from config file * More dead code removal * Added validator API to CI * Corrected manifest-path arguments * Constructing network monitor by passing config * Combined validator API CI with the main CI file * Using query_as for NodeStatus * Checking if historical uptimes were already calculated on particular day * Making id field NOT NULL * More query_as! action * Updated sqlx-data.json * Removed unused chrono feature * Renamed the migration file * Changed default validator endpoint to point to local validator * Removing unnecessary clone * More appropriate naming * Removed dead code * Lock file updates * Updated network monitor address in contract code * Don't stage node status api if network monitor is disabled * cargo fmt * Updated all license notices to SPDX
186 lines
6.9 KiB
Rust
186 lines
6.9 KiB
Rust
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
use futures::channel::mpsc;
|
|
use futures::StreamExt;
|
|
use log::*;
|
|
use ordered_buffer::{OrderedMessage, OrderedMessageBuffer};
|
|
use socks5_requests::ConnectionId;
|
|
use std::collections::{HashMap, HashSet};
|
|
|
|
/// A generic message produced after reading from a socket/connection. It includes data that was
|
|
/// actually read alongside boolean indicating whether the connection got closed so that
|
|
/// remote could act accordingly.
|
|
#[derive(Debug)]
|
|
pub struct ConnectionMessage {
|
|
pub payload: Vec<u8>,
|
|
pub socket_closed: bool,
|
|
}
|
|
|
|
/// Channel responsible for sending data that was received from mix network into particular connection.
|
|
/// Data includes the actual payload that is to be written onto the connection
|
|
/// alongside boolean indicating whether the remote connection was closed after producing this message,
|
|
/// so that the local connection should also shut down.
|
|
pub type ConnectionSender = mpsc::UnboundedSender<ConnectionMessage>;
|
|
|
|
/// Receiver part of the [`ConnectionSender`]
|
|
pub type ConnectionReceiver = mpsc::UnboundedReceiver<ConnectionMessage>;
|
|
|
|
pub type ControllerSender = mpsc::UnboundedSender<ControllerCommand>;
|
|
pub type ControllerReceiver = mpsc::UnboundedReceiver<ControllerCommand>;
|
|
|
|
pub enum ControllerCommand {
|
|
Insert(ConnectionId, ConnectionSender),
|
|
Remove(ConnectionId),
|
|
Send(ConnectionId, Vec<u8>, bool),
|
|
}
|
|
|
|
struct ActiveConnection {
|
|
is_closed: bool,
|
|
connection_sender: Option<ConnectionSender>,
|
|
ordered_buffer: OrderedMessageBuffer,
|
|
}
|
|
|
|
impl ActiveConnection {
|
|
fn write_to_buf(&mut self, payload: Vec<u8>) {
|
|
let ordered_message = match OrderedMessage::try_from_bytes(payload) {
|
|
Ok(msg) => msg,
|
|
Err(err) => {
|
|
error!("Malformed ordered message - {:?}", err);
|
|
return;
|
|
}
|
|
};
|
|
self.ordered_buffer.write(ordered_message);
|
|
}
|
|
|
|
fn read_from_buf(&mut self) -> Option<Vec<u8>> {
|
|
self.ordered_buffer.read()
|
|
}
|
|
}
|
|
|
|
/// Controller represents a way of managing multiple open connections that are used for socks5
|
|
/// proxy.
|
|
pub struct Controller {
|
|
active_connections: HashMap<ConnectionId, ActiveConnection>,
|
|
receiver: ControllerReceiver,
|
|
|
|
// TODO: this will need to be either completely removed (from code) or periodically cleaned
|
|
// to avoid memory issues
|
|
recently_closed: HashSet<ConnectionId>,
|
|
|
|
// TODO: this can potentially be abused to ddos and kill provider. Not sure at this point
|
|
// how to handle it more gracefully
|
|
|
|
// buffer for messages received before connection was established due to mixnet being able to
|
|
// un-order messages. Note we don't ever expect to have more than 1-2 messages per connection here
|
|
pending_messages: HashMap<ConnectionId, Vec<(Vec<u8>, bool)>>,
|
|
}
|
|
|
|
impl Controller {
|
|
pub fn new() -> (Self, ControllerSender) {
|
|
let (sender, receiver) = mpsc::unbounded();
|
|
(
|
|
Controller {
|
|
active_connections: HashMap::new(),
|
|
receiver,
|
|
recently_closed: HashSet::new(),
|
|
pending_messages: HashMap::new(),
|
|
},
|
|
sender,
|
|
)
|
|
}
|
|
|
|
fn insert_connection(&mut self, conn_id: ConnectionId, connection_sender: ConnectionSender) {
|
|
let active_connection = ActiveConnection {
|
|
is_closed: false,
|
|
connection_sender: Some(connection_sender),
|
|
ordered_buffer: OrderedMessageBuffer::new(),
|
|
};
|
|
if let Some(_active_conn) = self.active_connections.insert(conn_id, active_connection) {
|
|
error!("Received a duplicate 'Connect'!")
|
|
} else {
|
|
// check if there were any pending messages
|
|
if let Some(pending) = self.pending_messages.remove(&conn_id) {
|
|
debug!("There were some pending messages for {}", conn_id);
|
|
for (payload, is_closed) in pending {
|
|
self.send_to_connection(conn_id, payload, is_closed)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn remove_connection(&mut self, conn_id: ConnectionId) {
|
|
debug!("Removing {} from controller", conn_id);
|
|
if self.active_connections.remove(&conn_id).is_none() {
|
|
error!(
|
|
"tried to remove non-existing connection with id: {:?}",
|
|
conn_id
|
|
)
|
|
}
|
|
self.recently_closed.insert(conn_id);
|
|
}
|
|
|
|
fn send_to_connection(&mut self, conn_id: ConnectionId, payload: Vec<u8>, is_closed: bool) {
|
|
if let Some(active_connection) = self.active_connections.get_mut(&conn_id) {
|
|
if !payload.is_empty() {
|
|
active_connection.write_to_buf(payload);
|
|
} else if !is_closed {
|
|
error!("Tried to write an empty message to a not-closing connection. Please let us know if you see this message");
|
|
}
|
|
// if messages get unordered, make sure we don't lose information about
|
|
// remote socket getting closed!
|
|
active_connection.is_closed |= is_closed;
|
|
|
|
if let Some(payload) = active_connection.read_from_buf() {
|
|
if let Err(err) = active_connection
|
|
.connection_sender
|
|
.as_mut()
|
|
.unwrap()
|
|
.unbounded_send(ConnectionMessage {
|
|
payload,
|
|
socket_closed: active_connection.is_closed,
|
|
})
|
|
{
|
|
error!("WTF IS THIS: {:?}", err);
|
|
}
|
|
|
|
// TODO: ABOVE UNWRAP CAUSED A CRASH IN A NORMAL USE!!!!
|
|
// TODO:
|
|
// TODO: surprisingly it only happened on socks client, never on nSP
|
|
// TODO:
|
|
// TODO:
|
|
// TODO:
|
|
// TODO:
|
|
}
|
|
} else if !self.recently_closed.contains(&conn_id) {
|
|
debug!("Received a 'Send' before 'Connect' - going to buffer the data");
|
|
let pending = self
|
|
.pending_messages
|
|
.entry(conn_id)
|
|
.or_insert_with(Vec::new);
|
|
pending.push((payload, is_closed));
|
|
} else if !is_closed {
|
|
error!(
|
|
"Tried to write to closed connection ({} bytes were 'lost)",
|
|
payload.len()
|
|
);
|
|
} else {
|
|
debug!("Tried to write to closed connection, but remote is already closed")
|
|
}
|
|
}
|
|
|
|
pub async fn run(&mut self) {
|
|
while let Some(command) = self.receiver.next().await {
|
|
match command {
|
|
ControllerCommand::Send(conn_id, data, is_closed) => {
|
|
self.send_to_connection(conn_id, data, is_closed)
|
|
}
|
|
ControllerCommand::Insert(conn_id, sender) => {
|
|
self.insert_connection(conn_id, sender)
|
|
}
|
|
ControllerCommand::Remove(conn_id) => self.remove_connection(conn_id),
|
|
}
|
|
}
|
|
}
|
|
}
|