Fix MTU discovery.
This commit is contained in:
+17
-2
@@ -122,22 +122,37 @@ pub enum MtuCommands {
|
||||
target: String,
|
||||
#[arg(short, long, value_enum, default_value = "both")]
|
||||
ip_version: IpVersionArg,
|
||||
#[arg(
|
||||
long,
|
||||
help = "Use sudo for more accurate MTU testing (requires interactive password prompt)"
|
||||
)]
|
||||
sudo: bool,
|
||||
},
|
||||
#[command(about = "Test common MTU sizes")]
|
||||
Common {
|
||||
target: String,
|
||||
#[arg(short, long, value_enum, default_value = "both")]
|
||||
ip_version: IpVersionArg,
|
||||
#[arg(
|
||||
long,
|
||||
help = "Use sudo for more accurate MTU testing (requires interactive password prompt)"
|
||||
)]
|
||||
sudo: bool,
|
||||
},
|
||||
#[command(about = "Test custom MTU range")]
|
||||
Range {
|
||||
target: String,
|
||||
#[arg(short, long, default_value = "68")]
|
||||
#[arg(long, default_value = "68")]
|
||||
min: u16,
|
||||
#[arg(short, long, default_value = "1500")]
|
||||
#[arg(long, default_value = "1500")]
|
||||
max: u16,
|
||||
#[arg(short, long, value_enum, default_value = "both")]
|
||||
ip_version: IpVersionArg,
|
||||
#[arg(
|
||||
long,
|
||||
help = "Use sudo for more accurate MTU testing (requires interactive password prompt)"
|
||||
)]
|
||||
sudo: bool,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
+18
-8
@@ -189,18 +189,26 @@ async fn handle_dns_command(command: cli::DnsCommands, timeout: Duration) -> Vec
|
||||
|
||||
async fn handle_mtu_command(command: cli::MtuCommands, _timeout: Duration) -> Vec<TestResult> {
|
||||
match command {
|
||||
cli::MtuCommands::Discover { target, ip_version } => {
|
||||
cli::MtuCommands::Discover {
|
||||
target,
|
||||
ip_version,
|
||||
sudo,
|
||||
} => {
|
||||
let mut results = Vec::new();
|
||||
for version in ip_version.to_versions() {
|
||||
let result = mtu::full_mtu_discovery(&target, version).await;
|
||||
results.push(result);
|
||||
let discovery = mtu::MtuDiscovery::new(target.clone(), version).with_sudo(sudo);
|
||||
results.push(discovery.discover().await);
|
||||
}
|
||||
results
|
||||
}
|
||||
cli::MtuCommands::Common { target, ip_version } => {
|
||||
cli::MtuCommands::Common {
|
||||
target,
|
||||
ip_version,
|
||||
sudo,
|
||||
} => {
|
||||
let mut results = Vec::new();
|
||||
for version in ip_version.to_versions() {
|
||||
let common_results = mtu::test_common_mtu_sizes(&target, version).await;
|
||||
let common_results = mtu::test_common_mtu_sizes(&target, version, sudo).await;
|
||||
results.extend(common_results);
|
||||
}
|
||||
results
|
||||
@@ -210,11 +218,13 @@ async fn handle_mtu_command(command: cli::MtuCommands, _timeout: Duration) -> Ve
|
||||
min,
|
||||
max,
|
||||
ip_version,
|
||||
sudo,
|
||||
} => {
|
||||
let mut results = Vec::new();
|
||||
for version in ip_version.to_versions() {
|
||||
let discovery =
|
||||
mtu::MtuDiscovery::new(target.clone(), version).with_range(min, max);
|
||||
let discovery = mtu::MtuDiscovery::new(target.clone(), version)
|
||||
.with_range(min, max)
|
||||
.with_sudo(sudo);
|
||||
results.push(discovery.discover().await);
|
||||
}
|
||||
results
|
||||
@@ -272,7 +282,7 @@ async fn handle_full_test(
|
||||
pb.inc(1);
|
||||
|
||||
// Common MTU sizes
|
||||
let mtu_common = mtu::test_common_mtu_sizes(&target, version).await;
|
||||
let mtu_common = mtu::test_common_mtu_sizes(&target, version, false).await;
|
||||
all_results.extend(mtu_common);
|
||||
pb.inc(1);
|
||||
}
|
||||
|
||||
+153
-13
@@ -1,5 +1,6 @@
|
||||
use crate::network::IpVersion;
|
||||
use crate::utils::{measure_time, NetworkError, Result, TestResult};
|
||||
use std::net::{IpAddr, ToSocketAddrs};
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct MtuDiscovery {
|
||||
@@ -8,6 +9,7 @@ pub struct MtuDiscovery {
|
||||
pub timeout: Duration,
|
||||
pub max_mtu: u16,
|
||||
pub min_mtu: u16,
|
||||
pub use_sudo: bool,
|
||||
}
|
||||
|
||||
impl Default for MtuDiscovery {
|
||||
@@ -18,6 +20,7 @@ impl Default for MtuDiscovery {
|
||||
timeout: Duration::from_secs(5),
|
||||
max_mtu: 1500,
|
||||
min_mtu: 68,
|
||||
use_sudo: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,20 +40,42 @@ impl MtuDiscovery {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_sudo(mut self, use_sudo: bool) -> Self {
|
||||
self.use_sudo = use_sudo;
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn discover(&self) -> TestResult {
|
||||
let test_name = format!("MTU discovery for {} ({:?})", self.target, self.ip_version);
|
||||
|
||||
let (duration, result) = measure_time(|| async { self.binary_search_mtu().await }).await;
|
||||
|
||||
match result {
|
||||
Ok(mtu) => TestResult::new(test_name)
|
||||
.success(duration, format!("Discovered MTU: {} bytes", mtu)),
|
||||
Ok(mtu) => {
|
||||
// Check if the discovered MTU is reasonable for the IP version
|
||||
let warning = match self.ip_version {
|
||||
IpVersion::V6 if mtu < 1280 => {
|
||||
" (Warning: IPv6 minimum MTU is 1280 bytes - connectivity issue?)"
|
||||
}
|
||||
_ => "",
|
||||
};
|
||||
TestResult::new(test_name).success(
|
||||
duration,
|
||||
format!("Discovered MTU: {} bytes{}", mtu, warning),
|
||||
)
|
||||
}
|
||||
Err(error) => TestResult::new(test_name).failure(duration, error),
|
||||
}
|
||||
}
|
||||
|
||||
async fn binary_search_mtu(&self) -> Result<u16> {
|
||||
let mut low = self.min_mtu;
|
||||
// Adjust minimum MTU based on IP version
|
||||
let adjusted_min = match self.ip_version {
|
||||
IpVersion::V4 => self.min_mtu,
|
||||
IpVersion::V6 => self.min_mtu.max(1280), // IPv6 minimum MTU is 1280
|
||||
};
|
||||
|
||||
let mut low = adjusted_min;
|
||||
let mut high = self.max_mtu;
|
||||
let mut best_mtu = low;
|
||||
|
||||
@@ -71,6 +96,32 @@ impl MtuDiscovery {
|
||||
Ok(best_mtu)
|
||||
}
|
||||
|
||||
fn resolve_target_for_ipv6(&self) -> String {
|
||||
// For IPv6, try to resolve hostname to IPv6 address for better compatibility on macOS
|
||||
if matches!(self.ip_version, IpVersion::V6) {
|
||||
// Check if target is already an IP address
|
||||
if self.target.parse::<IpAddr>().is_ok() {
|
||||
return self.target.clone();
|
||||
}
|
||||
|
||||
// Try to resolve hostname to IPv6 address
|
||||
match format!("{}:80", self.target).to_socket_addrs() {
|
||||
Ok(addrs) => {
|
||||
for addr in addrs {
|
||||
if addr.is_ipv6() {
|
||||
return addr.ip().to_string();
|
||||
}
|
||||
}
|
||||
// If no IPv6 address found, return original target
|
||||
self.target.clone()
|
||||
}
|
||||
Err(_) => self.target.clone(),
|
||||
}
|
||||
} else {
|
||||
self.target.clone()
|
||||
}
|
||||
}
|
||||
|
||||
async fn test_mtu_size(&self, mtu_size: u16) -> Result<()> {
|
||||
// Check if we're in a CI environment where ping may not be available
|
||||
if std::env::var("CI").is_ok() || std::env::var("GITHUB_ACTIONS").is_ok() {
|
||||
@@ -85,6 +136,9 @@ impl MtuDiscovery {
|
||||
));
|
||||
}
|
||||
|
||||
// Resolve target for IPv6 compatibility
|
||||
let target = self.resolve_target_for_ipv6();
|
||||
|
||||
// Use system ping with packet size for MTU testing
|
||||
let ping_cmd = match self.ip_version {
|
||||
IpVersion::V4 => "ping",
|
||||
@@ -101,19 +155,85 @@ impl MtuDiscovery {
|
||||
}
|
||||
|
||||
// Add timeout wrapper to prevent hanging
|
||||
let ping_future = tokio::process::Command::new(ping_cmd)
|
||||
.args(&[
|
||||
let ping_future = if cfg!(target_os = "macos") {
|
||||
match self.ip_version {
|
||||
IpVersion::V4 => {
|
||||
// IPv4 on macOS: -D flag may work without sudo, but sudo gives more reliable results
|
||||
if self.use_sudo {
|
||||
let mut cmd = tokio::process::Command::new("sudo");
|
||||
cmd.args(&[
|
||||
ping_cmd,
|
||||
"-c",
|
||||
"1",
|
||||
"-t",
|
||||
"3", // timeout in seconds for macOS
|
||||
"-D", // Don't fragment (more reliable with sudo)
|
||||
"-s",
|
||||
&payload_size.to_string(),
|
||||
&target,
|
||||
]);
|
||||
cmd.output()
|
||||
} else {
|
||||
// Try -D flag without sudo (may work on some systems)
|
||||
let mut cmd = tokio::process::Command::new(ping_cmd);
|
||||
cmd.args(&[
|
||||
"-c",
|
||||
"1",
|
||||
"-t",
|
||||
"3", // timeout in seconds for macOS
|
||||
"-D", // Don't fragment (may require privileges)
|
||||
"-s",
|
||||
&payload_size.to_string(),
|
||||
&target,
|
||||
]);
|
||||
cmd.output()
|
||||
}
|
||||
}
|
||||
IpVersion::V6 => {
|
||||
// IPv6 on macOS requires sudo for don't fragment and has no timeout flag
|
||||
if self.use_sudo {
|
||||
let mut cmd = tokio::process::Command::new("sudo");
|
||||
cmd.args(&[
|
||||
ping_cmd,
|
||||
"-c",
|
||||
"1",
|
||||
"-D", // Don't fragment (requires sudo on macOS)
|
||||
"-s",
|
||||
&payload_size.to_string(),
|
||||
&target,
|
||||
]);
|
||||
cmd.output()
|
||||
} else {
|
||||
// Without sudo, IPv6 MTU discovery is less accurate (no don't fragment)
|
||||
let mut cmd = tokio::process::Command::new(ping_cmd);
|
||||
cmd.args(&["-c", "1", "-s", &payload_size.to_string(), &target]);
|
||||
cmd.output()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Linux ping syntax for both IPv4 and IPv6
|
||||
let mut cmd = if self.use_sudo {
|
||||
let mut c = tokio::process::Command::new("sudo");
|
||||
c.arg(ping_cmd);
|
||||
c
|
||||
} else {
|
||||
tokio::process::Command::new(ping_cmd)
|
||||
};
|
||||
|
||||
cmd.args(&[
|
||||
"-c",
|
||||
"1",
|
||||
"-W",
|
||||
"3000", // Reduce timeout from 5000 to 3000ms
|
||||
"3000", // timeout in milliseconds for Linux
|
||||
"-M",
|
||||
"do", // Don't fragment
|
||||
"do", // Don't fragment on Linux
|
||||
"-s",
|
||||
&payload_size.to_string(),
|
||||
&self.target,
|
||||
])
|
||||
.output();
|
||||
&target,
|
||||
]);
|
||||
cmd.output()
|
||||
};
|
||||
|
||||
let output = tokio::time::timeout(Duration::from_secs(5), ping_future)
|
||||
.await
|
||||
@@ -123,17 +243,37 @@ impl MtuDiscovery {
|
||||
if output.status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(NetworkError::Other("MTU test failed".to_string()))
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
if self.use_sudo && stderr.contains("password is required") {
|
||||
Err(NetworkError::Other(
|
||||
"Sudo password required for privileged ping operations".to_string(),
|
||||
))
|
||||
} else if stderr.contains("Operation not permitted") {
|
||||
Err(NetworkError::Other(
|
||||
"Permission denied - try using --sudo flag".to_string(),
|
||||
))
|
||||
} else {
|
||||
Err(NetworkError::Other(format!(
|
||||
"MTU test failed: {}",
|
||||
stderr.trim()
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn test_common_mtu_sizes(target: &str, ip_version: IpVersion) -> Vec<TestResult> {
|
||||
pub async fn test_common_mtu_sizes(
|
||||
target: &str,
|
||||
ip_version: IpVersion,
|
||||
use_sudo: bool,
|
||||
) -> Vec<TestResult> {
|
||||
let common_sizes = [68, 576, 1280, 1500, 4464, 9000];
|
||||
let mut results = Vec::new();
|
||||
|
||||
for &size in &common_sizes {
|
||||
let discovery = MtuDiscovery::new(target.to_string(), ip_version).with_range(size, size);
|
||||
let discovery = MtuDiscovery::new(target.to_string(), ip_version)
|
||||
.with_range(size, size)
|
||||
.with_sudo(use_sudo);
|
||||
|
||||
let mut result = discovery.discover().await;
|
||||
result.test_name = format!("MTU test {} bytes for {} ({:?})", size, target, ip_version);
|
||||
|
||||
Reference in New Issue
Block a user