Fix ICMP implementation.
This commit is contained in:
@@ -87,3 +87,5 @@ overly_complex_bool_expr = "allow"
|
|||||||
map_unwrap_or = "allow"
|
map_unwrap_or = "allow"
|
||||||
# Allow collapsible else if patterns
|
# Allow collapsible else if patterns
|
||||||
collapsible_else_if = "allow"
|
collapsible_else_if = "allow"
|
||||||
|
# Allow needless continue for clarity in loops
|
||||||
|
needless_continue = "allow"
|
||||||
|
|||||||
+78
-8
@@ -1,35 +1,105 @@
|
|||||||
use super::{IpVersion, NetworkTest};
|
use super::{IpVersion, NetworkTest};
|
||||||
use crate::utils::{NetworkError, Result, TestResult};
|
use crate::utils::{NetworkError, Result, TestResult};
|
||||||
|
use std::net::ToSocketAddrs;
|
||||||
use tokio::time::Duration;
|
use tokio::time::Duration;
|
||||||
|
|
||||||
impl NetworkTest {
|
impl NetworkTest {
|
||||||
pub async fn test_icmp(&self) -> Result<String> {
|
pub async fn test_icmp(&self) -> Result<String> {
|
||||||
// Use system ping command for simplicity and compatibility
|
// Resolve the target to an IP address first
|
||||||
let target = &self.target;
|
let target_ip = self.resolve_target_to_ip().await?;
|
||||||
|
|
||||||
let ping_cmd = match self.ip_version {
|
let ping_cmd = match self.ip_version {
|
||||||
IpVersion::V4 => "ping",
|
IpVersion::V4 => "ping",
|
||||||
IpVersion::V6 => "ping6",
|
IpVersion::V6 => "ping6",
|
||||||
};
|
};
|
||||||
|
|
||||||
let output = tokio::process::Command::new(ping_cmd)
|
let mut cmd = tokio::process::Command::new(ping_cmd);
|
||||||
.args(&["-c", "1", "-W", "5000", target]) // 1 ping, 5 second timeout
|
cmd.args(&["-c", "1"]);
|
||||||
.output()
|
|
||||||
|
// Add timeout for IPv4 ping, but not for ping6 on macOS (it uses different syntax)
|
||||||
|
match self.ip_version {
|
||||||
|
IpVersion::V4 => {
|
||||||
|
cmd.args(&["-W", "5000"]); // 5 second timeout for IPv4
|
||||||
|
}
|
||||||
|
IpVersion::V6 => {
|
||||||
|
// ping6 on macOS doesn't support -W flag in the same way
|
||||||
|
// We'll rely on Tokio's timeout wrapper instead
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.arg(&target_ip);
|
||||||
|
|
||||||
|
let output = tokio::time::timeout(Duration::from_secs(5), cmd.output())
|
||||||
.await
|
.await
|
||||||
|
.map_err(|_| NetworkError::Timeout)?
|
||||||
.map_err(|e| NetworkError::Io(e))?;
|
.map_err(|e| NetworkError::Io(e))?;
|
||||||
|
|
||||||
if output.status.success() {
|
if output.status.success() {
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
if let Some(line) = stdout.lines().find(|line| line.contains("time=")) {
|
if let Some(line) = stdout.lines().find(|line| line.contains("time=")) {
|
||||||
Ok(format!("ICMP ping successful: {}", line.trim()))
|
Ok(format!(
|
||||||
|
"ICMP ping to {} ({}): {}",
|
||||||
|
self.target,
|
||||||
|
target_ip,
|
||||||
|
line.trim()
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
Ok("ICMP ping successful".to_string())
|
Ok(format!(
|
||||||
|
"ICMP ping to {} ({}) successful",
|
||||||
|
self.target, target_ip
|
||||||
|
))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
Err(NetworkError::Other(format!("Ping failed: {}", stderr)))
|
Err(NetworkError::Other(format!(
|
||||||
|
"Ping to {} ({}) failed: {}",
|
||||||
|
self.target, target_ip, stderr
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn resolve_target_to_ip(&self) -> Result<String> {
|
||||||
|
// Try to parse as IP address first
|
||||||
|
if let Ok(ip) = self.target.parse::<std::net::IpAddr>() {
|
||||||
|
return match (ip, self.ip_version) {
|
||||||
|
(std::net::IpAddr::V4(_), IpVersion::V4) => Ok(self.target.clone()),
|
||||||
|
(std::net::IpAddr::V6(_), IpVersion::V6) => Ok(self.target.clone()),
|
||||||
|
_ => Err(NetworkError::Other(format!(
|
||||||
|
"IP version mismatch: {} is not {:?}",
|
||||||
|
ip, self.ip_version
|
||||||
|
))),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve domain name
|
||||||
|
let socket_addrs: Vec<_> = tokio::task::spawn_blocking({
|
||||||
|
let target = self.target.clone();
|
||||||
|
move || {
|
||||||
|
format!("{}:80", target)
|
||||||
|
.to_socket_addrs()
|
||||||
|
.map_err(|e| {
|
||||||
|
NetworkError::DnsResolution(format!("Failed to resolve {}: {}", target, e))
|
||||||
|
})
|
||||||
|
.map(|addrs| addrs.collect::<Vec<_>>())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| NetworkError::Other(format!("Task join error: {}", e)))??;
|
||||||
|
|
||||||
|
// Find the first matching IP version
|
||||||
|
for addr in socket_addrs {
|
||||||
|
match (addr.ip(), self.ip_version) {
|
||||||
|
(std::net::IpAddr::V4(ipv4), IpVersion::V4) => return Ok(ipv4.to_string()),
|
||||||
|
(std::net::IpAddr::V6(ipv6), IpVersion::V6) => return Ok(ipv6.to_string()),
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(NetworkError::DnsResolution(format!(
|
||||||
|
"No {:?} address found for {}",
|
||||||
|
self.ip_version, self.target
|
||||||
|
)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn ping_test(target: &str, ip_version: IpVersion, count: u32) -> Vec<TestResult> {
|
pub async fn ping_test(target: &str, ip_version: IpVersion, count: u32) -> Vec<TestResult> {
|
||||||
|
|||||||
Reference in New Issue
Block a user