ant_node_manager/cmd/
nat_detection.rs1use crate::{
10 config::get_node_registry_path, helpers::download_and_extract_release, VerbosityLevel,
11};
12use ant_bootstrap::ContactsFetcher;
13use ant_releases::{AntReleaseRepoActions, ReleaseType};
14use ant_service_management::{NatDetectionStatus, NodeRegistry};
15use color_eyre::eyre::{bail, OptionExt, Result};
16use libp2p::Multiaddr;
17use rand::seq::SliceRandom;
18use std::{
19 io::{BufRead, BufReader},
20 path::PathBuf,
21 process::{Command, Stdio},
22};
23
24const NAT_DETECTION_SERVERS_LIST_URL: &str =
25 "https://sn-testnet.s3.eu-west-2.amazonaws.com/nat-detection-servers";
26
27pub async fn run_nat_detection(
28 servers: Option<Vec<Multiaddr>>,
29 force_run: bool,
30 path: Option<PathBuf>,
31 url: Option<String>,
32 version: Option<String>,
33 verbosity: VerbosityLevel,
34) -> Result<()> {
35 let servers = match servers {
36 Some(servers) => servers,
37 None => {
38 let mut contacts_fetcher = ContactsFetcher::new()?;
39 contacts_fetcher.ignore_peer_id(true);
40 contacts_fetcher.insert_endpoint(NAT_DETECTION_SERVERS_LIST_URL.parse()?);
41
42 let servers = contacts_fetcher.fetch_addrs().await?;
43
44 servers
45 .choose_multiple(&mut rand::thread_rng(), 10)
46 .cloned()
47 .collect::<Vec<_>>()
48 }
49 };
50 info!("Running nat detection with servers: {servers:?}");
51 let mut node_registry = NodeRegistry::load(&get_node_registry_path()?)?;
52
53 if !force_run {
54 if let Some(status) = node_registry.nat_status {
55 if verbosity != VerbosityLevel::Minimal {
56 println!("NAT status has already been set as: {status:?}");
57 }
58 debug!("NAT status has already been set as: {status:?}, returning.");
59 return Ok(());
60 }
61 }
62
63 let nat_detection_path = if let Some(path) = path {
64 path
65 } else {
66 let release_repo = <dyn AntReleaseRepoActions>::default_config();
67
68 let (nat_detection_path, _) = download_and_extract_release(
69 ReleaseType::NatDetection,
70 url,
71 version,
72 &*release_repo,
73 verbosity,
74 None,
75 )
76 .await?;
77 nat_detection_path
78 };
79
80 if verbosity != VerbosityLevel::Minimal {
81 println!("Running NAT detection. This can take a while..");
82 }
83 debug!("Running NAT detection with path: {nat_detection_path:?}. This can take a while..");
84
85 let mut command = Command::new(nat_detection_path);
86 command.stdout(Stdio::piped()).stderr(Stdio::null());
87 command.arg(
88 servers
89 .iter()
90 .map(|addr| addr.to_string())
91 .collect::<Vec<String>>()
92 .join(","),
93 );
94 if tracing::level_enabled!(tracing::Level::TRACE) {
95 command.arg("-vvvv");
96 }
97 let mut child = command.spawn()?;
98
99 if tracing::level_enabled!(tracing::Level::TRACE) {
101 if let Some(ref mut stdout) = child.stdout {
103 let reader = BufReader::new(stdout);
104 for line in reader.lines() {
105 let line = line?;
106 let clean_line = strip_ansi_escapes(&line);
109 trace!("{clean_line}");
110 }
111 }
112 }
113
114 let status = child.wait()?;
115 let status = match status.code().ok_or_eyre("Failed to get the exit code")? {
116 10 => NatDetectionStatus::Public,
117 11 => NatDetectionStatus::UPnP,
118 12 => NatDetectionStatus::Private,
119 code => bail!("Failed to detect NAT status, exit code: {code}"),
120 };
121
122 if verbosity != VerbosityLevel::Minimal {
123 println!("NAT status has been found to be: {status:?}");
124 }
125
126 node_registry.nat_status = Some(status);
127 node_registry.save()?;
128
129 Ok(())
130}
131
132fn strip_ansi_escapes(input: &str) -> String {
133 let mut output = String::new();
134 let mut chars = input.chars();
135 while let Some(c) = chars.next() {
136 if c == '\x1b' {
137 for next_char in chars.by_ref() {
138 if next_char.is_ascii_lowercase() || next_char.is_ascii_uppercase() {
139 break;
140 }
141 }
142 } else {
143 output.push(c);
144 }
145 }
146 output
147}