pretty print and ability to save file added

Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
This commit is contained in:
Uncle Stretch 2024-12-28 19:23:42 +03:00
parent b3cd0d6386
commit 00579b38b2
Signed by: str3tch
GPG Key ID: 84F3190747EE79AA
5 changed files with 66 additions and 37 deletions

10
Cargo.lock generated
View File

@ -991,6 +991,7 @@ dependencies = [
"maxminddb", "maxminddb",
"parity-scale-codec", "parity-scale-codec",
"pin-project", "pin-project",
"pretty-table",
"primitive-types", "primitive-types",
"thiserror 2.0.9", "thiserror 2.0.9",
"tokio", "tokio",
@ -2432,6 +2433,15 @@ dependencies = [
"zerocopy", "zerocopy",
] ]
[[package]]
name = "pretty-table"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ebabdeeb9dde45278a6fd4caf2ec417c8c8d81b75d4450dc8d8305c25a4ac1d"
dependencies = [
"thiserror 1.0.69",
]
[[package]] [[package]]
name = "primitive-types" name = "primitive-types"
version = "0.13.1" version = "0.13.1"

View File

@ -15,6 +15,7 @@ ip_network = "0.4.1"
libp2p = { version = "0.52.0", features = ["dns", "identify", "kad", "macros", "mdns", "noise", "ping", "tcp", "tokio", "yamux", "websocket", "request-response"] } libp2p = { version = "0.52.0", features = ["dns", "identify", "kad", "macros", "mdns", "noise", "ping", "tcp", "tokio", "yamux", "websocket", "request-response"] }
maxminddb = "0.24.0" maxminddb = "0.24.0"
pin-project = "1.1.7" pin-project = "1.1.7"
pretty-table = "0.1.3"
primitive-types = { version = "0.13.1", default-features = false, features = ["codec", "scale-info", "serde"] } primitive-types = { version = "0.13.1", default-features = false, features = ["codec", "scale-info", "serde"] }
thiserror = "2.0.9" thiserror = "2.0.9"
tokio = { version = "1.42.0", features = ["macros", "time", "rt-multi-thread"] } tokio = { version = "1.42.0", features = ["macros", "time", "rt-multi-thread"] }

View File

