aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeckoEidechse <40122905+GeckoEidechse@users.noreply.github.com>2024-08-03 02:00:54 +0200
committerGitHub <noreply@github.com>2024-08-03 02:00:54 +0200
commit863a7fbedfb8d443a528e7475edb5de541499ce9 (patch)
tree7fa2f03bd46ea93f8d630c52218a4c22416d0969
parent1ac4198ec988ac49cb7740336df7a43bab3fbe52 (diff)
downloadFlightCore-863a7fbedfb8d443a528e7475edb5de541499ce9.tar.gz
FlightCore-863a7fbedfb8d443a528e7475edb5de541499ce9.zip
feat: Add initial CGNAT check logic (#969)
Adds some logic that gets external IP and runs tracert to count the number of hops to said IP address. The goal is to have an automated way to check for CGNAT.
-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(() => {