forest/cli/subcommands/
net_cmd.rs

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