hosts.rs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  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. use std::{
  19. collections::{HashMap, HashSet},
  20. sync::Arc,
  21. };
  22. use log::debug;
  23. use rand::{prelude::IteratorRandom, rngs::OsRng};
  24. use smol::lock::RwLock;
  25. use url::Url;
  26. use super::settings::SettingsPtr;
  27. use crate::{
  28. system::{Subscriber, SubscriberPtr, Subscription},
  29. Result,
  30. };
  31. /// Atomic pointer to hosts object
  32. pub type HostsPtr = Arc<Hosts>;
  33. // An array containing all possible local host strings
  34. // TODO: This could perhaps be more exhaustive?
  35. pub const LOCAL_HOST_STRS: [&str; 2] = ["localhost", "localhost.localdomain"];
  36. /// Manages a store of network addresses
  37. pub struct Hosts {
  38. /// Set of stored addresses
  39. addrs: RwLock<HashSet<Url>>,
  40. /// Set of stored addresses that are quarantined.
  41. /// We quarantine peers we've been unable to connect to, but we keep them
  42. /// around so we can potentially try them again, up to n tries. This should
  43. /// be helpful in order to self-heal the p2p connections in case we have an
  44. /// Internet interrupt (goblins unplugging cables)
  45. quarantine: RwLock<HashMap<Url, usize>>,
  46. /// Peers we reject from connecting
  47. rejected: RwLock<HashSet<String>>,
  48. /// Subscriber listening for store updates
  49. store_subscriber: SubscriberPtr<usize>,
  50. /// Pointer to configured P2P settings
  51. settings: SettingsPtr,
  52. }
  53. impl Hosts {
  54. /// Create a new hosts list>
  55. pub fn new(settings: SettingsPtr) -> HostsPtr {
  56. Arc::new(Self {
  57. addrs: RwLock::new(HashSet::new()),
  58. quarantine: RwLock::new(HashMap::new()),
  59. rejected: RwLock::new(HashSet::new()),
  60. store_subscriber: Subscriber::new(),
  61. settings,
  62. })
  63. }
  64. /// Append given addrs to the known set.
  65. pub async fn store(&self, addrs: &[Url]) {
  66. debug!(target: "net::hosts::store()", "hosts::store() [START]");
  67. let filtered_addrs = self.filter_addresses(addrs).await;
  68. let filtered_addrs_len = filtered_addrs.len();
  69. if !filtered_addrs.is_empty() {
  70. let mut addrs_map = self.addrs.write().await;
  71. for addr in filtered_addrs {
  72. debug!(target: "net::hosts::store()", "Inserting {}", addr);
  73. addrs_map.insert(addr);
  74. }
  75. }
  76. self.store_subscriber.notify(filtered_addrs_len).await;
  77. debug!(target: "net::hosts::store()", "hosts::store() [END]");
  78. }
  79. pub async fn subscribe_store(&self) -> Result<Subscription<usize>> {
  80. let sub = self.store_subscriber.clone().subscribe().await;
  81. Ok(sub)
  82. }
  83. // Verify whether a URL is local.
  84. // NOTE: This function is stateless and not specific to
  85. // `Hosts`. For this reason, it might make more sense
  86. // to move this function to a more appropriate location
  87. // in the codebase.
  88. pub async fn is_local_host(&self, url: Url) -> bool {
  89. // Reject Urls without host strings.
  90. if url.host_str().is_none() {
  91. return false
  92. }
  93. // We do this hack in order to parse IPs properly.
  94. // https://github.com/whatwg/url/issues/749
  95. let addr = Url::parse(&url.as_str().replace(url.scheme(), "http")).unwrap();
  96. // Filter private IP ranges
  97. match addr.host().unwrap() {
  98. url::Host::Ipv4(ip) => {
  99. if !ip.is_global() {
  100. return true
  101. }
  102. }
  103. url::Host::Ipv6(ip) => {
  104. if !ip.is_global() {
  105. return true
  106. }
  107. }
  108. url::Host::Domain(d) => {
  109. if LOCAL_HOST_STRS.contains(&d) {
  110. return true
  111. }
  112. }
  113. }
  114. false
  115. }
  116. /// Filter given addresses based on certain rulesets and validity.
  117. async fn filter_addresses(&self, addrs: &[Url]) -> Vec<Url> {
  118. debug!(target: "net::hosts::filter_addresses()", "Filtering addrs: {:?}", addrs);
  119. let mut ret = vec![];
  120. let localnet = self.settings.localnet;
  121. 'addr_loop: for addr_ in addrs {
  122. // Validate that the format is `scheme://host_str:port`
  123. if addr_.host_str().is_none() ||
  124. addr_.port().is_none() ||
  125. addr_.cannot_be_a_base() ||
  126. addr_.path_segments().is_some()
  127. {
  128. continue
  129. }
  130. if self.is_rejected(addr_).await {
  131. debug!(target: "net::hosts::filter_addresses()", "Peer {} is rejected", addr_);
  132. continue
  133. }
  134. let host_str = addr_.host_str().unwrap();
  135. if !localnet {
  136. // Our own external addresses should never enter the hosts set.
  137. for ext in &self.settings.external_addrs {
  138. if host_str == ext.host_str().unwrap() {
  139. continue 'addr_loop
  140. }
  141. }
  142. }
  143. // We do this hack in order to parse IPs properly.
  144. // https://github.com/whatwg/url/issues/749
  145. let addr = Url::parse(&addr_.as_str().replace(addr_.scheme(), "http")).unwrap();
  146. // Filter non-global ranges if we're not allowing localnet.
  147. // Should never be allowed in production, so we don't really care
  148. // about some of them (e.g. 0.0.0.0, or broadcast, etc.).
  149. if !localnet && self.is_local_host(addr).await {
  150. continue
  151. }
  152. match addr_.scheme() {
  153. // Validate that the address is an actual onion.
  154. #[cfg(feature = "p2p-tor")]
  155. "tor" | "tor+tls" => {
  156. use std::str::FromStr;
  157. if tor_hscrypto::pk::HsId::from_str(host_str).is_err() {
  158. continue
  159. }
  160. debug!(target: "net::hosts::filter_addresses()", "[Tor] Valid: {}", host_str);
  161. }
  162. #[cfg(feature = "p2p-nym")]
  163. "nym" | "nym+tls" => continue, // <-- Temp skip
  164. #[cfg(feature = "p2p-tcp")]
  165. "tcp" | "tcp+tls" => {
  166. debug!(target: "net::hosts::filter_addresses()", "[TCP] Valid: {}", host_str);
  167. }
  168. _ => continue,
  169. }
  170. ret.push(addr_.clone());
  171. }
  172. ret
  173. }
  174. pub async fn remove(&self, url: &Url) {
  175. debug!(target: "net::hosts::remove()", "Removing peer {}", url);
  176. self.addrs.write().await.remove(url);
  177. self.quarantine.write().await.remove(url);
  178. }
  179. /// Quarantine a peer.
  180. /// If they've been quarantined for more than a configured limit, forget them.
  181. pub async fn quarantine(&self, url: &Url) {
  182. debug!(target: "net::hosts::remove()", "Quarantining peer {}", url);
  183. // Remove from main hosts set
  184. self.addrs.write().await.remove(url);
  185. let mut q = self.quarantine.write().await;
  186. if let Some(retries) = q.get_mut(url) {
  187. *retries += 1;
  188. debug!(target: "net::hosts::quarantine()", "Peer {} quarantined {} times", url, retries);
  189. if *retries == self.settings.hosts_quarantine_limit {
  190. debug!(target: "net::hosts::quarantine()", "Banning peer {}", url);
  191. q.remove(url);
  192. self.mark_rejected(url).await;
  193. }
  194. } else {
  195. debug!(target: "net::hosts::remove()", "Added peer {} to quarantine", url);
  196. q.insert(url.clone(), 0);
  197. }
  198. }
  199. /// Check if a given peer (URL) is in the set of rejected hosts
  200. pub async fn is_rejected(&self, peer: &Url) -> bool {
  201. // Skip lookup for UNIX sockets and localhost connections
  202. // as they should never belong to the list of rejected URLs.
  203. let Some(hostname) = peer.host_str() else { return false };
  204. if self.is_local_host(peer.clone()).await {
  205. return false
  206. }
  207. self.rejected.read().await.contains(hostname)
  208. }
  209. /// Mark a peer as rejected by adding it to the set of rejected URLs.
  210. pub async fn mark_rejected(&self, peer: &Url) {
  211. // We ignore UNIX sockets here so we will just work
  212. // with stuff that has host_str().
  213. if let Some(hostname) = peer.host_str() {
  214. // Localhost connections should not be rejected
  215. // This however allows any Tor and Nym connections.
  216. if self.is_local_host(peer.clone()).await {
  217. return
  218. }
  219. self.rejected.write().await.insert(hostname.to_string());
  220. }
  221. }
  222. /// Unmark a rejected peer
  223. pub async fn unmark_rejected(&self, peer: &Url) {
  224. if let Some(hostname) = peer.host_str() {
  225. self.rejected.write().await.remove(hostname);
  226. }
  227. }
  228. /// Check if the host list is empty.
  229. pub async fn is_empty(&self) -> bool {
  230. self.addrs.read().await.is_empty()
  231. }
  232. /// Check if host is already in the set
  233. pub async fn contains(&self, addr: &Url) -> bool {
  234. self.addrs.read().await.contains(addr)
  235. }
  236. /// Return all known hosts
  237. pub async fn fetch_all(&self) -> Vec<Url> {
  238. self.addrs.read().await.iter().cloned().collect()
  239. }
  240. /// Get up to n random peers from the hosts set.
  241. pub async fn fetch_n_random(&self, n: u32) -> Vec<Url> {
  242. let n = n as usize;
  243. if n == 0 {
  244. return vec![]
  245. }
  246. let addrs = self.addrs.read().await;
  247. let urls = addrs.iter().choose_multiple(&mut OsRng, n.min(addrs.len()));
  248. urls.iter().map(|&url| url.clone()).collect()
  249. }
  250. /// Get up to n random peers that match the given transport schemes from the hosts set.
  251. pub async fn fetch_n_random_with_schemes(&self, schemes: &[String], n: u32) -> Vec<Url> {
  252. let n = n as usize;
  253. if n == 0 {
  254. return vec![]
  255. }
  256. // Retrieve all peers corresponding to that transport schemes
  257. let hosts = self.fetch_with_schemes(schemes, None).await;
  258. if hosts.is_empty() {
  259. return hosts
  260. }
  261. // Grab random ones
  262. let urls = hosts.iter().choose_multiple(&mut OsRng, n.min(hosts.len()));
  263. urls.iter().map(|&url| url.clone()).collect()
  264. }
  265. /// Get up to n random peers that don't match the given transport schemes from the hosts set.
  266. pub async fn fetch_n_random_excluding_schemes(&self, schemes: &[String], n: u32) -> Vec<Url> {
  267. let n = n as usize;
  268. if n == 0 {
  269. return vec![]
  270. }
  271. // Retrieve all peers not corresponding to that transport schemes
  272. let hosts = self.fetch_exluding_schemes(schemes, None).await;
  273. if hosts.is_empty() {
  274. return hosts
  275. }
  276. // Grab random ones
  277. let urls = hosts.iter().choose_multiple(&mut OsRng, n.min(hosts.len()));
  278. urls.iter().map(|&url| url.clone()).collect()
  279. }
  280. /// Get up to limit peers that match the given transport schemes from the hosts set.
  281. /// If limit was not provided, return all matching peers.
  282. pub async fn fetch_with_schemes(&self, schemes: &[String], limit: Option<usize>) -> Vec<Url> {
  283. let addrs = self.addrs.read().await;
  284. let mut limit = match limit {
  285. Some(l) => l.min(addrs.len()),
  286. None => addrs.len(),
  287. };
  288. let mut ret = vec![];
  289. if limit == 0 {
  290. return ret
  291. }
  292. for addr in addrs.iter() {
  293. if schemes.contains(&addr.scheme().to_string()) {
  294. ret.push(addr.clone());
  295. limit -= 1;
  296. if limit == 0 {
  297. return ret
  298. }
  299. }
  300. }
  301. // If we didn't find any, pick some from the quarantine zone
  302. if ret.is_empty() {
  303. for addr in self.quarantine.read().await.keys() {
  304. if schemes.contains(&addr.scheme().to_string()) {
  305. ret.push(addr.clone());
  306. limit -= 1;
  307. if limit == 0 {
  308. break
  309. }
  310. }
  311. }
  312. }
  313. ret
  314. }
  315. /// Get up to limit peers that don't match the given transport schemes from the hosts set.
  316. /// If limit was not provided, return all matching peers.
  317. pub async fn fetch_exluding_schemes(
  318. &self,
  319. schemes: &[String],
  320. limit: Option<usize>,
  321. ) -> Vec<Url> {
  322. let addrs = self.addrs.read().await;
  323. let mut limit = match limit {
  324. Some(l) => l.min(addrs.len()),
  325. None => addrs.len(),
  326. };
  327. let mut ret = vec![];
  328. if limit == 0 {
  329. return ret
  330. }
  331. for addr in addrs.iter() {
  332. if !schemes.contains(&addr.scheme().to_string()) {
  333. ret.push(addr.clone());
  334. limit -= 1;
  335. if limit == 0 {
  336. return ret
  337. }
  338. }
  339. }
  340. // If we didn't find any, pick some from the quarantine zone
  341. if ret.is_empty() {
  342. for addr in self.quarantine.read().await.keys() {
  343. if !schemes.contains(&addr.scheme().to_string()) {
  344. ret.push(addr.clone());
  345. limit -= 1;
  346. if limit == 0 {
  347. break
  348. }
  349. }
  350. }
  351. }
  352. ret
  353. }
  354. }
  355. #[cfg(test)]
  356. mod tests {
  357. use super::{super::settings::Settings, *};
  358. #[test]
  359. fn test_store_localnet() {
  360. smol::block_on(async {
  361. let settings = Settings {
  362. localnet: true,
  363. external_addrs: vec![
  364. Url::parse("tcp://foo.bar:123").unwrap(),
  365. Url::parse("tcp://lol.cat:321").unwrap(),
  366. ],
  367. ..Default::default()
  368. };
  369. let hosts = Hosts::new(Arc::new(settings.clone()));
  370. hosts.store(&settings.external_addrs).await;
  371. for i in settings.external_addrs {
  372. assert!(hosts.contains(&i).await);
  373. }
  374. let local_hosts = vec![
  375. Url::parse("tcp://localhost:3921").unwrap(),
  376. Url::parse("tcp://127.0.0.1:23957").unwrap(),
  377. Url::parse("tcp://[::1]:21481").unwrap(),
  378. Url::parse("tcp://192.168.10.65:311").unwrap(),
  379. Url::parse("tcp://0.0.0.0:2312").unwrap(),
  380. Url::parse("tcp://255.255.255.255:2131").unwrap(),
  381. ];
  382. hosts.store(&local_hosts).await;
  383. for i in local_hosts {
  384. assert!(hosts.contains(&i).await);
  385. }
  386. let remote_hosts = vec![
  387. Url::parse("tcp://dark.fi:80").unwrap(),
  388. Url::parse("tcp://top.kek:111").unwrap(),
  389. Url::parse("tcp://http.cat:401").unwrap(),
  390. ];
  391. hosts.store(&remote_hosts).await;
  392. for i in remote_hosts {
  393. assert!(hosts.contains(&i).await);
  394. }
  395. });
  396. }
  397. #[test]
  398. fn test_store() {
  399. smol::block_on(async {
  400. let settings = Settings {
  401. localnet: false,
  402. external_addrs: vec![
  403. Url::parse("tcp://foo.bar:123").unwrap(),
  404. Url::parse("tcp://lol.cat:321").unwrap(),
  405. ],
  406. ..Default::default()
  407. };
  408. let hosts = Hosts::new(Arc::new(settings.clone()));
  409. hosts.store(&settings.external_addrs).await;
  410. assert!(hosts.is_empty().await);
  411. let local_hosts = vec![
  412. Url::parse("tcp://localhost:3921").unwrap(),
  413. Url::parse("tor://[::1]:21481").unwrap(),
  414. Url::parse("tcp://192.168.10.65:311").unwrap(),
  415. Url::parse("tcp+tls://0.0.0.0:2312").unwrap(),
  416. Url::parse("tcp://255.255.255.255:2131").unwrap(),
  417. ];
  418. hosts.store(&local_hosts).await;
  419. assert!(hosts.is_empty().await);
  420. let remote_hosts = vec![
  421. Url::parse("tcp://dark.fi:80").unwrap(),
  422. Url::parse("tcp://http.cat:401").unwrap(),
  423. Url::parse("tcp://foo.bar:111").unwrap(),
  424. ];
  425. hosts.store(&remote_hosts).await;
  426. assert!(hosts.contains(&remote_hosts[0]).await);
  427. assert!(hosts.contains(&remote_hosts[1]).await);
  428. assert!(!hosts.contains(&remote_hosts[2]).await);
  429. });
  430. }
  431. #[test]
  432. fn test_is_local_host() {
  433. smol::block_on(async {
  434. let settings = Settings {
  435. localnet: false,
  436. external_addrs: vec![
  437. Url::parse("tcp://foo.bar:123").unwrap(),
  438. Url::parse("tcp://lol.cat:321").unwrap(),
  439. ],
  440. ..Default::default()
  441. };
  442. let hosts = Hosts::new(Arc::new(settings.clone()));
  443. let local_hosts: Vec<Url> = vec![
  444. Url::parse("tcp://localhost").unwrap(),
  445. Url::parse("tcp://127.0.0.1").unwrap(),
  446. Url::parse("tcp+tls://[::1]").unwrap(),
  447. Url::parse("tcp://localhost.localdomain").unwrap(),
  448. Url::parse("tcp://192.168.10.65").unwrap(),
  449. ];
  450. for host in local_hosts {
  451. eprintln!("{}", host);
  452. assert!(hosts.is_local_host(host).await);
  453. }
  454. let remote_hosts: Vec<Url> = vec![
  455. Url::parse("https://dyne.org").unwrap(),
  456. Url::parse("tcp://77.168.10.65:2222").unwrap(),
  457. Url::parse("tcp://[2345:0425:2CA1:0000:0000:0567:5673:23b5]").unwrap(),
  458. Url::parse("http://eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad.onion")
  459. .unwrap(),
  460. ];
  461. for host in remote_hosts {
  462. assert!(!(hosts.is_local_host(host).await))
  463. }
  464. });
  465. }
  466. }