/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2025 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 . */ //! Manual connections session. Manages the creation of manual sessions. //! Used to create a manual session and to stop and start the session. //! //! A manual session is a type of outbound session in which we attempt //! connection to a predefined set of peers. Manual sessions loop forever //! continually trying to connect to a given peer, and sleep //! `outbound_connect_timeout` times between each attempt. //! //! Class consists of a weak pointer to the p2p interface and a vector of //! outbound connection slots. Using a weak pointer to p2p allows us to //! avoid circular dependencies. The vector of slots is wrapped in a mutex //! lock. This is switched on every time we instantiate a connection slot //! and insures that no other part of the program uses the slots at the //! same time. use std::sync::{Arc, Weak}; use async_trait::async_trait; use futures::stream::{FuturesUnordered, StreamExt}; use smol::lock::{Mutex as AsyncMutex, RwLock as AsyncRwLock}; use tracing::{debug, error, warn}; use url::Url; use super::{ super::{ connector::Connector, p2p::{P2p, P2pPtr}, }, Session, SessionBitFlag, SESSION_MANUAL, }; use crate::{ net::{hosts::HostState, settings::Settings}, system::{sleep, StoppableTask, StoppableTaskPtr}, util::logger::verbose, Error, Result, }; pub type ManualSessionPtr = Arc; /// Defines manual connections session. pub struct ManualSession { pub(in crate::net) p2p: Weak, slots: AsyncMutex>>, } impl ManualSession { /// Create a new manual session. pub fn new(p2p: Weak) -> ManualSessionPtr { Arc::new(Self { p2p, slots: AsyncMutex::new(Vec::new()) }) } pub(crate) async fn start(self: Arc) { // Activate mutex lock on connection slots. let mut slots = self.slots.lock().await; let mut futures = FuturesUnordered::new(); let self_ = Arc::downgrade(&self); // Initialize a slot for each configured peer. // Connections will be started by not yet activated. for peer in &self.p2p().settings().read().await.peers { let slot = Slot::new(self_.clone(), peer.clone(), self.p2p().settings()); futures.push(slot.clone().start()); slots.push(slot); } while (futures.next().await).is_some() {} } /// Stops the manual session. pub async fn stop(&self) { let slots = &*self.slots.lock().await; let mut futures = FuturesUnordered::new(); for slot in slots { futures.push(slot.stop()); } while (futures.next().await).is_some() {} } } #[async_trait] impl Session for ManualSession { fn p2p(&self) -> P2pPtr { self.p2p.upgrade().unwrap() } fn type_id(&self) -> SessionBitFlag { SESSION_MANUAL } async fn reload(self: Arc) {} } struct Slot { addr: Url, process: StoppableTaskPtr, session: Weak, connector: Connector, } impl Slot { fn new( session: Weak, addr: Url, settings: Arc>, ) -> Arc { Arc::new(Self { addr, process: StoppableTask::new(), session: session.clone(), connector: Connector::new(settings, session), }) } async fn start(self: Arc) { let ex = self.p2p().executor(); self.process.clone().start( self.run(), |res| async { match res { Ok(()) | Err(Error::NetworkServiceStopped) => {} Err(e) => error!("net::manual_session {e}"), } }, Error::NetworkServiceStopped, ex, ); } /// Attempts a connection on the associated Connector object. async fn run(self: Arc) -> Result<()> { let ex = self.p2p().executor(); let mut attempts = 0; loop { attempts += 1; verbose!( target: "net::manual_session", "[P2P] Connecting to manual outbound [{}] (attempt #{})", self.addr, attempts ); let settings = self.p2p().settings().read_arc().await; let seeds = settings.seeds.clone(); let outbound_connect_timeout = settings.outbound_connect_timeout(self.addr.scheme()); drop(settings); // Do not establish a connection to a host that is also configured as a seed. // This indicates a user misconfiguration. if seeds.contains(&self.addr) { error!( target: "net::manual_session", "[P2P] Suspending manual connection to seed [{}]", self.addr.clone(), ); return Ok(()) } if let Err(e) = self.p2p().hosts().try_register(self.addr.clone(), HostState::Connect) { debug!(target: "net::manual_session", "Cannot connect to manual={}, err={e}", &self.addr); sleep(outbound_connect_timeout).await; continue } match self.connector.connect(&self.addr).await { Ok((_, channel)) => { verbose!( target: "net::manual_session", "[P2P] Manual outbound connected [{}]", channel.display_address() ); let stop_sub = channel.subscribe_stop().await?; // Channel is now connected but not yet setup // Register the new channel match self.session().register_channel(channel.clone(), ex.clone()).await { Ok(()) => { // Wait for channel to close stop_sub.receive().await; verbose!( target: "net::manual_session", "[P2P] Manual outbound disconnected [{}]", channel.display_address() ); } Err(e) => { warn!( target: "net::manual_session", "[P2P] Unable to connect to manual outbound [{}]: {e}", channel.display_address(), ); // Free up this addr for future operations. if let Err(e) = self.p2p().hosts().unregister(channel.address()) { warn!(target: "net::manual_session", "[P2P] Error while unregistering addr={}, err={e}", channel.display_address()); } } } } Err(e) => { warn!( target: "net::manual_session", "[P2P] Unable to connect to manual outbound: {e}", ); // Free up this addr for future operations. if let Err(e) = self.p2p().hosts().unregister(&self.addr) { warn!(target: "net::manual_session", "[P2P] Error while unregistering addr={}, err={e}", self.addr); } } } verbose!( target: "net::manual_session", "[P2P] Waiting {outbound_connect_timeout} seconds until next manual outbound connection attempt [{}]", self.addr, ); sleep(outbound_connect_timeout).await; } } fn session(&self) -> ManualSessionPtr { self.session.upgrade().unwrap() } fn p2p(&self) -> P2pPtr { self.session().p2p() } async fn stop(&self) { self.connector.stop(); self.process.stop().await; } }