sn_node_manager/cmd/
nat_detection.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// Copyright (C) 2024 MaidSafe.net limited.
//
// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. Please review the Licences for the specific language governing
// permissions and limitations relating to use of the SAFE Network Software.

use crate::{
    config::get_node_registry_path, helpers::download_and_extract_release, VerbosityLevel,
};
use color_eyre::eyre::{bail, OptionExt, Result};
use libp2p::Multiaddr;
use rand::seq::SliceRandom;
use sn_peers_acquisition::get_peers_from_url;
use sn_releases::{ReleaseType, SafeReleaseRepoActions};
use sn_service_management::{NatDetectionStatus, NodeRegistry};
use std::{
    io::{BufRead, BufReader},
    path::PathBuf,
    process::{Command, Stdio},
};

const NAT_DETECTION_SERVERS_LIST_URL: &str =
    "https://sn-testnet.s3.eu-west-2.amazonaws.com/nat-detection-servers";

pub async fn run_nat_detection(
    servers: Option<Vec<Multiaddr>>,
    force_run: bool,
    path: Option<PathBuf>,
    url: Option<String>,
    version: Option<String>,
    verbosity: VerbosityLevel,
) -> Result<()> {
    let servers = match servers {
        Some(servers) => servers,
        None => {
            let servers = get_peers_from_url(NAT_DETECTION_SERVERS_LIST_URL.parse()?).await?;

            servers
                .choose_multiple(&mut rand::thread_rng(), 10)
                .cloned()
                .collect::<Vec<_>>()
        }
    };
    info!("Running nat detection with servers: {servers:?}");
    let mut node_registry = NodeRegistry::load(&get_node_registry_path()?)?;

    if !force_run {
        if let Some(status) = node_registry.nat_status {
            if verbosity != VerbosityLevel::Minimal {
                println!("NAT status has already been set as: {status:?}");
            }
            debug!("NAT status has already been set as: {status:?}, returning.");
            return Ok(());
        }
    }

    let nat_detection_path = if let Some(path) = path {
        path
    } else {
        let release_repo = <dyn SafeReleaseRepoActions>::default_config();

        let (nat_detection_path, _) = download_and_extract_release(
            ReleaseType::NatDetection,
            url,
            version,
            &*release_repo,
            verbosity,
            None,
        )
        .await?;
        nat_detection_path
    };

    if verbosity != VerbosityLevel::Minimal {
        println!("Running NAT detection. This can take a while..");
    }
    debug!("Running NAT detection with path: {nat_detection_path:?}. This can take a while..");

    let mut command = Command::new(nat_detection_path);
    command.stdout(Stdio::piped()).stderr(Stdio::null());
    command.arg(
        servers
            .iter()
            .map(|addr| addr.to_string())
            .collect::<Vec<String>>()
            .join(","),
    );
    if tracing::level_enabled!(tracing::Level::TRACE) {
        command.arg("-vvvv");
    }
    let mut child = command.spawn()?;

    // only execute if log level is set to trace
    if tracing::level_enabled!(tracing::Level::TRACE) {
        // using buf reader to handle both stderr and stout is risky as it might block indefinitely.
        if let Some(ref mut stdout) = child.stdout {
            let reader = BufReader::new(stdout);
            for line in reader.lines() {
                let line = line?;
                // only if log level is trace

                let clean_line = strip_ansi_escapes(&line);
                trace!("{clean_line}");
            }
        }
    }

    let status = child.wait()?;
    let status = match status.code().ok_or_eyre("Failed to get the exit code")? {
        10 => NatDetectionStatus::Public,
        11 => NatDetectionStatus::UPnP,
        12 => NatDetectionStatus::Private,
        code => bail!("Failed to detect NAT status, exit code: {code}"),
    };

    if verbosity != VerbosityLevel::Minimal {
        println!("NAT status has been found to be: {status:?}");
    }

    node_registry.nat_status = Some(status);
    node_registry.save()?;

    Ok(())
}

fn strip_ansi_escapes(input: &str) -> String {
    let mut output = String::new();
    let mut chars = input.chars();
    while let Some(c) = chars.next() {
        if c == '\x1b' {
            for next_char in chars.by_ref() {
                if next_char.is_ascii_lowercase() || next_char.is_ascii_uppercase() {
                    break;
                }
            }
        } else {
            output.push(c);
        }
    }
    output
}