use std::collections::BTreeSet;
use std::io::{IsTerminal, stderr, stdout};
use std::net::IpAddr;
use std::sync::Arc;
mod util;
use util::{
PingBackend, can_open_raw_socket, command_exists, get_addresses, get_args,
hostname_resolution_supported, raw_socket_supported, resolve_hostname, select_ping_backend,
socket_ping, system_ping,
};
use tokio::sync::Semaphore;
type PingResults = Vec<tokio::task::JoinHandle<Option<String>>>;
fn main() {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
if let Err(e) = run().await {
eprintln!("Error: {}", e);
std::process::exit(1);
}
})
}
async fn run() -> Result<(), Box<dyn std::error::Error>> {
let args = get_args();
let resolve = if args.dont_resolve {
false
} else if hostname_resolution_supported() {
true
} else {
if cfg!(target_os = "linux") && stdout().is_terminal() {
eprintln!("`avahi-resolve` not found, hostname resolution disabled");
}
false
};
if args.raw_socket && !raw_socket_supported() && stderr().is_terminal() {
eprintln!(
"Raw socket mode is unsupported on this platform; falling back to system `ping`."
);
}
let ping_backend = select_ping_backend(args.raw_socket, command_exists("ping"))?;
if ping_backend == PingBackend::RawSocket
&& !can_open_raw_socket().await
&& stderr().is_terminal()
{
let err_msg = "Either run as root, or run `setcap cap_net_raw+ep $(which pingall)` to allow this app to open raw sockets.";
eprintln!("Error opening raw socket.\n{}", err_msg);
}
let addresses = get_addresses(args.interface);
let semaphore = Arc::new(Semaphore::new(150));
let mut results = vec![];
for address in addresses {
let octets = address.octets();
let ip_subnet = format!("{}.{}.{}.", octets[0], octets[1], octets[2]);
results.extend(
run_subnet(
&ip_subnet,
resolve,
ping_backend,
args.timeout,
semaphore.clone(),
)
.await?,
);
}
let mut seen = BTreeSet::new();
for ping in results {
match ping.await {
Ok(Some(result)) => {
if !seen.contains(&result) {
println!("{}", result);
seen.insert(result);
}
}
Ok(None) => {
}
Err(e) => {
if stderr().is_terminal() {
eprintln!("Warning: ping task failed: {}", e);
}
}
}
}
Ok(())
}
async fn run_subnet(
subnet: &str,
resolve_hostnames: bool,
ping_backend: PingBackend,
timeout: usize,
semaphore: Arc<Semaphore>,
) -> Result<PingResults, Box<dyn std::error::Error>> {
Ok((1..255)
.filter_map(|i| {
let ip_str = format!("{}{}", subnet, i);
match ip_str.parse() {
Ok(ip_v4) => {
let ip_addr = IpAddr::V4(ip_v4);
let semaphore = semaphore.clone();
Some(tokio::spawn(async move {
let _permit = match semaphore.acquire().await {
Ok(permit) => permit,
Err(_) => return None, };
let success = match ping_backend {
PingBackend::RawSocket => socket_ping(&ip_addr, timeout).await,
PingBackend::System => system_ping(&ip_addr, timeout).await,
};
match (success, resolve_hostnames) {
(true, true) => resolve_hostname(&ip_addr)
.await
.or_else(|| Some(ip_addr.to_string())),
(true, false) => Some(ip_addr.to_string()),
_ => None,
}
}))
}
Err(_) => {
None
}
}
})
.collect())
}