outbound_session.rs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2024 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::{
  28. sync::{
  29. atomic::{AtomicU32, Ordering},
  30. Arc, Weak,
  31. },
  32. time::{Duration, Instant},
  33. };
  34. use async_trait::async_trait;
  35. use log::{debug, error, info, warn};
  36. use smol::lock::Mutex;
  37. use url::Url;
  38. use super::{
  39. super::{
  40. channel::ChannelPtr,
  41. connector::Connector,
  42. dnet::{self, dnetev, DnetEvent},
  43. hosts::store::HostColor,
  44. message::GetAddrsMessage,
  45. p2p::{P2p, P2pPtr},
  46. },
  47. Session, SessionBitFlag, SESSION_OUTBOUND,
  48. };
  49. use crate::{
  50. system::{sleep, timeout::timeout, CondVar, LazyWeak, StoppableTask, StoppableTaskPtr},
  51. Error, Result,
  52. };
  53. pub type OutboundSessionPtr = Arc<OutboundSession>;
  54. /// Defines outbound connections session.
  55. pub struct OutboundSession {
  56. /// Weak pointer to parent p2p object
  57. pub(in crate::net) p2p: LazyWeak<P2p>,
  58. /// Outbound connection slots
  59. slots: Mutex<Vec<Arc<Slot>>>,
  60. /// Peer discovery task
  61. peer_discovery: Arc<PeerDiscovery>,
  62. }
  63. impl OutboundSession {
  64. /// Create a new outbound session.
  65. pub(crate) fn new() -> OutboundSessionPtr {
  66. let self_ = Arc::new(Self {
  67. p2p: LazyWeak::new(),
  68. slots: Mutex::new(Vec::new()),
  69. peer_discovery: PeerDiscovery::new(),
  70. });
  71. self_.peer_discovery.session.init(self_.clone());
  72. self_
  73. }
  74. /// Start the outbound session. Runs the channel connect loop.
  75. pub(crate) async fn start(self: Arc<Self>) {
  76. let n_slots = self.p2p().settings().outbound_connections;
  77. info!(target: "net::outbound_session", "[P2P] Starting {} outbound connection slots.", n_slots);
  78. // Activate mutex lock on connection slots.
  79. let mut slots = self.slots.lock().await;
  80. let self_ = Arc::downgrade(&self);
  81. for i in 0..n_slots as u32 {
  82. let slot = Slot::new(self_.clone(), i);
  83. slot.clone().start().await;
  84. slots.push(slot);
  85. }
  86. self.peer_discovery.clone().start().await;
  87. }
  88. /// Stops the outbound session.
  89. pub(crate) async fn stop(&self) {
  90. let slots = &*self.slots.lock().await;
  91. for slot in slots {
  92. slot.clone().stop().await;
  93. }
  94. self.peer_discovery.clone().stop().await;
  95. }
  96. pub async fn slot_info(&self) -> Vec<u32> {
  97. let mut info = Vec::new();
  98. let slots = &*self.slots.lock().await;
  99. for slot in slots {
  100. info.push(slot.channel_id.load(Ordering::Relaxed));
  101. }
  102. info
  103. }
  104. fn wakeup_peer_discovery(&self) {
  105. self.peer_discovery.notify()
  106. }
  107. async fn wakeup_slots(&self) {
  108. let slots = &*self.slots.lock().await;
  109. for slot in slots {
  110. slot.notify();
  111. }
  112. }
  113. }
  114. #[async_trait]
  115. impl Session for OutboundSession {
  116. fn p2p(&self) -> P2pPtr {
  117. self.p2p.upgrade()
  118. }
  119. fn type_id(&self) -> SessionBitFlag {
  120. SESSION_OUTBOUND
  121. }
  122. }
  123. pub struct Slot {
  124. slot: u32,
  125. process: StoppableTaskPtr,
  126. wakeup_self: CondVar,
  127. session: Weak<OutboundSession>,
  128. // For debugging
  129. channel_id: AtomicU32,
  130. }
  131. impl Slot {
  132. fn new(session: Weak<OutboundSession>, slot: u32) -> Arc<Self> {
  133. Arc::new(Self {
  134. slot,
  135. process: StoppableTask::new(),
  136. wakeup_self: CondVar::new(),
  137. session,
  138. channel_id: AtomicU32::new(0),
  139. })
  140. }
  141. async fn start(self: Arc<Self>) {
  142. // TODO: way too many clones, look into making this implicit. See implicit-clone crate
  143. let ex = self.p2p().executor();
  144. self.process.clone().start(
  145. async move {
  146. self.run().await;
  147. unreachable!();
  148. },
  149. // Ignore stop handler
  150. |_| async {},
  151. Error::NetworkServiceStopped,
  152. ex,
  153. );
  154. }
  155. async fn stop(self: Arc<Self>) {
  156. self.process.stop().await
  157. }
  158. // Address selection algorithm that works as follows: up to anchor_count, select from the
  159. // anchorlist. Up to white_count, select from the whitelist. For all other slots, select from
  160. // the greylist.
  161. //
  162. // If we didn't find an address with this selection logic, downgrade our preferences. Up to
  163. // anchor_count, select from the whitelist, up until white_count, select from the greylist.
  164. //
  165. // If we still didn't find an address, select from the greylist. In all other cases, return an
  166. // empty vector. This will trigger fetch_addrs() to return None and initiate peer discovery.
  167. async fn fetch_addrs_with_preference(
  168. &self,
  169. preference: usize,
  170. slot_count: usize,
  171. transports: &[String],
  172. transport_mixing: bool,
  173. white_count: usize,
  174. anchor_count: usize,
  175. ) -> Vec<(Url, u64)> {
  176. let hosts = &self.p2p().hosts().container;
  177. match preference {
  178. // Highest preference that corresponds to the anchor and white count preference set in
  179. // Settings.
  180. 0 => {
  181. if slot_count < anchor_count {
  182. hosts.fetch_addrs(HostColor::Gold, transports, transport_mixing).await
  183. } else if slot_count < white_count {
  184. hosts.fetch_addrs(HostColor::White, transports, transport_mixing).await
  185. } else {
  186. hosts.fetch_addrs(HostColor::Grey, transports, transport_mixing).await
  187. }
  188. }
  189. // Reduced preference in case we don't have sufficient hosts to satisfy our highest
  190. // preference.
  191. 1 => {
  192. if slot_count < anchor_count {
  193. hosts.fetch_addrs(HostColor::White, transports, transport_mixing).await
  194. } else if slot_count < white_count {
  195. hosts.fetch_addrs(HostColor::Grey, transports, transport_mixing).await
  196. } else {
  197. vec![]
  198. }
  199. }
  200. // Lowest preference if we still haven't been able to find a host.
  201. 2 => {
  202. if slot_count < anchor_count {
  203. hosts.fetch_addrs(HostColor::Grey, transports, transport_mixing).await
  204. } else {
  205. vec![]
  206. }
  207. }
  208. _ => {
  209. panic!()
  210. }
  211. }
  212. }
  213. // Fetch an address we can connect to acccording to the white and anchor connection counts
  214. // configured in Settings.
  215. async fn fetch_addrs(&self, slot_count: usize, transports: &[String]) -> Option<(Url, u64)> {
  216. let hosts = self.p2p().hosts();
  217. let transport_mixing = self.p2p().settings().transport_mixing;
  218. let anchor_count = self.p2p().settings().anchor_connection_count;
  219. let white_count = slot_count * self.p2p().settings().white_connection_percent / 100;
  220. // First select an addresses that match our white and anchor requirements configured in
  221. // Settings.
  222. let preference = 0;
  223. let addrs = self
  224. .fetch_addrs_with_preference(
  225. preference,
  226. slot_count,
  227. transports,
  228. transport_mixing,
  229. white_count,
  230. anchor_count,
  231. )
  232. .await;
  233. if !addrs.is_empty() {
  234. return hosts.check_addrs(addrs).await;
  235. }
  236. // If no addresses were returned, go for the second best thing (white and grey).
  237. let preference = 1;
  238. let addrs = self
  239. .fetch_addrs_with_preference(
  240. preference,
  241. slot_count,
  242. transports,
  243. transport_mixing,
  244. white_count,
  245. anchor_count,
  246. )
  247. .await;
  248. if !addrs.is_empty() {
  249. return hosts.check_addrs(addrs).await;
  250. }
  251. // If we still have no addresses, go for the least favored option.
  252. let preference = 2;
  253. let addrs = self
  254. .fetch_addrs_with_preference(
  255. preference,
  256. slot_count,
  257. transports,
  258. transport_mixing,
  259. white_count,
  260. anchor_count,
  261. )
  262. .await;
  263. if !addrs.is_empty() {
  264. return hosts.check_addrs(addrs).await;
  265. }
  266. // If we still don't have an address, return None and do peer discovery.
  267. None
  268. }
  269. // We first try to make connections to the addresses on our anchor list. We then find some
  270. // whitelist connections according to the whitelist percent default. Finally, any remaining
  271. // connections we make from the greylist.
  272. async fn run(self: Arc<Self>) {
  273. let hosts = self.p2p().hosts();
  274. let slot_count = self.p2p().settings().outbound_connections;
  275. loop {
  276. // Activate the slot
  277. debug!(
  278. target: "net::outbound_session::try_connect()",
  279. "[P2P] Finding a host to connect to for outbound slot #{}",
  280. self.slot,
  281. );
  282. // Retrieve outbound transports
  283. let transports = &self.p2p().settings().allowed_transports;
  284. // Do peer discovery if we don't have a hostlist (first time connecting
  285. // to the network).
  286. if hosts.container.is_empty(HostColor::Grey).await {
  287. dnetev!(self, OutboundSlotSleeping, {
  288. slot: self.slot,
  289. });
  290. self.wakeup_self.reset();
  291. // Peer discovery
  292. self.session().wakeup_peer_discovery();
  293. // Wait to be woken up by peer discovery
  294. self.wakeup_self.wait().await;
  295. continue
  296. }
  297. let addr = if let Some(addr) = self.fetch_addrs(slot_count, transports).await {
  298. debug!(target: "net::outbound_session::run()", "Fetched address: {:?}", addr);
  299. addr
  300. } else {
  301. debug!(target: "net::outbound_session::run()", "No address found! Activating peer discovery...");
  302. dnetev!(self, OutboundSlotSleeping, {
  303. slot: self.slot,
  304. });
  305. self.wakeup_self.reset();
  306. // Peer discovery
  307. self.session().wakeup_peer_discovery();
  308. // Wait to be woken up by peer discovery
  309. self.wakeup_self.wait().await;
  310. continue
  311. };
  312. let host = addr.0;
  313. let last_seen = addr.1;
  314. let slot = self.slot;
  315. info!(
  316. target: "net::outbound_session::try_connect()",
  317. "[P2P] Connecting outbound slot #{} [{}]",
  318. slot, host,
  319. );
  320. dnetev!(self, OutboundSlotConnecting, {
  321. slot,
  322. addr: host.clone(),
  323. });
  324. let (addr, channel) = match self.try_connect(host.clone(), last_seen).await {
  325. Ok(connect_info) => connect_info,
  326. Err(err) => {
  327. debug!(
  328. target: "net::outbound_session::try_connect()",
  329. "[P2P] Outbound slot #{} connection failed: {}",
  330. slot, err
  331. );
  332. dnetev!(self, OutboundSlotDisconnected, {
  333. slot,
  334. err: err.to_string()
  335. });
  336. self.channel_id.store(0, Ordering::Relaxed);
  337. continue
  338. }
  339. };
  340. info!(
  341. target: "net::outbound_session::try_connect()",
  342. "[P2P] Outbound slot #{} connected [{}]",
  343. slot, addr
  344. );
  345. dnetev!(self, OutboundSlotConnected, {
  346. slot: self.slot,
  347. addr: addr.clone(),
  348. channel_id: channel.info.id
  349. });
  350. // At this point we've managed to connect.
  351. let stop_sub = channel.subscribe_stop().await.expect("Channel should not be stopped");
  352. // Setup new channel
  353. if let Err(err) =
  354. self.session().register_channel(channel.clone(), self.p2p().executor()).await
  355. {
  356. info!(
  357. target: "net::outbound_session",
  358. "[P2P] Outbound slot #{} disconnected: {}",
  359. slot, err
  360. );
  361. dnetev!(self, OutboundSlotDisconnected, {
  362. slot: self.slot,
  363. err: err.to_string()
  364. });
  365. self.channel_id.store(0, Ordering::Relaxed);
  366. continue
  367. }
  368. self.channel_id.store(channel.info.id, Ordering::Relaxed);
  369. // Add this connection to the anchorlist
  370. hosts.move_host(&addr, last_seen, HostColor::Gold).await;
  371. // Wait for channel to close
  372. stop_sub.receive().await;
  373. self.channel_id.store(0, Ordering::Relaxed);
  374. }
  375. }
  376. /// Start making an outbound connection, using provided [`Connector`].
  377. /// Tries to find a valid address to connect to, otherwise does peer
  378. /// discovery. The peer discovery loops until some peer we can connect
  379. /// to is found. Once connected, registers the channel, removes it from
  380. /// the list of pending channels, and starts sending messages across the
  381. /// channel. In case of any failures, a network error is returned and the
  382. /// main connect loop (parent of this function) will iterate again.
  383. async fn try_connect(&self, addr: Url, last_seen: u64) -> Result<(Url, ChannelPtr)> {
  384. let parent = Arc::downgrade(&self.session());
  385. let connector = Connector::new(self.p2p().settings(), parent);
  386. match connector.connect(&addr).await {
  387. Ok((addr_final, channel)) => Ok((addr_final, channel)),
  388. Err(e) => {
  389. debug!(
  390. target: "net::outbound_session::try_connect()",
  391. "[P2P] Unable to connect outbound slot #{} [{}]: {}",
  392. self.slot, addr, e
  393. );
  394. // At this point we failed to connect. We'll downgrade this peer now.
  395. self.p2p().hosts().move_host(&addr, last_seen, HostColor::Grey).await;
  396. // Notify that channel processing failed
  397. self.p2p().hosts().channel_subscriber.notify(Err(Error::ConnectFailed)).await;
  398. Err(Error::ConnectFailed)
  399. }
  400. }
  401. }
  402. fn notify(&self) {
  403. self.wakeup_self.notify()
  404. }
  405. fn session(&self) -> OutboundSessionPtr {
  406. self.session.upgrade().unwrap()
  407. }
  408. fn p2p(&self) -> P2pPtr {
  409. self.session().p2p()
  410. }
  411. }
  412. /// PeerDiscoveryBase defines a common interface for multiple peer discovery processes. Currently
  413. /// only one Peer Discovery implementation exists. Making Peer Discovery generic enables us to
  414. /// support network swarming, since the peer discovery process will differ depending on whether it
  415. /// occurs on the overlay network or a subnet.
  416. #[async_trait]
  417. pub trait PeerDiscoveryBase {
  418. async fn start(self: Arc<Self>);
  419. async fn stop(self: Arc<Self>);
  420. async fn run(self: Arc<Self>);
  421. async fn wait(&self) -> bool;
  422. fn notify(&self);
  423. fn session(&self) -> OutboundSessionPtr;
  424. fn p2p(&self) -> P2pPtr;
  425. }
  426. /// Main PeerDiscovery process that loops through connected channels and sends out a `GetAddrs`
  427. /// when it is active.
  428. struct PeerDiscovery {
  429. process: StoppableTaskPtr,
  430. wakeup_self: CondVar,
  431. session: LazyWeak<OutboundSession>,
  432. }
  433. impl PeerDiscovery {
  434. fn new() -> Arc<Self> {
  435. Arc::new(Self {
  436. process: StoppableTask::new(),
  437. wakeup_self: CondVar::new(),
  438. session: LazyWeak::new(),
  439. })
  440. }
  441. }
  442. #[async_trait]
  443. impl PeerDiscoveryBase for PeerDiscovery {
  444. async fn start(self: Arc<Self>) {
  445. let ex = self.p2p().executor();
  446. self.process.clone().start(
  447. async move {
  448. self.run().await;
  449. unreachable!();
  450. },
  451. // Ignore stop handler
  452. |_| async {},
  453. Error::NetworkServiceStopped,
  454. ex,
  455. );
  456. }
  457. async fn stop(self: Arc<Self>) {
  458. self.process.stop().await
  459. }
  460. /// Activate peer discovery if not active already. This will loop through all
  461. /// connected P2P channels and send out a `GetAddrs` message to request more
  462. /// peers. Other parts of the P2P stack will then handle the incoming addresses
  463. /// and place them in the hosts list.
  464. /// This function will also sleep `Settings::outbound_connect_timeout` seconds
  465. /// after broadcasting in order to let the P2P stack receive and work through
  466. /// the addresses it is expecting.
  467. async fn run(self: Arc<Self>) {
  468. let mut current_attempt = 0;
  469. loop {
  470. dnetev!(self, OutboundPeerDiscovery, {
  471. attempt: current_attempt,
  472. state: "wait",
  473. });
  474. // wait to be woken up by notify()
  475. let sleep_was_instant = self.wait().await;
  476. let p2p = self.p2p();
  477. if sleep_was_instant {
  478. // Try again
  479. current_attempt += 1;
  480. } else {
  481. // reset back to start
  482. current_attempt = 1;
  483. }
  484. if current_attempt >= 4 {
  485. debug!("current attempt: {}", current_attempt);
  486. info!(
  487. target: "net::outbound_session::peer_discovery()",
  488. "[P2P] Sleeping and trying again..."
  489. );
  490. dnetev!(self, OutboundPeerDiscovery, {
  491. attempt: current_attempt,
  492. state: "sleep",
  493. });
  494. sleep(p2p.settings().outbound_peer_discovery_cooloff_time).await;
  495. current_attempt = 1;
  496. }
  497. // First 2 times try sending GetAddr to the network.
  498. // 3rd time do a seed sync.
  499. if p2p.is_connected().await && current_attempt <= 2 {
  500. // Broadcast the GetAddrs message to all active channels.
  501. // If we have no active channels, we will perform a SeedSyncSession instead.
  502. info!(
  503. target: "net::outbound_session::peer_discovery()",
  504. "[P2P] Requesting addrs from active channels. Attempt: {}",
  505. current_attempt
  506. );
  507. dnetev!(self, OutboundPeerDiscovery, {
  508. attempt: current_attempt,
  509. state: "getaddr",
  510. });
  511. let get_addrs = GetAddrsMessage {
  512. max: p2p.settings().outbound_connections as u32,
  513. transports: p2p.settings().allowed_transports.clone(),
  514. };
  515. p2p.broadcast(&get_addrs).await;
  516. // Wait for a hosts store update event
  517. let store_sub = self.p2p().hosts().subscribe_store().await.unwrap();
  518. let result = timeout(
  519. Duration::from_secs(p2p.settings().outbound_peer_discovery_attempt_time),
  520. store_sub.receive(),
  521. )
  522. .await;
  523. match result {
  524. Ok(addrs_len) => {
  525. info!(
  526. target: "net::outbound_session::peer_discovery()",
  527. "[P2P] Discovered {} addrs", addrs_len
  528. );
  529. }
  530. Err(_) => {
  531. warn!(
  532. target: "net::outbound_session::peer_discovery()",
  533. "[P2P] Peer discovery waiting for addrs timed out."
  534. );
  535. // TODO: Just do seed next time
  536. }
  537. }
  538. // TODO: check every subscribe() call has a corresponding unsubscribe()
  539. store_sub.unsubscribe().await;
  540. } else {
  541. info!(
  542. target: "net::outbound_session::peer_discovery()",
  543. "[P2P] Seeding hosts. Attempt: {}",
  544. current_attempt
  545. );
  546. dnetev!(self, OutboundPeerDiscovery, {
  547. attempt: current_attempt,
  548. state: "seed",
  549. });
  550. match p2p.clone().seed().await {
  551. Ok(()) => {
  552. info!(
  553. target: "net::outbound_session::peer_discovery()",
  554. "[P2P] Seeding hosts successful."
  555. );
  556. }
  557. Err(err) => {
  558. error!(
  559. target: "net::outbound_session::peer_discovery()",
  560. "[P2P] Network reseed failed: {}", err,
  561. );
  562. }
  563. }
  564. }
  565. self.wakeup_self.reset();
  566. self.session().wakeup_slots().await;
  567. // Give some time for new connections to be established
  568. sleep(p2p.settings().outbound_peer_discovery_attempt_time).await;
  569. }
  570. }
  571. async fn wait(&self) -> bool {
  572. let wakeup_start = Instant::now();
  573. self.wakeup_self.wait().await;
  574. let wakeup_end = Instant::now();
  575. let epsilon = Duration::from_millis(200);
  576. wakeup_end - wakeup_start <= epsilon
  577. }
  578. fn notify(&self) {
  579. self.wakeup_self.notify()
  580. }
  581. fn session(&self) -> OutboundSessionPtr {
  582. self.session.upgrade()
  583. }
  584. fn p2p(&self) -> P2pPtr {
  585. self.session().p2p()
  586. }
  587. }