outbound_session.rs 22 KB

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