From a88bd522e089111b9a4e49a41e0f6cc40c77b848 Mon Sep 17 00:00:00 2001 From: kill-ux Date: Sat, 31 Jan 2026 17:13:16 +0100 Subject: [PATCH 1/5] j --- src/cli.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index df95db5..fc07423 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -9,7 +9,6 @@ use crate::hostmapper::localnet::get_localnet; name = "pentestkit", version = "0.1.0", about = "Welcome to PentestKit, High-performance pentest toolkit in Rust", - long_about = None, propagate_version = true, arg_required_else_help = true )] @@ -81,23 +80,27 @@ pub struct Cli { )] pub scan_mode: String, + /// default is logger with info and errors | with it all the loggers work #[arg(short, long)] pub log: bool, - // threads number + /// threads number #[arg(short = 'c', long = "threads", default_value_t = 50)] pub threads: usize, - // extensions + /// extensions #[arg(short = 'x', long)] pub extensions: Option, + /// app gui #[arg(short = 'u', long, conflicts_with_all = ["target", "subnet", "header_url", "dir_url"])] pub gui: bool, + /// verify server name #[arg(short, long)] pub verify: bool, + /// localnet #[arg(long)] pub localnet: bool, } From b90d2985fd32f8484f394bfae1fc781dbe18d670 Mon Sep 17 00:00:00 2001 From: kill-ux Date: Sat, 31 Jan 2026 18:08:37 +0100 Subject: [PATCH 2/5] j --- src/cli.rs | 4 ++-- src/gui/mod.rs | 8 +------- src/utils/mod.rs | 3 ++- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index fc07423..28fd08c 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -9,7 +9,6 @@ use crate::hostmapper::localnet::get_localnet; name = "pentestkit", version = "0.1.0", about = "Welcome to PentestKit, High-performance pentest toolkit in Rust", - propagate_version = true, arg_required_else_help = true )] pub struct Cli { @@ -76,7 +75,8 @@ pub struct Cli { short = 's', value_name = "TYPE", default_value = "T", - verbatim_doc_comment + verbatim_doc_comment, + requires = "target" )] pub scan_mode: String, diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 6fac778..42aca28 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -138,7 +138,7 @@ impl App for PentestApp { ui.horizontal(|ui| { ui.label("Threads:"); ui.add(egui::Slider::new(&mut cli.threads, 1..=500)); - ui.label("output file:"); + ui.label("|| 📂 Output file:"); let mut output = cli.output.clone().unwrap_or_default(); if ui.text_edit_singleline(&mut output).changed() { cli.output = Some(output); @@ -155,9 +155,6 @@ impl App for PentestApp { { let results_clone = self.results.clone(); let scanning_clone = self.is_scanning.clone(); - // let target_url = self.url.clone(); - // let output_path = cli.output.clone(); - // let active_module_clone = self.active_module; let cli_guard = cli; let target_url = self.url.clone(); @@ -177,8 +174,6 @@ impl App for PentestApp { *scanning_clone.lock() = true; *results_clone.lock() = "Initializing connection...\n".to_string(); - // ##################################### - let run_result = match active_module_clone { ActiveModule::HeaderGrabber => { headergrabber::run(&target_url, &output_path, Some(tx_clone)) @@ -276,7 +271,6 @@ fn render_ansi_text(ui: &mut egui::Ui, text: &str) { ui.horizontal_wrapped(|ui| { ui.spacing_mut().item_spacing.x = 0.0; // Remove space between characters for seamless color - // This regex finds ANSI codes like \x1b[31m or \x1b[0m let ansi_re = regex::Regex::new(r"\x1b\[([0-9;]*)m").unwrap(); let mut last_end = 0; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 4adfb99..bed9bae 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -215,7 +215,8 @@ pub fn logger(cli: &Cli) { /// Launches the Eframe/Egui native window. /// This fulfills the 'GUI' requirement of the project using hardware acceleration. pub fn start_gui() -> Result<()> { - let options = eframe::NativeOptions::default(); + let mut options = eframe::NativeOptions::default(); + options.centered = true; eframe::run_native( "PentestKit", options, From 3af213cbb253221376ee78522dc14e76ad1ec891 Mon Sep 17 00:00:00 2001 From: kill-ux Date: Sun, 1 Feb 2026 12:05:42 +0100 Subject: [PATCH 3/5] just test icmp with row packets --- result3.txt | 4 + result4.xml | 1 + src/hostmapper/mod.rs | 265 ++++++++++++++++++++++++++++++++---------- 3 files changed, 211 insertions(+), 59 deletions(-) create mode 100644 result3.txt create mode 100644 result4.xml diff --git a/result3.txt b/result3.txt new file mode 100644 index 0000000..bd9d341 --- /dev/null +++ b/result3.txt @@ -0,0 +1,4 @@ +10.1.8.5 - LIVE (356.749µs) +10.1.8.6 - LIVE (25.179µs) +10.1.8.7 - LIVE (197.098µs) +10.1.8.3 - LIVE (335.722µs) \ No newline at end of file diff --git a/result4.xml b/result4.xml new file mode 100644 index 0000000..846dbd3 --- /dev/null +++ b/result4.xml @@ -0,0 +1 @@ +https://google.com301 Moved Permanentlygws20Content-Security-PolicyStrict-Transport-SecurityX-Content-Type-OptionsReferrer-Policyhttps://www.google.com/public, max-age=2592000text/html; charset=UTF-8gwsSAMEORIGINSun, 01 Feb 2026 10:10:50 GMT220object-src 'none';base-uri 'self';script-src 'nonce-g3bhEk0k5tugd_Hi6kTh2A' 'strict-dynamic' 'report-sample' 'unsafe-eval' 'unsafe-inline' https: http:;report-uri https://csp.withgoogle.com/csp/gws/other-hpTue, 03 Mar 2026 10:10:50 GMT0h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 \ No newline at end of file diff --git a/src/hostmapper/mod.rs b/src/hostmapper/mod.rs index f889c53..1ef0406 100644 --- a/src/hostmapper/mod.rs +++ b/src/hostmapper/mod.rs @@ -1,5 +1,12 @@ -use crate::utils::preluad::*; +use std::net::Ipv4Addr; +use std::time::Instant; + +use pnet::transport::icmp_packet_iter; +use pnet_packet::icmp::IcmpTypes; +use pnet_packet::icmpv6::echo_request::MutableEchoRequestPacket; + use crate::println_tx; +use crate::utils::preluad::*; pub mod localnet; @@ -14,88 +21,228 @@ pub mod localnet; /// * `threads` - Maximum number of simultaneous ping requests. /// * `output_path` - Optional file path to save the list of live hosts. /// * `tx_ui` - MPSC channel for sending real-time updates to the GUI/CLI. +// pub async fn run( +// net: Ipv4Net, +// threads: usize, +// output_path: &Option, +// tx_ui: Option>, +// ) -> Result<()> { +// let semaphore = Arc::new(Semaphore::new(threads)); +// let mut set = JoinSet::new(); +// let (tx, mut rx) = mpsc::channel::(100); + +// println_tx!(tx_ui, "Starting HostMapper on {}", net.to_string()); +// let payload = [0; 8]; +// let client = Client::new(&Config::default())?; +// let client = Arc::new(client); + +// let pb: ProgressBar = ProgressBar::new(net.hosts().count() as u64); + +// pb.set_style( +// ProgressStyle::default_bar() +// .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len}")? +// .progress_chars("█▓▒░"), +// ); +// let timeout_dur = Duration::from_millis(100); +// for ip in net.hosts() { +// // while set.len() >= threads { +// // set.join_next().await; +// // } +// let permit = semaphore.clone().acquire_owned().await.ok(); +// let tx_clone = tx.clone(); +// let client_clone = client.clone(); + +// // Generate a unique identifier for the ping based on the IP address. +// // This helps the surge-ping client match replies to requests. + +// let ip_u32 = u32::from(ip); +// let ident = (ip_u32 & 0xFFFF) as u16; + +// let tx_ui_clone = tx_ui.clone(); +// let pb_clone = pb.clone(); +// set.spawn(async move { +// let _permit = permit; +// let mut pinger = client_clone.pinger(ip.into(), PingIdentifier(ident)).await; +// pinger.timeout(timeout_dur); +// if let Result::Ok((_, duration)) = pinger.ping(PingSequence(0), &payload).await { +// let styled = format!( +// "{} {:<15} (up) in {:?}", +// " LIVE ".on_green().black(), +// ip.to_string().bold(), +// duration +// ); +// // let plain = format!("{} is up", ip); +// let res_host = HostResult { +// ip: ip.to_string(), +// status: " LIVE ".to_string(), +// latency: format!("{:?}", duration), +// }; + +// pb_clone.suspend(|| { +// println_tx!(tx_ui_clone, "{}", styled); +// }); + +// tx_clone.send(res_host).await?; +// } + +// pb_clone.inc(1); +// Ok(()) +// }); +// } + +// drop(tx); + +// let mut results = vec![]; +// while let Some(res) = rx.recv().await { +// results.push(res); +// } + +// // Ensure JoinSet finishes +// while set.join_next().await.is_some() {} +// if let Some(path) = output_path { +// save_results_host(path, results).await?; +// println_tx!(tx_ui, info!,"Live hosts saved to: {}", path); +// } + +// println_tx!(tx_ui,info!,"Network scan complete."); +// Ok(()) +// } +// use crate::println_tx; +// use crate::utils::preluad::*; +// use indicatif::{ProgressBar, ProgressStyle}; +// use std::sync::Arc; +// use std::time::{Duration, Instant}; +// use surge_ping::{Client, Config, PingIdentifier, PingSequence}; +// use tokio::sync::{Semaphore, mpsc}; +// use tokio::task::JoinSet; + +// pub mod localnet; + pub async fn run( net: Ipv4Net, threads: usize, output_path: &Option, tx_ui: Option>, ) -> Result<()> { - let semaphore = Arc::new(Semaphore::new(threads)); - let mut set = JoinSet::new(); - let (tx, mut rx) = mpsc::channel::(100); - println_tx!(tx_ui, "Starting HostMapper on {}", net.to_string()); - let payload = [0; 8]; - let client = Client::new(&Config::default())?; - let client = Arc::new(client); - let pb: ProgressBar = ProgressBar::new(net.hosts().count() as u64); + // Try raw socket method first (much faster) + println_tx!(tx_ui, "Using fast raw socket method (requires root)..."); + return run_fast_raw_socket_scan(net, threads, output_path, tx_ui).await; +} +/// Fast raw socket implementation +async fn run_fast_raw_socket_scan( + net: Ipv4Net, + _threads: usize, + output_path: &Option, + tx_ui: Option>, +) -> Result<()> { + use pnet::packet::Packet; + use pnet::packet::icmp::{IcmpTypes, echo_request::MutableEchoRequestPacket}; + use pnet::transport::TransportProtocol::Ipv4 as Ipv4Protocol; + use pnet::transport::{TransportChannelType::Layer3, icmp_packet_iter, transport_channel}; + + let start_time = Instant::now(); + let all_ips: Vec = net.hosts().collect(); + let total_hosts = all_ips.len(); + + // Progress bar + let pb = ProgressBar::new(total_hosts as u64); pb.set_style( ProgressStyle::default_bar() - .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len}")? + .template( + "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta})", + )? .progress_chars("█▓▒░"), ); - for ip in net.hosts() { - while set.len() >= threads { - set.join_next().await; + println_tx!(tx_ui, "Creating raw socket for ICMP..."); + + let protocol = Layer3(IpNextHeaderProtocols::Icmp); + let (mut tx, mut rx) = transport_channel(1024 * 1024, protocol) + .context("Failed to open raw socket. Try running with sudo.")?; + + let mut listener = icmp_packet_iter(&mut rx); + + println_tx!(tx_ui, "Sending {} ICMP Echo Requests...", total_hosts); + + // Send all pings first (very fast - almost instantaneous) + let mut sent_count = 0; + for ip in &all_ips { + // Create ICMP Echo Request packet + let mut buffer = vec![0u8; 64]; // ICMP header + payload + let mut packet = MutableEchoRequestPacket::new(&mut buffer).unwrap(); + + packet.set_icmp_type(IcmpTypes::EchoRequest); + packet.set_identifier(0x1234); + packet.set_sequence_number(1); + + // Calculate checksum + let checksum = pnet::util::checksum(packet.packet(), 1); + packet.set_checksum(checksum); + + // Send the packet + if tx.send_to(packet, std::net::IpAddr::V4(*ip)).is_ok() { + sent_count += 1; } - let semaphore_clone = semaphore.clone(); - let tx_clone = tx.clone(); - let client_clone = client.clone(); - - // Generate a unique identifier for the ping based on the IP address. - // This helps the surge-ping client match replies to requests. - let ip_u32 = u32::from(ip); - let ident = (ip_u32 & 0xFFFF) as u16; - - let tx_ui_clone = tx_ui.clone(); - let pb_clone = pb.clone(); - set.spawn(async move { - let _permit = semaphore_clone.acquire_owned().await.ok(); - let mut pinger = client_clone.pinger(ip.into(), PingIdentifier(ident)).await; - pinger.timeout(Duration::from_millis(1000)); - if let Result::Ok((_, duration)) = pinger.ping(PingSequence(0), &payload).await { - let styled = format!( - "{} {:<15} (up) in {:?}", - " LIVE ".on_green().black(), - ip.to_string().bold(), - duration - ); - // let plain = format!("{} is up", ip); - let res_host = HostResult { - ip: ip.to_string(), - status: " LIVE ".to_string(), - latency: format!("{:?}", duration), - }; - - pb_clone.suspend(|| { - println_tx!(tx_ui_clone, "{}", styled); - }); - - tx_clone.send(res_host).await?; - } - pb_clone.inc(1); - Ok(()) - }); + pb.inc(1); } - drop(tx); + pb.finish_with_message(format!("Sent {} packets", sent_count)); + + println_tx!(tx_ui, "Listening for responses (timeout: 2s)..."); + + // Listen for responses with timeout + let timeout = Duration::from_secs(2); + let deadline = Instant::now() + timeout; + let mut live_hosts = Vec::new(); + let mut results = Vec::new(); + + while Instant::now() < deadline { + match listener.next_with_timeout(Duration::from_millis(100)) { + Result::Ok(Some((packet, addr))) => { + if let std::net::IpAddr::V4(ipv4_addr) = addr { + if packet.get_icmp_type() == IcmpTypes::EchoReply { + if !live_hosts.contains(&ipv4_addr) { + live_hosts.push(ipv4_addr); + + let styled = format!( + "{} {:<15} (up)", + " LIVE ".on_green().black(), + ipv4_addr.to_string().bold(), + ); + + println_tx!(tx_ui, "{}", styled); - let mut results = vec![]; - while let Some(res) = rx.recv().await { - results.push(res); + results.push(HostResult { + ip: ipv4_addr.to_string(), + status: " LIVE ".to_string(), + latency: "N/A".to_string(), // Raw socket doesn't measure latency easily + }); + } + } + } + } + Result::Ok(None) => { + // Timeout, continue + } + Err(e) => { + eprintln!("Error receiving packet: {}", e); + break; + } + } } - // Ensure JoinSet finishes - while set.join_next().await.is_some() {} + let elapsed = start_time.elapsed(); + println_tx!(tx_ui, info!, "Raw socket scan complete in {:?}. Found {} live hosts.", elapsed, results.len()); + + // Save results if needed if let Some(path) = output_path { save_results_host(path, results).await?; - println_tx!(tx_ui, info!,"Live hosts saved to: {}", path); + println_tx!(tx_ui, info!, "Live hosts saved to: {}", path); } - println_tx!(tx_ui,info!,"Network scan complete."); Ok(()) } From b52a7c5675664d9866b77a3e7e3f140df4ec7db9 Mon Sep 17 00:00:00 2001 From: killux Date: Sun, 1 Feb 2026 12:54:29 +0100 Subject: [PATCH 4/5] j --- result.json | 1 + result3.json | 137 ++++++++++++++++++++ src/hostmapper/mod.rs | 290 +++++++++++------------------------------- 3 files changed, 213 insertions(+), 215 deletions(-) create mode 100644 result.json create mode 100644 result3.json diff --git a/result.json b/result.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/result.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/result3.json b/result3.json new file mode 100644 index 0000000..e7d839e --- /dev/null +++ b/result3.json @@ -0,0 +1,137 @@ +[ + { + "ip": "10.1.0.1", + "status": " LIVE ", + "latency": "3.082871ms" + }, + { + "ip": "10.1.1.11", + "status": " LIVE ", + "latency": "2.496818ms" + }, + { + "ip": "10.1.2.8", + "status": " LIVE ", + "latency": "3.260659ms" + }, + { + "ip": "10.1.3.2", + "status": " LIVE ", + "latency": "2.790927ms" + }, + { + "ip": "10.1.3.6", + "status": " LIVE ", + "latency": "2.677631ms" + }, + { + "ip": "10.1.3.8", + "status": " LIVE ", + "latency": "2.716745ms" + }, + { + "ip": "10.1.6.6", + "status": " LIVE ", + "latency": "5.435615ms" + }, + { + "ip": "10.1.8.6", + "status": " LIVE ", + "latency": "2.024272ms" + }, + { + "ip": "10.1.8.3", + "status": " LIVE ", + "latency": "1.920418ms" + }, + { + "ip": "10.1.8.7", + "status": " LIVE ", + "latency": "2.007437ms" + }, + { + "ip": "10.1.8.5", + "status": " LIVE ", + "latency": "2.480163ms" + }, + { + "ip": "10.1.12.4", + "status": " LIVE ", + "latency": "2.736251ms" + }, + { + "ip": "10.1.12.12", + "status": " LIVE ", + "latency": "2.388798ms" + }, + { + "ip": "10.1.12.7", + "status": " LIVE ", + "latency": "2.415666ms" + }, + { + "ip": "10.1.13.2", + "status": " LIVE ", + "latency": "4.049566ms" + }, + { + "ip": "10.1.14.9", + "status": " LIVE ", + "latency": "4.131567ms" + }, + { + "ip": "10.1.14.5", + "status": " LIVE ", + "latency": "4.006777ms" + }, + { + "ip": "10.1.14.11", + "status": " LIVE ", + "latency": "3.668083ms" + }, + { + "ip": "10.1.14.1", + "status": " LIVE ", + "latency": "3.812745ms" + }, + { + "ip": "10.1.18.2", + "status": " LIVE ", + "latency": "3.557259ms" + }, + { + "ip": "10.1.18.3", + "status": " LIVE ", + "latency": "3.444536ms" + }, + { + "ip": "10.1.18.16", + "status": " LIVE ", + "latency": "4.14696ms" + }, + { + "ip": "10.1.18.24", + "status": " LIVE ", + "latency": "3.824272ms" + }, + { + "ip": "10.1.19.4", + "status": " LIVE ", + "latency": "4.057099ms" + }, + { + "ip": "10.1.19.9", + "status": " LIVE ", + "latency": "3.856445ms" + }, + { + "ip": "10.1.19.10", + "status": " LIVE ", + "latency": "4.100233ms" + }, + { + "ip": "10.1.19.12", + "status": " LIVE ", + "latency": "5.078648ms" + } +] \ No newline at end of file diff --git a/src/hostmapper/mod.rs b/src/hostmapper/mod.rs index 1ef0406..270fa1e 100644 --- a/src/hostmapper/mod.rs +++ b/src/hostmapper/mod.rs @@ -1,248 +1,108 @@ use std::net::Ipv4Addr; use std::time::Instant; -use pnet::transport::icmp_packet_iter; -use pnet_packet::icmp::IcmpTypes; -use pnet_packet::icmpv6::echo_request::MutableEchoRequestPacket; +use egui::mutex::Mutex; +use pnet_packet::Packet; use crate::println_tx; use crate::utils::preluad::*; pub mod localnet; -/// Executes an asynchronous ICMP "Ping Sweep" across a specified IPv4 subnet. -/// -/// This function identifies live hosts by sending ICMP Echo Requests and waiting -/// for Echo Replies. It uses a `JoinSet` to manage concurrency and a `Semaphore` -/// to prevent overwhelming the local network stack. -/// -/// # Arguments -/// * `net` - The CIDR subnet to scan (e.g., 192.168.1.0/24). -/// * `threads` - Maximum number of simultaneous ping requests. -/// * `output_path` - Optional file path to save the list of live hosts. -/// * `tx_ui` - MPSC channel for sending real-time updates to the GUI/CLI. -// pub async fn run( -// net: Ipv4Net, -// threads: usize, -// output_path: &Option, -// tx_ui: Option>, -// ) -> Result<()> { -// let semaphore = Arc::new(Semaphore::new(threads)); -// let mut set = JoinSet::new(); -// let (tx, mut rx) = mpsc::channel::(100); - -// println_tx!(tx_ui, "Starting HostMapper on {}", net.to_string()); -// let payload = [0; 8]; -// let client = Client::new(&Config::default())?; -// let client = Arc::new(client); - -// let pb: ProgressBar = ProgressBar::new(net.hosts().count() as u64); - -// pb.set_style( -// ProgressStyle::default_bar() -// .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len}")? -// .progress_chars("█▓▒░"), -// ); -// let timeout_dur = Duration::from_millis(100); -// for ip in net.hosts() { -// // while set.len() >= threads { -// // set.join_next().await; -// // } -// let permit = semaphore.clone().acquire_owned().await.ok(); -// let tx_clone = tx.clone(); -// let client_clone = client.clone(); - -// // Generate a unique identifier for the ping based on the IP address. -// // This helps the surge-ping client match replies to requests. - -// let ip_u32 = u32::from(ip); -// let ident = (ip_u32 & 0xFFFF) as u16; - -// let tx_ui_clone = tx_ui.clone(); -// let pb_clone = pb.clone(); -// set.spawn(async move { -// let _permit = permit; -// let mut pinger = client_clone.pinger(ip.into(), PingIdentifier(ident)).await; -// pinger.timeout(timeout_dur); -// if let Result::Ok((_, duration)) = pinger.ping(PingSequence(0), &payload).await { -// let styled = format!( -// "{} {:<15} (up) in {:?}", -// " LIVE ".on_green().black(), -// ip.to_string().bold(), -// duration -// ); -// // let plain = format!("{} is up", ip); -// let res_host = HostResult { -// ip: ip.to_string(), -// status: " LIVE ".to_string(), -// latency: format!("{:?}", duration), -// }; - -// pb_clone.suspend(|| { -// println_tx!(tx_ui_clone, "{}", styled); -// }); - -// tx_clone.send(res_host).await?; -// } - -// pb_clone.inc(1); -// Ok(()) -// }); -// } - -// drop(tx); - -// let mut results = vec![]; -// while let Some(res) = rx.recv().await { -// results.push(res); -// } - -// // Ensure JoinSet finishes -// while set.join_next().await.is_some() {} -// if let Some(path) = output_path { -// save_results_host(path, results).await?; -// println_tx!(tx_ui, info!,"Live hosts saved to: {}", path); -// } - -// println_tx!(tx_ui,info!,"Network scan complete."); -// Ok(()) -// } -// use crate::println_tx; -// use crate::utils::preluad::*; -// use indicatif::{ProgressBar, ProgressStyle}; -// use std::sync::Arc; -// use std::time::{Duration, Instant}; -// use surge_ping::{Client, Config, PingIdentifier, PingSequence}; -// use tokio::sync::{Semaphore, mpsc}; -// use tokio::task::JoinSet; - -// pub mod localnet; - +// / Executes an asynchronous ICMP "Ping Sweep" across a specified IPv4 subnet. +// / +// / This function identifies live hosts by sending ICMP Echo Requests and waiting +// / for Echo Replies. It uses a `JoinSet` to manage concurrency and a `Semaphore` +// / to prevent overwhelming the local network stack. +// / +// / # Arguments +// / * `net` - The CIDR subnet to scan (e.g., 192.168.1.0/24). +// / * `threads` - Maximum number of simultaneous ping requests. +// / * `output_path` - Optional file path to save the list of live hosts. +// / * `tx_ui` - MPSC channel for sending real-time updates to the GUI/CLI. pub async fn run( net: Ipv4Net, threads: usize, output_path: &Option, tx_ui: Option>, ) -> Result<()> { - println_tx!(tx_ui, "Starting HostMapper on {}", net.to_string()); - - // Try raw socket method first (much faster) - println_tx!(tx_ui, "Using fast raw socket method (requires root)..."); - return run_fast_raw_socket_scan(net, threads, output_path, tx_ui).await; -} + let semaphore = Arc::new(Semaphore::new(threads)); + let mut set = JoinSet::new(); + let (tx, mut rx) = mpsc::channel::(100); -/// Fast raw socket implementation -async fn run_fast_raw_socket_scan( - net: Ipv4Net, - _threads: usize, - output_path: &Option, - tx_ui: Option>, -) -> Result<()> { - use pnet::packet::Packet; - use pnet::packet::icmp::{IcmpTypes, echo_request::MutableEchoRequestPacket}; - use pnet::transport::TransportProtocol::Ipv4 as Ipv4Protocol; - use pnet::transport::{TransportChannelType::Layer3, icmp_packet_iter, transport_channel}; + println_tx!(tx_ui, "Starting HostMapper on {}", net.to_string()); + let payload = [0; 8]; + let client = Client::new(&Config::default())?; + let client = Arc::new(client); - let start_time = Instant::now(); - let all_ips: Vec = net.hosts().collect(); - let total_hosts = all_ips.len(); + let pb: ProgressBar = ProgressBar::new(net.hosts().count() as u64); - // Progress bar - let pb = ProgressBar::new(total_hosts as u64); pb.set_style( ProgressStyle::default_bar() - .template( - "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta})", - )? + .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len}")? .progress_chars("█▓▒░"), ); + let timeout_dur = Duration::from_millis(100); + for ip in net.hosts() { + // while set.len() >= threads { + // set.join_next().await; + // } + let permit = semaphore.clone().acquire_owned().await.ok(); + let tx_clone = tx.clone(); + let client_clone = client.clone(); + + // Generate a unique identifier for the ping based on the IP address. + // This helps the surge-ping client match replies to requests. + + let ip_u32 = u32::from(ip); + let ident = (ip_u32 & 0xFFFF) as u16; + + let tx_ui_clone = tx_ui.clone(); + let pb_clone = pb.clone(); + set.spawn(async move { + let _permit = permit; + let mut pinger = client_clone.pinger(ip.into(), PingIdentifier(ident)).await; + pinger.timeout(timeout_dur); + if let Result::Ok((_, duration)) = pinger.ping(PingSequence(0), &payload).await { + let styled = format!( + "{} {:<15} (up) in {:?}", + " LIVE ".on_green().black(), + ip.to_string().bold(), + duration + ); + // let plain = format!("{} is up", ip); + let res_host = HostResult { + ip: ip.to_string(), + status: " LIVE ".to_string(), + latency: format!("{:?}", duration), + }; + + pb_clone.suspend(|| { + println_tx!(tx_ui_clone, "{}", styled); + }); + + tx_clone.send(res_host).await?; + } - println_tx!(tx_ui, "Creating raw socket for ICMP..."); - - let protocol = Layer3(IpNextHeaderProtocols::Icmp); - let (mut tx, mut rx) = transport_channel(1024 * 1024, protocol) - .context("Failed to open raw socket. Try running with sudo.")?; - - let mut listener = icmp_packet_iter(&mut rx); - - println_tx!(tx_ui, "Sending {} ICMP Echo Requests...", total_hosts); - - // Send all pings first (very fast - almost instantaneous) - let mut sent_count = 0; - for ip in &all_ips { - // Create ICMP Echo Request packet - let mut buffer = vec![0u8; 64]; // ICMP header + payload - let mut packet = MutableEchoRequestPacket::new(&mut buffer).unwrap(); - - packet.set_icmp_type(IcmpTypes::EchoRequest); - packet.set_identifier(0x1234); - packet.set_sequence_number(1); - - // Calculate checksum - let checksum = pnet::util::checksum(packet.packet(), 1); - packet.set_checksum(checksum); - - // Send the packet - if tx.send_to(packet, std::net::IpAddr::V4(*ip)).is_ok() { - sent_count += 1; - } - - pb.inc(1); + pb_clone.inc(1); + Ok(()) + }); } - pb.finish_with_message(format!("Sent {} packets", sent_count)); - - println_tx!(tx_ui, "Listening for responses (timeout: 2s)..."); - - // Listen for responses with timeout - let timeout = Duration::from_secs(2); - let deadline = Instant::now() + timeout; - let mut live_hosts = Vec::new(); - let mut results = Vec::new(); - - while Instant::now() < deadline { - match listener.next_with_timeout(Duration::from_millis(100)) { - Result::Ok(Some((packet, addr))) => { - if let std::net::IpAddr::V4(ipv4_addr) = addr { - if packet.get_icmp_type() == IcmpTypes::EchoReply { - if !live_hosts.contains(&ipv4_addr) { - live_hosts.push(ipv4_addr); - - let styled = format!( - "{} {:<15} (up)", - " LIVE ".on_green().black(), - ipv4_addr.to_string().bold(), - ); - - println_tx!(tx_ui, "{}", styled); - - results.push(HostResult { - ip: ipv4_addr.to_string(), - status: " LIVE ".to_string(), - latency: "N/A".to_string(), // Raw socket doesn't measure latency easily - }); - } - } - } - } - Result::Ok(None) => { - // Timeout, continue - } - Err(e) => { - eprintln!("Error receiving packet: {}", e); - break; - } - } - } + drop(tx); - let elapsed = start_time.elapsed(); - println_tx!(tx_ui, info!, "Raw socket scan complete in {:?}. Found {} live hosts.", elapsed, results.len()); + let mut results = vec![]; + while let Some(res) = rx.recv().await { + results.push(res); + } - // Save results if needed + // Ensure JoinSet finishes + while set.join_next().await.is_some() {} if let Some(path) = output_path { save_results_host(path, results).await?; - println_tx!(tx_ui, info!, "Live hosts saved to: {}", path); + println_tx!(tx_ui, info!,"Live hosts saved to: {}", path); } + println_tx!(tx_ui,info!,"Network scan complete."); Ok(()) } From 7e27c8bb545f638acc06ca838d1f9e22e07662a9 Mon Sep 17 00:00:00 2001 From: kill-ux Date: Sun, 1 Feb 2026 15:40:14 +0100 Subject: [PATCH 5/5] j --- result.json | 1 - result3.json | 137 ----------------------------------------- result3.txt | 4 -- result4.xml | 1 - src/hostmapper/mod.rs | 26 ++++---- src/tinyscanner/mod.rs | 22 ++++--- 6 files changed, 23 insertions(+), 168 deletions(-) delete mode 100644 result.json delete mode 100644 result3.json delete mode 100644 result3.txt delete mode 100644 result4.xml diff --git a/result.json b/result.json deleted file mode 100644 index 0637a08..0000000 --- a/result.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/result3.json b/result3.json deleted file mode 100644 index e7d839e..0000000 --- a/result3.json +++ /dev/null @@ -1,137 +0,0 @@ -[ - { - "ip": "10.1.0.1", - "status": " LIVE ", - "latency": "3.082871ms" - }, - { - "ip": "10.1.1.11", - "status": " LIVE ", - "latency": "2.496818ms" - }, - { - "ip": "10.1.2.8", - "status": " LIVE ", - "latency": "3.260659ms" - }, - { - "ip": "10.1.3.2", - "status": " LIVE ", - "latency": "2.790927ms" - }, - { - "ip": "10.1.3.6", - "status": " LIVE ", - "latency": "2.677631ms" - }, - { - "ip": "10.1.3.8", - "status": " LIVE ", - "latency": "2.716745ms" - }, - { - "ip": "10.1.6.6", - "status": " LIVE ", - "latency": "5.435615ms" - }, - { - "ip": "10.1.8.6", - "status": " LIVE ", - "latency": "2.024272ms" - }, - { - "ip": "10.1.8.3", - "status": " LIVE ", - "latency": "1.920418ms" - }, - { - "ip": "10.1.8.7", - "status": " LIVE ", - "latency": "2.007437ms" - }, - { - "ip": "10.1.8.5", - "status": " LIVE ", - "latency": "2.480163ms" - }, - { - "ip": "10.1.12.4", - "status": " LIVE ", - "latency": "2.736251ms" - }, - { - "ip": "10.1.12.12", - "status": " LIVE ", - "latency": "2.388798ms" - }, - { - "ip": "10.1.12.7", - "status": " LIVE ", - "latency": "2.415666ms" - }, - { - "ip": "10.1.13.2", - "status": " LIVE ", - "latency": "4.049566ms" - }, - { - "ip": "10.1.14.9", - "status": " LIVE ", - "latency": "4.131567ms" - }, - { - "ip": "10.1.14.5", - "status": " LIVE ", - "latency": "4.006777ms" - }, - { - "ip": "10.1.14.11", - "status": " LIVE ", - "latency": "3.668083ms" - }, - { - "ip": "10.1.14.1", - "status": " LIVE ", - "latency": "3.812745ms" - }, - { - "ip": "10.1.18.2", - "status": " LIVE ", - "latency": "3.557259ms" - }, - { - "ip": "10.1.18.3", - "status": " LIVE ", - "latency": "3.444536ms" - }, - { - "ip": "10.1.18.16", - "status": " LIVE ", - "latency": "4.14696ms" - }, - { - "ip": "10.1.18.24", - "status": " LIVE ", - "latency": "3.824272ms" - }, - { - "ip": "10.1.19.4", - "status": " LIVE ", - "latency": "4.057099ms" - }, - { - "ip": "10.1.19.9", - "status": " LIVE ", - "latency": "3.856445ms" - }, - { - "ip": "10.1.19.10", - "status": " LIVE ", - "latency": "4.100233ms" - }, - { - "ip": "10.1.19.12", - "status": " LIVE ", - "latency": "5.078648ms" - } -] \ No newline at end of file diff --git a/result3.txt b/result3.txt deleted file mode 100644 index bd9d341..0000000 --- a/result3.txt +++ /dev/null @@ -1,4 +0,0 @@ -10.1.8.5 - LIVE (356.749µs) -10.1.8.6 - LIVE (25.179µs) -10.1.8.7 - LIVE (197.098µs) -10.1.8.3 - LIVE (335.722µs) \ No newline at end of file diff --git a/result4.xml b/result4.xml deleted file mode 100644 index 846dbd3..0000000 --- a/result4.xml +++ /dev/null @@ -1 +0,0 @@ -https://google.com301 Moved Permanentlygws20Content-Security-PolicyStrict-Transport-SecurityX-Content-Type-OptionsReferrer-Policyhttps://www.google.com/public, max-age=2592000text/html; charset=UTF-8gwsSAMEORIGINSun, 01 Feb 2026 10:10:50 GMT220object-src 'none';base-uri 'self';script-src 'nonce-g3bhEk0k5tugd_Hi6kTh2A' 'strict-dynamic' 'report-sample' 'unsafe-eval' 'unsafe-inline' https: http:;report-uri https://csp.withgoogle.com/csp/gws/other-hpTue, 03 Mar 2026 10:10:50 GMT0h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 \ No newline at end of file diff --git a/src/hostmapper/mod.rs b/src/hostmapper/mod.rs index 270fa1e..ae5fbc8 100644 --- a/src/hostmapper/mod.rs +++ b/src/hostmapper/mod.rs @@ -1,8 +1,4 @@ use std::net::Ipv4Addr; -use std::time::Instant; - -use egui::mutex::Mutex; -use pnet_packet::Packet; use crate::println_tx; use crate::utils::preluad::*; @@ -35,27 +31,27 @@ pub async fn run( let client = Client::new(&Config::default())?; let client = Arc::new(client); - let pb: ProgressBar = ProgressBar::new(net.hosts().count() as u64); + let timeout_dur = Duration::from_millis(100); + let network_addr = net.network(); + let broadcast_addr = net.broadcast(); + let start = u32::from(network_addr); + let end = u32::from(broadcast_addr); + + let pb: ProgressBar = ProgressBar::new((end - start + 1) as u64); pb.set_style( ProgressStyle::default_bar() .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len}")? .progress_chars("█▓▒░"), ); - let timeout_dur = Duration::from_millis(100); - for ip in net.hosts() { - // while set.len() >= threads { - // set.join_next().await; - // } + + for i in start..=end { + let ip = Ipv4Addr::from(i); let permit = semaphore.clone().acquire_owned().await.ok(); let tx_clone = tx.clone(); let client_clone = client.clone(); - // Generate a unique identifier for the ping based on the IP address. - // This helps the surge-ping client match replies to requests. - - let ip_u32 = u32::from(ip); - let ident = (ip_u32 & 0xFFFF) as u16; + let ident = (i & 0xFFFF) as u16; let tx_ui_clone = tx_ui.clone(); let pb_clone = pb.clone(); diff --git a/src/tinyscanner/mod.rs b/src/tinyscanner/mod.rs index d6eaa86..58babec 100644 --- a/src/tinyscanner/mod.rs +++ b/src/tinyscanner/mod.rs @@ -1,9 +1,11 @@ +pub mod comman; pub mod dispatcher; pub mod scan_syn; -pub mod comman; - -use crate::{println_tx, utils::{TinyResult, preluad::*}}; +use crate::{ + println_tx, + utils::{TinyResult, preluad::*}, +}; /// The main entry point for the TinyScanner tool. /// @@ -136,22 +138,19 @@ fn process_sniffer_packet( state: &Arc>>, ) { let flags = packet.get_flags(); - if (flags & (TcpFlags::SYN | TcpFlags::ACK | TcpFlags::RST)) != 0 { + if (flags & TcpFlags::ACK) != 0 || (flags & TcpFlags::RST) != 0 { let my_random_port = packet.get_destination(); if let Some((_, tx_result)) = state.remove(&my_random_port) { - let status = if (flags & TcpFlags::SYN) != 0 && (flags & TcpFlags::ACK) != 0 { + let status = if (flags & TcpFlags::SYN) != 0 { "OPEN" - } else if (flags & TcpFlags::RST) != 0 { - "CLOSED" } else { - "FILTERED" + "CLOSED" }; let _ = tx_result.send(status); } } } - /// Collects scan results from the receiver channel and updates the UI progress bar. /// /// Returns a vector of formatted result strings. @@ -177,7 +176,10 @@ async fn collect_and_print_results( println_tx!(tx_ui, debug!, "Port {:<5} is {}", port, status.yellow()); } - results.push(TinyResult { port, state: status_upper }); + results.push(TinyResult { + port, + state: status_upper.split(" ").next().unwrap_or("").to_string(), + }); // results.push(format!("Port {:<5}: {}", port, status_upper)); if pb.position() == total as u64 { break;