use clap::{Args, Parser, Subcommand};
use anti::ping::{
resolve_hostname, PingConfig, PingError, PingResult, PingStatistics, PingerBuilder,
};
use std::process;
use std::time::Duration;
use anti::traceroute::{Tracer, TracerouteConfig};
const VERSION: &str = env!("CARGO_PKG_VERSION");
#[derive(Parser)]
#[command(name = "anti", version = VERSION, about = "Unified CLI toolbox")]
struct Cli {
#[command(subcommand)]
domain: Domain,
}
#[derive(Subcommand)]
enum Domain {
Net {
#[command(subcommand)]
command: NetCmd,
},
}
#[derive(Subcommand)]
enum NetCmd {
Ping(PingOpts),
Trace(TraceOpts),
}
#[derive(Args)]
struct PingOpts {
host: String,
#[arg(long, default_value_t = 4)]
count: u16,
#[arg(long, default_value_t = 5)]
timeout: u64,
}
#[derive(Args)]
struct TraceOpts {
host: String,
#[arg(long)]
tcp: bool,
}
fn main() {
let cli = Cli::parse();
if let Err(e) = match cli.domain {
Domain::Net { command: cmd } => match cmd {
NetCmd::Ping(opts) => net_ping(opts),
NetCmd::Trace(opts) => net_trace(opts),
},
} {
eprintln!("Error: {}", e);
process::exit(1);
}
}
fn net_ping(opts: PingOpts) -> PingResult<()> {
let target = resolve_hostname(&opts.host)?;
let config = PingConfig {
target,
count: opts.count,
timeout: Duration::from_secs(opts.timeout),
..Default::default()
};
let pinger = PingerBuilder::new()
.target(config.target)
.count(config.count)
.timeout(config.timeout)
.interval(config.interval)
.packet_size(config.packet_size)
.build_icmp()?;
println!("PING {} ({})", opts.host, target);
let stats = pinger.ping_with_callback(|seq, result| match result {
Ok(reply) => println!(
"{} bytes from {}: seq={} time={:.2} ms",
reply.bytes_received,
reply.from,
reply.sequence,
reply.rtt.as_secs_f64() * 1000.0
),
Err(PingError::Timeout { .. }) => println!("Request timeout for seq={}", seq),
Err(e) => println!("Error for seq={}: {}", seq, e),
})?;
print_summary(&opts.host, &stats);
Ok(())
}
fn print_summary(host: &str, stats: &PingStatistics) {
println!("\n--- {} ping statistics ---", host);
println!(
"{} packets transmitted, {} received, {:.1}% packet loss",
stats.packets_transmitted, stats.packets_received, stats.packet_loss
);
if stats.packets_received > 0 {
if let (Some(min), Some(avg), Some(max)) = (stats.min_rtt, stats.avg_rtt, stats.max_rtt) {
println!(
"round-trip min/avg/max = {:.2}/{:.2}/{:.2} ms",
min.as_secs_f64() * 1000.0,
avg.as_secs_f64() * 1000.0,
max.as_secs_f64() * 1000.0,
);
}
}
}
fn net_trace(opts: TraceOpts) -> PingResult<()> {
let target = resolve_hostname(&opts.host)?;
if opts.tcp {
eprintln!("TCP traceroute not implemented, using default UDP/ICMP method.");
}
let config = TracerouteConfig {
target,
..Default::default()
};
println!("traceroute to {} ({})", opts.host, target);
let tracer = Tracer::new(config)?;
let hops = tracer.trace()?;
for hop in hops {
print!("{:>2} ", hop.ttl);
if let Some(addr) = hop.addr {
print!("{} ", addr);
if let Some(rtt) = hop.rtt {
print!("{:.3} ms", rtt.as_secs_f64() * 1000.0);
}
} else {
print!("*");
}
println!();
}
Ok(())
}