sn_node_manager/cmd/
nat_detection.rs1use crate::{
10 config::get_node_registry_path, helpers::download_and_extract_release, VerbosityLevel,
11};
12use color_eyre::eyre::{bail, OptionExt, Result};
13use libp2p::Multiaddr;
14use rand::seq::SliceRandom;
15use sn_peers_acquisition::get_peers_from_url;
16use sn_releases::{ReleaseType, SafeReleaseRepoActions};
17use sn_service_management::{NatDetectionStatus, NodeRegistry};
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 servers = get_peers_from_url(NAT_DETECTION_SERVERS_LIST_URL.parse()?).await?;
39
40 servers
41 .choose_multiple(&mut rand::thread_rng(), 10)
42 .cloned()
43 .collect::<Vec<_>>()
44 }
45 };
46 info!("Running nat detection with servers: {servers:?}");
47 let mut node_registry = NodeRegistry::load(&get_node_registry_path()?)?;
48
49 if !force_run {
50 if let Some(status) = node_registry.nat_status {
51 if verbosity != VerbosityLevel::Minimal {
52 println!("NAT status has already been set as: {status:?}");
53 }
54 debug!("NAT status has already been set as: {status:?}, returning.");
55 return Ok(());
56 }
57 }
58
59 let nat_detection_path = if let Some(path) = path {
60 path
61 } else {
62 let release_repo = <dyn SafeReleaseRepoActions>::default_config();
63
64 let (nat_detection_path, _) = download_and_extract_release(
65 ReleaseType::NatDetection,
66 url,
67 version,
68 &*release_repo,
69 verbosity,
70 None,
71 )
72 .await?;
73 nat_detection_path
74 };
75
76 if verbosity != VerbosityLevel::Minimal {
77 println!("Running NAT detection. This can take a while..");
78 }
79 debug!("Running NAT detection with path: {nat_detection_path:?}. This can take a while..");
80
81 let mut command = Command::new(nat_detection_path);
82 command.stdout(Stdio::piped()).stderr(Stdio::null());
83 command.arg(
84 servers
85 .iter()
86 .map(|addr| addr.to_string())
87 .collect::<Vec<String>>()
88 .join(","),
89 );
90 if tracing::level_enabled!(tracing::Level::TRACE) {
91 command.arg("-vvvv");
92 }
93 let mut child = command.spawn()?;
94
95 if tracing::level_enabled!(tracing::Level::TRACE) {
97 if let Some(ref mut stdout) = child.stdout {
99 let reader = BufReader::new(stdout);
100 for line in reader.lines() {
101 let line = line?;
102 let clean_line = strip_ansi_escapes(&line);
105 trace!("{clean_line}");
106 }
107 }
108 }
109
110 let status = child.wait()?;
111 let status = match status.code().ok_or_eyre("Failed to get the exit code")? {
112 10 => NatDetectionStatus::Public,
113 11 => NatDetectionStatus::UPnP,
114 12 => NatDetectionStatus::Private,
115 code => bail!("Failed to detect NAT status, exit code: {code}"),
116 };
117
118 if verbosity != VerbosityLevel::Minimal {
119 println!("NAT status has been found to be: {status:?}");
120 }
121
122 node_registry.nat_status = Some(status);
123 node_registry.save()?;
124
125 Ok(())
126}
127
128fn strip_ansi_escapes(input: &str) -> String {
129 let mut output = String::new();
130 let mut chars = input.chars();
131 while let Some(c) = chars.next() {
132 if c == '\x1b' {
133 for next_char in chars.by_ref() {
134 if next_char.is_ascii_lowercase() || next_char.is_ascii_uppercase() {
135 break;
136 }
137 }
138 } else {
139 output.push(c);
140 }
141 }
142 output
143}