diff options
-rw-r--r-- | src-tauri/src/main.rs | 1 | ||||
-rw-r--r-- | src-tauri/src/platform_specific/mod.rs | 10 | ||||
-rw-r--r-- | src-tauri/src/platform_specific/windows.rs | 70 | ||||
-rw-r--r-- | src-vue/src/views/DeveloperView.vue | 12 |
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(() => { |