Files
NetTest/src/mtu/mod.rs
T
2025-08-09 18:09:37 +02:00

133 lines
3.7 KiB
Rust

use crate::network::IpVersion;
use crate::utils::{measure_time, NetworkError, Result, TestResult};
use std::time::Duration;
pub struct MtuDiscovery {
pub target: String,
pub ip_version: IpVersion,
pub timeout: Duration,
pub max_mtu: u16,
pub min_mtu: u16,
}
impl Default for MtuDiscovery {
fn default() -> Self {
Self {
target: String::new(),
ip_version: IpVersion::V4,
timeout: Duration::from_secs(5),
max_mtu: 1500,
min_mtu: 68,
}
}
}
impl MtuDiscovery {
pub fn new(target: String, ip_version: IpVersion) -> Self {
Self {
target,
ip_version,
..Default::default()
}
}
pub fn with_range(mut self, min_mtu: u16, max_mtu: u16) -> Self {
self.min_mtu = min_mtu;
self.max_mtu = max_mtu;
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)),
Err(error) => TestResult::new(test_name).failure(duration, error),
}
}
async fn binary_search_mtu(&self) -> Result<u16> {
let mut low = self.min_mtu;
let mut high = self.max_mtu;
let mut best_mtu = low;
while low <= high {
let mid = (low + high) / 2;
match self.test_mtu_size(mid).await {
Ok(_) => {
best_mtu = mid;
low = mid + 1;
}
Err(_) => {
high = mid - 1;
}
}
}
Ok(best_mtu)
}
async fn test_mtu_size(&self, mtu_size: u16) -> Result<()> {
// Use system ping with packet size for MTU testing
let ping_cmd = match self.ip_version {
IpVersion::V4 => "ping",
IpVersion::V6 => "ping6",
};
let payload_size = match self.ip_version {
IpVersion::V4 => mtu_size.saturating_sub(28), // IP header 20 + ICMP header 8
IpVersion::V6 => mtu_size.saturating_sub(48), // IPv6 header 40 + ICMP header 8
};
if payload_size < 8 {
return Err(NetworkError::InvalidMtu(mtu_size));
}
let output = tokio::process::Command::new(ping_cmd)
.args(&[
"-c",
"1",
"-W",
"5000",
"-M",
"do", // Don't fragment
"-s",
&payload_size.to_string(),
&self.target,
])
.output()
.await
.map_err(|e| NetworkError::Io(e))?;
if output.status.success() {
Ok(())
} else {
Err(NetworkError::Other("MTU test failed".to_string()))
}
}
}
pub async fn test_common_mtu_sizes(target: &str, ip_version: IpVersion) -> 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 mut result = discovery.discover().await;
result.test_name = format!("MTU test {} bytes for {} ({:?})", size, target, ip_version);
results.push(result);
}
results
}
pub async fn full_mtu_discovery(target: &str, ip_version: IpVersion) -> TestResult {
let discovery = MtuDiscovery::new(target.to_string(), ip_version);
discovery.discover().await
}