@ -1,16 +1,18 @@
use std::{collections::HashMap, net::IpAddr}; use std::{collections::HashMap, net::IpAddr};
use pretty_table::prelude::*;
use ip_network::IpNetwork;
use trust_dns_resolver::{ use trust_dns_resolver::{
config::{ResolverConfig, ResolverOpts}, config::{ResolverConfig, ResolverOpts},
TokioAsyncResolver, TokioAsyncResolver,
}; };
use maxminddb::{geoip2::City, Reader as GeoIpReader}; use maxminddb::{geoip2::City, Reader as GeoIpReader};
use libp2p::{ use libp2p::{
identify::Info, multiaddr::Protocol, identify::Info, multiaddr::Protocol, Multiaddr, PeerId
PeerId
}; };
pub struct Locator<'a> { pub struct Locator<'a> {
maybe_filename: &'a Option<String>,
peers: &'a HashMap<&'a PeerId, &'a Info>, peers: &'a HashMap<&'a PeerId, &'a Info>,
db: maxminddb::Reader<&'static [u8]>, db: maxminddb::Reader<&'static [u8]>,
} }
@ -18,24 +20,39 @@ pub struct Locator<'a> {
impl<'a> Locator<'a> { impl<'a> Locator<'a> {
const CITY_DATA: &'static [u8] = include_bytes!("../artifacts/GeoLite2-City.mmdb"); const CITY_DATA: &'static [u8] = include_bytes!("../artifacts/GeoLite2-City.mmdb");
pub fn new(peers: &'a HashMap<&'a PeerId, &'a Info>) -> Self { pub fn new(
peers: &'a HashMap<&'a PeerId, &'a Info>,
maybe_filename: &'a Option<String>,
) -> Self {
Self { Self {
peers, peers,
maybe_filename,
db: GeoIpReader::from_source(Self::CITY_DATA) db: GeoIpReader::from_source(Self::CITY_DATA)
.expect("City data is always valid; qed"), .expect("City data is always valid; qed"),
} }
} }
fn is_public_adress(&self, addr: &Multiaddr) -> bool {
let ip = match addr.iter().next() {
Some(Protocol::Ip4(ip)) => IpNetwork::from(ip),
Some(Protocol::Ip6(ip)) => IpNetwork::from(ip),
Some(Protocol::Dns(_)) | Some(Protocol::Dns4(_)) | Some(Protocol::Dns6(_)) => return true,
_ => return false,
};
ip.is_global()
}
pub async fn locate_peers(&self) { pub async fn locate_peers(&self) {
let mut geo_peers: HashMap<PeerId, String> = HashMap::new(); let mut geo_peers = Vec::new();
let resolver = TokioAsyncResolver::tokio( let resolver = TokioAsyncResolver::tokio(
ResolverConfig::default(), ResolverConfig::default(),
ResolverOpts::default(), ResolverOpts::default(),
); );
for (peer, info) in self.peers { for (_, info) in self.peers {
for addr in &info.listen_addrs { for addr in &info.listen_addrs {
if !self.is_public_adress(addr) { continue; }
let located = match addr.iter().next() { let located = match addr.iter().next() {
Some(Protocol::Ip4(ip)) => self.locate(IpAddr::V4(ip)), Some(Protocol::Ip4(ip)) => self.locate(IpAddr::V4(ip)),
Some(Protocol::Ip6(ip)) => self.locate(IpAddr::V6(ip)), Some(Protocol::Ip6(ip)) => self.locate(IpAddr::V6(ip)),
@ -50,14 +67,22 @@ impl<'a> Locator<'a> {
_ => continue, _ => continue,
}; };
let Some(located) = located else { continue; }; geo_peers.push(vec![
geo_peers.insert(**peer, located); addr.to_string(),
located.unwrap_or_default(),
]);
} }
} }
for (peer, location) in geo_peers { match &self.maybe_filename {
println!("\tPeer={peer} location={location}"); Some(filename) => {
if let Err(err) = write_table_to_file(filename.clone(), geo_peers) {
tracing::error!("Could not write to file {} because of: {err}", &filename);
} }
println!("Results saved to {}", &filename);
},
None => print_table!(geo_peers),
};
} }
fn locate(&self, ip: IpAddr) -> Option<String> { fn locate(&self, ip: IpAddr) -> Option<String> {

View File

@ -29,6 +29,9 @@ struct Opts {
value_parser = parse_duration, value_parser = parse_duration,
)] )]
timeout: std::time::Duration, timeout: std::time::Duration,
#[clap(short, long, default_value = None, value_parser)]
filename: Option<String>,
} }
fn parse_duration(arg: &str) -> Result<std::time::Duration, std::num::ParseIntError> { fn parse_duration(arg: &str) -> Result<std::time::Duration, std::num::ParseIntError> {
@ -48,6 +51,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.with_genesis(args.genesis) .with_genesis(args.genesis)
.with_bootnodes(args.bootnodes) .with_bootnodes(args.bootnodes)
.with_timeout(args.timeout) .with_timeout(args.timeout)
.with_filename(args.filename)
.build()? .build()?
.walk_around() .walk_around()
.await .await

View File

@ -6,14 +6,12 @@ use std::{
use futures::StreamExt; use futures::StreamExt;
use codec::Decode; use codec::Decode;
use ip_network::IpNetwork;
use primitive_types::H256; use primitive_types::H256;
use libp2p::{ use libp2p::{
identify::Info, identity, kad::{ identify::Info, identity, kad::{
Event as KademliaEvent, GetClosestPeersError, GetClosestPeersOk, Event as KademliaEvent, GetClosestPeersError, GetClosestPeersOk,
QueryId, QueryResult, QueryId, QueryResult,
}, },
multiaddr::Protocol,
swarm::{Config as SwarmConfig, SwarmEvent}, swarm::{Config as SwarmConfig, SwarmEvent},
Multiaddr, PeerId, Swarm Multiaddr, PeerId, Swarm
}; };
@ -37,6 +35,7 @@ use super::p2p::{
pub struct Walker { pub struct Walker {
genesis: String, genesis: String,
timeout: Duration, timeout: Duration,
maybe_filename: Option<String>,
swarm: Swarm<Behaviour>, swarm: Swarm<Behaviour>,
queries: HashSet<QueryId>, queries: HashSet<QueryId>,
discovered_with_address: HashMap<PeerId, HashSet<Multiaddr>>, discovered_with_address: HashMap<PeerId, HashSet<Multiaddr>>,
@ -159,22 +158,12 @@ impl Walker {
} }
} }
fn is_public_adress(&self, addr: &Multiaddr) -> bool {
let ip = match addr.iter().next() {
Some(Protocol::Ip4(ip)) => IpNetwork::from(ip),
Some(Protocol::Ip6(ip)) => IpNetwork::from(ip),
Some(Protocol::Dns(_)) | Some(Protocol::Dns4(_)) | Some(Protocol::Dns6(_)) => return true,
_ => return false,
};
ip.is_global()
}
pub async fn walk_around(&mut self) -> Result<(), Box<dyn Error>> { pub async fn walk_around(&mut self) -> Result<(), Box<dyn Error>> {
let _ = tokio::time::timeout(self.timeout, self.inner_walk()).await; let _ = tokio::time::timeout(self.timeout, self.inner_walk()).await;
println!("Number of dialed peers: {}", self.dialed_peers.len()); println!("Number of dialed peers : {}", self.dialed_peers.len());
println!("Total peers discovered: {}", self.discovered_with_address.len()); println!("Total peers discovered : {}", self.discovered_with_address.len());
println!("Peers with local iden.: {}", self.peer_details.len()); println!("Peers with local identity : {}", self.peer_details.len());
let infos: HashMap<_, _> = self let infos: HashMap<_, _> = self
.peer_details .peer_details
@ -185,19 +174,10 @@ impl Walker {
.any(|stream_proto| stream_proto.as_ref().contains(&self.genesis)) .any(|stream_proto| stream_proto.as_ref().contains(&self.genesis))
}) })
.collect(); .collect();
println!("Support correct genesis {}: {}", &self.genesis, infos.len()); println!("Support correct genesis : {}", infos.len());
let peers_with_public: HashMap<_, _> = infos
.iter()
.filter(|(_, info)| info.listen_addrs
.iter()
.any(|a| self.is_public_adress(&a)))
.collect();
println!("Peers with public address: {}", peers_with_public.len());
println!("Peers with private address: {}", infos.len() - peers_with_public.len());
println!("Peers with associated role: {}", self.peer_role.len()); println!("Peers with associated role: {}", self.peer_role.len());
Locator::new(&infos).locate_peers().await; Locator::new(&infos, &self.maybe_filename).locate_peers().await;
Ok(()) Ok(())
} }
@ -207,6 +187,7 @@ pub struct WalkerBuilder {
genesis: String, genesis: String,
bootnodes: Vec<String>, bootnodes: Vec<String>,
timeout: Duration, timeout: Duration,
maybe_filename: Option<String>,
} }
impl Default for WalkerBuilder { impl Default for WalkerBuilder {
@ -215,6 +196,7 @@ impl Default for WalkerBuilder {
genesis: Default::default(), genesis: Default::default(),
bootnodes: Default::default(), bootnodes: Default::default(),
timeout: Default::default(), timeout: Default::default(),
maybe_filename: None,
} }
} }
} }
@ -255,7 +237,8 @@ impl WalkerBuilder {
let discovery = DiscoveryBuilder::new() let discovery = DiscoveryBuilder::new()
.record_ttl(Some(Duration::from_secs(0))) .record_ttl(Some(Duration::from_secs(0)))
.provider_ttl(Some(Duration::from_secs(0))) .provider_ttl(Some(Duration::from_secs(0)))
.query_timeout(Duration::from_secs(5 * 60)) //.query_timeout(Duration::from_secs(5 * 60))
.query_timeout(self.timeout)
.build(local_peer_id, &self.genesis); .build(local_peer_id, &self.genesis);
let peer_info = PeerBehaviour::new(local_key.public()); let peer_info = PeerBehaviour::new(local_key.public());
@ -296,10 +279,16 @@ impl WalkerBuilder {
self self
} }
pub fn with_filename(mut self, maybe_filename: Option<String>) -> Self {
self.maybe_filename = maybe_filename;
self
}
pub fn build(&self) -> Result<Walker, Box<dyn Error>> { pub fn build(&self) -> Result<Walker, Box<dyn Error>> {
let swarm = self.create_swarm()?; let swarm = self.create_swarm()?;
Ok(Walker { Ok(Walker {
swarm, swarm,
maybe_filename: self.maybe_filename.clone(),
genesis: self.genesis.clone(), genesis: self.genesis.clone(),
timeout: self.timeout, timeout: self.timeout,
queries: HashSet::with_capacity(1024), queries: HashSet::with_capacity(1024),