tasks.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2025 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. use std::{sync::Arc, time::UNIX_EPOCH};
  19. use tracing::{error, info, warn};
  20. use crate::{
  21. dht::{event::DhtEvent, ChannelCacheItem, DhtHandler, DhtNode, SESSION_MANUAL},
  22. net::{
  23. hosts::HostColor,
  24. session::{SESSION_DIRECT, SESSION_INBOUND, SESSION_OUTBOUND},
  25. },
  26. system::sleep,
  27. util::time::Timestamp,
  28. Result,
  29. };
  30. /// Handle DHT events.
  31. pub async fn events_task<H: DhtHandler>(handler: Arc<H>) -> Result<()> {
  32. let dht = handler.dht();
  33. let sub = dht.event_publisher.clone().subscribe().await;
  34. loop {
  35. let event = sub.receive().await;
  36. match event {
  37. // On [`DhtEvent::PingReceived`] set channel_cache.ping_received = true
  38. DhtEvent::PingReceived { from, .. } => {
  39. let channel_cache_lock = dht.channel_cache.clone();
  40. let mut channel_cache = channel_cache_lock.write().await;
  41. if let Some(cached) = channel_cache.get_mut(&from.info.id) {
  42. cached.ping_received = true;
  43. }
  44. }
  45. // On [`DhtEvent::PingSent`] set channel_cache.ping_sent = true
  46. DhtEvent::PingSent { to, .. } => {
  47. let channel_cache_lock = dht.channel_cache.clone();
  48. let mut channel_cache = channel_cache_lock.write().await;
  49. if let Some(cached) = channel_cache.get_mut(&to.info.id) {
  50. cached.ping_sent = true;
  51. }
  52. }
  53. _ => {}
  54. }
  55. }
  56. }
  57. /// Send a DHT ping request when there is a new channel, to know the node id of the new peer,
  58. /// Then fill the channel cache and the buckets
  59. pub async fn channel_task<H: DhtHandler>(handler: Arc<H>) -> Result<()> {
  60. let dht = handler.dht();
  61. let p2p = dht.p2p.clone();
  62. let channel_sub = p2p.hosts().subscribe_channel().await;
  63. loop {
  64. let res = channel_sub.receive().await;
  65. if res.is_err() {
  66. continue;
  67. }
  68. let channel = res.unwrap();
  69. let channel_cache_lock = dht.channel_cache.clone();
  70. let mut channel_cache = channel_cache_lock.write().await;
  71. // Skip this channel if it's not new
  72. if channel_cache.keys().any(|&k| k == channel.info.id) {
  73. continue;
  74. }
  75. channel_cache.insert(
  76. channel.info.id,
  77. ChannelCacheItem {
  78. node: None,
  79. last_used: Timestamp::current_time(),
  80. ping_received: false,
  81. ping_sent: false,
  82. },
  83. );
  84. drop(channel_cache);
  85. // It's a manual connection
  86. if channel.session_type_id() & SESSION_MANUAL != 0 {
  87. let ping_res = dht.ping(channel.clone()).await;
  88. if let Err(e) = ping_res {
  89. warn!(target: "dht::channel_task()", "Error while pinging manual connection (requesting node id) {}: {e}", channel.display_address());
  90. continue;
  91. }
  92. }
  93. // It's an outbound connection
  94. if channel.session_type_id() & SESSION_OUTBOUND != 0 {
  95. let _ = dht.ping(channel.clone()).await;
  96. continue;
  97. }
  98. // It's a direct connection
  99. if channel.session_type_id() & SESSION_DIRECT != 0 {
  100. p2p.session_direct().inc_channel_usage(&channel, 1).await;
  101. let _ = dht.ping(channel.clone()).await;
  102. dht.cleanup_channel(channel).await;
  103. continue;
  104. }
  105. }
  106. }
  107. /// Periodically send a DHT ping to known hosts. If the ping is successful, we
  108. /// move the host to the whitelist (updating the last seen field).
  109. ///
  110. /// This is necessary to prevent unresponsive nodes staying on the whitelist,
  111. /// as the DHT does not require any outbound slot.
  112. pub async fn dht_refinery_task<H: DhtHandler>(handler: Arc<H>) -> Result<()> {
  113. let interval = 60; // TODO: Make a setting
  114. let min_ping_interval = 10 * 60; // TODO: Make a setting
  115. let dht = handler.dht();
  116. let hosts = dht.p2p.hosts();
  117. loop {
  118. let mut hostlist = hosts.container.fetch_all(HostColor::Gold);
  119. hostlist.extend(hosts.container.fetch_all(HostColor::White));
  120. // Include the greylist only if the DHT is not bootstrapped yet
  121. if !handler.dht().is_bootstrapped().await {
  122. hostlist.extend(hosts.container.fetch_all(HostColor::Grey));
  123. }
  124. for entry in &hostlist {
  125. let url = &entry.0;
  126. let host_cache = dht.host_cache.read().await;
  127. let last_ping = host_cache.get(url).map(|h| h.last_ping.inner());
  128. if last_ping.is_some() &&
  129. last_ping.unwrap() > Timestamp::current_time().inner() - min_ping_interval
  130. {
  131. continue
  132. }
  133. drop(host_cache);
  134. let res = dht.create_channel(url).await;
  135. if res.is_err() {
  136. continue
  137. }
  138. let (channel, _) = res.unwrap();
  139. dht.cleanup_channel(channel).await;
  140. let last_seen = UNIX_EPOCH.elapsed().unwrap().as_secs();
  141. if let Err(e) = hosts.whitelist_host(url, last_seen).await {
  142. error!(target: "dht::tasks::whitelist_refinery_task()", "Could not send {url} to the whitelist: {e}");
  143. }
  144. break
  145. }
  146. match hostlist.is_empty() {
  147. true => sleep(5).await,
  148. false => sleep(interval).await,
  149. }
  150. }
  151. }
  152. /// Add a node to the DHT buckets.
  153. /// If the bucket is already full, we ping the least recently seen node in the
  154. /// bucket: if successful it becomes the most recently seen node, if the ping
  155. /// fails we remove it and add the new node.
  156. /// [`Dht::update_node()`] increments a channel's usage count (in the direct
  157. /// session) and triggers this task. This task decrements the usage count
  158. /// using [`Dht::cleanup_channel()`].
  159. pub async fn add_node_task<H: DhtHandler>(handler: Arc<H>) -> Result<()> {
  160. let dht = handler.dht();
  161. loop {
  162. let (node, channel) = dht.add_node_rx.recv().await.unwrap();
  163. let self_node = handler.node().await;
  164. if self_node.is_err() {
  165. continue;
  166. }
  167. let self_node = self_node.unwrap();
  168. let bucket_index = dht.get_bucket_index(&self_node.id(), &node.id()).await;
  169. let buckets_lock = dht.buckets.clone();
  170. let mut buckets = buckets_lock.write().await;
  171. let bucket = &mut buckets[bucket_index];
  172. // Do not add ourselves to the buckets
  173. if node.id() == self_node.id() {
  174. dht.cleanup_channel(channel).await;
  175. continue;
  176. }
  177. // Don't add this node if it has any external address that is the same as one of ours
  178. let node_addresses = node.addresses();
  179. if self_node.addresses().iter().any(|addr| node_addresses.contains(addr)) {
  180. dht.cleanup_channel(channel).await;
  181. continue;
  182. }
  183. // Do not add a node to the buckets if it does not have an address
  184. if node.addresses().is_empty() {
  185. dht.cleanup_channel(channel).await;
  186. continue;
  187. }
  188. // We already have this node, move it to the tail of the bucket
  189. if let Some(node_index) = bucket.nodes.iter().position(|n| n.id() == node.id()) {
  190. bucket.nodes.remove(node_index);
  191. bucket.nodes.push(node);
  192. dht.cleanup_channel(channel).await;
  193. continue;
  194. }
  195. // Bucket is full
  196. if bucket.nodes.len() >= dht.settings.k {
  197. // Ping the least recently seen node
  198. if let Ok((channel2, node)) = dht.get_channel(&bucket.nodes[0]).await {
  199. // Ping was successful, move the least recently seen node to the tail
  200. let n = bucket.nodes.remove(0);
  201. bucket.nodes.push(n);
  202. drop(buckets);
  203. dht.on_new_node(&node.clone(), channel2.clone()).await;
  204. dht.cleanup_channel(channel2).await;
  205. dht.cleanup_channel(channel).await;
  206. continue;
  207. }
  208. // Ping was not successful, remove the least recently seen node and add the new node
  209. bucket.nodes.remove(0);
  210. bucket.nodes.push(node.clone());
  211. drop(buckets);
  212. dht.on_new_node(&node.clone(), channel.clone()).await;
  213. dht.cleanup_channel(channel).await;
  214. continue;
  215. }
  216. // Bucket is not full, just add the node
  217. bucket.nodes.push(node.clone());
  218. drop(buckets);
  219. dht.on_new_node(&node.clone(), channel.clone()).await;
  220. dht.cleanup_channel(channel).await;
  221. }
  222. }
  223. /// Close inbound connections that are unused for too long.
  224. pub async fn disconnect_inbounds_task<H: DhtHandler>(handler: Arc<H>) -> Result<()> {
  225. let interval = 10; // TODO: Make a setting
  226. let dht = handler.dht();
  227. loop {
  228. sleep(interval).await;
  229. let min_last_used = Timestamp::current_time().inner() - dht.settings.inbound_timeout;
  230. let channel_cache_lock = dht.channel_cache.clone();
  231. let mut channel_cache = channel_cache_lock.write().await;
  232. for (channel_id, cached) in channel_cache.clone() {
  233. // Check that:
  234. // The channel timed out,
  235. if cached.last_used.inner() >= min_last_used {
  236. continue;
  237. }
  238. // The channel exists,
  239. let channel = dht.p2p.get_channel(channel_id);
  240. if channel.is_none() {
  241. channel_cache.remove(&channel_id);
  242. continue;
  243. }
  244. let channel = channel.unwrap();
  245. // And the channel is inbound.
  246. if channel.session_type_id() & SESSION_INBOUND == 0 {
  247. continue;
  248. }
  249. // Now we can stop it and remove it from the channel cache
  250. info!(target: "dht::disconnect_inbounds_task()", "Closing expired inbound channel [{}]", channel.display_address());
  251. channel.stop().await;
  252. channel_cache.remove(&channel.info.id);
  253. }
  254. }
  255. }
  256. /// Removes entries from [`crate::dht::Dht::channel_cache`] when a channel is
  257. /// stopped.
  258. pub async fn cleanup_channels_task<H: DhtHandler>(handler: Arc<H>) -> Result<()> {
  259. let interval = 60; // TODO: Make a setting
  260. let dht = handler.dht();
  261. loop {
  262. sleep(interval).await;
  263. let channel_cache_lock = dht.channel_cache.clone();
  264. let mut channel_cache = channel_cache_lock.write().await;
  265. for (channel_id, _) in channel_cache.clone() {
  266. match dht.p2p.get_channel(channel_id) {
  267. Some(channel) => {
  268. if channel.is_stopped() {
  269. channel_cache.remove(&channel_id);
  270. }
  271. }
  272. None => {
  273. channel_cache.remove(&channel_id);
  274. }
  275. }
  276. }
  277. }
  278. }