114d92f93f
* changed NymConfigTemplate trait to call 'template' by reference * network requester lib * introduced generic parameter to 'MixTrafficController' to allow non-remote gateways * allowing for custom gateway sender * types cleanup * minor gateway cmds refactor + initial NR work * wip * running a NR inside gateway note: this NR isnt tied to the gateway yet * rebase fixes * propagating same shutdown handle * wip * starting NR with appropriate local transceiver * fixed premature shutdown * wiring up PacketRouter * both ends wired together * actually working so much cleanup to do now * started removing dead code * wip * temp: hardcode gateway * further cleanup * fixed build of other binaries * setup-network-requester subcmd * overriding NR config in gateway init/run * wip making it wasm-compatible [again] * refactored 'GatewaySetup' * clippy and friends * removed debug code * rust 1.72 lints * ensuring local gateway is available + some comments * correctly putting network requester data in the same underlying details struct * improved gateway errors * changed 'network_requester_config' deserialization * missing clap annotation for 'enabled' flag in 'setup-network-requester' command * saving config file after 'setup-network-requester' * removed dead code * review comments * make embedded NR wait for gateway to come online (for at most 70min) * fixed shutdown on successful gateway wait * updated NR config override
228 lines
8.4 KiB
Rust
228 lines
8.4 KiB
Rust
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
use crate::error::GatewayClientError;
|
|
use crate::packet_router::PacketRouter;
|
|
use crate::traits::GatewayPacketRouter;
|
|
use crate::{cleanup_socket_messages, try_decrypt_binary_message};
|
|
use futures::channel::oneshot;
|
|
use futures::stream::{SplitSink, SplitStream};
|
|
use futures::{SinkExt, StreamExt};
|
|
use log::*;
|
|
use nym_gateway_requests::registration::handshake::SharedKeys;
|
|
use nym_task::TaskClient;
|
|
use std::sync::Arc;
|
|
use tungstenite::Message;
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
use tokio::net::TcpStream;
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
use wasm_bindgen_futures;
|
|
#[cfg(target_arch = "wasm32")]
|
|
use wasm_utils::websocket::JSWebsocket;
|
|
|
|
// type alias for not having to type the whole thing every single time (and now it makes it easier
|
|
// to use different types based on compilation target)
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
type WsConn = WebSocketStream<MaybeTlsStream<TcpStream>>;
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
type WsConn = JSWebsocket;
|
|
|
|
// We have ownership over sink half of the connection, but the stream is owned
|
|
// by some other task, however, we can notify it to get the stream back.
|
|
|
|
type SplitStreamReceiver = oneshot::Receiver<Result<SplitStream<WsConn>, GatewayClientError>>;
|
|
|
|
pub(crate) struct PartiallyDelegated {
|
|
sink_half: SplitSink<WsConn, Message>,
|
|
delegated_stream: (SplitStreamReceiver, oneshot::Sender<()>),
|
|
}
|
|
|
|
impl PartiallyDelegated {
|
|
fn recover_received_plaintexts(ws_msgs: Vec<Message>, shared_key: &SharedKeys) -> Vec<Vec<u8>> {
|
|
let mut plaintexts = Vec::with_capacity(ws_msgs.len());
|
|
for ws_msg in ws_msgs {
|
|
match ws_msg {
|
|
Message::Binary(bin_msg) => {
|
|
// this function decrypts the request and checks the MAC
|
|
if let Some(plaintext) = try_decrypt_binary_message(bin_msg, shared_key) {
|
|
plaintexts.push(plaintext)
|
|
}
|
|
}
|
|
// I think that in the future we should perhaps have some sequence number system, i.e.
|
|
// so each request/response pair can be easily identified, so that if messages are
|
|
// not ordered (for some peculiar reason) we wouldn't lose anything.
|
|
// This would also require NOT discarding any text responses here.
|
|
|
|
// TODO: those can return the "send confirmations" - perhaps it should be somehow worked around?
|
|
Message::Text(text) => {
|
|
trace!(
|
|
"received a text message - probably a response to some previous query! - {}",
|
|
text
|
|
);
|
|
continue;
|
|
}
|
|
_ => continue,
|
|
}
|
|
}
|
|
plaintexts
|
|
}
|
|
|
|
fn route_socket_messages(
|
|
ws_msgs: Vec<Message>,
|
|
packet_router: &mut PacketRouter,
|
|
shared_key: &SharedKeys,
|
|
) -> Result<(), GatewayClientError> {
|
|
let plaintexts = Self::recover_received_plaintexts(ws_msgs, shared_key);
|
|
packet_router.route_received(plaintexts)
|
|
}
|
|
|
|
pub(crate) fn split_and_listen_for_mixnet_messages(
|
|
conn: WsConn,
|
|
packet_router: PacketRouter,
|
|
shared_key: Arc<SharedKeys>,
|
|
mut shutdown: TaskClient,
|
|
) -> Self {
|
|
// when called for, it NEEDS TO yield back the stream so that we could merge it and
|
|
// read control request responses.
|
|
let (notify_sender, notify_receiver) = oneshot::channel();
|
|
let (stream_sender, stream_receiver) = oneshot::channel();
|
|
|
|
let (sink, mut stream) = conn.split();
|
|
|
|
let mixnet_receiver_future = async move {
|
|
let mut notify_receiver = notify_receiver;
|
|
let mut chunk_stream = (&mut stream).ready_chunks(8);
|
|
let mut packet_router = packet_router;
|
|
|
|
let ret_err = loop {
|
|
tokio::select! {
|
|
_ = shutdown.recv() => {
|
|
log::trace!("GatewayClient listener: Received shutdown");
|
|
log::debug!("GatewayClient listener: Exiting");
|
|
return;
|
|
}
|
|
_ = &mut notify_receiver => {
|
|
break Ok(());
|
|
}
|
|
msgs = chunk_stream.next() => {
|
|
let ws_msgs = match cleanup_socket_messages(msgs) {
|
|
Err(err) => break Err(err),
|
|
Ok(msgs) => msgs
|
|
};
|
|
|
|
if let Err(err) = Self::route_socket_messages(ws_msgs, &mut packet_router, shared_key.as_ref()) {
|
|
log::warn!("Route socket messages failed: {err}");
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
if match ret_err {
|
|
Err(err) => stream_sender.send(Err(err)),
|
|
Ok(_) => {
|
|
shutdown.mark_as_success();
|
|
stream_sender.send(Ok(stream))
|
|
}
|
|
}
|
|
.is_err()
|
|
{
|
|
warn!("failed to send back `mixnet_receiver_future` result on the oneshot channel")
|
|
}
|
|
};
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
wasm_bindgen_futures::spawn_local(mixnet_receiver_future);
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
tokio::spawn(mixnet_receiver_future);
|
|
|
|
PartiallyDelegated {
|
|
sink_half: sink,
|
|
delegated_stream: (stream_receiver, notify_sender),
|
|
}
|
|
}
|
|
|
|
// if we want to send a message and don't care about response, we can don't need to reunite the split,
|
|
// the sink itself is enough
|
|
pub(crate) async fn send_without_response(
|
|
&mut self,
|
|
msg: Message,
|
|
) -> Result<(), GatewayClientError> {
|
|
Ok(self.sink_half.send(msg).await?)
|
|
}
|
|
|
|
pub(crate) async fn batch_send_without_response(
|
|
&mut self,
|
|
messages: Vec<Message>,
|
|
) -> Result<(), GatewayClientError> {
|
|
let stream_messages: Vec<_> = messages.into_iter().map(Ok).collect();
|
|
let mut send_stream = futures::stream::iter(stream_messages);
|
|
Ok(self.sink_half.send_all(&mut send_stream).await?)
|
|
}
|
|
|
|
pub(crate) async fn merge(self) -> Result<WsConn, GatewayClientError> {
|
|
let (mut stream_receiver, notify) = self.delegated_stream;
|
|
|
|
// check if the split stream didn't error out
|
|
let receive_res = stream_receiver
|
|
.try_recv()
|
|
.expect("stream sender was somehow dropped without sending anything!");
|
|
|
|
if let Some(res) = receive_res {
|
|
let _res = res?;
|
|
panic!(
|
|
"This should have NEVER happened - returned a stream before receiving notification"
|
|
)
|
|
}
|
|
|
|
// this call failing is incredibly unlikely, but not impossible.
|
|
// basically the gateway connection must have failed after executing previous line but
|
|
// before starting execution of this one.
|
|
notify
|
|
.send(())
|
|
.map_err(|_| GatewayClientError::ConnectionAbruptlyClosed)?;
|
|
|
|
let stream_results: Result<_, GatewayClientError> = stream_receiver
|
|
.await
|
|
// Address cancellation of the underlying future past the check
|
|
// in receive_res
|
|
.map_err(|_| GatewayClientError::ConnectionAbruptlyClosed)?;
|
|
let stream = stream_results?;
|
|
// the error is thrown when trying to reunite sink and stream that did not originate
|
|
// from the same split which is impossible to happen here
|
|
Ok(self.sink_half.reunite(stream).unwrap())
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
pub(crate) enum SocketState {
|
|
Available(Box<WsConn>),
|
|
PartiallyDelegated(PartiallyDelegated),
|
|
NotConnected,
|
|
Invalid,
|
|
}
|
|
|
|
impl SocketState {
|
|
pub(crate) fn is_available(&self) -> bool {
|
|
matches!(self, SocketState::Available(_))
|
|
}
|
|
|
|
pub(crate) fn is_partially_delegated(&self) -> bool {
|
|
matches!(self, SocketState::PartiallyDelegated(_))
|
|
}
|
|
|
|
pub(crate) fn is_established(&self) -> bool {
|
|
matches!(
|
|
self,
|
|
SocketState::Available(_) | SocketState::PartiallyDelegated(_)
|
|
)
|
|
}
|
|
}
|