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    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    // only execute if log level is set to trace
100    if tracing::level_enabled!(tracing::Level::TRACE) {
101        // using buf reader to handle both stderr and stout is risky as it might block indefinitely.
102        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                // only if log level is trace
107
108                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}