use std::io::{IsTerminal, stderr, stdout};
use clap::{ArgAction, Parser};
use pingall::cli_support::{
PingBackend, can_open_raw_socket, command_exists, hostname_resolution_supported,
raw_socket_supported, select_ping_backend,
};
use pingall::{ScanOptions, scan_each};
#[derive(Debug, Parser)]
#[command(version, about)]
struct Args {
#[arg(short, long)]
interface: Option<String>,
#[arg(short = 'd', long = "dont-resolve", alias = "no-resolve", action = ArgAction::SetTrue)]
dont_resolve: bool,
#[arg(short = 'r', long = "raw-socket", action = ArgAction::SetTrue)]
raw_socket: bool,
#[arg(short, long, default_value_t = 1)]
timeout: usize,
#[arg(short = '4', long = "ipv4", conflicts_with = "ipv6", action = ArgAction::SetTrue)]
ipv4: bool,
#[arg(short = '6', long = "ipv6", conflicts_with = "ipv4", action = ArgAction::SetTrue)]
ipv6: bool,
}
impl Args {
fn scan_ipv4(&self) -> bool {
!self.ipv6
}
fn scan_ipv6(&self) -> bool {
!self.ipv4
}
}
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 = Args::parse();
let resolve_hostnames = 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 system_ping_exists = command_exists("ping");
if args.scan_ipv6() && !system_ping_exists {
return Err("system `ping` command not found and IPv6 discovery requires it".into());
}
let ping_backend = select_ping_backend(args.raw_socket, system_ping_exists)?;
if args.scan_ipv4()
&& 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 ipv4 = args.scan_ipv4();
let ipv6 = args.scan_ipv6();
let options = ScanOptions {
interface: args.interface,
resolve_hostnames,
raw_socket: args.raw_socket,
timeout: args.timeout,
ipv4,
ipv6,
};
scan_each(options, |result| println!("{}", result)).await?;
Ok(())
}