aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src-tauri/src/main.rs1
-rw-r--r--src-tauri/src/platform_specific/mod.rs10
-rw-r--r--src-tauri/src/platform_specific/windows.rs70
-rw-r--r--src-vue/src/views/DeveloperView.vue12
4 files changed, 93 insertions, 0 deletions
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index ee7da27f..a9f484f5 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -136,6 +136,7 @@ fn main() {
northstar::profile::delete_profile,
northstar::profile::fetch_profiles,
northstar::profile::validate_profile,
+ platform_specific::check_cgnat,
platform_specific::get_host_os,
platform_specific::get_local_northstar_proton_wrapper_version,
platform_specific::install_northstar_proton_wrapper,
diff --git a/src-tauri/src/platform_specific/mod.rs b/src-tauri/src/platform_specific/mod.rs
index 6fdb1ed1..4e0514d4 100644
--- a/src-tauri/src/platform_specific/mod.rs
+++ b/src-tauri/src/platform_specific/mod.rs
@@ -38,3 +38,13 @@ pub async fn get_local_northstar_proton_wrapper_version() -> Result<String, Stri
#[cfg(target_os = "windows")]
Err("Not supported on Windows".to_string())
}
+
+/// Check whether the current device might be behind a CGNAT
+#[tauri::command]
+pub async fn check_cgnat() -> Result<String, String> {
+ #[cfg(target_os = "linux")]
+ return Err("Not supported on Linux".to_string());
+
+ #[cfg(target_os = "windows")]
+ windows::check_cgnat().await
+}
diff --git a/src-tauri/src/platform_specific/windows.rs b/src-tauri/src/platform_specific/windows.rs
index 678e5be5..fc6aab5d 100644
--- a/src-tauri/src/platform_specific/windows.rs
+++ b/src-tauri/src/platform_specific/windows.rs
@@ -1,5 +1,6 @@
/// Windows specific code
use anyhow::{anyhow, Result};
+use std::net::Ipv4Addr;
#[cfg(target_os = "windows")]
use winreg::{enums::HKEY_LOCAL_MACHINE, RegKey};
@@ -32,3 +33,72 @@ pub fn origin_install_location_detection() -> Result<String, anyhow::Error> {
Err(anyhow!("No Origin / EA App install path found"))
}
+
+/// Check whether the current device might be behind a CGNAT
+pub async fn check_cgnat() -> Result<String, String> {
+ // Use external service to grap IP
+ let url = "https://api.ipify.org";
+ let response = reqwest::get(url).await.unwrap().text().await.unwrap();
+
+ // Check if valid IPv4 address and return early if not
+ if response.parse::<Ipv4Addr>().is_err() {
+ return Err(format!("Not valid IPv4 address: {}", response));
+ }
+
+ let hops_count = run_tracert(&response)?;
+ Ok(format!("Counted {} hops to {}", hops_count, response))
+}
+
+/// Count number of hops in tracert output
+fn count_hops(output: &str) -> usize {
+ // Split the output into lines
+ let lines: Vec<&str> = output.lines().collect();
+
+ // Filter lines that appear to represent hops
+ let hop_lines: Vec<&str> = lines
+ .iter()
+ .filter(|&line| line.contains("ms") || line.contains("*")) // TODO check if it contains just the `ms` surrounded by whitespace, otherwise it might falsely pick up some domain names as well
+ .cloned()
+ .collect();
+
+ // Return the number of hops
+ hop_lines.len()
+}
+
+/// Run `tracert`
+fn run_tracert(target_ip: &str) -> Result<usize, String> {
+ // Ensure valid IPv4 address to avoid prevent command injection
+ assert!(target_ip.parse::<Ipv4Addr>().is_ok());
+
+ // Execute the `tracert` command
+ let output = match std::process::Command::new("tracert")
+ .arg("-4") // Force IPv4
+ .arg("-d") // Prevent resolving intermediate IP addresses
+ .arg("-w") // Set timeout to 1 second
+ .arg("1000")
+ .arg("-h") // Set max hop count
+ .arg("5")
+ .arg(target_ip)
+ .output()
+ {
+ Ok(res) => res,
+ Err(err) => return Err(format!("Failed running tracert: {}", err)),
+ };
+
+ // Check if the command was successful
+ if output.status.success() {
+ // Convert the output to a string
+ let stdout =
+ std::str::from_utf8(&output.stdout).expect("Invalid UTF-8 sequence in command output");
+ println!("{}", stdout);
+
+ // Count the number of hops
+ let hop_count = count_hops(stdout);
+ Ok(hop_count)
+ } else {
+ let stderr = std::str::from_utf8(&output.stderr)
+ .expect("Invalid UTF-8 sequence in command error output");
+ println!("{}", stderr);
+ Err(format!("Failed collecting tracert output: {}", stderr))
+ }
+}
diff --git a/src-vue/src/views/DeveloperView.vue b/src-vue/src/views/DeveloperView.vue
index a08c73f3..19214157 100644
--- a/src-vue/src/views/DeveloperView.vue
+++ b/src-vue/src/views/DeveloperView.vue
@@ -65,6 +65,9 @@
<h3>Repair:</h3>
+ <el-button type="primary" @click="checkCgnat">
+ Run tracert and collect hop count
+ </el-button>
<el-button type="primary" @click="getInstalledMods">
Get installed mods
@@ -330,6 +333,15 @@ export default defineComponent({
.then((message) => { showNotification(`NSProton Version`, message as string); })
.catch((error) => { showNotification(`Error`, error, "error"); })
},
+ async checkCgnat() {
+ await invoke<string>("check_cgnat")
+ .then((message) => {
+ showNotification(message);
+ })
+ .catch((error) => {
+ showErrorNotification(error);
+ });
+ },
async copyReleaseNotesToClipboard() {
navigator.clipboard.writeText(this.release_notes_text)
.then(() => {