Fix MTU discovery.
This commit is contained in:
+17
-2
@@ -122,22 +122,37 @@ pub enum MtuCommands {
|
|||||||
target: String,
|
target: String,
|
||||||
#[arg(short, long, value_enum, default_value = "both")]
|
#[arg(short, long, value_enum, default_value = "both")]
|
||||||
ip_version: IpVersionArg,
|
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")]
|
#[command(about = "Test common MTU sizes")]
|
||||||
Common {
|
Common {
|
||||||
target: String,
|
target: String,
|
||||||
#[arg(short, long, value_enum, default_value = "both")]
|
#[arg(short, long, value_enum, default_value = "both")]
|
||||||
ip_version: IpVersionArg,
|
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")]
|
#[command(about = "Test custom MTU range")]
|
||||||
Range {
|
Range {
|
||||||
target: String,
|
target: String,
|
||||||
#[arg(short, long, default_value = "68")]
|
#[arg(long, default_value = "68")]
|
||||||
min: u16,
|
min: u16,
|
||||||
#[arg(short, long, default_value = "1500")]
|
#[arg(long, default_value = "1500")]
|
||||||
max: u16,
|
max: u16,
|
||||||
#[arg(short, long, value_enum, default_value = "both")]
|
#[arg(short, long, value_enum, default_value = "both")]
|
||||||
ip_version: IpVersionArg,
|
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> {
|
async fn handle_mtu_command(command: cli::MtuCommands, _timeout: Duration) -> Vec<TestResult> {
|
||||||
match command {
|
match command {
|
||||||
cli::MtuCommands::Discover { target, ip_version } => {
|
cli::MtuCommands::Discover {
|
||||||
|
target,
|
||||||
|
ip_version,
|
||||||
|
sudo,
|
||||||
|
} => {
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
for version in ip_version.to_versions() {
|
for version in ip_version.to_versions() {
|
||||||
let result = mtu::full_mtu_discovery(&target, version).await;
|
let discovery = mtu::MtuDiscovery::new(target.clone(), version).with_sudo(sudo);
|
||||||
results.push(result);
|
results.push(discovery.discover().await);
|
||||||
}
|
}
|
||||||
results
|
results
|
||||||
}
|
}
|
||||||
cli::MtuCommands::Common { target, ip_version } => {
|
cli::MtuCommands::Common {
|
||||||
|
target,
|
||||||
|
ip_version,
|
||||||
|
sudo,
|
||||||
|
} => {
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
for version in ip_version.to_versions() {
|
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.extend(common_results);
|
||||||
}
|
}
|
||||||
results
|
results
|
||||||
@@ -210,11 +218,13 @@ async fn handle_mtu_command(command: cli::MtuCommands, _timeout: Duration) -> Ve
|
|||||||
min,
|
min,
|
||||||
max,
|
max,
|
||||||
ip_version,
|
ip_version,
|
||||||
|
sudo,
|
||||||
} => {
|
} => {
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
for version in ip_version.to_versions() {
|
for version in ip_version.to_versions() {
|
||||||
let discovery =
|
let discovery = mtu::MtuDiscovery::new(target.clone(), version)
|
||||||
mtu::MtuDiscovery::new(target.clone(), version).with_range(min, max);
|
.with_range(min, max)
|
||||||
|
.with_sudo(sudo);
|
||||||
results.push(discovery.discover().await);
|
results.push(discovery.discover().await);
|
||||||
}
|
}
|
||||||
results
|
results
|
||||||
@@ -272,7 +282,7 @@ async fn handle_full_test(
|
|||||||
pb.inc(1);
|
pb.inc(1);
|
||||||
|
|
||||||
// Common MTU sizes
|
// 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);
|
all_results.extend(mtu_common);
|
||||||
pb.inc(1);
|
pb.inc(1);
|
||||||
}
|
}
|
||||||
|
|||||||
+153
-13
@@ -1,5 +1,6 @@
|
|||||||
use crate::network::IpVersion;
|
use crate::network::IpVersion;
|
||||||
use crate::utils::{measure_time, NetworkError, Result, TestResult};
|
use crate::utils::{measure_time, NetworkError, Result, TestResult};
|
||||||
|
use std::net::{IpAddr, ToSocketAddrs};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
pub struct MtuDiscovery {
|
pub struct MtuDiscovery {
|
||||||
@@ -8,6 +9,7 @@ pub struct MtuDiscovery {
|
|||||||
pub timeout: Duration,
|
pub timeout: Duration,
|
||||||
pub max_mtu: u16,
|
pub max_mtu: u16,
|
||||||
pub min_mtu: u16,
|
pub min_mtu: u16,
|
||||||
|
pub use_sudo: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MtuDiscovery {
|
impl Default for MtuDiscovery {
|
||||||
@@ -18,6 +20,7 @@ impl Default for MtuDiscovery {
|
|||||||
timeout: Duration::from_secs(5),
|
timeout: Duration::from_secs(5),
|
||||||
max_mtu: 1500,
|
max_mtu: 1500,
|
||||||
min_mtu: 68,
|
min_mtu: 68,
|
||||||
|
use_sudo: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,20 +40,42 @@ impl MtuDiscovery {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_sudo(mut self, use_sudo: bool) -> Self {
|
||||||
|
self.use_sudo = use_sudo;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn discover(&self) -> TestResult {
|
pub async fn discover(&self) -> TestResult {
|
||||||
let test_name = format!("MTU discovery for {} ({:?})", self.target, self.ip_version);
|
let test_name = format!("MTU discovery for {} ({:?})", self.target, self.ip_version);
|
||||||
|
|
||||||
let (duration, result) = measure_time(|| async { self.binary_search_mtu().await }).await;
|
let (duration, result) = measure_time(|| async { self.binary_search_mtu().await }).await;
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(mtu) => TestResult::new(test_name)
|
Ok(mtu) => {
|
||||||
.success(duration, format!("Discovered MTU: {} bytes", 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),
|
Err(error) => TestResult::new(test_name).failure(duration, error),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn binary_search_mtu(&self) -> Result<u16> {
|
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 high = self.max_mtu;
|
||||||
let mut best_mtu = low;
|
let mut best_mtu = low;
|
||||||
|
|
||||||
@@ -71,6 +96,32 @@ impl MtuDiscovery {
|
|||||||
Ok(best_mtu)
|
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<()> {
|
async fn test_mtu_size(&self, mtu_size: u16) -> Result<()> {
|
||||||
// Check if we're in a CI environment where ping may not be available
|
// 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() {
|
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
|
// Use system ping with packet size for MTU testing
|
||||||
let ping_cmd = match self.ip_version {
|
let ping_cmd = match self.ip_version {
|
||||||
IpVersion::V4 => "ping",
|
IpVersion::V4 => "ping",
|
||||||
@@ -101,19 +155,85 @@ impl MtuDiscovery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add timeout wrapper to prevent hanging
|
// Add timeout wrapper to prevent hanging
|
||||||
let ping_future = tokio::process::Command::new(ping_cmd)
|
let ping_future = if cfg!(target_os = "macos") {
|
||||||
.args(&[
|
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",
|
"-c",
|
||||||
"1",
|
"1",
|
||||||
"-W",
|
"-W",
|
||||||
"3000", // Reduce timeout from 5000 to 3000ms
|
"3000", // timeout in milliseconds for Linux
|
||||||
"-M",
|
"-M",
|
||||||
"do", // Don't fragment
|
"do", // Don't fragment on Linux
|
||||||
"-s",
|
"-s",
|
||||||
&payload_size.to_string(),
|
&payload_size.to_string(),
|
||||||
&self.target,
|
&target,
|
||||||
])
|
]);
|
||||||
.output();
|
cmd.output()
|
||||||
|
};
|
||||||
|
|
||||||
let output = tokio::time::timeout(Duration::from_secs(5), ping_future)
|
let output = tokio::time::timeout(Duration::from_secs(5), ping_future)
|
||||||
.await
|
.await
|
||||||
@@ -123,17 +243,37 @@ impl MtuDiscovery {
|
|||||||
if output.status.success() {
|
if output.status.success() {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} 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 common_sizes = [68, 576, 1280, 1500, 4464, 9000];
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
|
|
||||||
for &size in &common_sizes {
|
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;
|
let mut result = discovery.discover().await;
|
||||||
result.test_name = format!("MTU test {} bytes for {} ({:?})", size, target, ip_version);
|
result.test_name = format!("MTU test {} bytes for {} ({:?})", size, target, ip_version);
|
||||||
|
|||||||
Reference in New Issue
Block a user