outbound_session.rs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2023 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. //! Outbound connections session. Manages the creation of outbound sessions.
  19. //! Used to create an outbound session and to stop and start the session.
  20. //!
  21. //! Class consists of a weak pointer to the p2p interface and a vector of
  22. //! outbound connection slots. Using a weak pointer to p2p allows us to
  23. //! avoid circular dependencies. The vector of slots is wrapped in a mutex
  24. //! lock. This is switched on every time we instantiate a connection slot
  25. //! and insures that no other part of the program uses the slots at the
  26. //! same time.
  27. use std::collections::HashSet;
  28. use async_std::sync::{Arc, Mutex, Weak};
  29. use async_trait::async_trait;
  30. use log::{debug, error, info, warn};
  31. use smol::Executor;
  32. use url::Url;
  33. use super::{
  34. super::{
  35. channel::{ChannelInfo, ChannelPtr},
  36. connector::Connector,
  37. message::GetAddrsMessage,
  38. p2p::{DnetInfo, P2p, P2pPtr},
  39. },
  40. Session, SessionBitFlag, SESSION_OUTBOUND,
  41. };
  42. use crate::{
  43. system::{StoppableTask, StoppableTaskPtr, Subscriber, SubscriberPtr},
  44. util::async_util::sleep,
  45. Error, Result,
  46. };
  47. pub type OutboundSessionPtr = Arc<OutboundSession>;
  48. /// Connection state
  49. #[derive(Eq, PartialEq, Copy, Clone)]
  50. pub enum OutboundState {
  51. Open,
  52. Pending,
  53. Connected,
  54. }
  55. impl std::fmt::Display for OutboundState {
  56. fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
  57. write!(
  58. f,
  59. "{}",
  60. match self {
  61. Self::Open => "open",
  62. Self::Pending => "pending",
  63. Self::Connected => "connected",
  64. }
  65. )
  66. }
  67. }
  68. /// dnet info for an outbound connection
  69. #[derive(Clone)]
  70. pub struct OutboundInfo {
  71. /// Remote address
  72. pub addr: Option<Url>,
  73. /// Channel info
  74. pub channel: Option<ChannelInfo>,
  75. /// Connection state
  76. pub state: OutboundState,
  77. }
  78. impl OutboundInfo {
  79. async fn dnet_info(&self, p2p: P2pPtr) -> Option<Self> {
  80. let Some(ref addr) = self.addr else { return None };
  81. let Some(chan) = p2p.channels().lock().await.get(addr).cloned() else { return None };
  82. Some(Self {
  83. addr: self.addr.clone(),
  84. channel: Some(chan.dnet_info().await),
  85. state: self.state,
  86. })
  87. }
  88. }
  89. impl Default for OutboundInfo {
  90. fn default() -> Self {
  91. Self { addr: None, channel: None, state: OutboundState::Open }
  92. }
  93. }
  94. /// Defines outbound connections session.
  95. pub struct OutboundSession {
  96. /// Weak pointer to parent p2p object
  97. p2p: Weak<P2p>,
  98. /// Outbound connection slots
  99. connect_slots: Mutex<Vec<StoppableTaskPtr>>,
  100. /// Subscriber used to signal channels processing
  101. channel_subscriber: SubscriberPtr<Result<ChannelPtr>>,
  102. /// Flag to toggle channel_subscriber notifications
  103. notify: Mutex<bool>,
  104. /// Channel debug info, corresponds to `connect_slots`
  105. slot_info: Mutex<Vec<OutboundInfo>>,
  106. }
  107. impl OutboundSession {
  108. /// Create a new outbound session.
  109. pub fn new(p2p: Weak<P2p>) -> OutboundSessionPtr {
  110. Arc::new(Self {
  111. p2p,
  112. connect_slots: Mutex::new(vec![]),
  113. channel_subscriber: Subscriber::new(),
  114. notify: Mutex::new(false),
  115. slot_info: Mutex::new(vec![]),
  116. })
  117. }
  118. /// Start the outbound session. Runs the channel connect loop.
  119. pub async fn start(self: Arc<Self>, ex: Arc<Executor<'_>>) -> Result<()> {
  120. let n_slots = self.p2p().settings().outbound_connections;
  121. info!(target: "net::outbound_session", "[P2P] Starting {} outbound connection slots.", n_slots);
  122. // Activate mutex lock on connection slots.
  123. let mut connect_slots = self.connect_slots.lock().await;
  124. // Create dnet stub
  125. self.slot_info.lock().await.resize(n_slots, Default::default());
  126. for i in 0..n_slots {
  127. let task = StoppableTask::new();
  128. task.clone().start(
  129. self.clone().channel_connect_loop(i, ex.clone()),
  130. // Ignore stop handler
  131. |_| async {},
  132. Error::NetworkServiceStopped,
  133. ex.clone(),
  134. );
  135. connect_slots.push(task);
  136. }
  137. Ok(())
  138. }
  139. /// Stops the outbound session.
  140. pub async fn stop(&self) {
  141. let connect_slots = &*self.connect_slots.lock().await;
  142. for slot in connect_slots {
  143. slot.stop().await;
  144. }
  145. }
  146. /// Creates a connector object and tries to connect using it.
  147. pub async fn channel_connect_loop(
  148. self: Arc<Self>,
  149. slot_number: usize,
  150. ex: Arc<Executor<'_>>,
  151. ) -> Result<()> {
  152. let parent = Arc::downgrade(&self);
  153. let connector = Connector::new(self.p2p().settings(), Arc::new(parent));
  154. // Retrieve whitelisted outbound transports
  155. let transports = &self.p2p().settings().allowed_transports;
  156. // This is the main outbound connection loop where we try to establish
  157. // a connection in the slot. The `try_connect` function will block in
  158. // case the connection was sucessfully established. If it fails, then
  159. // we will wait for a defined number of seconds and try to fill the
  160. // slot again. This function should never exit during the lifetime of
  161. // the P2P network, as it is supposed to represent an outbound slot we
  162. // want to fill.
  163. // The actual connection logic and peer selection is in `try_connect`.
  164. // If the connection is successful, `try_connect` will wait for a stop
  165. // signal and then exit. Once it exits, we'll run `try_connect` again
  166. // and attempt to fill the slot with another peer.
  167. loop {
  168. match self.try_connect(slot_number, &connector, transports, ex.clone()).await {
  169. Ok(()) => {
  170. info!(
  171. target: "net::outbound_session",
  172. "[P2P] Outbound slot #{} disconnected",
  173. slot_number
  174. );
  175. }
  176. Err(e) => {
  177. error!(
  178. target: "net::outbound_session",
  179. "[P2P] Outbound slot #{} connection failed: {}",
  180. slot_number, e,
  181. );
  182. }
  183. }
  184. }
  185. }
  186. /// Start making an outbound connection, using provided [`Connector`].
  187. /// Tries to find a valid address to connect to, otherwise does peer
  188. /// discovery. The peer discovery loops until some peer we can connect
  189. /// to is found. Once connected, registers the channel, removes it from
  190. /// the list of pending channels, and starts sending messages across the
  191. /// channel. In case of any failures, a network error is returned and the
  192. /// main connect loop (parent of this function) will iterate again.
  193. async fn try_connect(
  194. &self,
  195. slot_number: usize,
  196. connector: &Connector,
  197. transports: &[String],
  198. ex: Arc<Executor<'_>>,
  199. ) -> Result<()> {
  200. debug!(
  201. target: "net::outbound_session::try_connect()",
  202. "[P2P] Finding a host to connect to for outbound slot #{}",
  203. slot_number,
  204. );
  205. // Find an address to connect to. We also do peer discovery here if needed.
  206. let addr = self.load_address(slot_number, transports, ex.clone()).await?;
  207. info!(
  208. target: "net::outbound_session::try_connect()",
  209. "[P2P] Connecting outbound slot #{} [{}]",
  210. slot_number, addr,
  211. );
  212. match connector.connect(&addr).await {
  213. Ok((url, channel)) => {
  214. info!(
  215. target: "net::outbound_session::try_connect()",
  216. "[P2P] Outbound slot #{} connected [{}]",
  217. slot_number, url
  218. );
  219. let stop_sub =
  220. channel.subscribe_stop().await.expect("Channel should not be stopped");
  221. // Register the new channel
  222. self.register_channel(channel.clone(), ex.clone()).await?;
  223. // Channel is now connected but not yet setup
  224. // Remove pending lock since register_channel will add the channel to p2p
  225. self.p2p().remove_pending(&addr).await;
  226. dnet!(self,
  227. let info = &mut self.slot_info.lock().await[slot_number];
  228. info.state = OutboundState::Connected;
  229. );
  230. // Notify that channel processing has been finished
  231. if *self.notify.lock().await {
  232. self.channel_subscriber.notify(Ok(channel)).await;
  233. }
  234. // Wait for channel to close
  235. stop_sub.receive().await;
  236. return Ok(())
  237. }
  238. Err(e) => {
  239. error!(
  240. target: "net::outbound_session::try_connect()",
  241. "[P2P] Unable to connect outbound slot #{} [{}]: {}",
  242. slot_number, addr, e
  243. );
  244. }
  245. }
  246. // At this point we failed to connect. We'll quarantine this peer now.
  247. self.p2p().hosts().quarantine(&addr).await;
  248. dnet!(self,
  249. let info = &mut self.slot_info.lock().await[slot_number];
  250. info.addr = None;
  251. info.state = OutboundState::Open;
  252. );
  253. // Notify that channel processing failed
  254. if *self.notify.lock().await {
  255. self.channel_subscriber.notify(Err(Error::ConnectFailed)).await;
  256. }
  257. Err(Error::ConnectFailed)
  258. }
  259. /// Loops through host addresses to find an outbound address that we can
  260. /// connect to. Check whether the address is valid by making sure it isn't
  261. /// our own inbound address, then checks whether it is already connected
  262. /// (exists) or connecting (pending). If no address was found, we'll attempt
  263. /// to do peer discovery and try to fill the slot again.
  264. async fn load_address(
  265. &self,
  266. slot_number: usize,
  267. transports: &[String],
  268. ex: Arc<Executor<'_>>,
  269. ) -> Result<Url> {
  270. loop {
  271. let p2p = self.p2p();
  272. let retry_sleep = p2p.settings().outbound_connect_timeout;
  273. if *p2p.peer_discovery_running.lock().await {
  274. debug!(
  275. target: "net::outbound_session::load_address()",
  276. "[P2P] #{} Peer discovery active, waiting {} seconds...",
  277. slot_number, retry_sleep,
  278. );
  279. sleep(retry_sleep).await;
  280. }
  281. // Collect hosts
  282. let mut hosts = HashSet::new();
  283. // If transport mixing is enabled, then for example we're allowed to
  284. // use tor:// to connect to tcp:// and tor+tls:// to connect to tcp+tls://.
  285. // However, **do not** mix tor:// and tcp+tls://, nor tor+tls:// and tcp://.
  286. let transport_mixing = self.p2p().settings().transport_mixing;
  287. macro_rules! mix_transport {
  288. ($a:expr, $b:expr) => {
  289. if transports.contains(&$a.to_string()) && transport_mixing {
  290. let mut a_to_b = p2p.hosts().load_with_schemes(&[$b.to_string()]).await;
  291. for addr in a_to_b.iter_mut() {
  292. addr.set_scheme($a).unwrap();
  293. hosts.insert(addr.clone());
  294. }
  295. }
  296. };
  297. }
  298. mix_transport!("tor", "tcp");
  299. mix_transport!("tor+tls", "tcp+tls");
  300. mix_transport!("nym", "tcp");
  301. mix_transport!("nym+tls", "tcp+tls");
  302. // And now the actual requested transports
  303. for addr in p2p.hosts().load_with_schemes(transports).await {
  304. hosts.insert(addr);
  305. }
  306. // Try to find an unused host in the set.
  307. for host in &hosts {
  308. // Check if we already have this connection established
  309. if p2p.exists(host).await {
  310. continue
  311. }
  312. // Check if we already have this configured as a manual peer
  313. if p2p.settings().peers.contains(host) {
  314. continue
  315. }
  316. // Obtain a lock on this address to prevent duplicate connection
  317. if !p2p.add_pending(host).await {
  318. continue
  319. }
  320. dnet!(self,
  321. let info = &mut self.slot_info.lock().await[slot_number];
  322. info.addr = Some(host.clone());
  323. info.state = OutboundState::Pending;
  324. );
  325. return Ok(host.clone())
  326. }
  327. // We didn't find a host to connect to, let's try to find more peers.
  328. info!(
  329. target: "net::outbound_session::load_address()",
  330. "[P2P] Outbound #{}: No peers found. Starting peer discovery...",
  331. slot_number,
  332. );
  333. // NOTE: A design decision here is to do a sleep inside peer_discovery()
  334. // so that there's a certain period (outbound_connect_timeout) of time
  335. // to send the GetAddr, receive Addrs, and sort things out. By sleeping
  336. // inside peer_discovery, it will block here in the slot sessions, while
  337. // other slots can keep trying to find hosts. This is also why we sleep
  338. // in the beginning of this loop if peer discovery is currently active.
  339. self.peer_discovery(slot_number, ex.clone()).await;
  340. }
  341. }
  342. /// Activate peer discovery if not active already. This will loop through all
  343. /// connected P2P channels and send out a `GetAddrs` message to request more
  344. /// peers. Other parts of the P2P stack will then handle the incoming addresses
  345. /// and place them in the hosts list.
  346. /// This function will also sleep `Settings::outbound_connect_timeout` seconds
  347. /// after broadcasting in order to let the P2P stack receive and work through
  348. /// the addresses it is expecting.
  349. async fn peer_discovery(&self, slot_number: usize, ex: Arc<Executor<'_>>) {
  350. let p2p = self.p2p();
  351. if *p2p.peer_discovery_running.lock().await {
  352. info!(
  353. target: "net::outbound_session::peer_discovery()",
  354. "[P2P] Outbound #{}: Peer discovery already active",
  355. slot_number,
  356. );
  357. return
  358. }
  359. info!(
  360. target: "net::outbound_session::peer_discovery()",
  361. "[P2P] Outbound #{}: Started peer discovery",
  362. slot_number,
  363. );
  364. *p2p.peer_discovery_running.lock().await = true;
  365. // Broadcast the GetAddrs message to all active channels.
  366. // If we have no active channels, we will perform a SeedSyncSession instead.
  367. if p2p.random_channel().await.is_some() {
  368. let get_addrs = GetAddrsMessage { max: p2p.settings().outbound_connections as u32 };
  369. info!(
  370. target: "net::outbound_session::peer_discovery()",
  371. "[P2P] Outbound #{}: Broadcasting GetAddrs across active channels",
  372. slot_number,
  373. );
  374. p2p.broadcast(&get_addrs).await;
  375. } else {
  376. warn!(
  377. target: "net::outbound_session::peer_discovery()",
  378. "[P2P] No connected channels found for peer discovery. Reseeding.",
  379. );
  380. if let Err(e) = p2p.clone().reseed(ex.clone()).await {
  381. error!(
  382. target: "net::outbound_session::peer_discovery()",
  383. "[P2P] Network reseed failed: {}", e,
  384. );
  385. }
  386. }
  387. // Now sleep to let the GetAddrs propagate, and hopefully
  388. // in the meantime we'll get some peers.
  389. debug!(
  390. target: "net::outbound_session::peer_discovery()",
  391. "[P2P] Outbound #{}: Sleeping {} seconds",
  392. slot_number, p2p.settings().outbound_connect_timeout,
  393. );
  394. sleep(p2p.settings().outbound_connect_timeout).await;
  395. *p2p.peer_discovery_running.lock().await = false;
  396. }
  397. /// Enable channel_subscriber notifications.
  398. pub async fn enable_notify(self: Arc<Self>) {
  399. *self.notify.lock().await = true;
  400. }
  401. /// Disable channel_subscriber notifications.
  402. pub async fn disable_notify(self: Arc<Self>) {
  403. *self.notify.lock().await = false;
  404. }
  405. }
  406. /// Dnet information for the outbound session
  407. pub struct OutboundDnet {
  408. /// Slot information
  409. pub slots: Vec<Option<OutboundInfo>>,
  410. }
  411. #[async_trait]
  412. impl Session for OutboundSession {
  413. fn p2p(&self) -> P2pPtr {
  414. self.p2p.upgrade().unwrap()
  415. }
  416. fn type_id(&self) -> SessionBitFlag {
  417. SESSION_OUTBOUND
  418. }
  419. async fn dnet_info(&self) -> DnetInfo {
  420. // We fetch channel infos for all outbound slots.
  421. // If a slot is not connected, it will be `None`.
  422. let mut slots = vec![];
  423. for slot in self.slot_info.lock().await.iter() {
  424. slots.push(slot.dnet_info(self.p2p()).await);
  425. }
  426. DnetInfo::Outbound(OutboundDnet { slots })
  427. }
  428. }