ant_node_manager/cmd/
nat_detection.rs

1// Copyright (C) 2024 MaidSafe.net limited.
2//
3// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
4// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
5// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
6// KIND, either express or implied. Please review the Licences for the specific language governing
7// permissions and limitations relating to use of the SAFE Network Software.
8
9use 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}