First full working version.

This commit is contained in:
2025-08-09 18:09:37 +02:00
commit a543458658
23 changed files with 3238 additions and 0 deletions
+51
View File
@@ -0,0 +1,51 @@
use super::{IpVersion, NetworkTest};
use crate::utils::{NetworkError, Result, TestResult};
use tokio::time::Duration;
impl NetworkTest {
pub async fn test_icmp(&self) -> Result<String> {
// Use system ping command for simplicity and compatibility
let target = &self.target;
let ping_cmd = match self.ip_version {
IpVersion::V4 => "ping",
IpVersion::V6 => "ping6",
};
let output = tokio::process::Command::new(ping_cmd)
.args(&["-c", "1", "-W", "5000", target]) // 1 ping, 5 second timeout
.output()
.await
.map_err(|e| NetworkError::Io(e))?;
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(line) = stdout.lines().find(|line| line.contains("time=")) {
Ok(format!("ICMP ping successful: {}", line.trim()))
} else {
Ok("ICMP ping successful".to_string())
}
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
Err(NetworkError::Other(format!("Ping failed: {}", stderr)))
}
}
}
pub async fn ping_test(target: &str, ip_version: IpVersion, count: u32) -> Vec<TestResult> {
let mut results = Vec::new();
for i in 0..count {
let test = NetworkTest::new(target.to_string(), ip_version, super::NetworkProtocol::Icmp);
let mut result = test.run().await;
result.test_name = format!("ICMP ping #{} to {} ({:?})", i + 1, target, ip_version);
results.push(result);
if i < count - 1 {
tokio::time::sleep(Duration::from_secs(1)).await;
}
}
results
}
+106
View File
@@ -0,0 +1,106 @@
use crate::utils::{measure_time, NetworkError, Result, TestResult};
use std::net::IpAddr;
use std::time::Duration;
pub mod icmp;
pub mod tcp;
pub mod udp;
pub use icmp::*;
pub use tcp::*;
pub use udp::*;
#[derive(Debug, Clone, Copy)]
pub enum IpVersion {
V4,
V6,
}
#[derive(Debug, Clone, Copy)]
pub enum NetworkProtocol {
Tcp,
Udp,
Icmp,
}
#[derive(Debug, Clone)]
pub struct NetworkTest {
pub target: String,
pub ip_version: IpVersion,
pub protocol: NetworkProtocol,
pub port: Option<u16>,
pub timeout: Duration,
}
impl NetworkTest {
pub fn new(target: String, ip_version: IpVersion, protocol: NetworkProtocol) -> Self {
Self {
target,
ip_version,
protocol,
port: None,
timeout: Duration::from_secs(5),
}
}
pub fn with_port(mut self, port: u16) -> Self {
self.port = Some(port);
self
}
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
pub async fn run(&self) -> TestResult {
let test_name = format!(
"{:?} test to {} {:?} {}",
self.protocol,
self.target,
self.ip_version,
self.port.map(|p| format!(":{}", p)).unwrap_or_default()
);
let (duration, result) = measure_time(|| async {
match self.protocol {
NetworkProtocol::Tcp => self.test_tcp().await,
NetworkProtocol::Udp => self.test_udp().await,
NetworkProtocol::Icmp => self.test_icmp().await,
}
})
.await;
match result {
Ok(details) => TestResult::new(test_name).success(duration, details),
Err(error) => TestResult::new(test_name).failure(duration, error),
}
}
async fn resolve_target(&self) -> Result<IpAddr> {
use trust_dns_resolver::config::*;
use trust_dns_resolver::TokioAsyncResolver;
let resolver =
TokioAsyncResolver::tokio(ResolverConfig::default(), ResolverOpts::default());
let lookup = match self.ip_version {
IpVersion::V4 => {
let response = resolver
.ipv4_lookup(&self.target)
.await
.map_err(|e| NetworkError::DnsResolution(e.to_string()))?;
response.iter().next().map(|ip| IpAddr::V4(**ip))
}
IpVersion::V6 => {
let response = resolver
.ipv6_lookup(&self.target)
.await
.map_err(|e| NetworkError::DnsResolution(e.to_string()))?;
response.iter().next().map(|ip| IpAddr::V6(**ip))
}
};
lookup.ok_or_else(|| NetworkError::DnsResolution("No IP found".to_string()))
}
}
+43
View File
@@ -0,0 +1,43 @@
use super::{IpVersion, NetworkTest};
use crate::utils::{NetworkError, Result, TestResult};
use std::net::SocketAddr;
use tokio::net::TcpStream;
use tokio::time::timeout;
impl NetworkTest {
pub async fn test_tcp(&self) -> Result<String> {
let ip = self.resolve_target().await?;
let port = self.port.unwrap_or(80);
let addr = SocketAddr::new(ip, port);
let stream = timeout(self.timeout, TcpStream::connect(addr))
.await
.map_err(|_| NetworkError::Timeout)?
.map_err(|e| NetworkError::Io(e))?;
let local_addr = stream.local_addr().map_err(NetworkError::Io)?;
let peer_addr = stream.peer_addr().map_err(NetworkError::Io)?;
Ok(format!(
"TCP connection successful: {} -> {}",
local_addr, peer_addr
))
}
}
pub async fn test_tcp_ports(target: &str, ports: &[u16], ip_version: IpVersion) -> Vec<TestResult> {
let mut results = Vec::new();
for &port in ports {
let test = NetworkTest::new(target.to_string(), ip_version, super::NetworkProtocol::Tcp)
.with_port(port);
results.push(test.run().await);
}
results
}
pub async fn test_tcp_common_ports(target: &str, ip_version: IpVersion) -> Vec<TestResult> {
let common_ports = [22, 25, 53, 80, 110, 143, 443, 993, 995, 8080, 8443];
test_tcp_ports(target, &common_ports, ip_version).await
}
+63
View File
@@ -0,0 +1,63 @@
use super::{IpVersion, NetworkTest};
use crate::utils::{NetworkError, Result, TestResult};
use std::net::SocketAddr;
use tokio::net::UdpSocket;
use tokio::time::timeout;
impl NetworkTest {
pub async fn test_udp(&self) -> Result<String> {
let ip = self.resolve_target().await?;
let port = self.port.unwrap_or(53);
let addr = SocketAddr::new(ip, port);
let bind_addr = match self.ip_version {
IpVersion::V4 => "0.0.0.0:0",
IpVersion::V6 => "[::]:0",
};
let socket = UdpSocket::bind(bind_addr).await.map_err(NetworkError::Io)?;
socket.connect(addr).await.map_err(NetworkError::Io)?;
let test_data = b"test";
let send_result = timeout(self.timeout, socket.send(test_data))
.await
.map_err(|_| NetworkError::Timeout)?
.map_err(NetworkError::Io)?;
let mut buf = [0; 1024];
let recv_result =
timeout(std::time::Duration::from_millis(100), socket.recv(&mut buf)).await;
let details = match recv_result {
Ok(Ok(bytes)) => format!(
"UDP test successful: sent {} bytes, received {} bytes to {}",
send_result, bytes, addr
),
_ => format!(
"UDP test (send only): sent {} bytes to {} (no response expected for basic connectivity test)",
send_result, addr
),
};
Ok(details)
}
}
pub async fn test_udp_ports(target: &str, ports: &[u16], ip_version: IpVersion) -> Vec<TestResult> {
let mut results = Vec::new();
for &port in ports {
let test = NetworkTest::new(target.to_string(), ip_version, super::NetworkProtocol::Udp)
.with_port(port);
results.push(test.run().await);
}
results
}
pub async fn test_udp_common_ports(target: &str, ip_version: IpVersion) -> Vec<TestResult> {
let common_ports = [53, 67, 68, 123, 161, 162, 514, 1194, 5353];
test_udp_ports(target, &common_ports, ip_version).await
}