ant_node_manager/cmd/
nat_detection.rs1use crate::{
10 VerbosityLevel, config::get_node_registry_path, helpers::download_and_extract_release,
11};
12use ant_bootstrap::ContactsFetcher;
13use ant_releases::{AntReleaseRepoActions, ReleaseType};
14use ant_service_management::{NatDetectionStatus, NodeRegistryManager};
15use color_eyre::eyre::{Context, Result, bail};
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 && let Some(status) = node_registry.nat_status.read().await.as_ref() {
56 if verbosity != VerbosityLevel::Minimal {
57 println!("NAT status has already been set as: {status:?}");
58 }
59 debug!("NAT status already set as: {status:?}, skipping.");
60 return Ok(());
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 let (path, _) = download_and_extract_release(
68 ReleaseType::NatDetection,
69 url,
70 version,
71 &*release_repo,
72 verbosity,
73 None,
74 )
75 .await?;
76 path
77 };
78
79 if verbosity != VerbosityLevel::Minimal {
80 println!("Running NAT detection. This can take a while..");
81 }
82 debug!(
83 "Running NAT detection with binary path: {:?}",
84 nat_detection_path
85 );
86
87 let servers_arg = servers
88 .iter()
89 .map(|a| a.to_string())
90 .collect::<Vec<_>>()
91 .join(",");
92 let trace_enabled = tracing::level_enabled!(tracing::Level::TRACE);
93 let timeout_duration = Duration::from_secs(NAT_DETECTION_TIMEOUT_SECS);
94 debug!(
95 "NAT detection timeout set to {} seconds",
96 NAT_DETECTION_TIMEOUT_SECS
97 );
98
99 let output = timeout(
100 timeout_duration,
101 task::spawn_blocking(move || -> Result<i32> {
102 let mut command = Command::new(&nat_detection_path);
103 command.stdout(Stdio::piped()).stderr(Stdio::null());
104 command.arg(servers_arg);
105 if trace_enabled {
106 command.arg("-vvvv");
107 }
108
109 let mut child = command
110 .spawn()
111 .wrap_err("Failed to spawn NAT detection process")?;
112
113 if trace_enabled && let Some(ref mut stdout) = child.stdout {
114 let reader = BufReader::new(stdout);
115 for line in reader.lines() {
116 let line = line?;
117 let clean_line = strip_ansi_escapes(&line);
118 trace!("{clean_line}");
119 }
120 }
121
122 let status = child
123 .wait()
124 .wrap_err("Failed to wait on NAT detection process")?;
125 Ok(status.code().unwrap_or(-1))
126 }),
127 )
128 .await;
129
130 let exit_code = match output {
131 Ok(Ok(code)) => code,
132 Ok(Err(e)) => bail!("Failed to detect NAT status, exit code: {:?}", e),
133 Err(_) => {
134 debug!(
135 "NAT detection timed out after {} seconds",
136 NAT_DETECTION_TIMEOUT_SECS
137 );
138 bail!(
139 "NAT detection timed out after {} seconds",
140 NAT_DETECTION_TIMEOUT_SECS
141 );
142 }
143 };
144
145 let status = match exit_code {
146 Ok(10) => NatDetectionStatus::Public,
147 Ok(11) => NatDetectionStatus::UPnP,
148 Ok(12) => NatDetectionStatus::Private,
149 code => bail!("Failed to detect NAT status, exit code: {:?}", code),
150 };
151
152 if verbosity != VerbosityLevel::Minimal {
153 println!("NAT status has been found to be: {status:?}");
154 }
155
156 *node_registry.nat_status.write().await = Some(status);
157 node_registry.save().await?;
158
159 Ok(())
160}
161
162fn strip_ansi_escapes(input: &str) -> String {
163 let mut output = String::new();
164 let mut chars = input.chars();
165 while let Some(c) = chars.next() {
166 if c == '\x1b' {
167 for next_char in chars.by_ref() {
168 if next_char.is_ascii_lowercase() || next_char.is_ascii_uppercase() {
169 break;
170 }
171 }
172 } else {
173 output.push(c);
174 }
175 }
176 output
177}