hosts.rs 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2026 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. //! Host management for the P2P network.
  19. //!
  20. //! `Hosts` is the main interface managing the registry and container.
  21. //! Filters addresses before storing and publishes events on host/channel changes.
  22. //!
  23. //! `HostRegistry` maps peer addresses to their current `HostState`.
  24. //!
  25. //! `HostContainer` stores the hostlists (Grey, White, Gold, Black, Dark) behind a
  26. //! single lock for atomic cross-list operations.
  27. //!
  28. //! # Host Colors
  29. //!
  30. //! - `Grey`: Recently received hosts pending refinement.
  31. //! - `White`: Hosts that passed refinement successfully.
  32. //! - `Gold`: Hosts we've connected to in OutboundSession.
  33. //! - `Black`: Hostile hosts, blocked for the program duration.
  34. //! - `Dark`: Hosts with unsupported transports. Shared with peers but not used locally.
  35. //! Cleared daily to avoid propagating stale entries.
  36. use parking_lot::{Mutex, RwLock};
  37. use rand::{prelude::IteratorRandom, rngs::OsRng, Rng};
  38. use smol::lock::RwLock as AsyncRwLock;
  39. use std::{
  40. collections::HashMap,
  41. fmt, fs,
  42. fs::File,
  43. net::{IpAddr, Ipv4Addr, Ipv6Addr},
  44. sync::{
  45. atomic::{AtomicBool, Ordering},
  46. Arc,
  47. },
  48. time::{Instant, UNIX_EPOCH},
  49. };
  50. use tracing::debug;
  51. use url::{Host, Url};
  52. use super::{
  53. session::{SESSION_REFINE, SESSION_SEED},
  54. settings::Settings,
  55. ChannelPtr,
  56. };
  57. use crate::{
  58. system::{Publisher, PublisherPtr, Subscription},
  59. util::{
  60. file::{load_file, save_file},
  61. logger::verbose,
  62. most_frequent_or_any,
  63. path::expand_path,
  64. ringbuffer::RingBuffer,
  65. },
  66. Error, Result,
  67. };
  68. pub const LOCAL_HOST_STRS: [&str; 2] = ["localhost", "localhost.localdomain"];
  69. const WHITELIST_MAX_LEN: usize = 5000;
  70. const GREYLIST_MAX_LEN: usize = 2000;
  71. const DARKLIST_MAX_LEN: usize = 1000;
  72. const BLACKLIST_MAX_LEN: usize = 10000;
  73. /// How long a host can remain in Free state before being pruned from the registry.
  74. /// 24 hours is appropriate for long-running daemons.
  75. const REGISTRY_PRUNE_AGE_SECS: u64 = 86400;
  76. pub type HostsPtr = Arc<Hosts>;
  77. /// Mutually exclusive states for host lifecycle management.
  78. ///
  79. /// ```text
  80. /// +------+
  81. /// | free |
  82. /// +------+
  83. /// ^
  84. /// |
  85. /// v
  86. /// +------+ +---------+
  87. /// +------> | move | ---> | suspend |
  88. /// | +------+ +---------+
  89. /// | | | +--------+
  90. /// | | v | insert |
  91. /// +---------+ | +--------+ +--------+
  92. /// | connect | | | refine | ^
  93. /// +---------+ | +--------+ |
  94. /// | v | v
  95. /// | +-----------+ | +------+
  96. /// +---> | connected | <-------+-------> | free |
  97. /// +-----------+ +------+
  98. /// ^
  99. /// |
  100. /// v
  101. /// +------+
  102. /// | free |
  103. /// +------+
  104. ///
  105. /// ```
  106. #[derive(Clone, Debug)]
  107. pub(crate) enum HostState {
  108. /// Being inserted into the hostlist.
  109. Insert,
  110. /// Being refined (greylist -> whitelist check).
  111. Refine,
  112. /// Being connected to in Outbound/Manual Session.
  113. Connect,
  114. /// Failed connection, awaiting refinement.
  115. Suspend,
  116. /// Successfully connected.
  117. Connected(ChannelPtr),
  118. /// Moving between hostlists.
  119. Move,
  120. /// Available for any operation. Contains timestamp when freed.
  121. Free(u64),
  122. }
  123. impl HostState {
  124. fn try_transition(&self, target: HostState) -> Result<HostState> {
  125. use HostState::*;
  126. let allowed = matches!(
  127. (&target, self),
  128. (Insert, Free(_)) |
  129. (Refine, Free(_) | Suspend) |
  130. (Connect, Free(_)) |
  131. (Connected(_), Free(_) | Connect | Refine | Move) |
  132. (Move, Free(_) | Connect | Refine | Connected(_)) |
  133. (Suspend, Move) |
  134. (Free(_), _)
  135. );
  136. if allowed {
  137. Ok(target)
  138. } else {
  139. Err(Error::HostStateBlocked(self.to_string(), target.to_string()))
  140. }
  141. }
  142. }
  143. impl fmt::Display for HostState {
  144. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  145. match self {
  146. HostState::Insert => write!(f, "Insert"),
  147. HostState::Refine => write!(f, "Refine"),
  148. HostState::Connect => write!(f, "Connect"),
  149. HostState::Suspend => write!(f, "Suspend"),
  150. HostState::Connected(_) => write!(f, "Connected"),
  151. HostState::Move => write!(f, "Move"),
  152. HostState::Free(_) => write!(f, "Free"),
  153. }
  154. }
  155. }
  156. #[repr(u8)]
  157. #[derive(Clone, Copy, Debug, PartialEq, Eq)]
  158. pub enum HostColor {
  159. /// Intermediary nodes that are periodically probed and updated to White.
  160. Grey = 0,
  161. /// Recently seen hosts. Shared with other nodes.
  162. White = 1,
  163. /// Nodes to which we have already been able to establish a connection.
  164. Gold = 2,
  165. /// Hostile peers that can neither be connected to nor establish
  166. /// connections to us for the duration of the program.
  167. Black = 3,
  168. /// Peers that do not match our accepted transports. We are blind to
  169. /// these nodes (we do not use them) but we send them around the network
  170. /// anyway to ensure all transports are propagated.
  171. Dark = 4,
  172. }
  173. impl HostColor {
  174. const ALL: [HostColor; 5] =
  175. [HostColor::Grey, HostColor::White, HostColor::Gold, HostColor::Black, HostColor::Dark];
  176. fn max_len(self) -> Option<usize> {
  177. match self {
  178. HostColor::Grey => Some(GREYLIST_MAX_LEN),
  179. HostColor::White => Some(WHITELIST_MAX_LEN),
  180. HostColor::Dark => Some(DARKLIST_MAX_LEN),
  181. HostColor::Black => Some(BLACKLIST_MAX_LEN),
  182. HostColor::Gold => None, // Limited by connection slots
  183. }
  184. }
  185. fn name(self) -> &'static str {
  186. match self {
  187. HostColor::Grey => "grey",
  188. HostColor::White => "white",
  189. HostColor::Gold => "gold",
  190. HostColor::Black => "black",
  191. HostColor::Dark => "dark",
  192. }
  193. }
  194. fn from_name(name: &str) -> Option<Self> {
  195. match name {
  196. "grey" => Some(HostColor::Grey),
  197. "white" => Some(HostColor::White),
  198. "gold" => Some(HostColor::Gold),
  199. "black" => Some(HostColor::Black),
  200. "dark" => Some(HostColor::Dark),
  201. _ => None,
  202. }
  203. }
  204. }
  205. impl TryFrom<usize> for HostColor {
  206. type Error = Error;
  207. fn try_from(value: usize) -> Result<Self> {
  208. HostColor::ALL.get(value).copied().ok_or(Error::InvalidHostColor)
  209. }
  210. }
  211. /// Container for all hostlists. Uses a single lock for atomic cross-list operations.
  212. pub struct HostContainer {
  213. pub(in crate::net) lists: RwLock<[Vec<(Url, u64)>; 5]>,
  214. }
  215. impl HostContainer {
  216. fn new() -> Self {
  217. Self { lists: RwLock::new([Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new()]) }
  218. }
  219. /// Store or update an address on a hostlist.
  220. pub fn store(&self, color: HostColor, addr: Url, last_seen: u64) {
  221. let mut lists = self.lists.write();
  222. let list = &mut lists[color as usize];
  223. if let Some(entry) = list.iter_mut().find(|(u, _)| *u == addr) {
  224. entry.1 = last_seen;
  225. } else {
  226. list.push((addr, last_seen));
  227. }
  228. }
  229. /// Store, sort by last_seen (descending), and enforce max size.
  230. pub fn store_and_trim(&self, color: HostColor, addr: Url, last_seen: u64) {
  231. let mut lists = self.lists.write();
  232. let list = &mut lists[color as usize];
  233. if let Some(entry) = list.iter_mut().find(|(u, _)| *u == addr) {
  234. entry.1 = last_seen;
  235. } else {
  236. list.push((addr, last_seen));
  237. }
  238. list.sort_by_key(|e| std::cmp::Reverse(e.1));
  239. if let Some(max) = color.max_len() {
  240. list.truncate(max);
  241. }
  242. }
  243. /// Remove an address from a hostlist if it exists.
  244. pub fn remove(&self, color: HostColor, addr: &Url) {
  245. let mut lists = self.lists.write();
  246. lists[color as usize].retain(|(u, _)| u != addr);
  247. }
  248. /// Check if an address exists in a hostlist.
  249. pub fn contains(&self, color: HostColor, addr: &Url) -> bool {
  250. self.lists.read()[color as usize].iter().any(|(u, _)| u == addr)
  251. }
  252. /// Check if an address exists in any of the specified hostlists.
  253. pub fn contains_any(&self, colors: &[HostColor], addr: &Url) -> bool {
  254. let lists = self.lists.read();
  255. colors.iter().any(|&c| lists[c as usize].iter().any(|(u, _)| u == addr))
  256. }
  257. /// Check if any host with the given hostname exists in the specified lists.
  258. pub fn contains_hostname(&self, colors: &[HostColor], hostname: &str) -> bool {
  259. let lists = self.lists.read();
  260. colors
  261. .iter()
  262. .any(|&c| lists[c as usize].iter().any(|(u, _)| u.host_str() == Some(hostname)))
  263. }
  264. /// Check if a hostlist is empty.
  265. pub fn is_empty(&self, color: HostColor) -> bool {
  266. self.lists.read()[color as usize].is_empty()
  267. }
  268. /// Update the last_seen field for an address.
  269. pub fn update_last_seen(&self, color: HostColor, addr: &Url, last_seen: u64) {
  270. let mut lists = self.lists.write();
  271. if let Some(entry) = lists[color as usize].iter_mut().find(|(u, _)| u == addr) {
  272. entry.1 = last_seen;
  273. }
  274. }
  275. /// Get the last_seen field for an address.
  276. pub fn get_last_seen(&self, color: HostColor, addr: &Url) -> Option<u64> {
  277. self.lists.read()[color as usize].iter().find(|(u, _)| u == addr).map(|(_, ls)| *ls)
  278. }
  279. /// Return all hosts from a hostlist.
  280. pub fn fetch_all(&self, color: HostColor) -> Vec<(Url, u64)> {
  281. self.lists.read()[color as usize].clone()
  282. }
  283. /// Get the oldest entry (last in sorted list) from a hostlist.
  284. pub fn fetch_last(&self, color: HostColor) -> Option<(Url, u64)> {
  285. self.lists.read()[color as usize].last().cloned()
  286. }
  287. /// Get hosts matching the given transport schemes.
  288. pub fn fetch_with_schemes(
  289. &self,
  290. color: HostColor,
  291. schemes: &[String],
  292. limit: Option<usize>,
  293. ) -> Vec<(Url, u64)> {
  294. let lists = self.lists.read();
  295. lists[color as usize]
  296. .iter()
  297. .filter(|(addr, _)| schemes.contains(&addr.scheme().to_string()))
  298. .take(limit.unwrap_or(usize::MAX))
  299. .cloned()
  300. .collect()
  301. }
  302. /// Get hosts NOT matching the given transport schemes.
  303. pub fn fetch_excluding_schemes(
  304. &self,
  305. color: HostColor,
  306. schemes: &[String],
  307. limit: Option<usize>,
  308. ) -> Vec<(Url, u64)> {
  309. let lists = self.lists.read();
  310. lists[color as usize]
  311. .iter()
  312. .filter(|(addr, _)| !schemes.contains(&addr.scheme().to_string()))
  313. .take(limit.unwrap_or(usize::MAX))
  314. .cloned()
  315. .collect()
  316. }
  317. /// Get a random host matching the given schemes.
  318. pub fn fetch_random_with_schemes(
  319. &self,
  320. color: HostColor,
  321. schemes: &[String],
  322. ) -> Option<(Url, u64)> {
  323. let hosts = self.fetch_with_schemes(color, schemes, None);
  324. if hosts.is_empty() {
  325. return None
  326. }
  327. let idx = rand::thread_rng().gen_range(0..hosts.len());
  328. Some(hosts[idx].clone())
  329. }
  330. /// Get up to n random hosts.
  331. pub fn fetch_n_random(&self, color: HostColor, n: usize) -> Vec<(Url, u64)> {
  332. if n == 0 {
  333. return vec![]
  334. }
  335. let lists = self.lists.read();
  336. lists[color as usize].iter().cloned().choose_multiple(&mut OsRng, n)
  337. }
  338. /// Get up to n random hosts matching the given schemes.
  339. pub fn fetch_n_random_with_schemes(
  340. &self,
  341. color: HostColor,
  342. schemes: &[String],
  343. n: usize,
  344. ) -> Vec<(Url, u64)> {
  345. if n == 0 {
  346. return vec![]
  347. }
  348. let hosts = self.fetch_with_schemes(color, schemes, None);
  349. hosts.into_iter().choose_multiple(&mut OsRng, n)
  350. }
  351. /// Get up to n random hosts NOT matching the given schemes.
  352. pub fn fetch_n_random_excluding_schemes(
  353. &self,
  354. color: HostColor,
  355. schemes: &[String],
  356. n: usize,
  357. ) -> Vec<(Url, u64)> {
  358. if n == 0 {
  359. return vec![]
  360. }
  361. let hosts = self.fetch_excluding_schemes(color, schemes, None);
  362. hosts.into_iter().choose_multiple(&mut OsRng, n)
  363. }
  364. /// Atomically move a host between lists.
  365. pub fn move_host(&self, addr: &Url, last_seen: u64, dest: HostColor) -> Result<()> {
  366. let mut lists = self.lists.write();
  367. // Remove from source lists based on destination
  368. match dest {
  369. HostColor::Grey => {
  370. lists[HostColor::Gold as usize].retain(|(u, _)| u != addr);
  371. lists[HostColor::White as usize].retain(|(u, _)| u != addr);
  372. }
  373. HostColor::White => {
  374. lists[HostColor::Grey as usize].retain(|(u, _)| u != addr);
  375. }
  376. HostColor::Gold => {
  377. lists[HostColor::Grey as usize].retain(|(u, _)| u != addr);
  378. lists[HostColor::White as usize].retain(|(u, _)| u != addr);
  379. }
  380. HostColor::Black => {
  381. lists[HostColor::Grey as usize].retain(|(u, _)| u != addr);
  382. lists[HostColor::White as usize].retain(|(u, _)| u != addr);
  383. lists[HostColor::Gold as usize].retain(|(u, _)| u != addr);
  384. }
  385. HostColor::Dark => return Err(Error::InvalidHostColor),
  386. }
  387. // Add to destination
  388. let dest_list = &mut lists[dest as usize];
  389. if let Some(entry) = dest_list.iter_mut().find(|(u, _)| u == addr) {
  390. entry.1 = last_seen;
  391. } else {
  392. dest_list.push((addr.clone(), last_seen));
  393. }
  394. // Sort and trim
  395. dest_list.sort_by_key(|e| std::cmp::Reverse(e.1));
  396. if let Some(max) = dest.max_len() {
  397. dest_list.truncate(max);
  398. }
  399. Ok(())
  400. }
  401. /// Remove entries older than max_age seconds.
  402. pub fn refresh(&self, color: HostColor, max_age: u64) {
  403. let now = UNIX_EPOCH.elapsed().unwrap().as_secs();
  404. let mut lists = self.lists.write();
  405. let original_len = lists[color as usize].len();
  406. lists[color as usize].retain(|(addr, last_seen)| {
  407. // Keep if last_seen is in future (clock skew protection)
  408. if now < *last_seen {
  409. return true
  410. }
  411. let age = now - last_seen;
  412. if age <= max_age {
  413. return true
  414. }
  415. debug!(target: "net::hosts::refresh", "Removing {addr} (age: {age}s)");
  416. false
  417. });
  418. let removed = original_len - lists[color as usize].len();
  419. if removed > 0 {
  420. debug!(target: "net::hosts::refresh", "Removed {removed} old entries from {:?}", color);
  421. }
  422. }
  423. pub fn load_all(&self, path: &str) -> Result<()> {
  424. let path = expand_path(path)?;
  425. if !path.exists() {
  426. if let Some(parent) = path.parent() {
  427. fs::create_dir_all(parent)?;
  428. }
  429. File::create(path.clone())?;
  430. }
  431. let contents = match load_file(&path) {
  432. Ok(c) => c,
  433. Err(e) => {
  434. verbose!(target: "net::hosts::load_all", "[P2P] Failed retrieving saved hosts: {e}");
  435. return Ok(())
  436. }
  437. };
  438. let mut lists = self.lists.write();
  439. for line in contents.lines() {
  440. let parts: Vec<&str> = line.split('\t').collect();
  441. if parts.len() < 3 {
  442. continue;
  443. }
  444. let color = match HostColor::from_name(parts[0]) {
  445. Some(c) => c,
  446. None => continue,
  447. };
  448. let url = match Url::parse(parts[1]) {
  449. Ok(u) => u,
  450. Err(_) => continue,
  451. };
  452. let last_seen = match parts[2].parse::<u64>() {
  453. Ok(t) => t,
  454. Err(_) => continue,
  455. };
  456. let list = &mut lists[color as usize];
  457. list.push((url, last_seen));
  458. list.sort_by_key(|e| std::cmp::Reverse(e.1));
  459. if let Some(max) = color.max_len() {
  460. list.truncate(max);
  461. }
  462. }
  463. // Refresh dark list (remove entries older than one day)
  464. drop(lists);
  465. self.refresh(HostColor::Dark, 86400);
  466. Ok(())
  467. }
  468. pub fn save_all(&self, path: &str) -> Result<()> {
  469. let path = expand_path(path)?;
  470. let lists = self.lists.read();
  471. let mut tsv = String::new();
  472. for color in [HostColor::Dark, HostColor::Grey, HostColor::White, HostColor::Gold] {
  473. for (url, last_seen) in &lists[color as usize] {
  474. tsv.push_str(&format!("{}\t{}\t{}\n", color.name(), url, last_seen));
  475. }
  476. }
  477. if !tsv.is_empty() {
  478. verbose!(target: "net::hosts::save_all", "[P2P] Saving hosts to: {path:?}");
  479. if let Err(e) = save_file(&path, &tsv) {
  480. verbose!(target: "net::hosts::save_all", "[P2P] Failed saving hosts: {e}");
  481. }
  482. }
  483. Ok(())
  484. }
  485. /// Perform transport mixing for a URL, returning alternative connection addresses.
  486. pub fn mix_host(
  487. addr: &Url,
  488. transports: &[String],
  489. mixed_transports: &[String],
  490. tor_socks5_proxy: &Option<Url>,
  491. nym_socks5_proxy: &Option<Url>,
  492. ) -> Vec<Url> {
  493. if !mixed_transports.contains(&addr.scheme().to_string()) {
  494. return vec![]
  495. }
  496. let mut hosts = vec![];
  497. let mix = |scheme: &str, target: &str, hosts: &mut Vec<Url>| {
  498. if transports.contains(&scheme.to_string()) && addr.scheme() == target {
  499. let mut url = addr.clone();
  500. let _ = url.set_scheme(scheme);
  501. hosts.push(url);
  502. }
  503. };
  504. let mix_socks5 =
  505. |scheme: &str, target: &str, proxies: &[&Option<Url>], hosts: &mut Vec<Url>| {
  506. if transports.contains(&scheme.to_string()) && addr.scheme() == target {
  507. for proxy in proxies {
  508. if let Some(base) = proxy.as_ref() {
  509. let mut endpoint = base.clone();
  510. endpoint.set_path(&format!(
  511. "{}:{}",
  512. addr.host().unwrap(),
  513. addr.port().unwrap()
  514. ));
  515. let _ = endpoint.set_scheme(scheme);
  516. hosts.push(endpoint);
  517. }
  518. }
  519. }
  520. };
  521. mix("tor", "tcp", &mut hosts);
  522. mix("tor+tls", "tcp+tls", &mut hosts);
  523. mix("nym", "tcp", &mut hosts);
  524. mix("nym+tls", "tcp+tls", &mut hosts);
  525. mix_socks5("socks5", "tcp", &[tor_socks5_proxy, nym_socks5_proxy], &mut hosts);
  526. mix_socks5("socks5+tls", "tcp+tls", &[tor_socks5_proxy, nym_socks5_proxy], &mut hosts);
  527. mix_socks5("socks5", "tor", &[tor_socks5_proxy], &mut hosts);
  528. mix_socks5("socks5+tls", "tor+tls", &[tor_socks5_proxy], &mut hosts);
  529. hosts
  530. }
  531. }
  532. /// Main interface for host management.
  533. pub struct Hosts {
  534. /// A registry that tracks hosts and their current state.
  535. registry: Mutex<HashMap<Url, HostState>>,
  536. /// Hostlists and associated methods
  537. pub container: HostContainer,
  538. /// Publisher listening for store updates
  539. store_publisher: PublisherPtr<usize>,
  540. /// Publisher for notifications of new channels
  541. pub(crate) channel_publisher: PublisherPtr<Result<ChannelPtr>>,
  542. /// Publisher listening for network disconnects
  543. pub(crate) disconnect_publisher: PublisherPtr<Error>,
  544. /// Keeps track of the last time a connection was made.
  545. pub(crate) last_connection: Mutex<Instant>,
  546. /// Marker for IPv6 availability
  547. pub(crate) ipv6_available: AtomicBool,
  548. /// Auto self discovered addresses. Used for filtering self connections.
  549. auto_self_addrs: Mutex<RingBuffer<Ipv6Addr, 20>>,
  550. /// Pointer to configured P2P settings
  551. settings: Arc<AsyncRwLock<Settings>>,
  552. }
  553. impl Hosts {
  554. /// Create a new hosts list
  555. pub(crate) fn new(settings: Arc<AsyncRwLock<Settings>>) -> HostsPtr {
  556. Arc::new(Self {
  557. registry: Mutex::new(HashMap::new()),
  558. container: HostContainer::new(),
  559. store_publisher: Publisher::new(),
  560. channel_publisher: Publisher::new(),
  561. disconnect_publisher: Publisher::new(),
  562. last_connection: Mutex::new(Instant::now()),
  563. ipv6_available: AtomicBool::new(true),
  564. auto_self_addrs: Mutex::new(RingBuffer::new()),
  565. settings,
  566. })
  567. }
  568. /// Try to register a host with a new state.
  569. pub(crate) fn try_register(&self, addr: Url, new_state: HostState) -> Result<HostState> {
  570. let mut registry = self.registry.lock();
  571. let result = if let Some(current) = registry.get(&addr) {
  572. current.try_transition(new_state)
  573. } else {
  574. Ok(new_state)
  575. };
  576. if let Ok(ref state) = result {
  577. registry.insert(addr, state.clone());
  578. }
  579. result
  580. }
  581. /// Mark a host as Free.
  582. pub(crate) fn unregister(&self, addr: &Url) -> Result<()> {
  583. let age = UNIX_EPOCH.elapsed().unwrap().as_secs();
  584. self.try_register(addr.clone(), HostState::Free(age))?;
  585. debug!(target: "net::hosts::unregister", "Unregistered: {addr}");
  586. Ok(())
  587. }
  588. /// Prune stale entries from the registry.
  589. ///
  590. /// Removes hosts that have been in `Free` state longer than `REGISTRY_PRUNE_AGE_SECS`.
  591. /// This prevents unbounded growth of the registry over long-running sessions.
  592. ///
  593. /// Returns the number of entries pruned.
  594. pub fn prune_registry(&self) -> usize {
  595. let now = UNIX_EPOCH.elapsed().unwrap().as_secs();
  596. let mut registry = self.registry.lock();
  597. let before = registry.len();
  598. registry.retain(|url, state| {
  599. if let HostState::Free(age) = state {
  600. let elapsed = now.saturating_sub(*age);
  601. if elapsed > REGISTRY_PRUNE_AGE_SECS {
  602. debug!(
  603. target: "net::hosts::prune_registry",
  604. "Pruning stale entry {url} (idle for {elapsed}s)",
  605. );
  606. return false
  607. }
  608. }
  609. true
  610. });
  611. let pruned = before - registry.len();
  612. if pruned > 0 {
  613. debug!(target: "net::hosts::prune_registry", "Pruned {pruned} stale entries");
  614. }
  615. pruned
  616. }
  617. /// Check if a host can be refined.
  618. pub fn refinable(&self, addr: &Url) -> bool {
  619. let registry = self.registry.lock();
  620. match registry.get(addr) {
  621. Some(state) => state.try_transition(HostState::Refine).is_ok(),
  622. None => true,
  623. }
  624. }
  625. /// Return all connected channels.
  626. pub fn channels(&self) -> Vec<ChannelPtr> {
  627. self.registry
  628. .lock()
  629. .values()
  630. .filter_map(
  631. |state| {
  632. if let HostState::Connected(c) = state {
  633. Some(c.clone())
  634. } else {
  635. None
  636. }
  637. },
  638. )
  639. .collect()
  640. }
  641. /// Return connected peers (excluding seed and refinery connections).
  642. pub fn peers(&self) -> Vec<ChannelPtr> {
  643. self.registry
  644. .lock()
  645. .values()
  646. .filter_map(|state| {
  647. if let HostState::Connected(c) = state {
  648. if c.session_type_id() & (SESSION_SEED | SESSION_REFINE) == 0 {
  649. return Some(c.clone())
  650. }
  651. }
  652. None
  653. })
  654. .collect()
  655. }
  656. /// Get a channel by ID.
  657. pub fn get_channel(&self, id: u32) -> Option<ChannelPtr> {
  658. self.channels().into_iter().find(|c| c.info.id == id)
  659. }
  660. /// Get a random connected channel.
  661. pub fn random_channel(&self) -> Option<ChannelPtr> {
  662. let channels = self.channels();
  663. if channels.is_empty() {
  664. return None
  665. }
  666. let idx = rand::thread_rng().gen_range(0..channels.len());
  667. Some(channels[idx].clone())
  668. }
  669. /// Return suspended hosts.
  670. pub(crate) fn suspended(&self) -> Vec<Url> {
  671. self.registry
  672. .lock()
  673. .iter()
  674. .filter_map(
  675. |(url, state)| {
  676. if matches!(state, HostState::Suspend) {
  677. Some(url.clone())
  678. } else {
  679. None
  680. }
  681. },
  682. )
  683. .collect()
  684. }
  685. /// Register a channel as connected.
  686. pub(crate) async fn register_channel(&self, channel: ChannelPtr) {
  687. let address = channel.address().clone();
  688. // Skip Tor-style inbound connections
  689. if channel.p2p().settings().read().await.inbound_addrs.contains(&address) {
  690. return
  691. }
  692. if let Err(e) = self.try_register(address, HostState::Connected(channel.clone())) {
  693. verbose!(target: "net::hosts::register_channel", "[P2P] Error registering channel: {e:?}");
  694. return
  695. }
  696. self.channel_publisher.notify(Ok(channel)).await;
  697. *self.last_connection.lock() = Instant::now();
  698. }
  699. /// Insert addresses into the greylist after filtering.
  700. pub(crate) async fn insert(&self, color: HostColor, addrs: &[(Url, u64)]) {
  701. let filtered = self.filter_addresses(addrs).await;
  702. let mut count = 0;
  703. for (addr, last_seen) in filtered {
  704. if self.try_register(addr.clone(), HostState::Insert).is_err() {
  705. continue;
  706. }
  707. self.container.store_and_trim(color, addr.clone(), last_seen);
  708. let _ = self.unregister(&addr);
  709. count += 1;
  710. }
  711. if count > 0 {
  712. self.store_publisher.notify(count).await;
  713. }
  714. }
  715. /// Find a connectable address from the given hosts.
  716. pub(crate) async fn check_addrs(&self, hosts: Vec<(Url, u64)>) -> Option<(Url, u64)> {
  717. let settings = self.settings.read().await;
  718. let seeds = &settings.seeds;
  719. let external = self.external_addrs().await;
  720. for (host, last_seen) in hosts {
  721. if seeds.contains(&host) || external.contains(&host) {
  722. continue;
  723. }
  724. if self.try_register(host.clone(), HostState::Connect).is_ok() {
  725. return Some((host, last_seen))
  726. }
  727. }
  728. None
  729. }
  730. /// Move a host to the greylist.
  731. pub async fn greylist_host(&self, addr: &Url, last_seen: u64) -> Result<()> {
  732. self.move_host(addr, last_seen, HostColor::Grey).await?;
  733. self.unregister(addr)
  734. }
  735. /// Move a host to the whitelist.
  736. pub async fn whitelist_host(&self, addr: &Url, last_seen: u64) -> Result<()> {
  737. self.move_host(addr, last_seen, HostColor::White).await?;
  738. self.unregister(addr)
  739. }
  740. /// Move a host between lists (requires Move state).
  741. pub(crate) async fn move_host(
  742. &self,
  743. addr: &Url,
  744. last_seen: u64,
  745. dest: HostColor,
  746. ) -> Result<()> {
  747. self.try_register(addr.clone(), HostState::Move)?;
  748. if dest == HostColor::Black {
  749. if addr.host_str().is_none() {
  750. return Ok(())
  751. }
  752. if !self.settings.read().await.localnet && self.is_local_host(addr) {
  753. return Ok(())
  754. }
  755. }
  756. self.container.move_host(addr, last_seen, dest)
  757. }
  758. /// Get the last_seen for an address across all active lists.
  759. pub fn fetch_last_seen(&self, addr: &Url) -> Option<u64> {
  760. for color in [HostColor::Gold, HostColor::White, HostColor::Grey] {
  761. if let Some(ls) = self.container.get_last_seen(color, addr) {
  762. return Some(ls)
  763. }
  764. }
  765. None
  766. }
  767. /// Check if we have an existing connection to a host (any port).
  768. pub fn has_existing_connection(&self, url: &Url) -> bool {
  769. let host_str = match url.host_str() {
  770. Some(h) => h,
  771. None => return false,
  772. };
  773. self.container.contains_hostname(&[HostColor::Gold, HostColor::White], host_str)
  774. }
  775. async fn filter_addresses(&self, addrs: &[(Url, u64)]) -> Vec<(Url, u64)> {
  776. let settings = self.settings.read().await;
  777. let external_addrs = self.external_addrs().await;
  778. let mut result = vec![];
  779. 'addr_loop: for (addr, last_seen) in addrs {
  780. // Validate format
  781. if addr.host_str().is_none() || addr.port().is_none() || addr.cannot_be_a_base() {
  782. verbose!(target: "net::hosts::filter_addresses", "Filtered {addr}: invalid format");
  783. continue;
  784. }
  785. // Skip configured seeds and peers
  786. if settings.seeds.contains(addr) || settings.peers.contains(addr) {
  787. verbose!(target: "net::hosts::filter_addresses", "Filtered {addr}: seed or peer");
  788. continue;
  789. }
  790. // Skip blacklisted
  791. if self.container.contains(HostColor::Black, addr) || self.block_all_ports(addr) {
  792. verbose!(target: "net::hosts::filter_addresses", "Filtered {addr}: blacklisted");
  793. continue;
  794. }
  795. let host = addr.host().unwrap();
  796. // Skip our own addresses
  797. if !settings.localnet {
  798. for ext in &external_addrs {
  799. if host == ext.host().unwrap() {
  800. verbose!(target: "net::hosts::filter_addresses", "Filtered {addr}: own address");
  801. continue 'addr_loop;
  802. }
  803. }
  804. } else {
  805. for ext in &settings.external_addrs {
  806. if addr.port() == ext.port() {
  807. verbose!(target: "net::hosts::filter_addresses", "Filtered {addr}: own address (localnet)");
  808. continue 'addr_loop;
  809. }
  810. }
  811. }
  812. // Skip local addresses in production
  813. if !settings.localnet && self.is_local_host(addr) {
  814. verbose!(target: "net::hosts::filter_addresses", "Filtered {addr}: local address");
  815. continue;
  816. }
  817. // Validate transport-specific formats
  818. if !self.validate_transport(addr) {
  819. verbose!(target: "net::hosts::filter_addresses", "Filtered {addr}: invalid transport");
  820. continue;
  821. }
  822. // Store unsupported transports on dark list
  823. if !settings.active_profiles.contains(&addr.scheme().to_string()) ||
  824. (!self.ipv6_available.load(Ordering::SeqCst) && self.is_ipv6(addr))
  825. {
  826. verbose!(target: "net::hosts::filter_addresses", "Filtered {addr}: unsupported transport (darklist)");
  827. self.container.store_and_trim(HostColor::Dark, addr.clone(), *last_seen);
  828. self.container.refresh(HostColor::Dark, 86400);
  829. if !settings.mixed_profiles.contains(&addr.scheme().to_string()) {
  830. continue;
  831. }
  832. }
  833. // Skip if already in active lists
  834. if self
  835. .container
  836. .contains_any(&[HostColor::Gold, HostColor::White, HostColor::Grey], addr)
  837. {
  838. verbose!(target: "net::hosts::filter_addresses", "Filtered {addr}: already in active lists");
  839. continue;
  840. }
  841. result.push((addr.clone(), *last_seen));
  842. }
  843. result
  844. }
  845. fn validate_transport(&self, addr: &Url) -> bool {
  846. match addr.scheme() {
  847. "tcp" | "tcp+tls" => true,
  848. #[cfg(feature = "p2p-tor")]
  849. "tor" | "tor+tls" => {
  850. use std::str::FromStr;
  851. tor_hscrypto::pk::HsId::from_str(addr.host_str().unwrap()).is_ok()
  852. }
  853. #[cfg(feature = "p2p-nym")]
  854. "nym" | "nym+tls" => false, // Temp skip
  855. #[cfg(feature = "p2p-i2p")]
  856. "i2p" | "i2p+tls" => Self::is_i2p_host(addr.host_str().unwrap()),
  857. #[cfg(feature = "p2p-quic")]
  858. "quic" => true,
  859. _ => false,
  860. }
  861. }
  862. pub(crate) async fn import_blacklist(&self) -> Result<()> {
  863. let settings = self.settings.read().await;
  864. for (hostname, schemes, ports) in &settings.blacklist {
  865. let schemes =
  866. if schemes.is_empty() { vec!["tcp+tls".to_string()] } else { schemes.clone() };
  867. let ports = if ports.is_empty() { vec![0] } else { ports.clone() };
  868. for scheme in &schemes {
  869. for &port in &ports {
  870. let url_string = if port == 0 {
  871. format!("{scheme}://{hostname}")
  872. } else {
  873. format!("{scheme}://{hostname}:{port}")
  874. };
  875. if let Ok(url) = Url::parse(&url_string) {
  876. self.container.store_and_trim(HostColor::Black, url, 0);
  877. }
  878. }
  879. }
  880. }
  881. Ok(())
  882. }
  883. /// Check if a host is blacklisted without a port (blocks all ports).
  884. pub(crate) fn block_all_ports(&self, url: &Url) -> bool {
  885. let host = match url.host() {
  886. Some(h) => h,
  887. None => return false,
  888. };
  889. self.container.lists.read()[HostColor::Black as usize]
  890. .iter()
  891. .any(|(u, _)| u.host() == Some(host.clone()) && u.port().is_none())
  892. }
  893. pub fn is_local_host(&self, url: &Url) -> bool {
  894. match url.host() {
  895. None => false,
  896. Some(Host::Ipv4(ip)) => !ip.unstable_is_global(),
  897. Some(Host::Ipv6(ip)) => !ip.unstable_is_global(),
  898. Some(Host::Domain(d)) => LOCAL_HOST_STRS.contains(&d),
  899. }
  900. }
  901. pub fn is_ipv6(&self, url: &Url) -> bool {
  902. matches!(url.host(), Some(Host::Ipv6(_)))
  903. }
  904. pub(crate) fn add_auto_addr(&self, addr: Ipv6Addr) {
  905. self.auto_self_addrs.lock().push(addr);
  906. }
  907. pub fn guess_auto_addr(&self) -> Option<Ipv6Addr> {
  908. let mut addrs = self.auto_self_addrs.lock();
  909. most_frequent_or_any(addrs.make_contiguous())
  910. }
  911. pub async fn external_addrs(&self) -> Vec<Url> {
  912. let mut addrs = self.settings.read().await.external_addrs.clone();
  913. for addr in &mut addrs {
  914. self.patch_port(addr);
  915. self.patch_auto_addr(addr);
  916. }
  917. addrs
  918. }
  919. fn patch_auto_addr(&self, addr: &mut Url) {
  920. if addr.scheme() != "tcp" && addr.scheme() != "tcp+tls" {
  921. return
  922. }
  923. if let Some(Host::Ipv6(ip)) = addr.host() {
  924. if ip.is_unspecified() {
  925. if let Some(auto) = self.guess_auto_addr() {
  926. let _ = addr.set_ip_host(IpAddr::V6(auto));
  927. }
  928. }
  929. }
  930. }
  931. fn patch_port(&self, _addr: &mut Url) {
  932. // TODO: Lookup port from InboundSession when port is 0
  933. }
  934. #[cfg(feature = "p2p-i2p")]
  935. fn is_i2p_host(host: &str) -> bool {
  936. if !host.ends_with(".i2p") {
  937. return false
  938. }
  939. let name = host.trim_end_matches(".i2p");
  940. if name.ends_with(".b32") {
  941. let b32 = name.trim_end_matches(".b32");
  942. let decoded = crate::util::encoding::base32::decode(b32);
  943. return decoded.is_some() && decoded.unwrap().len() == 32
  944. }
  945. name.chars().all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '.')
  946. }
  947. pub async fn subscribe_store(&self) -> Subscription<usize> {
  948. self.store_publisher.clone().subscribe().await
  949. }
  950. pub async fn subscribe_channel(&self) -> Subscription<Result<ChannelPtr>> {
  951. self.channel_publisher.clone().subscribe().await
  952. }
  953. pub async fn subscribe_disconnect(&self) -> Subscription<Error> {
  954. self.disconnect_publisher.clone().subscribe().await
  955. }
  956. }
  957. // Copied from https://doc.rust-lang.org/stable/src/core/net/ip_addr.rs.html#839
  958. trait UnstableFeatureIp {
  959. fn unstable_is_global(&self) -> bool;
  960. fn unstable_is_shared(&self) -> bool;
  961. fn unstable_is_benchmarking(&self) -> bool;
  962. fn unstable_is_reserved(&self) -> bool;
  963. fn unstable_is_documentation(&self) -> bool;
  964. }
  965. impl UnstableFeatureIp for Ipv4Addr {
  966. #[inline]
  967. fn unstable_is_global(&self) -> bool {
  968. !(self.octets()[0] == 0 // "This network"
  969. || self.is_private()
  970. || self.unstable_is_shared()
  971. || self.is_loopback()
  972. || self.is_link_local()
  973. // addresses reserved for future protocols (`192.0.0.0/24`)
  974. // .9 and .10 are documented as globally reachable so they're excluded
  975. || (
  976. self.octets()[0] == 192 && self.octets()[1] == 0 && self.octets()[2] == 0
  977. && self.octets()[3] != 9 && self.octets()[3] != 10
  978. )
  979. || self.unstable_is_documentation()
  980. || self.unstable_is_benchmarking()
  981. || self.unstable_is_reserved()
  982. || self.is_broadcast())
  983. }
  984. #[inline]
  985. fn unstable_is_shared(&self) -> bool {
  986. self.octets()[0] == 100 && (self.octets()[1] & 0b1100_0000 == 0b0100_0000)
  987. }
  988. #[inline]
  989. fn unstable_is_benchmarking(&self) -> bool {
  990. self.octets()[0] == 198 && (self.octets()[1] & 0xfe) == 18
  991. }
  992. #[inline]
  993. fn unstable_is_reserved(&self) -> bool {
  994. self.octets()[0] & 240 == 240 && !self.is_broadcast()
  995. }
  996. #[inline]
  997. fn unstable_is_documentation(&self) -> bool {
  998. matches!(self.octets(), [192, 0, 2, _] | [198, 51, 100, _] | [203, 0, 113, _])
  999. }
  1000. }
  1001. impl UnstableFeatureIp for Ipv6Addr {
  1002. fn unstable_is_global(&self) -> bool {
  1003. !(self.is_unspecified()
  1004. || self.is_loopback()
  1005. // IPv4-mapped Address (`::ffff:0:0/96`)
  1006. || matches!(self.segments(), [0, 0, 0, 0, 0, 0xffff, _, _])
  1007. // IPv4-IPv6 Translat. (`64:ff9b:1::/48`)
  1008. || matches!(self.segments(), [0x64, 0xff9b, 1, _, _, _, _, _])
  1009. // Discard-Only Address Block (`100::/64`)
  1010. || matches!(self.segments(), [0x100, 0, 0, 0, _, _, _, _])
  1011. // IETF Protocol Assignments (`2001::/23`)
  1012. || (matches!(self.segments(), [0x2001, b, _, _, _, _, _, _] if b < 0x200)
  1013. && !(
  1014. // Port Control Protocol Anycast (`2001:1::1`)
  1015. u128::from_be_bytes(self.octets()) == 0x2001_0001_0000_0000_0000_0000_0000_0001
  1016. // Traversal Using Relays around NAT Anycast (`2001:1::2`)
  1017. || u128::from_be_bytes(self.octets()) == 0x2001_0001_0000_0000_0000_0000_0000_0002
  1018. // AMT (`2001:3::/32`)
  1019. || matches!(self.segments(), [0x2001, 3, _, _, _, _, _, _])
  1020. // AS112-v6 (`2001:4:112::/48`)
  1021. || matches!(self.segments(), [0x2001, 4, 0x112, _, _, _, _, _])
  1022. // ORCHIDv2 (`2001:20::/28`)
  1023. // Drone Remote ID Protocol Entity Tags (DETs) Prefix (`2001:30::/28`)`
  1024. || matches!(self.segments(), [0x2001, b, _, _, _, _, _, _] if (0x20..=0x3F).contains(&b))
  1025. ))
  1026. // 6to4 (`2002::/16`) – it's not explicitly documented as globally reachable,
  1027. // IANA says N/A.
  1028. || matches!(self.segments(), [0x2002, _, _, _, _, _, _, _])
  1029. || self.unstable_is_documentation()
  1030. // Segment Routing (SRv6) SIDs (`5f00::/16`)
  1031. || matches!(self.segments(), [0x5f00, ..])
  1032. || self.is_unique_local()
  1033. || self.is_unicast_link_local())
  1034. }
  1035. #[inline]
  1036. fn unstable_is_shared(&self) -> bool {
  1037. // Noop for ipv6
  1038. false
  1039. }
  1040. #[inline]
  1041. fn unstable_is_benchmarking(&self) -> bool {
  1042. (self.segments()[0] == 0x2001) && (self.segments()[1] == 0x2) && (self.segments()[2] == 0)
  1043. }
  1044. #[inline]
  1045. fn unstable_is_reserved(&self) -> bool {
  1046. // Noop for ipv6
  1047. false
  1048. }
  1049. #[inline]
  1050. fn unstable_is_documentation(&self) -> bool {
  1051. matches!(self.segments(), [0x2001, 0xdb8, ..] | [0x3fff, 0..=0x0fff, ..])
  1052. }
  1053. }
  1054. #[cfg(test)]
  1055. mod tests {
  1056. use super::*;
  1057. fn make_hosts() -> HostsPtr {
  1058. let settings = Settings::default();
  1059. Hosts::new(Arc::new(AsyncRwLock::new(settings)))
  1060. }
  1061. #[test]
  1062. fn test_is_local_host() {
  1063. let hosts = make_hosts();
  1064. let local = vec![
  1065. "tcp://localhost:1234",
  1066. "tcp://127.0.0.1:1234",
  1067. "tcp+tls://[::1]:1234",
  1068. "tcp://192.168.10.65:1234",
  1069. ];
  1070. for url in local {
  1071. assert!(hosts.is_local_host(&Url::parse(url).unwrap()), "{url} should be local");
  1072. }
  1073. let remote = vec![
  1074. "https://dyne.org:443",
  1075. "tcp://77.168.10.65:2222",
  1076. "tcp://[2345:0425:2CA1::5673:23b5]:1234",
  1077. ];
  1078. for url in remote {
  1079. assert!(!hosts.is_local_host(&Url::parse(url).unwrap()), "{url} should be remote");
  1080. }
  1081. }
  1082. #[test]
  1083. fn test_container_operations() {
  1084. let container = HostContainer::new();
  1085. let url = Url::parse("tcp://test.com:1234").unwrap();
  1086. let now = UNIX_EPOCH.elapsed().unwrap().as_secs();
  1087. // Store and retrieve
  1088. container.store(HostColor::Grey, url.clone(), now);
  1089. assert!(container.contains(HostColor::Grey, &url));
  1090. assert!(!container.contains(HostColor::White, &url));
  1091. // Move atomically
  1092. container.move_host(&url, now, HostColor::White).unwrap();
  1093. assert!(!container.contains(HostColor::Grey, &url));
  1094. assert!(container.contains(HostColor::White, &url));
  1095. // Remove
  1096. container.remove(HostColor::White, &url);
  1097. assert!(!container.contains(HostColor::White, &url));
  1098. }
  1099. #[test]
  1100. fn test_contains_any() {
  1101. let container = HostContainer::new();
  1102. let url = Url::parse("tcp://test.com:1234").unwrap();
  1103. let now = UNIX_EPOCH.elapsed().unwrap().as_secs();
  1104. container.store(HostColor::Gold, url.clone(), now);
  1105. assert!(container.contains_any(&[HostColor::Grey, HostColor::Gold], &url));
  1106. assert!(!container.contains_any(&[HostColor::Grey, HostColor::White], &url));
  1107. }
  1108. #[test]
  1109. fn test_host_state_transitions() {
  1110. let valid = [
  1111. (HostState::Free(0), HostState::Insert),
  1112. (HostState::Free(0), HostState::Refine),
  1113. (HostState::Free(0), HostState::Connect),
  1114. (HostState::Suspend, HostState::Refine),
  1115. (HostState::Move, HostState::Suspend),
  1116. ];
  1117. for (from, to) in valid {
  1118. assert!(from.try_transition(to).is_ok());
  1119. }
  1120. let invalid = [
  1121. (HostState::Insert, HostState::Connect),
  1122. (HostState::Refine, HostState::Insert),
  1123. (HostState::Suspend, HostState::Connect),
  1124. ];
  1125. for (from, to) in invalid {
  1126. assert!(from.try_transition(to).is_err());
  1127. }
  1128. }
  1129. #[test]
  1130. fn test_random_channel_empty() {
  1131. let hosts = make_hosts();
  1132. assert!(hosts.random_channel().is_none());
  1133. }
  1134. #[test]
  1135. fn test_block_all_ports() {
  1136. let hosts = make_hosts();
  1137. let with_port = Url::parse("tcp+tls://example.com:333").unwrap();
  1138. let without_port = Url::parse("tcp+tls://blocked.com").unwrap();
  1139. hosts.container.store(HostColor::Black, with_port.clone(), 0);
  1140. hosts.container.store(HostColor::Black, without_port.clone(), 0);
  1141. let test_url = Url::parse("tcp+tls://blocked.com:9999").unwrap();
  1142. assert!(hosts.block_all_ports(&test_url));
  1143. let test_url2 = Url::parse("tcp+tls://example.com:9999").unwrap();
  1144. assert!(!hosts.block_all_ports(&test_url2));
  1145. }
  1146. #[test]
  1147. fn test_refresh() {
  1148. let container = HostContainer::new();
  1149. let old_time = 1720000000u64;
  1150. let now = UNIX_EPOCH.elapsed().unwrap().as_secs();
  1151. // Add old entries
  1152. for i in 0..5 {
  1153. let url = Url::parse(&format!("tcp://old{i}.com:123")).unwrap();
  1154. container.store(HostColor::Dark, url, old_time);
  1155. }
  1156. // Add new entries
  1157. for i in 0..5 {
  1158. let url = Url::parse(&format!("tcp://new{i}.com:123")).unwrap();
  1159. container.store(HostColor::Dark, url, now);
  1160. }
  1161. container.refresh(HostColor::Dark, 86400);
  1162. let all = container.fetch_all(HostColor::Dark);
  1163. assert_eq!(all.len(), 5);
  1164. assert!(all.iter().all(|(_, ls)| *ls > old_time));
  1165. }
  1166. #[test]
  1167. fn test_transport_mixing() {
  1168. let hosts = HostContainer::mix_host(
  1169. &Url::parse("tcp://dark.fi:28880").unwrap(),
  1170. &["tor".to_string(), "tcp".to_string()],
  1171. &["tcp".to_string()],
  1172. &Url::parse("socks5://127.0.0.1:9050").ok(),
  1173. &None,
  1174. );
  1175. assert_eq!(hosts.len(), 1);
  1176. assert_eq!(hosts[0].scheme(), "tor");
  1177. }
  1178. #[test]
  1179. fn test_prune_registry() {
  1180. let hosts = make_hosts();
  1181. let now = UNIX_EPOCH.elapsed().unwrap().as_secs();
  1182. // Insert an entry that should be pruned (old Free)
  1183. let old_url = Url::parse("tcp://old.example.com:123").unwrap();
  1184. let old_age = now.saturating_sub(super::REGISTRY_PRUNE_AGE_SECS + 1000);
  1185. hosts.registry.lock().insert(old_url.clone(), HostState::Free(old_age));
  1186. // Insert an entry that should NOT be pruned (recent Free)
  1187. let new_url = Url::parse("tcp://new.example.com:123").unwrap();
  1188. hosts.registry.lock().insert(new_url.clone(), HostState::Free(now));
  1189. // Insert an entry that should NOT be pruned (non-Free state)
  1190. let active_url = Url::parse("tcp://active.example.com:123").unwrap();
  1191. hosts.registry.lock().insert(active_url.clone(), HostState::Connect);
  1192. assert_eq!(hosts.registry.lock().len(), 3);
  1193. let pruned = hosts.prune_registry();
  1194. assert_eq!(pruned, 1);
  1195. let registry = hosts.registry.lock();
  1196. assert_eq!(registry.len(), 2);
  1197. assert!(!registry.contains_key(&old_url));
  1198. assert!(registry.contains_key(&new_url));
  1199. assert!(registry.contains_key(&active_url));
  1200. }
  1201. }