| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640 |
- /* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2024 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
- use std::{
- collections::{HashMap, HashSet},
- fs,
- fs::File,
- sync::Arc,
- time::UNIX_EPOCH,
- };
- use log::{debug, error, info, trace, warn};
- use rand::{prelude::IteratorRandom, rngs::OsRng, Rng};
- use smol::lock::RwLock;
- use url::Url;
- use super::super::{p2p::P2pPtr, settings::SettingsPtr};
- use crate::{
- system::{Subscriber, SubscriberPtr, Subscription},
- util::{
- file::{load_file, save_file},
- path::expand_path,
- },
- Result,
- };
- /// Atomic pointer to hosts object
- pub type HostsPtr = Arc<Hosts>;
- // An array containing all possible local host strings
- // TODO: This could perhaps be more exhaustive?
- pub const LOCAL_HOST_STRS: [&str; 2] = ["localhost", "localhost.localdomain"];
- const WHITELIST_MAX_LEN: usize = 5000;
- const GREYLIST_MAX_LEN: usize = 2000;
- /// Manages a store of network addresses
- // TODO: Test the performance overhead of using vectors for white/grey/anchor lists.
- // TODO: Check whether anchorlist has a max size in Monero.
- // TODO: we can probably clean up a lot of the repetitive code in this module.
- pub struct Hosts {
- /// Intermediary node list that is periodically probed and updated to whitelist.
- pub greylist: RwLock<Vec<(Url, u64)>>,
- /// Recently seen hosts. Shared with other nodes.
- pub whitelist: RwLock<Vec<(Url, u64)>>,
- /// Nodes to which we have already been able to establish a connection.
- pub anchorlist: RwLock<Vec<(Url, u64)>>,
- /// Set of stored addresses that are quarantined.
- /// We quarantine peers we've been unable to connect to, but we keep them
- /// around so we can potentially try them again, up to n tries. This should
- /// be helpful in order to self-heal the p2p connections in case we have an
- /// Internet interrupt (goblins unplugging cables)
- quarantine: RwLock<HashMap<Url, usize>>,
- /// Peers we reject from connecting to
- rejected: RwLock<HashSet<String>>,
- /// Peers that are currently being removed from the hostlist
- migrating: RwLock<HashSet<Url>>,
- /// Subscriber listening for store updates
- store_subscriber: SubscriberPtr<usize>,
- /// Pointer to configured P2P settings
- settings: SettingsPtr,
- }
- impl Hosts {
- /// Create a new hosts list>
- pub fn new(settings: SettingsPtr) -> HostsPtr {
- Arc::new(Self {
- greylist: RwLock::new(Vec::new()),
- whitelist: RwLock::new(Vec::new()),
- anchorlist: RwLock::new(Vec::new()),
- quarantine: RwLock::new(HashMap::new()),
- rejected: RwLock::new(HashSet::new()),
- migrating: RwLock::new(HashSet::new()),
- store_subscriber: Subscriber::new(),
- settings,
- })
- }
- /// Loops through greylist addresses to find an outbound address that we can
- /// connect to. Check whether the address is valid by making sure it isn't
- /// our own inbound address, then checks whether it is already connected
- /// (exists) or connecting (pending).
- /// Lastly adds matching address to the pending list.
- pub async fn greylist_fetch_address(&self, transports: &[String]) -> Vec<(Url, u64)> {
- trace!(target: "store", "greylist_fetch_address() [START]");
- // Collect hosts
- let mut hosts = vec![];
- // If transport mixing is enabled, then for example we're allowed to
- // use tor:// to connect to tcp:// and tor+tls:// to connect to tcp+tls://.
- // However, **do not** mix tor:// and tcp+tls://, nor tor+tls:// and tcp://.
- let transport_mixing = self.settings.transport_mixing;
- macro_rules! mix_transport {
- ($a:expr, $b:expr) => {
- if transports.contains(&$a.to_string()) && transport_mixing {
- let mut a_to_b =
- self.greylist_fetch_with_schemes(&[$b.to_string()], None).await;
- for (addr, last_seen) in a_to_b.iter_mut() {
- addr.set_scheme($a).unwrap();
- hosts.push((addr.clone(), last_seen.clone()));
- }
- }
- };
- }
- mix_transport!("tor", "tcp");
- mix_transport!("tor+tls", "tcp+tls");
- mix_transport!("nym", "tcp");
- mix_transport!("nym+tls", "tcp+tls");
- // And now the actual requested transports
- for (addr, last_seen) in self.greylist_fetch_with_schemes(transports, None).await {
- hosts.push((addr, last_seen));
- }
- hosts
- }
- /// Loops through whitelist addresses to find an outbound address that we can
- /// connect to. Check whether the address is valid by making sure it isn't
- /// our own inbound address, then checks whether it is already connected
- /// (exists) or connecting (pending).
- /// Lastly adds matching address to the pending list.
- pub async fn whitelist_fetch_address(&self, transports: &[String]) -> Vec<(Url, u64)> {
- trace!(target: "store", "whitelist_fetch_address() [START]");
- // Collect hosts
- let mut hosts = vec![];
- // If transport mixing is enabled, then for example we're allowed to
- // use tor:// to connect to tcp:// and tor+tls:// to connect to tcp+tls://.
- // However, **do not** mix tor:// and tcp+tls://, nor tor+tls:// and tcp://.
- let transport_mixing = self.settings.transport_mixing;
- macro_rules! mix_transport {
- ($a:expr, $b:expr) => {
- if transports.contains(&$a.to_string()) && transport_mixing {
- let mut a_to_b =
- self.whitelist_fetch_with_schemes(&[$b.to_string()], None).await;
- for (addr, last_seen) in a_to_b.iter_mut() {
- addr.set_scheme($a).unwrap();
- hosts.push((addr.clone(), last_seen.clone()));
- }
- }
- };
- }
- mix_transport!("tor", "tcp");
- mix_transport!("tor+tls", "tcp+tls");
- mix_transport!("nym", "tcp");
- mix_transport!("nym+tls", "tcp+tls");
- // And now the actual requested transports
- for (addr, last_seen) in self.whitelist_fetch_with_schemes(transports, None).await {
- hosts.push((addr, last_seen));
- }
- trace!(target: "store::whitelist_fetch_address()",
- "Grabbed hosts, length: {}", hosts.len());
- hosts
- }
- /// Loops through anchorlist addresses to find an outbound address that we can
- /// connect to. Check whether the address is valid by making sure it isn't
- /// our own inbound address, then checks whether it is already connected
- /// (exists) or connecting (pending).
- /// Lastly adds matching address to the pending list.
- pub async fn anchorlist_fetch_address(&self, transports: &[String]) -> Vec<(Url, u64)> {
- trace!(target: "store", "anchorlist_fetch_address() [START]");
- // Collect hosts
- let mut hosts = vec![];
- // If transport mixing is enabled, then for example we're allowed to
- // use tor:// to connect to tcp:// and tor+tls:// to connect to tcp+tls://.
- // However, **do not** mix tor:// and tcp+tls://, nor tor+tls:// and tcp://.
- let transport_mixing = self.settings.transport_mixing;
- macro_rules! mix_transport {
- ($a:expr, $b:expr) => {
- if transports.contains(&$a.to_string()) && transport_mixing {
- let mut a_to_b =
- self.anchorlist_fetch_with_schemes(&[$b.to_string()], None).await;
- for (addr, last_seen) in a_to_b.iter_mut() {
- addr.set_scheme($a).unwrap();
- hosts.push((addr.clone(), last_seen.clone()));
- }
- }
- };
- }
- mix_transport!("tor", "tcp");
- mix_transport!("tor+tls", "tcp+tls");
- mix_transport!("nym", "tcp");
- mix_transport!("nym+tls", "tcp+tls");
- // And now the actual requested transports
- for (addr, last_seen) in self.anchorlist_fetch_with_schemes(transports, None).await {
- hosts.push((addr, last_seen));
- }
- trace!(target: "store::anchorlist_fetch_address()",
- "Grabbed hosts, length: {}", hosts.len());
- hosts
- }
- /// Check whether:
- /// * We already have this connection established
- /// * We already have this configured as a manual peer
- /// * This address is already pending a connection
- pub async fn check_address_with_lock(
- &self,
- p2p: P2pPtr,
- hosts: Vec<(Url, u64)>,
- ) -> Option<(Url, u64)> {
- // Try to find an unused host in the set.
- for (host, last_seen) in hosts {
- debug!(target: "store::check_address_with_lock()",
- "Starting checks");
- // Check if we already have this connection established
- if p2p.exists(&host).await {
- debug!(
- target: "store::check_address_with_lock()",
- "Host '{}' exists so skipping",
- host
- );
- continue
- }
- // Check if we already have this configured as a manual peer
- if self.settings.peers.contains(&host) {
- debug!(
- target: "store::check_address_with_lock()",
- "Host '{}' configured as manual peer so skipping",
- host
- );
- continue
- }
- // Obtain a lock on this address to prevent duplicate connection
- if !p2p.add_pending(&host).await {
- debug!(
- target: "store::check_address_with_lock()",
- "Host '{}' pending so skipping",
- host
- );
- continue
- }
- debug!(
- target: "store::check_address_with_lock()",
- "Found valid host {}",
- host
- );
- return Some((host.clone(), last_seen))
- }
- None
- }
- /// Upgrade a connection to the anchorlist. Called after a connection has been successfully
- /// established in Outbound and Manual sessions.
- pub async fn upgrade_host(&self, addr: &Url) {
- let last_seen = UNIX_EPOCH.elapsed().unwrap().as_secs();
- self.anchorlist_store_or_update(&[(addr.clone(), last_seen)]).await;
- }
- /// Remove an entry from the hostlist. Called when we cannot establish a connection to a host or
- /// when a pre-existing connection disconnects.
- pub async fn remove_host(&self, addr: &Url) {
- debug!(target: "store::remove_host", "Removing host {}", addr);
- self.mark_migrating(addr).await;
- // Remove channel from anchorlist
- if self.anchorlist_contains(addr).await {
- debug!(target: "store::remove_host", "Removing from anchorlist {}", addr);
- let index = self
- .get_anchorlist_index_at_addr(addr.clone())
- .await
- .expect("Expected anchorlist index to exist");
- self.anchorlist_remove(addr, index).await;
- }
- // Remove channel from whitelist
- if self.whitelist_contains(addr).await {
- debug!(target: "store::remove_host", "Removing from whitelist {}", addr);
- let index = self
- .get_whitelist_index_at_addr(addr.clone())
- .await
- .expect("Expected whitelist index to exist");
- self.whitelist_remove(addr, index).await;
- }
- // Remove channel the greylist
- if self.greylist_contains(addr).await {
- debug!(target: "store::remove_host", "Removing from greylist {}", addr);
- let index = self
- .get_greylist_index_at_addr(addr.clone())
- .await
- .expect("Expected greylist index to exist");
- self.greylist_remove(addr, index).await;
- }
- self.unmark_migrating(addr).await;
- }
- /// Stores an address on the greylist or updates its last_seen field if we already
- /// have the address.
- pub async fn greylist_store_or_update(&self, addrs: &[(Url, u64)]) {
- trace!(target: "store::greylist_store_or_update()", "[START]");
- // Filter addresses before writing to the greylist.
- let filtered_addrs = self.filter_addresses(addrs).await;
- let filtered_addrs_len = filtered_addrs.len();
- if filtered_addrs.is_empty() {
- debug!(target: "store::greylist_store_or_update()", "Filtered out all received addresses");
- }
- for (addr, last_seen) in filtered_addrs {
- if !self.greylist_contains(&addr).await {
- debug!(target: "store::greylist_store_or_update()",
- "We do not have this entry in the hostlist. Adding to store...");
- self.greylist_store(addr.clone(), last_seen).await;
- } else {
- debug!(target: "store::greylist_store_or_update()",
- "We have this entry in the greylist. Updating last seen...");
- let index = self
- .get_greylist_index_at_addr(addr.clone())
- .await
- .expect("Expected greylist entry to exist");
- self.greylist_update_last_seen(&addr, last_seen, index).await;
- self.store_subscriber.notify(filtered_addrs_len).await;
- }
- }
- }
- /// Stores an address on the whitelist or updates its last_seen field if we already
- /// have the address.
- pub async fn whitelist_store_or_update(&self, addrs: &[(Url, u64)]) {
- trace!(target: "store::whitelist_store_or_update()", "[START]");
- // No address filtering for whitelist (whitelist is created from greylist)
- for (addr, last_seen) in addrs {
- if !self.whitelist_contains(addr).await {
- debug!(target: "store::whitelist_store_or_update()",
- "We do not have this entry in the whitelist. Adding to store...");
- self.whitelist_store(addr.clone(), *last_seen).await;
- } else {
- debug!(target: "store::whitelist_store_or_update()",
- "We have this entry in the whitelist. Updating last seen...");
- let index = self
- .get_whitelist_index_at_addr(addr.clone())
- .await
- .expect("Expected whitelist entry to exist");
- self.whitelist_update_last_seen(addr, *last_seen, index).await;
- }
- }
- }
- /// Stores an address on the anchorlist or updates its last_seen field if we already
- /// have the address.
- pub async fn anchorlist_store_or_update(&self, addrs: &[(Url, u64)]) {
- trace!(target: "store::anchor_store_or_update()", "[START]");
- // No address filtering for anchorlist (contains addresses we have already connected to)
- for (addr, last_seen) in addrs {
- if !self.anchorlist_contains(addr).await {
- debug!(target: "store::anchorlist_store_or_update()",
- "We do not have this entry in the whitelist. Adding to store...");
- self.anchorlist_store(addr.clone(), *last_seen).await;
- } else {
- debug!(target: "store::anchorlist_store_or_update()",
- "We have this entry in the anchorlist. Updating last seen...");
- let index = self
- .get_anchorlist_index_at_addr(addr.clone())
- .await
- .expect("Expected anchorlist entry to exist");
- self.anchorlist_update_last_seen(addr, *last_seen, index).await;
- }
- }
- }
- /// Append host to the greylist. Called on learning of a new peer.
- pub async fn greylist_store(&self, addr: Url, last_seen: u64) {
- trace!(target: "store::greylist_store()", "hosts::greylist_store() [START]");
- let mut greylist = self.greylist.write().await;
- // Remove oldest element if the greylist reaches max size.
- if greylist.len() == GREYLIST_MAX_LEN {
- let last_entry = greylist.pop().unwrap();
- debug!(target: "store::greylist_store()", "Greylist reached max size. Removed {:?}", last_entry);
- }
- debug!(target: "store::greylist_store()", "Inserting {}", addr);
- greylist.push((addr, last_seen));
- // Sort the list by last_seen.
- greylist.sort_by_key(|entry| entry.1);
- greylist.reverse();
- trace!(target: "store::greylist_store()", "[END]");
- }
- /// Append host to the whitelist. Called after a successful interaction with an online peer.
- pub async fn whitelist_store(&self, addr: Url, last_seen: u64) {
- trace!(target: "store::whitelist_store()", "[START]");
- let mut whitelist = self.whitelist.write().await;
- // Remove oldest element if the whitelist reaches max size.
- if whitelist.len() == WHITELIST_MAX_LEN {
- let last_entry = whitelist.pop().unwrap();
- debug!(target: "store::whitelist_store()", "Whitelist reached max size. Removed {:?}", last_entry);
- }
- trace!(target: "store::whitelist_store()", "Inserting {}. Last seen {:?}", addr, last_seen);
- whitelist.push((addr, last_seen));
- // Sort the list by last_seen.
- whitelist.sort_by_key(|entry| entry.1);
- whitelist.reverse();
- trace!(target: "store::whitelist_store()", "[END]");
- }
- /// Append host to the anchorlist. Called after we have successfully established a connection
- /// to a peer.
- pub async fn anchorlist_store(&self, addr: Url, last_seen: u64) {
- trace!(target: "store::anchorlist_store()", "[START]");
- let mut anchorlist = self.anchorlist.write().await;
- trace!(target: "store::anchorlist_store()", "Inserting {}", addr);
- anchorlist.push((addr, last_seen));
- // Sort the list by last_seen.
- anchorlist.sort_by_key(|entry| entry.1);
- anchorlist.reverse();
- trace!(target: "store::anchorlist_store()", "[END]");
- }
- /// Update the last_seen field of a peer on the greylist.
- pub async fn greylist_update_last_seen(&self, addr: &Url, last_seen: u64, index: usize) {
- trace!(target: "store::greylist_update_last_seen()", "[START]");
- let mut greylist = self.greylist.write().await;
- greylist[index] = (addr.clone(), last_seen);
- // Sort the list by last_seen.
- greylist.sort_by_key(|entry| entry.1);
- greylist.reverse();
- trace!(target: "store::greylist_update_last_seen()", "[END]");
- }
- /// Update the last_seen field of a peer on the whitelist.
- pub async fn whitelist_update_last_seen(&self, addr: &Url, last_seen: u64, index: usize) {
- trace!(target: "store::whitelist_update_last_seen()", "[START]");
- let mut whitelist = self.whitelist.write().await;
- whitelist[index] = (addr.clone(), last_seen);
- // Sort the list by last_seen.
- whitelist.sort_by_key(|entry| entry.1);
- whitelist.reverse();
- trace!(target: "store::whitelist_update_last_seen()", "[END]");
- }
- /// Update the last_seen field of a peer on the anchorlist.
- pub async fn anchorlist_update_last_seen(&self, addr: &Url, last_seen: u64, index: usize) {
- trace!(target: "store::anchorlist_update_last_seen()", "[START]");
- let mut anchorlist = self.anchorlist.write().await;
- anchorlist[index] = (addr.clone(), last_seen);
- // Sort the list by last_seen.
- anchorlist.sort_by_key(|entry| entry.1);
- anchorlist.reverse();
- trace!(target: "store::anchorlist_update_last_seen()", "[END]");
- }
- /// Remove an entry from the greylist.
- pub async fn greylist_remove(&self, addr: &Url, index: usize) {
- debug!(target: "store::greylist_remove", "Removing peer {} from greylist", addr);
- self.greylist.write().await.remove(index);
- }
- /// Remove an entry from the whitelist.
- pub async fn whitelist_remove(&self, addr: &Url, index: usize) {
- debug!(target: "store::whitelist_remove", "Removing peer {} from whitelist", addr);
- self.whitelist.write().await.remove(index);
- }
- /// Remove an entry from the anchorlist.
- pub async fn anchorlist_remove(&self, addr: &Url, index: usize) {
- debug!(target: "store::anchorlist_remove", "Removing peer {} from anchorlist", addr);
- self.anchorlist.write().await.remove(index);
- }
- pub async fn subscribe_store(&self) -> Result<Subscription<usize>> {
- let sub = self.store_subscriber.clone().subscribe().await;
- Ok(sub)
- }
- // Verify whether a URL is local.
- // NOTE: This function is stateless and not specific to
- // `Hosts`. For this reason, it might make more sense
- // to move this function to a more appropriate location
- // in the codebase.
- /// Check whether a URL is local host
- pub async fn is_local_host(&self, url: Url) -> bool {
- // Reject Urls without host strings.
- if url.host_str().is_none() {
- return false
- }
- // We do this hack in order to parse IPs properly.
- // https://github.com/whatwg/url/issues/749
- let addr = Url::parse(&url.as_str().replace(url.scheme(), "http")).unwrap();
- // Filter private IP ranges
- match addr.host().unwrap() {
- url::Host::Ipv4(ip) => {
- if !ip.is_global() {
- return true
- }
- }
- url::Host::Ipv6(ip) => {
- if !ip.is_global() {
- return true
- }
- }
- url::Host::Domain(d) => {
- if LOCAL_HOST_STRS.contains(&d) {
- return true
- }
- }
- }
- false
- }
- /// Filter given addresses based on certain rulesets and validity.
- async fn filter_addresses(&self, addrs: &[(Url, u64)]) -> Vec<(Url, u64)> {
- trace!(target: "store::filter_addresses()", "Filtering addrs: {:?}", addrs);
- let mut ret = vec![];
- let localnet = self.settings.localnet;
- 'addr_loop: for (addr_, last_seen) in addrs {
- // Validate that the format is `scheme://host_str:port`
- if addr_.host_str().is_none() ||
- addr_.port().is_none() ||
- addr_.cannot_be_a_base() ||
- addr_.path_segments().is_some()
- {
- continue
- }
- if self.is_rejected(addr_).await {
- debug!(target: "store::filter_addresses()", "Peer {} is rejected", addr_);
- continue
- }
- let host_str = addr_.host_str().unwrap();
- if !localnet {
- // Our own external addresses should never enter the hosts set.
- for ext in &self.settings.external_addrs {
- if host_str == ext.host_str().unwrap() {
- continue 'addr_loop
- }
- }
- }
- // On localnet, make sure ours ports don't enter the host set.
- for ext in &self.settings.external_addrs {
- if addr_.port() == ext.port() {
- continue 'addr_loop
- }
- }
- // We do this hack in order to parse IPs properly.
- // https://github.com/whatwg/url/issues/749
- let addr = Url::parse(&addr_.as_str().replace(addr_.scheme(), "http")).unwrap();
- // Filter non-global ranges if we're not allowing localnet.
- // Should never be allowed in production, so we don't really care
- // about some of them (e.g. 0.0.0.0, or broadcast, etc.).
- if !localnet && self.is_local_host(addr).await {
- continue
- }
- match addr_.scheme() {
- // Validate that the address is an actual onion.
- #[cfg(feature = "p2p-tor")]
- "tor" | "tor+tls" => {
- use std::str::FromStr;
- if tor_hscrypto::pk::HsId::from_str(host_str).is_err() {
- continue
- }
- trace!(target: "store::filter_addresses()", "[Tor] Valid: {}", host_str);
- }
- #[cfg(feature = "p2p-nym")]
- "nym" | "nym+tls" => continue, // <-- Temp skip
- #[cfg(feature = "p2p-tcp")]
- "tcp" | "tcp+tls" => {
- trace!(target: "store::filter_addresses()", "[TCP] Valid: {}", host_str);
- }
- _ => continue,
- }
- ret.push((addr_.clone(), *last_seen));
- }
- ret
- }
- /// Quarantine a peer.
- /// If they've been quarantined for more than a configured limit, forget them.
- pub async fn quarantine(&self, url: &Url) {
- debug!(target: "store::remove()", "Quarantining peer {}", url);
- // Remove from the entire hosts set
- self.remove_host(url).await;
- let mut q = self.quarantine.write().await;
- if let Some(retries) = q.get_mut(url) {
- *retries += 1;
- debug!(target: "net::hosts::quarantine()", "Peer {} quarantined {} times", url, retries);
- if *retries == self.settings.hosts_quarantine_limit {
- debug!(target: "net::hosts::quarantine()", "Banning peer {}", url);
- q.remove(url);
- self.mark_rejected(url).await;
- }
- } else {
- debug!(target: "net::hosts::remove()", "Added peer {} to quarantine", url);
- q.insert(url.clone(), 0);
- }
- }
- /// Check if a given peer (URL) is in the set of rejected hosts
- pub async fn is_rejected(&self, peer: &Url) -> bool {
- // Skip lookup for UNIX sockets and localhost connections
- // as they should never belong to the list of rejected URLs.
- let Some(hostname) = peer.host_str() else { return false };
- if self.is_local_host(peer.clone()).await {
- return false
- }
- self.rejected.read().await.contains(hostname)
- }
- /// Mark a peer as rejected by adding it to the set of rejected URLs.
- pub async fn mark_rejected(&self, peer: &Url) {
- // We ignore UNIX sockets here so we will just work
- // with stuff that has host_str().
- if let Some(hostname) = peer.host_str() {
- // Localhost connections should not be rejected
- // This however allows any Tor and Nym connections.
- if self.is_local_host(peer.clone()).await {
- return
- }
- self.rejected.write().await.insert(hostname.to_string());
- }
- }
- /// Unmark a rejected peer
- pub async fn unmark_rejected(&self, peer: &Url) {
- if let Some(hostname) = peer.host_str() {
- self.rejected.write().await.remove(hostname);
- }
- }
- /// Peer that is currently being removed from hostlists.
- pub async fn is_migrating(&self, peer: &Url) -> bool {
- self.migrating.read().await.contains(peer)
- }
- /// Mark a peer as currently migrating.
- pub async fn mark_migrating(&self, peer: &Url) {
- self.migrating.write().await.insert(peer.clone());
- }
- /// Unmark a migrating peer.
- pub async fn unmark_migrating(&self, peer: &Url) {
- self.migrating.write().await.remove(peer);
- }
- /// Check if the greylist is empty.
- pub async fn is_empty_greylist(&self) -> bool {
- self.greylist.read().await.is_empty()
- }
- /// Check if the whitelist is empty.
- pub async fn is_empty_whitelist(&self) -> bool {
- self.whitelist.read().await.is_empty()
- }
- /// Check if the anchorlist is empty.
- pub async fn is_empty_anchorlist(&self) -> bool {
- self.anchorlist.read().await.is_empty()
- }
- /// Check if the hostlist is empty.
- pub async fn is_empty_hostlist(&self) -> bool {
- self.is_empty_greylist().await &&
- self.is_empty_whitelist().await &&
- self.is_empty_anchorlist().await
- }
- /// Check if host is in the greylist
- pub async fn greylist_contains(&self, addr: &Url) -> bool {
- self.greylist.read().await.iter().any(|(u, _t)| u == addr)
- }
- /// Check if host is in the whitelist
- pub async fn whitelist_contains(&self, addr: &Url) -> bool {
- self.whitelist.read().await.iter().any(|(u, _t)| u == addr)
- }
- /// Check if host is in the anchorlist
- pub async fn anchorlist_contains(&self, addr: &Url) -> bool {
- self.anchorlist.read().await.iter().any(|(u, _t)| u == addr)
- }
- /// Get the index for a given addr on the greylist.
- pub async fn get_greylist_index_at_addr(&self, addr: Url) -> Option<usize> {
- self.greylist.read().await.iter().position(|a| a.0 == addr)
- }
- /// Get the index for a given addr on the whitelist.
- pub async fn get_whitelist_index_at_addr(&self, addr: Url) -> Option<usize> {
- self.whitelist.read().await.iter().position(|a| a.0 == addr)
- }
- /// Get the index for a given addr on the anchorlist.
- pub async fn get_anchorlist_index_at_addr(&self, addr: Url) -> Option<usize> {
- self.anchorlist.read().await.iter().position(|a| a.0 == addr)
- }
- /// Get the entry for a given addr on the whitelist.
- pub async fn get_whitelist_entry_at_addr(&self, addr: &Url) -> Option<(Url, u64)> {
- self.whitelist
- .read()
- .await
- .iter()
- .find(|(url, _)| url == addr)
- .map(|(url, time)| (url.clone(), *time))
- }
- /// Get the entry for a given addr on the anchorlist.
- pub async fn get_anchorlist_entry_at_addr(&self, addr: &Url) -> Option<(Url, u64)> {
- self.anchorlist
- .read()
- .await
- .iter()
- .find(|(url, _)| url == addr)
- .map(|(url, time)| (url.clone(), *time))
- }
- /// Return all known whitelisted hosts
- pub async fn whitelist_fetch_all(&self) -> Vec<(Url, u64)> {
- self.whitelist.read().await.iter().cloned().collect()
- }
- /// Return all known greylisted hosts
- pub async fn greylist_fetch_all(&self) -> Vec<(Url, u64)> {
- self.greylist.read().await.iter().cloned().collect()
- }
- /// Return all known anchorlisted hosts
- pub async fn anchorlist_fetch_all(&self) -> Vec<(Url, u64)> {
- self.anchorlist.read().await.iter().cloned().collect()
- }
- /// Return all greylist and anchorlist hosts. Called on stop().
- /// Note: we do not return whitelist entries here since whitelist entries must go via the
- /// greylist refinery in the lifetime of the p2p network.
- pub async fn hostlist_fetch_safe(&self) -> HashMap<String, Vec<(Url, u64)>> {
- let mut hostlist = HashMap::new();
- hostlist.insert(
- "anchorlist".to_string(),
- self.anchorlist.read().await.iter().cloned().collect(),
- );
- hostlist
- .insert("greylist".to_string(), self.greylist.read().await.iter().cloned().collect());
- hostlist
- }
- /// Get up to n random peers from the whitelist.
- pub async fn whitelist_fetch_n_random(&self, n: u32) -> Vec<(Url, u64)> {
- let n = n as usize;
- if n == 0 {
- return vec![]
- }
- let addrs = self.whitelist.read().await;
- let urls = addrs.iter().choose_multiple(&mut OsRng, n.min(addrs.len()));
- urls.iter().map(|&url| url.clone()).collect()
- }
- /// Get a random peer from the greylist.
- pub async fn greylist_fetch_random(&self) -> ((Url, u64), usize) {
- let greylist = self.greylist.read().await;
- let position = rand::thread_rng().gen_range(0..greylist.len());
- let entry = &greylist[position];
- (entry.clone(), position)
- }
- /// Get a random peer from the whitelist.
- pub async fn whitelist_fetch_random(&self) -> ((Url, u64), usize) {
- let whitelist = self.whitelist.read().await;
- let position = rand::thread_rng().gen_range(0..whitelist.len());
- let entry = &whitelist[position];
- (entry.clone(), position)
- }
- /// Get the oldest entry from the whitelist.
- pub async fn whitelist_fetch_last(&self) -> ((Url, u64), usize) {
- let whitelist = self.whitelist.read().await;
- let position = whitelist.len() - 1;
- let entry = &whitelist[position];
- (entry.clone(), position)
- }
- /// Get a random greylist peer that matches the given transport schemes.
- pub async fn greylist_fetch_random_with_schemes(&self) -> Option<((Url, u64), usize)> {
- trace!(target: "store::greylist_fetch_random_with_schemes", "[START]");
- // Retrieve all peers corresponding to that transport schemes
- let schemes = &self.settings.allowed_transports;
- let greylist = self.greylist_fetch_with_schemes(schemes, None).await;
- if greylist.is_empty() {
- return None
- }
- let position = rand::thread_rng().gen_range(0..greylist.len());
- let entry = &greylist[position];
- Some((entry.clone(), position))
- }
- /// Get up to n random greylist peers. Schemes are not taken into account.
- pub async fn greylist_fetch_n_random(&self, n: u32) -> Vec<(Url, u64)> {
- trace!(target: "store::greylist_fetch_n_random", "[START]");
- let n = n as usize;
- if n == 0 {
- return vec![]
- }
- let mut hosts = vec![];
- let greylist = self.greylist.read().await;
- for (addr, last_seen) in greylist.iter() {
- hosts.push((addr.clone(), *last_seen));
- }
- if hosts.is_empty() {
- debug!(target: "store::greylist_fetch_n_random", "No greylist entries found!");
- return hosts
- }
- // Grab random ones
- let urls = hosts.iter().choose_multiple(&mut OsRng, n.min(hosts.len()));
- urls.iter().map(|&url| url.clone()).collect()
- }
- /// Get up to n random greylist peers that match the given transport schemes.
- pub async fn greylist_fetch_n_random_with_schemes(
- &self,
- schemes: &[String],
- n: u32,
- ) -> Vec<(Url, u64)> {
- let n = n as usize;
- if n == 0 {
- return vec![]
- }
- trace!(target: "store::greylist_fetch_n_random_with_schemes", "[START]");
- // Retrieve all peers corresponding to that transport schemes
- let hosts = self.greylist_fetch_with_schemes(schemes, None).await;
- if hosts.is_empty() {
- debug!(target: "store::greylist_fetch_n_random_with_schemes",
- "No such schemes found on greylist!");
- return hosts
- }
- // Grab random ones
- let urls = hosts.iter().choose_multiple(&mut OsRng, n.min(hosts.len()));
- urls.iter().map(|&url| url.clone()).collect()
- }
- /// Get up to n random whitelist peers that match the given transport schemes.
- pub async fn whitelist_fetch_n_random_with_schemes(
- &self,
- schemes: &[String],
- n: u32,
- ) -> Vec<(Url, u64)> {
- let n = n as usize;
- if n == 0 {
- return vec![]
- }
- trace!(target: "store::whitelist_fetch_n_random_with_schemes", "[START]");
- // Retrieve all peers corresponding to that transport schemes
- let hosts = self.whitelist_fetch_with_schemes(schemes, None).await;
- if hosts.is_empty() {
- debug!(target: "store::whitelist_fetch_n_random_with_schemes",
- "No such schemes found on whitelist!");
- return hosts
- }
- // Grab random ones
- let urls = hosts.iter().choose_multiple(&mut OsRng, n.min(hosts.len()));
- urls.iter().map(|&url| url.clone()).collect()
- }
- /// Get up to n random anchorlist peers that match the given transport schemes.
- pub async fn anchorlist_fetch_n_random_with_schemes(
- &self,
- schemes: &[String],
- n: u32,
- ) -> Vec<(Url, u64)> {
- let n = n as usize;
- if n == 0 {
- return vec![]
- }
- trace!(target: "store::anchorlist_fetch_n_random_with_schemes", "[START]");
- // Retrieve all peers corresponding to that transport schemes
- let hosts = self.anchorlist_fetch_with_schemes(schemes, None).await;
- if hosts.is_empty() {
- debug!(target: "store::anchorlist_fetch_n_random_with_schemes",
- "No such schemes found on anchorlist!");
- return hosts
- }
- // Grab random ones
- let urls = hosts.iter().choose_multiple(&mut OsRng, n.min(hosts.len()));
- urls.iter().map(|&url| url.clone()).collect()
- }
- /// Get up to limit peers that don't match the given transport schemes from the greylist.
- /// If limit was not provided, return all matching peers.
- pub async fn greylist_fetch_excluding_schemes(
- &self,
- schemes: &[String],
- limit: Option<usize>,
- ) -> Vec<(Url, u64)> {
- let greylist = self.greylist.read().await;
- let mut limit = match limit {
- Some(l) => l.min(greylist.len()),
- None => greylist.len(),
- };
- let mut ret = vec![];
- if limit == 0 {
- return ret
- }
- for (addr, last_seen) in greylist.iter() {
- if !schemes.contains(&addr.scheme().to_string()) {
- ret.push((addr.clone(), *last_seen));
- limit -= 1;
- if limit == 0 {
- return ret
- }
- }
- }
- if ret.is_empty() {
- debug!(target: "store::greylist_fetch_excluding_schemes",
- "No such schemes found on greylist!")
- }
- ret
- }
- /// Get up to limit peers that don't match the given transport schemes from the whitelist.
- /// If limit was not provided, return all matching peers.
- pub async fn whitelist_fetch_excluding_schemes(
- &self,
- schemes: &[String],
- limit: Option<usize>,
- ) -> Vec<(Url, u64)> {
- let addrs = self.whitelist.read().await;
- let mut limit = match limit {
- Some(l) => l.min(addrs.len()),
- None => addrs.len(),
- };
- let mut ret = vec![];
- if limit == 0 {
- return ret
- }
- for (addr, last_seen) in addrs.iter() {
- if !schemes.contains(&addr.scheme().to_string()) {
- ret.push((addr.clone(), *last_seen));
- limit -= 1;
- if limit == 0 {
- return ret
- }
- }
- }
- if ret.is_empty() {
- debug!(target: "store::whiteist_fetch_excluding_schemes",
- "No such schemes found on whitelist!")
- }
- ret
- }
- /// Get up to n random whitelisted peers that don't match the given transport schemes from the
- /// hosts set.
- pub async fn whitelist_fetch_n_random_excluding_schemes(
- &self,
- schemes: &[String],
- n: u32,
- ) -> Vec<(Url, u64)> {
- let n = n as usize;
- if n == 0 {
- return vec![]
- }
- trace!(target: "store::whitelist_fetch_excluding_schemes", "[START]");
- // Retrieve all peers not corresponding to that transport schemes
- let hosts = self.whitelist_fetch_excluding_schemes(schemes, None).await;
- if hosts.is_empty() {
- debug!(target: "store::whitelist_fetch_n_random_excluding_schemes",
- "No such schemes found on whitelist!");
- return hosts
- }
- // Grab random ones
- let urls = hosts.iter().choose_multiple(&mut OsRng, n.min(hosts.len()));
- urls.iter().map(|&url| url.clone()).collect()
- }
- /// Get up to limit peers that match the given transport schemes from the greylist.
- /// If limit was not provided, return all matching peers.
- async fn greylist_fetch_with_schemes(
- &self,
- schemes: &[String],
- limit: Option<usize>,
- ) -> Vec<(Url, u64)> {
- trace!(target: "store::greylist_fetch_with_schemes", "[START]");
- let greylist = self.greylist.read().await;
- let mut limit = match limit {
- Some(l) => l.min(greylist.len()),
- None => greylist.len(),
- };
- let mut ret = vec![];
- if limit == 0 {
- return ret
- }
- for (addr, last_seen) in greylist.iter() {
- if schemes.contains(&addr.scheme().to_string()) {
- ret.push((addr.clone(), *last_seen));
- limit -= 1;
- if limit == 0 {
- debug!(target: "store::greylist_fetch_with_schemes",
- "Found matching scheme, returning {} grey addresses",
- ret.len());
- return ret
- }
- }
- }
- if ret.is_empty() {
- debug!(target: "store::greylist_fetch_with_schemes",
- "No such schemes found on greylist!")
- }
- trace!(target: "store::greylist_fetch_with_schemes", "END");
- ret
- }
- /// Get up to limit peers that match the given transport schemes from the whitelist.
- /// If limit was not provided, return all matching peers.
- async fn whitelist_fetch_with_schemes(
- &self,
- schemes: &[String],
- limit: Option<usize>,
- ) -> Vec<(Url, u64)> {
- trace!(target: "store::whitelist_fetch_with_schemes", "[START]");
- let whitelist = self.whitelist.read().await;
- let mut limit = match limit {
- Some(l) => l.min(whitelist.len()),
- None => whitelist.len(),
- };
- let mut ret = vec![];
- if limit == 0 {
- return ret
- }
- for (addr, last_seen) in whitelist.iter() {
- if schemes.contains(&addr.scheme().to_string()) {
- ret.push((addr.clone(), *last_seen));
- limit -= 1;
- if limit == 0 {
- debug!(target: "store::whitelist_fetch_with_schemes",
- "Found matching scheme, returning {} white addresses",
- ret.len());
- return ret
- }
- }
- }
- if ret.is_empty() {
- debug!(target: "store::whitelist_fetch_with_schemes",
- "No such schemes found on whitelist!")
- }
- trace!(target: "store::whitelist_fetch_with_schemes", "END");
- ret
- }
- /// Get up to limit peers that match the given transport schemes from the anchorlist.
- /// If limit was not provided, return all matching peers.
- async fn anchorlist_fetch_with_schemes(
- &self,
- schemes: &[String],
- limit: Option<usize>,
- ) -> Vec<(Url, u64)> {
- //trace!(target: "store::anchorlist_fetch_with_schemes", "[START]");
- let anchorlist = self.anchorlist.read().await;
- let mut limit = match limit {
- Some(l) => l.min(anchorlist.len()),
- None => anchorlist.len(),
- };
- let mut ret = vec![];
- if limit == 0 {
- return ret
- }
- for (addr, last_seen) in anchorlist.iter() {
- if schemes.contains(&addr.scheme().to_string()) {
- ret.push((addr.clone(), *last_seen));
- limit -= 1;
- if limit == 0 {
- debug!(target: "store::anchorlist_fetch_with_schemes",
- "Found matching scheme, returning {} anchor addresses",
- ret.len());
- return ret
- }
- }
- }
- if ret.is_empty() {
- warn!(target: "store::anchorlist_fetch_with_schemes",
- "No matching schemes found on anchorlist")
- }
- trace!(target: "store::anchorlist_fetch_with_schemes", "END");
- ret
- }
- /// Load the hostlist from a file.
- pub async fn load_hosts(&self) -> Result<()> {
- let path = expand_path(&self.settings.hostlist)?;
- if !path.exists() {
- if let Some(parent) = path.parent() {
- fs::create_dir_all(parent)?;
- }
- File::create(path.clone())?;
- }
- let contents = load_file(&path);
- if let Err(e) = contents {
- warn!(target: "store", "Failed retrieving saved hosts: {}", e);
- return Ok(())
- }
- for line in contents.unwrap().lines() {
- let data: Vec<&str> = line.split('\t').collect();
- let url = match Url::parse(data[1]) {
- Ok(u) => u,
- Err(e) => {
- debug!(target: "store", "load_hosts(): Skipping malformed URL {}", e);
- continue
- }
- };
- let last_seen = match data[2].parse::<u64>() {
- Ok(t) => t,
- Err(e) => {
- debug!(target: "store", "load_hosts(): Skipping malformed last seen {}", e);
- continue
- }
- };
- match data[0] {
- "greylist" => {
- self.greylist_store(url, last_seen).await;
- }
- "whitelist" => {
- self.whitelist_store(url, last_seen).await;
- }
- "anchorlist" => {
- self.anchorlist_store(url, last_seen).await;
- }
- _ => {
- debug!(target: "store", "load_hosts(): Malformed list name...");
- }
- }
- }
- Ok(())
- }
- /// Save the hostlist to a file. Whitelist gets written to the greylist to force
- /// whitelist entries through the refinery on start.
- pub async fn save_hosts(&self) -> Result<()> {
- let path = expand_path(&self.settings.hostlist)?;
- let mut tsv = String::new();
- let mut whitelist = vec![];
- // First gather all the whitelist entries we don't have in greylist.
- for (url, last_seen) in self.whitelist_fetch_all().await {
- if !self.greylist_contains(&url).await {
- whitelist.push((url, last_seen))
- }
- }
- // Collect the greylist and anchorlist entries, and append any whitelist entries to the
- // greylist before saving.
- for (name, mut list) in self.hostlist_fetch_safe().await {
- if name == *"greylist".to_string() {
- list.append(&mut whitelist)
- }
- for (url, last_seen) in list {
- tsv.push_str(&format!("{}\t{}\t{}\n", name, url, last_seen));
- }
- }
- if !tsv.eq("") {
- info!(target: "store", "Saving hosts to: {:?}",
- path);
- if let Err(e) = save_file(&path, &tsv) {
- error!(target: "store", "Failed saving hosts: {}", e);
- }
- }
- Ok(())
- }
- }
- #[cfg(test)]
- mod tests {
- use super::{
- super::super::{settings::Settings, P2p},
- *,
- };
- use crate::{net::hosts::refinery::ping_node, system::sleep};
- use smol::Executor;
- use std::{sync::Arc, time::UNIX_EPOCH};
- #[test]
- fn test_ping_node() {
- smol::block_on(async {
- let settings = Settings {
- localnet: false,
- external_addrs: vec![
- Url::parse("tcp://foo.bar:123").unwrap(),
- Url::parse("tcp://lol.cat:321").unwrap(),
- ],
- ..Default::default()
- };
- let ex = Arc::new(Executor::new());
- let p2p = P2p::new(settings, ex.clone()).await;
- let url = Url::parse("tcp://xeno.systems.wtf").unwrap();
- println!("Pinging node...");
- let task = ex.spawn(ping_node(url.clone(), p2p));
- ex.run(task).await;
- println!("Ping node complete!");
- });
- }
- #[test]
- fn test_is_local_host() {
- smol::block_on(async {
- let settings = Settings {
- localnet: false,
- external_addrs: vec![
- Url::parse("tcp://foo.bar:123").unwrap(),
- Url::parse("tcp://lol.cat:321").unwrap(),
- ],
- ..Default::default()
- };
- let hosts = Hosts::new(Arc::new(settings.clone()));
- let local_hosts: Vec<Url> = vec![
- Url::parse("tcp://localhost").unwrap(),
- Url::parse("tcp://127.0.0.1").unwrap(),
- Url::parse("tcp+tls://[::1]").unwrap(),
- Url::parse("tcp://localhost.localdomain").unwrap(),
- Url::parse("tcp://192.168.10.65").unwrap(),
- ];
- for host in local_hosts {
- eprintln!("{}", host);
- assert!(hosts.is_local_host(host).await);
- }
- let remote_hosts: Vec<Url> = vec![
- Url::parse("https://dyne.org").unwrap(),
- Url::parse("tcp://77.168.10.65:2222").unwrap(),
- Url::parse("tcp://[2345:0425:2CA1:0000:0000:0567:5673:23b5]").unwrap(),
- Url::parse("http://eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad.onion")
- .unwrap(),
- ];
- for host in remote_hosts {
- assert!(!(hosts.is_local_host(host).await))
- }
- });
- }
- #[test]
- fn test_greylist_store() {
- let last_seen = UNIX_EPOCH.elapsed().unwrap().as_secs();
- smol::block_on(async {
- let settings = Settings {
- localnet: false,
- external_addrs: vec![
- Url::parse("tcp://foo.bar:123").unwrap(),
- Url::parse("tcp://lol.cat:321").unwrap(),
- ],
- ..Default::default()
- };
- let hosts = Hosts::new(Arc::new(settings.clone()));
- for addr in settings.external_addrs {
- hosts.greylist_store(addr, last_seen).await;
- }
- assert!(!hosts.is_empty_greylist().await);
- let local_hosts = vec![
- (Url::parse("tcp://localhost:3921").unwrap()),
- (Url::parse("tor://[::1]:21481").unwrap()),
- (Url::parse("tcp://192.168.10.65:311").unwrap()),
- (Url::parse("tcp+tls://0.0.0.0:2312").unwrap()),
- (Url::parse("tcp://255.255.255.255:2131").unwrap()),
- ];
- for host in &local_hosts {
- hosts.greylist_store(host.clone(), last_seen).await;
- }
- assert!(!hosts.is_empty_greylist().await);
- let remote_hosts = vec![
- (Url::parse("tcp://dark.fi:80").unwrap()),
- (Url::parse("tcp://http.cat:401").unwrap()),
- (Url::parse("tcp://foo.bar:111").unwrap()),
- ];
- for host in &remote_hosts {
- hosts.greylist_store(host.clone(), last_seen).await;
- }
- assert!(hosts.greylist_contains(&remote_hosts[0]).await);
- assert!(hosts.greylist_contains(&remote_hosts[1]).await);
- assert!(hosts.greylist_contains(&remote_hosts[2]).await);
- });
- }
- #[test]
- fn test_whitelist_store() {
- smol::block_on(async {
- let settings = Settings {
- localnet: false,
- external_addrs: vec![
- Url::parse("tcp://foo.bar:123").unwrap(),
- Url::parse("tcp://lol.cat:321").unwrap(),
- ],
- ..Default::default()
- };
- let hosts = Hosts::new(Arc::new(settings.clone()));
- assert!(hosts.is_empty_whitelist().await);
- let url = Url::parse("tcp://dark.renaissance:333").unwrap();
- let last_seen = UNIX_EPOCH.elapsed().unwrap().as_secs();
- hosts.whitelist_store(url.clone(), last_seen).await;
- assert!(!hosts.is_empty_whitelist().await);
- assert!(hosts.whitelist_contains(&url).await);
- });
- }
- #[test]
- fn test_whitelist_get_last() {
- smol::block_on(async {
- let settings = Settings {
- localnet: false,
- external_addrs: vec![
- Url::parse("tcp://foo.bar:123").unwrap(),
- Url::parse("tcp://lol.cat:321").unwrap(),
- ],
- ..Default::default()
- };
- let hosts = Hosts::new(Arc::new(settings.clone()));
- // Build up a hostlist
- for i in 0..10 {
- sleep(1).await;
- let last_seen = UNIX_EPOCH.elapsed().unwrap().as_secs();
- let url = Url::parse(&format!("tcp://whitelist{}:123", i)).unwrap();
- hosts.whitelist_store(url.clone(), last_seen).await;
- }
- for (url, last_seen) in hosts.whitelist.read().await.iter() {
- println!("{} {}", url, last_seen);
- }
- let (entry, _position) = hosts.whitelist_fetch_last().await;
- println!("last entry: {} {}", entry.0, entry.1);
- });
- }
- #[test]
- fn test_hostlist_get_entry() {
- smol::block_on(async {
- let settings = Settings {
- localnet: false,
- external_addrs: vec![
- Url::parse("tcp://foo.bar:123").unwrap(),
- Url::parse("tcp://lol.cat:321").unwrap(),
- ],
- ..Default::default()
- };
- let hosts = Hosts::new(Arc::new(settings.clone()));
- let url = Url::parse("tcp://dark.renaissance:333").unwrap();
- let last_seen = UNIX_EPOCH.elapsed().unwrap().as_secs();
- hosts.whitelist_store(url.clone(), last_seen).await;
- hosts.anchorlist_store(url.clone(), last_seen).await;
- assert!(hosts.get_whitelist_entry_at_addr(&url).await.is_some());
- assert!(hosts.get_anchorlist_entry_at_addr(&url).await.is_some());
- });
- }
- #[test]
- fn test_remove() {
- smol::block_on(async {
- let settings = Settings {
- outbound_connections: 8,
- allowed_transports: vec!["tcp".to_string()],
- ..Default::default()
- };
- let hosts = Hosts::new(Arc::new(settings.clone()));
- let url = Url::parse("tcp://dark.renaissance:333").unwrap();
- let last_seen = UNIX_EPOCH.elapsed().unwrap().as_secs();
- hosts.whitelist_store(url.clone(), last_seen).await;
- sleep(1).await;
- let url = Url::parse("tcp://milady:333").unwrap();
- let last_seen = UNIX_EPOCH.elapsed().unwrap().as_secs();
- hosts.whitelist_store(url.clone(), last_seen).await;
- sleep(1).await;
- let url = Url::parse("tcp://king-ted:333").unwrap();
- let last_seen = UNIX_EPOCH.elapsed().unwrap().as_secs();
- hosts.whitelist_store(url.clone(), last_seen).await;
- for (url, last_seen) in hosts.whitelist.read().await.iter() {
- println!("{}, {}", url, last_seen);
- }
- let position = hosts.get_whitelist_index_at_addr(url.clone()).await.unwrap();
- hosts.whitelist_remove(&url, position).await;
- for (url, last_seen) in hosts.whitelist.read().await.iter() {
- println!("{}, {}", url, last_seen);
- }
- });
- }
- #[test]
- fn test_fetch_address() {
- smol::block_on(async {
- let mut hostlist = vec![];
- let mut grey_urls = vec![];
- let mut white_urls = vec![];
- let mut anchor_urls = vec![];
- let ex = Arc::new(Executor::new());
- let settings = Settings {
- outbound_connections: 8,
- allowed_transports: vec!["tcp".to_string()],
- ..Default::default()
- };
- let p2p = P2p::new(settings, ex.clone()).await;
- let hosts = p2p.hosts();
- // Build up a hostlist
- for i in 0..5 {
- let last_seen = UNIX_EPOCH.elapsed().unwrap().as_secs();
- hosts
- .anchorlist_store(
- Url::parse(&format!("tcp://anchorlist{}:123", i)).unwrap(),
- last_seen,
- )
- .await;
- hosts
- .whitelist_store(
- Url::parse(&format!("tcp://whitelist{}:123", i)).unwrap(),
- last_seen,
- )
- .await;
- hosts
- .greylist_store(
- Url::parse(&format!("tcp://greylist{}:123", i)).unwrap(),
- last_seen,
- )
- .await;
- grey_urls
- .push((Url::parse(&format!("tcp://greylist{}:123", i)).unwrap(), last_seen));
- white_urls
- .push((Url::parse(&format!("tcp://whitelist{}:123", i)).unwrap(), last_seen));
- anchor_urls
- .push((Url::parse(&format!("tcp://anchorlist{}:123", i)).unwrap(), last_seen));
- }
- assert!(!hosts.is_empty_anchorlist().await);
- assert!(!hosts.is_empty_whitelist().await);
- assert!(!hosts.is_empty_greylist().await);
- let transports = &p2p.settings().allowed_transports;
- let white_count =
- p2p.settings().outbound_connections * p2p.settings().white_connection_percent / 100;
- // Simulate the address selection logic found in outbound_session::fetch_address()
- for i in 0..8 {
- if i < p2p.settings().anchor_connection_count {
- if !hosts.anchorlist_fetch_address(transports).await.is_empty() {
- let addrs = hosts.anchorlist_fetch_address(transports).await;
- hostlist.push(addrs);
- }
- if !hosts.whitelist_fetch_address(transports).await.is_empty() {
- let addrs = hosts.whitelist_fetch_address(transports).await;
- hostlist.push(addrs);
- }
- if !hosts.greylist_fetch_address(transports).await.is_empty() {
- let addrs = hosts.greylist_fetch_address(transports).await;
- hostlist.push(addrs);
- }
- } else if i < white_count {
- if !hosts.whitelist_fetch_address(transports).await.is_empty() {
- let addrs = hosts.whitelist_fetch_address(transports).await;
- hostlist.push(addrs);
- }
- if !hosts.greylist_fetch_address(transports).await.is_empty() {
- let addrs = hosts.greylist_fetch_address(transports).await;
- hostlist.push(addrs);
- }
- } else if !hosts.greylist_fetch_address(transports).await.is_empty() {
- let addrs = hosts.greylist_fetch_address(transports).await;
- hostlist.push(addrs);
- }
- }
- //// Check we're returning the correct addresses.
- anchor_urls.sort();
- white_urls.sort();
- grey_urls.sort();
- hostlist[0].sort();
- hostlist[4].sort();
- hostlist[7].sort();
- assert!(anchor_urls == hostlist[0]);
- assert!(white_urls == hostlist[4]);
- assert!(grey_urls == hostlist[7]);
- })
- }
- }
|