/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2026 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::{ io::ErrorKind, sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, Arc, }, }; use url::Url; #[cfg(feature = "upnp-igd")] use smol::lock::Mutex as AsyncMutex; use super::{ channel::{Channel, ChannelPtr}, hosts::HostColor, session::SessionWeakPtr, transport::{Listener, PtListener}, }; #[cfg(feature = "upnp-igd")] use super::upnp::{setup_port_mappings, PortMapping}; use crate::{ system::{ CondVar, ExecutorPtr, Publisher, PublisherPtr, StoppableTask, StoppableTaskPtr, Subscription, }, util::logger::verbose, Error, Result, }; /// Atomic pointer to Acceptor pub type AcceptorPtr = Arc; /// Create inbound socket connections pub struct Acceptor { channel_publisher: PublisherPtr>, task: StoppableTaskPtr, session: SessionWeakPtr, conn_count: AtomicUsize, #[cfg(feature = "upnp-igd")] port_mappings: AsyncMutex>>, } impl Acceptor { /// Create new Acceptor object. pub fn new(session: SessionWeakPtr) -> AcceptorPtr { Arc::new(Self { channel_publisher: Publisher::new(), task: StoppableTask::new(), session, conn_count: AtomicUsize::new(0), #[cfg(feature = "upnp-igd")] port_mappings: AsyncMutex::new(Vec::new()), }) } /// Start accepting inbound socket connections pub async fn start(self: Arc, endpoint: Url, ex: ExecutorPtr) -> Result<()> { let datastore = self.session.upgrade().unwrap().p2p().settings().read().await.p2p_datastore.clone(); // Initialize listener let listener = Listener::new(endpoint.clone(), datastore, true).await?; // Open socket let ptlistener = listener.listen().await?; #[cfg(feature = "p2p-tor")] if endpoint.scheme() == "tor" { let onion_addr = listener.endpoint().await; verbose!("[P2P] Adding {onion_addr} to external_addrs"); self.session .upgrade() .unwrap() .p2p() .settings() .write() .await .external_addrs .push(onion_addr); } #[cfg(feature = "upnp-igd")] { let actual_endpoint = listener.endpoint().await; let settings = self.session.upgrade().unwrap().p2p().settings(); let mappings = setup_port_mappings(&actual_endpoint, settings, ex.clone()); self.port_mappings.lock().await.extend(mappings); } self.accept(ptlistener, ex); Ok(()) } /// Stop accepting inbound socket connections pub async fn stop(&self) { // Stop all port mappings #[cfg(feature = "upnp-igd")] { let mappings = std::mem::take(&mut *self.port_mappings.lock().await); for mapping in mappings { mapping.stop(); } } // Send stop signal self.task.stop().await; } /// Start receiving network messages. pub async fn subscribe(self: Arc) -> Subscription> { self.channel_publisher.clone().subscribe().await } /// Run the accept loop in a new thread and error if a connection problem occurs fn accept(self: Arc, listener: Box, ex: ExecutorPtr) { let self_ = self.clone(); self.task.clone().start( self.run_accept_loop(listener, ex.clone()), |result| self_.handle_stop(result), Error::NetworkServiceStopped, ex, ); } /// Run the accept loop. async fn run_accept_loop( self: Arc, listener: Box, ex: ExecutorPtr, ) -> Result<()> { // CondVar used to notify the loop to recheck if new connections can // be accepted by the listener. let cv = Arc::new(CondVar::new()); let hosts = self.session.upgrade().unwrap().p2p().hosts(); loop { // Refuse new connections if we're up to the connection limit let limit = self.session.upgrade().unwrap().p2p().settings().read().await.inbound_connections; if self.clone().conn_count.load(SeqCst) >= limit { // This will get notified every time an inbound channel is stopped. // These channels are the channels spawned below on listener.next().is_ok(). // After the notification, we reset the condvar and retry this loop to see // if we can accept more connections, and if not - we'll be back here. verbose!(target: "net::acceptor::run_accept_loop", "Reached incoming conn limit, waiting..."); cv.wait().await; cv.reset(); continue } // Now we wait for a new connection. match listener.next().await { Ok((stream, url)) => { // Check if we reject this peer if hosts.container.contains(HostColor::Black, &url) || hosts.block_all_ports(&url) { verbose!(target: "net::acceptor::run_accept_loop", "Peer {url} is blacklisted"); continue } // Create the new Channel. let session = self.session.clone(); let channel = Channel::new(stream, None, url, session, false).await; // Increment the connection counter self.conn_count.fetch_add(1, SeqCst); // This task will subscribe on the new channel and decrement // the connection counter. Along with that, it will notify // the CondVar that might be waiting to allow new connections. let self_ = self.clone(); let channel_ = channel.clone(); let cv_ = cv.clone(); ex.spawn(async move { let stop_sub = channel_.subscribe_stop().await?; stop_sub.receive().await; self_.conn_count.fetch_sub(1, SeqCst); cv_.notify(); Ok::<(), crate::Error>(()) }) .detach(); // Finally, notify any publishers about the new channel. self.channel_publisher.notify(Ok(channel)).await; } // As per accept(2) recommendation: Err(e) if e.raw_os_error().is_some() => match e.raw_os_error().unwrap() { libc::EAGAIN | libc::ECONNABORTED | libc::EPROTO | libc::EINTR => continue, libc::ECONNRESET => { verbose!( target: "net::acceptor::run_accept_loop", "[P2P] Connection reset by peer in accept_loop" ); continue } libc::ETIMEDOUT => { verbose!( target: "net::acceptor::run_accept_loop", "[P2P] Connection timed out in accept_loop" ); continue } libc::EPIPE => { verbose!( target: "net::acceptor::run_accept_loop", "[P2P] Broken pipe in accept_loop" ); continue } x => { verbose!( target: "net::acceptor::run_accept_loop", "[P2P] Unhandled OS Error: {e} {x}" ); continue } }, // In case a TLS handshake fails, we'll get this: Err(e) if e.kind() == ErrorKind::UnexpectedEof => continue, // Handle ErrorKind::Other Err(e) if e.kind() == ErrorKind::Other => { if let Some(inner) = std::error::Error::source(&e) { if let Some(inner) = inner.downcast_ref::() { verbose!( target: "net::acceptor::run_accept_loop", "[P2P] rustls listener error: {inner:?}" ); continue } } verbose!( target: "net::acceptor::run_accept_loop", "[P2P] Unhandled ErrorKind::Other error: {e:?}" ); continue } // Errors we didn't handle above: Err(e) => { verbose!( target: "net::acceptor::run_accept_loop", "[P2P] Unhandled listener.next() error: {e}" ); continue } } } } /// Handles network errors. Panics if errors pass silently, otherwise broadcasts it /// to all channel publishers. async fn handle_stop(self: Arc, result: Result<()>) { match result { Ok(()) => panic!("Acceptor task should never complete without error status"), Err(err) => self.channel_publisher.notify(Err(err)).await, } } }