/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2024 Dyne.org foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use std::{ fmt, sync::{ atomic::{AtomicBool, Ordering::SeqCst}, Arc, }, time::UNIX_EPOCH, }; use darkfi_serial::{ async_trait, AsyncDecodable, AsyncEncodable, SerialDecodable, SerialEncodable, VarInt, }; use log::{debug, error, info, trace}; use rand::{rngs::OsRng, Rng}; use smol::{ io::{self, AsyncRead, AsyncReadExt, AsyncWriteExt, ReadHalf, WriteHalf}, lock::Mutex, Executor, }; use url::Url; use super::{ dnet::{self, dnetev, DnetEvent}, hosts::HostColor, message, message::{VersionMessage, MAGIC_BYTES}, message_publisher::{MessageSubscription, MessageSubsystem}, p2p::P2pPtr, session::{Session, SessionBitFlag, SessionWeakPtr, SESSION_ALL, SESSION_REFINE}, transport::PtStream, }; use crate::{ system::{Publisher, PublisherPtr, StoppableTask, StoppableTaskPtr, Subscription}, util::time::NanoTimestamp, Error, Result, }; /// Atomic pointer to async channel pub type ChannelPtr = Arc; /// Channel debug info #[derive(Clone, Debug, SerialEncodable, SerialDecodable)] pub struct ChannelInfo { pub resolve_addr: Option, pub connect_addr: Url, pub start_time: u64, pub id: u32, } impl ChannelInfo { fn new(resolve_addr: Option, connect_addr: Url, start_time: u64) -> Self { Self { resolve_addr, connect_addr, start_time, id: OsRng.gen() } } } /// Async channel for communication between nodes. pub struct Channel { /// The reading half of the transport stream reader: Mutex>>, /// The writing half of the transport stream writer: Mutex>>, /// The message subsystem instance for this channel message_subsystem: MessageSubsystem, /// Publisher listening for stop signal for closing this channel stop_publisher: PublisherPtr, /// Task that is listening for the stop signal receive_task: StoppableTaskPtr, /// A boolean marking if this channel is stopped stopped: AtomicBool, /// Weak pointer to respective session session: SessionWeakPtr, /// The version message of the node we are connected to. /// Some if the version exchange has already occurred, None /// otherwise. version: Mutex>>, /// Channel debug info pub info: ChannelInfo, } impl Channel { /// Sets up a new channel. Creates a reader and writer [`PtStream`] and /// the message publisher subsystem. Performs a network handshake on the /// subsystem dispatchers. pub async fn new( stream: Box, resolve_addr: Option, connect_addr: Url, session: SessionWeakPtr, ) -> Arc { let (reader, writer) = io::split(stream); let reader = Mutex::new(reader); let writer = Mutex::new(writer); let message_subsystem = MessageSubsystem::new(); Self::setup_dispatchers(&message_subsystem).await; let version = Mutex::new(None); let start_time = UNIX_EPOCH.elapsed().unwrap().as_secs(); let info = ChannelInfo::new(resolve_addr, connect_addr.clone(), start_time); Arc::new(Self { reader, writer, message_subsystem, stop_publisher: Publisher::new(), receive_task: StoppableTask::new(), stopped: AtomicBool::new(false), session, version, info, }) } /// Perform network handshake for message subsystem dispatchers. async fn setup_dispatchers(subsystem: &MessageSubsystem) { subsystem.add_dispatch::().await; subsystem.add_dispatch::().await; subsystem.add_dispatch::().await; subsystem.add_dispatch::().await; subsystem.add_dispatch::().await; subsystem.add_dispatch::().await; } /// Starts the channel. Runs a receive loop to start receiving messages /// or handles a network failure. pub fn start(self: Arc, executor: Arc>) { debug!(target: "net::channel::start()", "START {:?}", self); let self_ = self.clone(); self.receive_task.clone().start( self.clone().main_receive_loop(), |result| self_.handle_stop(result), Error::ChannelStopped, executor, ); debug!(target: "net::channel::start()", "END {:?}", self); } /// Stops the channel. /// Notifies all publishers that the channel has been closed in `handle_stop()`. pub async fn stop(&self) { debug!(target: "net::channel::stop()", "START {:?}", self); self.receive_task.stop().await; debug!(target: "net::channel::stop()", "END {:?}", self); } /// Creates a subscription to a stopped signal. /// If the channel is stopped then this will return a ChannelStopped error. pub async fn subscribe_stop(&self) -> Result> { debug!(target: "net::channel::subscribe_stop()", "START {:?}", self); if self.is_stopped() { return Err(Error::ChannelStopped) } let sub = self.stop_publisher.clone().subscribe().await; debug!(target: "net::channel::subscribe_stop()", "END {:?}", self); Ok(sub) } pub fn is_stopped(&self) -> bool { self.stopped.load(SeqCst) } /// Sends a message across a channel. Calls `send_message` that creates /// a new payload and sends it over the network transport as a packet. /// Returns an error if something goes wrong. pub async fn send(&self, message: &M) -> Result<()> { debug!( target: "net::channel::send()", "[START] command={} {:?}", M::NAME, self, ); if self.is_stopped() { return Err(Error::ChannelStopped) } // Catch failure and stop channel, return a net error if let Err(e) = self.send_message(message).await { if self.session.upgrade().unwrap().type_id() & (SESSION_ALL & !SESSION_REFINE) != 0 { error!( target: "net::channel::send()", "[P2P] Channel send error for [{:?}]: {}", self, e ); } self.stop().await; return Err(Error::ChannelStopped) } debug!( target: "net::channel::send()", "[END] command={} {:?}", M::NAME, self ); Ok(()) } /// Sends an outbound Message by writing data to the given async stream. async fn send_message(&self, payload: &M) -> Result<()> { let command = M::NAME.to_string(); assert!(!command.is_empty()); assert!(std::mem::size_of::() <= std::mem::size_of::()); let stream = &mut *self.writer.lock().await; let mut buffer = Vec::::new(); let mut written: usize = 0; dnetev!(self, SendMessage, { chan: self.info.clone(), cmd: command, time: NanoTimestamp::current_time(), }); trace!(target: "net::channel::send_message()", "Sending magic..."); written += MAGIC_BYTES.encode_async(stream).await?; trace!(target: "net::channel::send_message()", "Sent magic"); trace!(target: "net::channel::send_message()", "Sending command..."); written += M::NAME.to_string().encode_async(stream).await?; trace!(target: "net::channel::send_message()", "Sent command: {}", M::NAME.to_string()); trace!(target: "net::channel::send_message()", "Sending payload..."); // First encode the payload to an intermediate buffer. payload.encode_async(&mut buffer).await?; // Then extract the length of the intermediate buffer as a VarInt // and write to the stream. This is the length of the payload. // Then encode the payload itself to the stream. written += VarInt(buffer.len() as u64).encode_async(stream).await?; written += payload.encode_async(stream).await?; trace!(target: "net::channel::send_message()", "Sent payload {} bytes, total bytes {}", buffer.len(), written); stream.flush().await?; Ok(()) } /// Returns a decoded Message command. /// We start by extracting the length from the stream, then allocate /// the precise buffer for this length using stream.take(). This provides /// a basic DDOS protection. pub async fn read_command( &self, stream: &mut R, ) -> Result { // Messages should have a 4 byte header of magic digits. // This is used for network debugging. let mut magic = [0u8; 4]; trace!(target: "net::channel::read_command()", "Reading magic..."); stream.read_exact(&mut magic).await?; trace!(target: "net::channel::read_command()", "Read magic {:?}", magic); if magic != MAGIC_BYTES { error!(target: "net::channel::read_command", "Error: Magic bytes mismatch"); return Err(Error::MalformedPacket) } let cmd_len = VarInt::decode_async(stream).await?.0; let mut take = stream.take(cmd_len); let mut bytes = Vec::new(); for _ in 0..cmd_len { bytes.push(AsyncDecodable::decode_async(&mut take).await?); } let command = String::from_utf8(bytes)?; Ok(command) } /// Subscribe to a message on the message subsystem. pub async fn subscribe_msg(&self) -> Result> { debug!( target: "net::channel::subscribe_msg()", "[START] command={} {:?}", M::NAME, self ); let sub = self.message_subsystem.subscribe::().await; debug!( target: "net::channel::subscribe_msg()", "[END] command={} {:?}", M::NAME, self ); sub } /// Handle network errors. Panic if error passes silently, otherwise /// broadcast the error. async fn handle_stop(self: Arc, result: Result<()>) { debug!(target: "net::channel::handle_stop()", "[START] {:?}", self); self.stopped.store(true, SeqCst); match result { Ok(()) => panic!("Channel task should never complete without error status"), // Send this error to all channel subscribers Err(e) => { self.stop_publisher.notify(Error::ChannelStopped).await; self.message_subsystem.trigger_error(e).await; } } debug!(target: "net::channel::handle_stop()", "[END] {:?}", self); } /// Run the receive loop. Start receiving messages or handle network failure. async fn main_receive_loop(self: Arc) -> Result<()> { debug!(target: "net::channel::main_receive_loop()", "[START] {:?}", self); // Acquire reader lock let reader = &mut *self.reader.lock().await; // Run loop loop { let command = match self.read_command(reader).await { Ok(command) => command, Err(err) => { if Self::is_eof_error(&err) { info!( target: "net::channel::main_receive_loop()", "[P2P] Channel inbound connection {} disconnected", self.address(), ); } else if self.session.upgrade().unwrap().type_id() & (SESSION_ALL & !SESSION_REFINE) != 0 { error!( target: "net::channel::main_receive_loop()", "[P2P] Read error on channel {}: {}", self.address(), err, ); } debug!( target: "net::channel::main_receive_loop()", "Stopping channel {:?}", self ); return Err(Error::ChannelStopped) } }; dnetev!(self, RecvMessage, { chan: self.info.clone(), cmd: command.clone(), time: NanoTimestamp::current_time(), }); // Send result to our publishers match self.message_subsystem.notify(&command, reader).await { Ok(()) => {} // If we're getting messages without dispatchers, it's spam. Err(Error::MissingDispatcher) => { debug!(target: "net::channel::main_receive_loop()", "Stopping channel {:?}", self); // We will reject further connections from this peer self.ban(self.address()).await; return Err(Error::ChannelStopped) } Err(_) => unreachable!("You added a new error in notify()"), } } } /// Ban a malicious peer and stop the channel. pub async fn ban(&self, peer: &Url) { debug!(target: "net::channel::ban()", "START {:?}", self); let last_seen = UNIX_EPOCH.elapsed().unwrap().as_secs(); self.p2p().hosts().move_host(peer, last_seen, HostColor::Black).await.unwrap(); self.stop().await; debug!(target: "net::channel::ban()", "STOP {:?}", self); } /// Returns the relevant socket address for this connection. If this is /// an outbound connection, the transport-processed resolve_addr will /// be returned. Otherwise for inbound connections it will default /// to connect_addr. pub fn address(&self) -> &Url { if self.info.resolve_addr.is_some() { self.info.resolve_addr.as_ref().unwrap() } else { &self.info.connect_addr } } /// Returns the socket address that has undergone transport /// processing, if it exists. Returns None otherwise. pub fn resolve_addr(&self) -> Option { self.info.resolve_addr.clone() } /// Return the socket address without transport processing. pub fn connect_addr(&self) -> &Url { &self.info.connect_addr } /// Set the VersionMessage of the node this channel is connected /// to. Called on receiving a version message in `ProtocolVersion`. pub(crate) async fn set_version(&self, version: Arc) { *self.version.lock().await = Some(version); } /// Returns the inner [`MessageSubsystem`] reference pub fn message_subsystem(&self) -> &MessageSubsystem { &self.message_subsystem } fn session(&self) -> Arc { self.session.upgrade().unwrap() } pub fn session_type_id(&self) -> SessionBitFlag { let session = self.session(); session.type_id() } fn p2p(&self) -> P2pPtr { self.session().p2p() } fn is_eof_error(err: &Error) -> bool { match err { Error::Io(ioerr) => ioerr == &std::io::ErrorKind::UnexpectedEof, _ => false, } } } impl fmt::Debug for Channel { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "", self.address(), self.info.id) } }