From 3dc7b6ff8a558d05d88fc9568702b572aef04e50 Mon Sep 17 00:00:00 2001 From: Michael Mikovsky <77305074+Astatin3@users.noreply.github.com> Date: Wed, 16 Apr 2025 11:49:51 -0600 Subject: [PATCH] Create the bandwidth destroyer --- Cargo.toml | 10 ++ src/lib.rs | 2 + src/main.rs | 28 ++++++ src/online_scan/mod.rs | 4 + src/online_scan/online_scan.rs | 19 ++++ src/online_scan/ping_scanner.rs | 173 ++++++++++++++++++++++++++++++++ src/parse_ip_range.rs | 116 +++++++++++++++++++++ src/port_scan/mod.rs | 1 + src/port_scan/port_scan.rs | 0 src/port_scan/tcp_scan.rs | 1 + 10 files changed, 354 insertions(+) create mode 100644 Cargo.toml create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/online_scan/mod.rs create mode 100644 src/online_scan/online_scan.rs create mode 100644 src/online_scan/ping_scanner.rs create mode 100644 src/parse_ip_range.rs create mode 100644 src/port_scan/mod.rs create mode 100644 src/port_scan/port_scan.rs create mode 100644 src/port_scan/tcp_scan.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7c50313 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "untitled" +version = "0.1.0" +edition = "2024" + +[dependencies] +loadingbar = "1.0.1" +pnet = "0.35.0" +rand = "0.9.0" +rocksdb = "0.23.0" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..a605ff7 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,2 @@ +pub mod online_scan; +pub mod parse_ip_range; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..a7f44af --- /dev/null +++ b/src/main.rs @@ -0,0 +1,28 @@ +pub mod online_scan; +pub mod parse_ip_range; + +use std::env; + +use online_scan::ping_scanner; +use parse_ip_range::parse_ip_targets; + +fn main() -> Result<(), Box> { + let args: Vec = env::args().collect(); + + // Set default targets or use command line input + let targets = if args.len() > 1 { + args[1].clone() + } else { + "".to_string() + }; + + // Parse the targets into IP addresses + let hosts = parse_ip_targets(&targets)?; + + let length = hosts.len(); + + let results = ping_scanner::ping_scan(hosts).unwrap(); + + println!("Finished! {} Scanned, {} Up", length, results.len()); + Ok(()) +} diff --git a/src/online_scan/mod.rs b/src/online_scan/mod.rs new file mode 100644 index 0000000..da14aa2 --- /dev/null +++ b/src/online_scan/mod.rs @@ -0,0 +1,4 @@ +pub mod online_scan; +pub mod ping_scanner; + +pub use online_scan::PingResult; diff --git a/src/online_scan/online_scan.rs b/src/online_scan/online_scan.rs new file mode 100644 index 0000000..9cb8332 --- /dev/null +++ b/src/online_scan/online_scan.rs @@ -0,0 +1,19 @@ +use std::{net::IpAddr, time::Duration}; + +// Structure to hold ping results +#[derive(Debug)] +pub struct PingResult { + pub host: IpAddr, + pub is_up: bool, + pub response_time: Option, +} + +impl PingResult { + pub fn create(addr: IpAddr) -> Self { + Self { + host: addr, + is_up: false, + response_time: None, + } + } +} diff --git a/src/online_scan/ping_scanner.rs b/src/online_scan/ping_scanner.rs new file mode 100644 index 0000000..ee60c80 --- /dev/null +++ b/src/online_scan/ping_scanner.rs @@ -0,0 +1,173 @@ +use pnet::packet::ip::IpNextHeaderProtocols; +use pnet::packet::ipv4::Ipv4OptionNumbers::TR; +use pnet::packet::{ + Packet, + icmp::{IcmpTypes, echo_request::MutableEchoRequestPacket}, +}; +use pnet::transport::{ + TransportChannelType, TransportProtocol, icmp_packet_iter, transport_channel, +}; +use pnet::util::checksum; +use std::cmp::max; +use std::collections::HashMap; +use std::net::IpAddr; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::{Duration, Instant}; + +static TIMEOUT: Duration = Duration::from_secs(3); +// static MAX_PINGS_PER_SECOND: u64 = 10000; +static SEND_DELAY_NANOS: Duration = Duration::from_nanos(500); + +use crate::online_scan::PingResult; + +pub fn ping_scan(hosts: Vec) -> Result, Box> { + let results = Arc::new(Mutex::new(Vec::::new())); + + // Create a receiver channel for ICMP packets + let (_, mut rx) = transport_channel( + 1024, + TransportChannelType::Layer4(TransportProtocol::Ipv4(IpNextHeaderProtocols::Icmp)), + )?; + + // Create a map to store host identifiers + let requests: Arc>> = Arc::new(Mutex::new(HashMap::new())); + + let finished_sending_time = Arc::new(AtomicBool::new(false)); + + // Set up the receiver thread + // let recv_identifiers = Arc::clone(&identifiers); + let recv_results = Arc::clone(&results); + let recv_requests = Arc::clone(&requests); + let recv_finished_sending_time = Arc::clone(&finished_sending_time); + let receiver_handle = thread::spawn(move || { + let mut iter = icmp_packet_iter(&mut rx); + let start_time = Instant::now(); + let mut finish_sending_time: Option = None; + + // Keep receiving until timeout or all hosts are accounted for + loop { + // Stop reciving loop if timeout is reached + // let time = finished_sending_time; + if finish_sending_time.is_some() && finish_sending_time.unwrap().elapsed() >= TIMEOUT { + break; + } else if finish_sending_time.is_none() + && recv_finished_sending_time.load(Ordering::Relaxed) + { + finish_sending_time = Some(Instant::now()); + println!("Waiting {} seconds for timeout...", TIMEOUT.as_secs()) + } + // if time.is_some() { + // println!("{}", time.unwrap().elapsed().as_millis()) + // } + // if time.is_some() && time.unwrap().elapsed() >= TIMEOUT { + // break; + // }; + + match iter.next_with_timeout(Duration::from_millis(1)) { + Ok(Some((packet, _))) => { + if packet.get_icmp_type() == IcmpTypes::EchoReply { + let payload = packet.payload(); + let id = ((payload[2] as u16) << 8) + (payload[3] as u16); + + let host_option = { + let ids = recv_requests.lock().unwrap(); + ids.get(&id).cloned() + }; + + if let Some(host) = host_option { + let mut results = recv_results.lock().unwrap(); + let response_time = start_time.elapsed(); + results.push(PingResult { + host, + is_up: true, + response_time: Some(response_time), + }); + + // println!("Up! {0} {1}ms", host, response_time.as_millis()); + } + } + } + Ok(None) => { /* Timeout, continue */ } + Err(_) => break, + } + } + }); + + // Spawn sender threads + + let sender_requests = Arc::clone(&requests); + let sender_results = Arc::clone(&results); + let sender_finished_sending_time = Arc::clone(&finished_sending_time); + let sender_handle = thread::spawn(move || { + // let mut last_send_time = Instant::now(); + for (i, host) in hosts.iter().enumerate() { + let host_clone = host.clone(); + + // Use the index as a unique identifier for each host + let identifier: u16 = i as u16; + + // Store the host-identifier mapping + { + let mut ids = sender_requests.lock().unwrap(); + ids.insert(identifier, host_clone); + } + + let response = send_ping(host_clone, identifier); + + match response { + Ok(_) => {} + Err(_) => { + let mut results = sender_results.lock().unwrap(); + results.push(PingResult { + host: host_clone, + is_up: false, + response_time: None, + }); + } + } + + // let now = Instant::now(); + // let delay = MAX_RATE_NANOS - last_send_time.duration_since(now).as_nanos() as u64; + // last_send_time = now; + thread::sleep(SEND_DELAY_NANOS); + } + println!("Finished Sending!"); + + sender_finished_sending_time.swap(true, Ordering::Relaxed); + }); + // Wait for all sender threads to complete + // for handle in sender_handles {a + // handle.join().unwrap(); + // } + sender_handle.join().unwrap(); + receiver_handle.join().unwrap(); + + let results = Arc::try_unwrap(results).unwrap().into_inner().unwrap(); + Ok(results) +} + +fn send_ping(target: IpAddr, identifier: u16) -> Result<(), Box> { + // Create a transport channel + let (mut tx, _) = transport_channel( + 64, + TransportChannelType::Layer4(TransportProtocol::Ipv4(IpNextHeaderProtocols::Icmp)), + )?; + + // Create an ICMP packet + let mut vec = vec![0; 8]; + let mut echo_packet = MutableEchoRequestPacket::new(&mut vec[..]).unwrap(); + + // Fill in the ICMP packet details + echo_packet.set_icmp_type(IcmpTypes::EchoRequest); + echo_packet.set_sequence_number(identifier); + + let checksum = checksum(echo_packet.packet(), 1); + echo_packet.set_checksum(checksum); + + // Send the ICMP packet + tx.send_to(echo_packet, target)?; + + Ok(()) +} diff --git a/src/parse_ip_range.rs b/src/parse_ip_range.rs new file mode 100644 index 0000000..b5897cf --- /dev/null +++ b/src/parse_ip_range.rs @@ -0,0 +1,116 @@ +use std::{ + net::{IpAddr, Ipv4Addr}, + str::FromStr, +}; + +// static MAX_HOSTS: u32 = 1024; + +/// Parse a comma-separated list of IP targets +/// Each target can be: +/// - Single IP: 192.168.1.1 +/// - IP range: 192.168.1.1-192.168.1.10 +/// - CIDR notation: 192.168.1.0/24 +pub fn parse_ip_targets(targets: &str) -> Result, Box> { + let mut ips = Vec::new(); + + // Split the input by commas + for target in targets.split(',') { + let target = target.trim(); + + if target.contains('/') { + // CIDR notation + parse_cidr(target, &mut ips)?; + } else if target.contains('-') { + // IP range + parse_ip_range(target, &mut ips)?; + } else { + // Single IP + let ip = IpAddr::from_str(target)?; + ips.push(ip); + } + } + + Ok(ips) +} + +/// Parse CIDR notation (e.g., 192.168.1.0/24) +fn parse_cidr(cidr: &str, ips: &mut Vec) -> Result<(), Box> { + let parts: Vec<&str> = cidr.split('/').collect(); + if parts.len() != 2 { + return Err("Invalid CIDR format".into()); + } + + let base_ip = Ipv4Addr::from_str(parts[0])?; + let prefix_len: u8 = parts[1].parse()?; + + if prefix_len > 32 { + return Err("Invalid CIDR prefix length".into()); + } + + // Calculate the number of hosts in this CIDR + let hosts = if prefix_len == 32 { + 1 // Just one host for /32 + } else { + 1u32 << (32 - prefix_len) // 2^(32-prefix_len) + }; + + // If the CIDR is too large, warn and limit + // if hosts > MAX_HOSTS { + // println!( + // "Warning: CIDR {} contains {} hosts. Limiting to first 1024.", + // cidr, hosts + // ); + // } + + // Convert base IP to u32 for easier manipulation + let base_ip_u32 = u32::from(base_ip); + + // Generate all IPs in the CIDR block (up to limit) + // let limit = std::cmp::min(hosts, MAX_HOSTS); + for i in 0..hosts { + let ip_u32 = base_ip_u32 & (0xFFFFFFFF << (32 - prefix_len)) | i; + let ip = Ipv4Addr::from(ip_u32); + ips.push(IpAddr::V4(ip)); + } + + Ok(()) +} + +/// Parse IP range (e.g., 192.168.1.1-192.168.1.10) +fn parse_ip_range(range: &str, ips: &mut Vec) -> Result<(), Box> { + let parts: Vec<&str> = range.split('-').collect(); + if parts.len() != 2 { + return Err("Invalid IP range format".into()); + } + + let start_ip = Ipv4Addr::from_str(parts[0])?; + let end_ip = Ipv4Addr::from_str(parts[1])?; + + let start_u32 = u32::from(start_ip); + let end_u32 = u32::from(end_ip); + + if start_u32 > end_u32 { + return Err("Invalid IP range: start IP is greater than end IP".into()); + } + + let range_size = end_u32 - start_u32 + 1; + + // // If the range is too large, warn and limit + // if range_size > MAX_HOSTS { + // println!( + // "Warning: IP range {}-{} contains {} hosts. Limiting to first 1024.", + // start_ip, end_ip, range_size + // ); + // } + + // let limit = std::cmp::min(range_size, MAX_HOSTS); + + // Generate all IPs in the range (up to limit) + for i in 0..range_size { + let ip_u32 = start_u32 + i; + let ip = Ipv4Addr::from(ip_u32); + ips.push(IpAddr::V4(ip)); + } + + Ok(()) +} diff --git a/src/port_scan/mod.rs b/src/port_scan/mod.rs new file mode 100644 index 0000000..98f5ead --- /dev/null +++ b/src/port_scan/mod.rs @@ -0,0 +1 @@ +pub mod tcp_scan; diff --git a/src/port_scan/port_scan.rs b/src/port_scan/port_scan.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/port_scan/tcp_scan.rs b/src/port_scan/tcp_scan.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/port_scan/tcp_scan.rs @@ -0,0 +1 @@ +