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, NodeRegistryManager};
15use color_eyre::eyre::{bail, Context, Result};
16use libp2p::Multiaddr;
17use rand::seq::SliceRandom;
18use std::{
19 io::{BufRead, BufReader},
20 path::PathBuf,
21 process::{Command, Stdio},
22 time::Duration,
23};
24use tokio::{task, time::timeout};
25pub const NAT_DETECTION_TIMEOUT_SECS: u64 = 180;
26
27const NAT_DETECTION_SERVERS_LIST_URL: &str =
28 "https://sn-testnet.s3.eu-west-2.amazonaws.com/nat-detection-servers";
29pub async fn run_nat_detection(
30 servers: Option<Vec<Multiaddr>>,
31 force_run: bool,
32 path: Option<PathBuf>,
33 url: Option<String>,
34 version: Option<String>,
35 verbosity: VerbosityLevel,
36) -> Result<()> {
37 let servers = match servers {
38 Some(servers) => servers,
39 None => {
40 let mut contacts_fetcher = ContactsFetcher::new()?;
41 contacts_fetcher.ignore_peer_id(true);
42 contacts_fetcher.insert_endpoint(NAT_DETECTION_SERVERS_LIST_URL.parse()?);
43
44 let fetched = contacts_fetcher.fetch_addrs().await?;
45 fetched
46 .choose_multiple(&mut rand::thread_rng(), 10)
47 .cloned()
48 .collect::<Vec<_>>()
49 }
50 };
51 info!("Running nat detection with servers: {servers:?}");
52
53 let node_registry = NodeRegistryManager::load(&get_node_registry_path()?).await?;
54
55 if !force_run {
56 if let Some(status) = node_registry.nat_status.read().await.as_ref() {
57 if verbosity != VerbosityLevel::Minimal {
58 println!("NAT status has already been set as: {status:?}");
59 }
60 debug!("NAT status already set as: {status:?}, skipping.");
61 return Ok(());
62 }
63 }
64
65 let nat_detection_path = if let Some(path) = path {
66 path
67 } else {
68 let release_repo = <dyn AntReleaseRepoActions>::default_config();
69 let (path, _) = download_and_extract_release(
70 ReleaseType::NatDetection,
71 url,
72 version,
73 &*release_repo,
74 verbosity,
75 None,
76 )
77 .await?;
78 path
79 };
80
81 if verbosity != VerbosityLevel::Minimal {
82 println!("Running NAT detection. This can take a while..");
83 }
84 debug!(
85 "Running NAT detection with binary path: {:?}",
86 nat_detection_path
87 );
88
89 let servers_arg = servers
90 .iter()
91 .map(|a| a.to_string())
92 .collect::<Vec<_>>()
93 .join(",");
94 let trace_enabled = tracing::level_enabled!(tracing::Level::TRACE);
95 let timeout_duration = Duration::from_secs(NAT_DETECTION_TIMEOUT_SECS);
96 debug!(
97 "NAT detection timeout set to {} seconds",
98 NAT_DETECTION_TIMEOUT_SECS
99 );
100
101 let output = timeout(
102 timeout_duration,
103 task::spawn_blocking(move || -> Result<i32> {
104 let mut command = Command::new(&nat_detection_path);
105 command.stdout(Stdio::piped()).stderr(Stdio::null());
106 command.arg(servers_arg);
107 if trace_enabled {
108 command.arg("-vvvv");
109 }
110
111 let mut child = command
112 .spawn()
113 .wrap_err("Failed to spawn NAT detection process")?;
114
115 if trace_enabled {
116 if let Some(ref mut stdout) = child.stdout {
117 let reader = BufReader::new(stdout);
118 for line in reader.lines() {
119 let line = line?;
120 let clean_line = strip_ansi_escapes(&line);
121 trace!("{clean_line}");
122 }
123 }
124 }
125
126 let status = child
127 .wait()
128 .wrap_err("Failed to wait on NAT detection process")?;
129 Ok(status.code().unwrap_or(-1))
130 }),
131 )
132 .await;
133
134 let exit_code = match output {
135 Ok(Ok(code)) => code,
136 Ok(Err(e)) => bail!("Failed to detect NAT status, exit code: {:?}", e),
137 Err(_) => {
138 debug!(
139 "NAT detection timed out after {} seconds",
140 NAT_DETECTION_TIMEOUT_SECS
141 );
142 bail!(
143 "NAT detection timed out after {} seconds",
144 NAT_DETECTION_TIMEOUT_SECS
145 );
146 }
147 };
148
149 let status = match exit_code {
150 Ok(10) => NatDetectionStatus::Public,
151 Ok(11) => NatDetectionStatus::UPnP,
152 Ok(12) => NatDetectionStatus::Private,
153 code => bail!("Failed to detect NAT status, exit code: {:?}", code),
154 };
155
156 if verbosity != VerbosityLevel::Minimal {
157 println!("NAT status has been found to be: {status:?}");
158 }
159
160 *node_registry.nat_status.write().await = Some(status);
161 node_registry.save().await?;
162
163 Ok(())
164}
165
166fn strip_ansi_escapes(input: &str) -> String {
167 let mut output = String::new();
168 let mut chars = input.chars();
169 while let Some(c) = chars.next() {
170 if c == '\x1b' {
171 for next_char in chars.by_ref() {
172 if next_char.is_ascii_lowercase() || next_char.is_ascii_uppercase() {
173 break;
174 }
175 }
176 } else {
177 output.push(c);
178 }
179 }
180 output
181}