1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
// Copyright 2019-2024 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT
use crate::libp2p::{Multiaddr, Protocol};
use crate::rpc::{self, net::AddrInfo, prelude::*};
use ahash::{HashMap, HashSet};
use cid::multibase;
use clap::Subcommand;
use itertools::Itertools;
use crate::cli::subcommands::cli_error_and_die;
#[derive(Debug, Subcommand)]
pub enum NetCommands {
/// Lists `libp2p` swarm listener addresses
Listen,
/// Lists `libp2p` swarm network info
Info,
/// Lists `libp2p` swarm peers
Peers {
/// Print agent name
#[arg(short, long)]
agent: bool,
},
/// Connects to a peer by its peer ID and multi-addresses
Connect {
/// Multi-address (with `/p2p/` protocol)
address: String,
},
/// Disconnects from a peer by it's peer ID
Disconnect {
/// Peer ID to disconnect from
id: String,
},
/// Print information about reachability from the internet
Reachability,
}
impl NetCommands {
pub async fn run(self, client: rpc::Client) -> anyhow::Result<()> {
match self {
Self::Listen => {
let info = NetAddrsListen::call(&client, ()).await?;
let addresses: Vec<String> = info
.addrs
.iter()
.map(|addr| format!("{}/p2p/{}", addr, info.id))
.collect();
println!("{}", addresses.join("\n"));
Ok(())
}
Self::Info => {
let info = NetInfo::call(&client, ()).await?;
println!("forest libp2p swarm info:");
println!("num peers: {}", info.num_peers);
println!("num connections: {}", info.num_connections);
println!("num pending: {}", info.num_pending);
println!("num pending incoming: {}", info.num_pending_incoming);
println!("num pending outgoing: {}", info.num_pending_outgoing);
println!("num established: {}", info.num_established);
Ok(())
}
Self::Peers { agent } => {
let addrs = NetPeers::call(&client, ()).await?;
let peer_to_agents: HashMap<String, String> = if agent {
let agents = futures::future::join_all(
addrs
.iter()
.map(|info| NetAgentVersion::call(&client, (info.id.clone(),))),
)
.await
.into_iter()
.map(|res| res.unwrap_or_else(|_| "<agent unknown>".to_owned()));
HashMap::from_iter(
addrs
.iter()
.map(|info| info.id.to_owned())
.zip(agents.into_iter()),
)
} else {
HashMap::default()
};
let output: Vec<String> = addrs
.into_iter()
.filter_map(|info| {
let addresses: Vec<String> = info
.addrs
.into_iter()
.filter(|addr| match addr.iter().next().unwrap() {
Protocol::Ip4(ip_addr) => !ip_addr.is_loopback(),
Protocol::Ip6(ip_addr) => !ip_addr.is_loopback(),
_ => true,
})
.map(|addr| addr.to_string())
.unique()
.collect();
if addresses.is_empty() {
return None;
}
let result = format!("{}, [{}]", info.id, addresses.join(", "));
if agent {
Some(
[
result,
peer_to_agents
.get(&info.id)
.cloned()
.unwrap_or_else(|| "<agent unknown>".to_owned()),
]
.join(", "),
)
} else {
Some(result)
}
})
.collect();
println!("{}", output.join("\n"));
Ok(())
}
Self::Connect { address } => {
let addr: Multiaddr = address
.parse()
.map_err(|e| {
cli_error_and_die(format!("Error parsing multiaddr. Error was: {e}"), 1);
})
.expect("Parse provided multiaddr from string");
let mut id = "".to_owned();
for protocol in addr.iter() {
if let Protocol::P2p(p2p) = protocol {
id = multibase::encode(multibase::Base::Base58Btc, p2p.to_bytes());
id = id.split_off(1);
}
}
if id.is_empty() {
cli_error_and_die("Needs a /p2p/ protocol present in multiaddr", 1)
}
let addrs = HashSet::from_iter([addr]);
let addr_info = AddrInfo {
id: id.clone(),
addrs,
};
NetConnect::call(&client, (addr_info,)).await?;
println!("connect {id}: success");
Ok(())
}
Self::Disconnect { id } => {
NetDisconnect::call(&client, (id.clone(),)).await?;
println!("disconnect {id}: success");
Ok(())
}
Self::Reachability => {
let nat_status = NetAutoNatStatus::call(&client, ()).await?;
println!("AutoNAT status: {}", nat_status.reachability_as_str());
if let Some(public_addrs) = nat_status.public_addrs {
if !public_addrs.is_empty() {
// Format is compatible with Go code:
// `fmt.Println("Public address:", []string{"foo", "bar"})`
println!("Public address: [{}]", public_addrs.join(" "));
}
}
Ok(())
}
}
}
}