use std::net::IpAddr;
use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Protocol {
Icmp,
Tcp(u16),
Udp(u16),
}
impl Protocol {
pub fn port(&self) -> Option<u16> {
match self {
Protocol::Icmp => None,
Protocol::Tcp(port) | Protocol::Udp(port) => Some(*port),
}
}
pub fn name(&self) -> &'static str {
match self {
Protocol::Icmp => "ICMP",
Protocol::Tcp(_) => "TCP",
Protocol::Udp(_) => "UDP",
}
}
}
impl std::fmt::Display for Protocol {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Protocol::Icmp => write!(f, "ICMP"),
Protocol::Tcp(port) => write!(f, "TCP/{port}"),
Protocol::Udp(port) => write!(f, "UDP/{port}"),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct TimingBreakdown {
pub dns_time: Option<Duration>,
pub connect_time: Duration,
pub response_time: Duration,
pub total_time: Duration,
}
impl TimingBreakdown {
pub fn new(
dns_time: Option<Duration>,
connect_time: Duration,
response_time: Duration,
) -> Self {
let total_time = dns_time.unwrap_or_default() + connect_time + response_time;
Self {
dns_time,
connect_time,
response_time,
total_time,
}
}
pub fn dns_ms(&self) -> Option<f64> {
self.dns_time.map(|d| d.as_secs_f64() * 1000.0)
}
pub fn connect_ms(&self) -> f64 {
self.connect_time.as_secs_f64() * 1000.0
}
pub fn response_ms(&self) -> f64 {
self.response_time.as_secs_f64() * 1000.0
}
pub fn total_ms(&self) -> f64 {
self.total_time.as_secs_f64() * 1000.0
}
}
#[derive(Debug, Clone)]
pub struct ProbeResult {
pub target: String,
pub resolved_ip: IpAddr,
pub protocol: Protocol,
pub success: bool,
pub timing: TimingBreakdown,
pub ttl: Option<u8>,
pub error: Option<String>,
pub response_data: Option<ResponseData>,
}
#[derive(Debug, Clone)]
pub enum ResponseData {
Icmp {
sequence: u16,
identifier: u16,
},
Tcp {
connected: bool,
reset: bool,
},
Udp {
bytes_received: usize,
port_unreachable: bool,
},
}
impl ProbeResult {
pub fn success(
target: String,
resolved_ip: IpAddr,
protocol: Protocol,
timing: TimingBreakdown,
) -> Self {
Self {
target,
resolved_ip,
protocol,
success: true,
timing,
ttl: None,
error: None,
response_data: None,
}
}
pub fn failure(
target: String,
resolved_ip: IpAddr,
protocol: Protocol,
error: String,
timing: TimingBreakdown,
) -> Self {
Self {
target,
resolved_ip,
protocol,
success: false,
timing,
ttl: None,
error: Some(error),
response_data: None,
}
}
pub fn with_ttl(mut self, ttl: u8) -> Self {
self.ttl = Some(ttl);
self
}
pub fn with_response_data(mut self, data: ResponseData) -> Self {
self.response_data = Some(data);
self
}
}
#[derive(Debug, Clone)]
pub struct ProbeOptions {
pub timeout: Duration,
pub retries: u32,
pub ttl: Option<u8>,
pub source_ip: Option<IpAddr>,
pub source_port: Option<u16>,
}
impl Default for ProbeOptions {
fn default() -> Self {
Self {
timeout: Duration::from_secs(5),
retries: 0,
ttl: None,
source_ip: None,
source_port: None,
}
}
}
impl ProbeOptions {
pub fn with_timeout(timeout: Duration) -> Self {
Self {
timeout,
..Default::default()
}
}
}
#[derive(Debug, Clone)]
pub struct MultiProbeResult {
pub target: String,
pub results: Vec<ProbeResult>,
pub classification: Option<PathClassification>,
}
impl MultiProbeResult {
pub fn get(&self, protocol: &Protocol) -> Option<&ProbeResult> {
self.results.iter().find(|r| &r.protocol == protocol)
}
pub fn icmp_success(&self) -> bool {
self.results.iter()
.any(|r| matches!(r.protocol, Protocol::Icmp) && r.success)
}
pub fn tcp_success(&self, port: u16) -> bool {
self.results.iter()
.any(|r| matches!(r.protocol, Protocol::Tcp(p) if p == port) && r.success)
}
pub fn udp_success(&self, port: u16) -> bool {
self.results.iter()
.any(|r| matches!(r.protocol, Protocol::Udp(p) if p == port) && r.success)
}
pub fn classify(&self) -> String {
if let Some(ref class) = self.classification {
class.to_string()
} else {
"Unknown".to_string()
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PathClassification {
Open,
IcmpFiltered,
SelectiveFirewall {
open_ports: Vec<u16>,
closed_ports: Vec<u16>,
},
Blocked,
TcpFiltered,
NatDetected,
Unknown,
}
impl std::fmt::Display for PathClassification {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PathClassification::Open => write!(f, "Open (all protocols pass)"),
PathClassification::IcmpFiltered => write!(f, "ICMP Filtered (TCP/UDP open)"),
PathClassification::SelectiveFirewall { open_ports, closed_ports } => {
write!(f, "Selective Firewall (open: {open_ports:?}, closed: {closed_ports:?})")
}
PathClassification::Blocked => write!(f, "Blocked (all protocols fail)"),
PathClassification::TcpFiltered => write!(f, "TCP Filtered (ICMP open)"),
PathClassification::NatDetected => write!(f, "NAT/Load Balancer detected"),
PathClassification::Unknown => write!(f, "Unknown"),
}
}
}