use clap::Parser;
use std::net::{IpAddr, ToSocketAddrs};
use tokio::sync::mpsc;
use tokio::sync::mpsc::error::TryRecvError;
use tokio::time::sleep;
mod args;
mod ip;
mod pinger;
mod print;
mod state;
mod stats;
use args::Args;
use ip::parse_ip;
use pinger::{PingResult, spawn_ping};
use print::{print_legend_line, print_ping_line, print_stats, print_status_line};
use state::State;
use stats::compute_stats;
fn parse_target(target_str: &str) -> IpAddr {
if let Ok(ip) = parse_ip(target_str) {
ip
} else {
let dns_str = format!("{}:0", target_str);
match dns_str.to_socket_addrs() {
Ok(mut addrs) => {
if let Some(addr) = addrs.next() {
addr.ip()
} else {
eprintln!("Error: no DNS addresses found");
std::process::exit(1);
}
}
Err(e) => {
eprintln!("Error: DNS resolution failed: {}", e);
std::process::exit(1);
}
}
}
}
fn main_loop(
args: &Args,
states: &mut Vec<State>,
target: &IpAddr,
tx: &mpsc::Sender<PingResult>,
rx: &mut mpsc::Receiver<PingResult>,
packet_id: &mut u32,
) {
let current_time = State::current_time_ms();
for state in states.iter_mut() {
if let State::NotYetReceived { time_sent } = state {
if current_time - *time_sent > 2000 {
*state = State::Lost;
}
}
}
states.push(State::NotYetReceived {
time_sent: current_time,
});
spawn_ping(*target, tx.clone(), *packet_id, args.ttl.unwrap_or(64));
*packet_id += 1;
loop {
match rx.try_recv() {
Ok(result) => {
if args.beep {
print!("\x07"); }
match result {
PingResult::Ping { rtt, id } => {
if (id as usize) < states.len() {
states[id as usize] = State::Received { rtt };
}
}
PingResult::NoPingErr { id } => {
states[id as usize] = State::SendError;
}
}
}
Err(TryRecvError::Disconnected) => {
eprintln!("Critical error, pinger thread killed");
std::process::exit(2);
}
Err(TryRecvError::Empty) => {
break;
}
}
}
if !std::process::Command::new("tput")
.arg("rc")
.status()
.map(|s| s.success())
.unwrap_or(false)
{
println!("Process failed too");
std::process::exit(1);
}
print_ping_line(states);
if args.stats {
if let Some(stats) = compute_stats(states) {
print_stats(&stats);
}
let first_index = if (states.len() as i32) - 30 > 0 {
states.len() - 30
} else {
0
};
if let Some(stats) = compute_stats(&states[first_index..]) {
print_stats(&stats);
}
}
}
#[tokio::main]
async fn main() {
let args = Args::parse();
let target_str: String;
let target: IpAddr = if let Some(target_arg) = args.target.clone() {
target_str = target_arg;
parse_target(&target_str)
} else if let Some(default_target_str) = args.default_target.clone() {
target_str = default_target_str;
parse_target(&target_str)
} else {
eprintln!("Error: target address is required");
std::process::exit(1);
};
let (tx, mut rx) = mpsc::channel::<PingResult>(10);
let mut states: Vec<State> = Vec::new();
let mut packet_id: u32 = 0;
if args.legend {
print_legend_line();
}
if args.status {
print_status_line(&target_str, &target);
}
if !std::process::Command::new("tput")
.arg("sc")
.status()
.map(|s| s.success())
.unwrap_or(false)
{
println!("Process failed");
std::process::exit(1);
}
let wait_time = args.interval.unwrap_or(1000);
match args.count {
Some(c) => {
for _ in 1..c {
main_loop(&args, &mut states, &target, &tx, &mut rx, &mut packet_id);
sleep(tokio::time::Duration::from_millis(wait_time as u64)).await;
}
}
None => loop {
main_loop(&args, &mut states, &target, &tx, &mut rx, &mut packet_id);
sleep(tokio::time::Duration::from_millis(wait_time as u64)).await;
},
}
}