use std::io::{BufRead, BufReader};
use std::net::IpAddr;
use std::process::{Command, Stdio};
use std::sync::mpsc::Sender;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use std::time::{Duration, Instant};
use crate::bridge::NetMessage;
use crate::util::make_display_host_str;
#[allow(dead_code)]
pub fn real_system_ping_loop(
token_id: usize,
ip: IpAddr,
maybe_host: Option<String>,
count: Option<usize>,
interval_ms: Option<u64>,
tx: Sender<NetMessage>,
stop_flag: Arc<AtomicBool>,
verbose: bool,
) {
let dest_display = make_display_host_str(&maybe_host, ip);
let dest_for_cli = maybe_host.clone().unwrap_or_else(|| ip.to_string());
#[cfg(unix)]
let args: Vec<String> = {
let mut v = Vec::new();
v.push("-n".to_string());
if ip.is_ipv6() {
v.push("-6".to_string());
} else {
#[cfg(any(target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "macos"))]
v.push("-4".to_string());
}
if let Some(c) = count {
v.push("-c".into());
v.push(c.to_string());
}
if let Some(ms) = interval_ms {
let secs = (ms as f64) / 1000.0;
v.push("-i".into());
v.push(format!("{:.3}", secs.max(0.01)));
}
v.push(dest_for_cli.clone());
v
};
#[cfg(windows)]
let args: Vec<String> = {
let mut v = Vec::new();
if let Some(c) = count {
v.push("-n".into());
v.push(c.to_string());
}
if let Some(ms) = interval_ms {
v.push("-w".into());
v.push(ms.to_string());
}
v.push(dest_for_cli.clone());
v
};
if verbose {
eprintln!("[net:ping] launching system ping: ping {:?}", args);
}
let spawn_res = Command::new("ping")
.args(&args)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn();
let mut child = match spawn_res {
Ok(c) => c,
Err(e) => {
let _ = tx.send(NetMessage::PingErr(
token_id,
format!("Failed to spawn `ping`: {e}"),
));
let _ = tx.send(NetMessage::PingDone(token_id));
return;
}
};
if let Some(stdout) = child.stdout.take() {
let reader = BufReader::new(stdout);
for line in reader.lines() {
if stop_flag.load(Ordering::Relaxed) {
let _ = child.kill();
let _ = tx.send(NetMessage::PingDone(token_id));
return;
}
match line {
Ok(mut s) => {
if s.contains("bytes from") && !dest_display.is_empty() {
if !s.contains(&dest_display) && s.contains(&dest_for_cli) {
s = s.replace(&dest_for_cli, &dest_display);
}
}
if !s.trim().is_empty() {
let _ = tx.send(NetMessage::PingLine(token_id, s));
}
}
Err(e) => {
let _ = tx.send(NetMessage::PingErr(
token_id,
format!("Error reading `ping` output: {e}"),
));
break;
}
}
}
}
if let Some(mut stderr) = child.stderr.take() {
use std::io::Read;
let mut _buf = String::new();
let _ = stderr.read_to_string(&mut _buf);
}
let _ = child.kill(); let _ = tx.send(NetMessage::PingDone(token_id));
}
#[cfg(unix)]
#[allow(dead_code)]
pub fn real_raw_icmp_loop(
token_id: usize,
ip: IpAddr,
maybe_host: Option<String>,
count: Option<usize>,
interval_ms: Option<u64>,
tx: Sender<NetMessage>,
stop_flag: Arc<AtomicBool>,
verbose: bool,
) {
use std::mem::size_of_val;
let v4 = match ip {
IpAddr::V4(v4) => v4,
IpAddr::V6(_) => {
let _ = tx.send(NetMessage::PingErr(
token_id,
"raw ICMP: IPv6 not implemented in this plugin".to_string(),
));
let _ = tx.send(NetMessage::PingDone(token_id));
return;
}
};
unsafe {
let fd = libc::socket(libc::AF_INET, libc::SOCK_RAW, libc::IPPROTO_ICMP);
if fd < 0 {
let _ = tx.send(NetMessage::PingErr(
token_id,
format!(
"raw ICMP: socket() failed (are you root?) errno={}",
*libc::__errno_location()
),
));
let _ = tx.send(NetMessage::PingDone(token_id));
return;
}
let tv = libc::timeval {
tv_sec: 1,
tv_usec: 0,
};
let r = libc::setsockopt(
fd,
libc::SOL_SOCKET,
libc::SO_RCVTIMEO,
&tv as *const libc::timeval as *const libc::c_void,
size_of_val(&tv) as libc::socklen_t,
);
if r != 0 && verbose {
eprintln!(
"[raw-icmp] warning: setsockopt SO_RCVTIMEO failed errno={}",
*libc::__errno_location()
);
}
let oct = v4.octets();
let mut addr: libc::sockaddr_in = std::mem::zeroed();
addr.sin_family = libc::AF_INET as libc::sa_family_t;
addr.sin_port = 0;
addr.sin_addr = libc::in_addr {
s_addr: u32::from_be_bytes(oct),
};
let ident: u16 = (libc::getpid() as u16) ^ 0xBEEF;
let max_count = count.unwrap_or(4);
let sleep_ms = interval_ms.unwrap_or(1000).max(10);
let dest_display = make_display_host_str(&maybe_host, ip);
for seq in 1..=max_count {
if stop_flag.load(Ordering::Relaxed) {
break;
}
let mut packet = Vec::<u8>::new();
packet.push(8); packet.push(0); packet.extend_from_slice(&[0, 0]); packet.extend_from_slice(&ident.to_be_bytes());
packet.extend_from_slice(&(seq as u16).to_be_bytes());
let now_ns = Instant::now();
let stamp = now_ns.elapsed().as_nanos() as u64; packet.extend_from_slice(&stamp.to_be_bytes());
if packet.len() < 56 {
packet.resize(56, 0);
}
{
let cksum = icmp_checksum(&packet);
packet[2] = (cksum >> 8) as u8;
packet[3] = (cksum & 0xFF) as u8;
}
let send_start = Instant::now();
let send_res = libc::sendto(
fd,
packet.as_ptr() as *const libc::c_void,
packet.len(),
0,
&addr as *const libc::sockaddr_in as *const libc::sockaddr,
std::mem::size_of::<libc::sockaddr_in>() as libc::socklen_t,
);
if send_res < 0 {
let _ = tx.send(NetMessage::PingErr(
token_id,
format!(
"raw ICMP: sendto() failed errno={}",
*libc::__errno_location()
),
));
break;
}
let mut buf = [0u8; 2048];
let mut got_reply = false;
loop {
if stop_flag.load(Ordering::Relaxed) {
break;
}
let n = libc::recv(
fd,
buf.as_mut_ptr() as *mut libc::c_void,
buf.len(),
0,
);
if n < 0 {
break;
}
if (n as usize) < 28 {
continue;
}
let ip_header_len = ((buf[0] & 0x0F) * 4) as usize;
if ip_header_len + 8 > n as usize {
continue;
}
let icmp = &buf[ip_header_len..];
let icmp_type = icmp[0];
let icmp_code = icmp[1];
if icmp_type != 0 || icmp_code != 0 {
continue; }
let recv_ident = u16::from_be_bytes([icmp[4], icmp[5]]);
let recv_seq = u16::from_be_bytes([icmp[6], icmp[7]]);
if recv_ident != ident || recv_seq != (seq as u16) {
continue;
}
let rtt_ms = send_start.elapsed().as_secs_f64() * 1000.0;
let line = format!(
"{} bytes from {}: icmp_seq={} ttl=? time={:.2} ms",
n,
dest_display,
seq,
rtt_ms
);
let _ = tx.send(NetMessage::PingLine(token_id, line));
got_reply = true;
break;
}
if !got_reply {
let _ = tx.send(NetMessage::PingLine(
token_id,
format!("Request timeout for icmp_seq {}", seq),
));
}
let mut slept = 0u64;
while slept < sleep_ms {
if stop_flag.load(Ordering::Relaxed) {
break;
}
let step = (sleep_ms - slept).min(50);
std::thread::sleep(Duration::from_millis(step));
slept += step;
}
}
let _ = tx.send(NetMessage::PingDone(token_id));
let _ = libc::close(fd);
}
}
#[cfg(not(unix))]
#[allow(dead_code)]
pub fn real_raw_icmp_loop(
token_id: usize,
_ip: IpAddr,
_maybe_host: Option<String>,
_count: Option<usize>,
_interval_ms: Option<u64>,
tx: Sender<NetMessage>,
_stop_flag: Arc<AtomicBool>,
_verbose: bool,
) {
let _ = tx.send(NetMessage::PingErr(
token_id,
"raw ICMP: not supported on this platform".to_string(),
));
let _ = tx.send(NetMessage::PingDone(token_id));
}
#[allow(dead_code)]
pub fn icmp_checksum(data: &[u8]) -> u16 {
let mut sum: u32 = 0;
let mut chunks = data.chunks_exact(2);
for chunk in &mut chunks {
let word = u16::from_be_bytes([chunk[0], chunk[1]]) as u32;
sum = sum.wrapping_add(word);
}
if let [last] = chunks.remainder() {
let word = u16::from_be_bytes([*last, 0]) as u32;
sum = sum.wrapping_add(word);
}
while (sum >> 16) != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
!(sum as u16)
}