Inital commit 🤗
This commit is contained in:
commit
23ce3ef304
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
18
Cargo.toml
Normal file
18
Cargo.toml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
[package]
|
||||||
|
name = "blues"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
nbd = { git = "https://github.com/tchajed/rust-nbd" }
|
||||||
|
internet-checksum = "0.2.1"
|
||||||
|
serde_json = "1.0.91"
|
||||||
|
env_logger = "0.10.0"
|
||||||
|
num_cpus = "1.15.0"
|
||||||
|
serde = { version = "1.0.152", features = ["derive"] }
|
||||||
|
tokio = { version = "1.24.1", features = ["full"] }
|
||||||
|
clap = { version = "4.0.32", features = ["derive"] }
|
||||||
|
icmp = "0.3.0"
|
||||||
|
rand = "0.8.5"
|
||||||
|
log = "0.4.17"
|
26
Makefile
Normal file
26
Makefile
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
export RUST_LOG = trace
|
||||||
|
CARGO := cargo
|
||||||
|
SU := doas
|
||||||
|
|
||||||
|
.PHONY: run debug release setcap
|
||||||
|
|
||||||
|
run: debug setcap
|
||||||
|
target/debug/blues -t 1 recon -ro 3000 -p 1 -t 150 -l 1
|
||||||
|
|
||||||
|
nbd: debug setcap
|
||||||
|
target/debug/blues nbd
|
||||||
|
|
||||||
|
debug:
|
||||||
|
$(CARGO) build
|
||||||
|
|
||||||
|
release:
|
||||||
|
$(CARGO) build --release
|
||||||
|
|
||||||
|
masscan: release setcap
|
||||||
|
target/release/blues -t 48 recon -ro 7000 -p 48 -t 350 -l 420 # 69
|
||||||
|
|
||||||
|
setcap:
|
||||||
|
test -d target/release && $(SU) setcap cap_net_raw+ep target/release/blues
|
||||||
|
test -d target/debug && $(SU) setcap cap_net_raw+ep target/debug/blues
|
||||||
|
|
3
TODO.md
Normal file
3
TODO.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# TODO
|
||||||
|
* Fuuuck ICMP -> no ports -> gotta do de sequence and idents
|
||||||
|
* Load only non-small round trip sorted ips for storage
|
110
src/bin/blues.rs
Normal file
110
src/bin/blues.rs
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
use std::{
|
||||||
|
io, net::IpAddr,
|
||||||
|
time::Duration,
|
||||||
|
thread::sleep};
|
||||||
|
|
||||||
|
use blues::{TIMEOUT, PingStore, IPStore, Scanner, get_rt};
|
||||||
|
use clap::{builder::ArgAction, Subcommand, Parser};
|
||||||
|
use nbd::server::Blocks;
|
||||||
|
use log::{trace, debug, info, error};
|
||||||
|
|
||||||
|
/// Blues "cloud" storage engine
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(author, version, about, long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
/// Max worker threads to use for multi threaded operations
|
||||||
|
#[arg(short, long, value_parser, default_value_t = num_cpus::get())]
|
||||||
|
threads: usize,
|
||||||
|
|
||||||
|
/// Save file to store ping destinations
|
||||||
|
#[arg(short, long, value_parser, default_value = "ips.json")]
|
||||||
|
file: String,
|
||||||
|
|
||||||
|
/// Subcommands
|
||||||
|
#[command(subcommand)]
|
||||||
|
sub: Command,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subcommands (enum)
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
#[command()]
|
||||||
|
enum Command {
|
||||||
|
/// Scan the internet to find optimal destinations
|
||||||
|
Recon {
|
||||||
|
/// Please be a good netizen, how many ms beetween ping bursts
|
||||||
|
#[arg(short, long, value_parser, default_value_t = 150)]
|
||||||
|
throttle: usize,
|
||||||
|
|
||||||
|
/// How many parallel outstanding pings to have during scanning
|
||||||
|
#[arg(short, long, value_parser, default_value_t = 420)]
|
||||||
|
parallel: usize,
|
||||||
|
|
||||||
|
/// Ping timeout while scanning in ms
|
||||||
|
#[arg(short = 'o', long, value_parser, default_value_t = 7000)]
|
||||||
|
timeout: u64,
|
||||||
|
|
||||||
|
/// Scan until limit is reached
|
||||||
|
#[arg(short, long, value_parser, default_value_t = 0)]
|
||||||
|
limit: usize,
|
||||||
|
|
||||||
|
/// Random order of IPs
|
||||||
|
#[arg(short, long, action = ArgAction::SetTrue)]
|
||||||
|
rand: bool,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// NBD server and client operations
|
||||||
|
NBD {
|
||||||
|
/// NBD device path
|
||||||
|
#[arg(short, long, value_parser, default_value = "/dev/nbd0")]
|
||||||
|
device: String,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> io::Result<()> {
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
env_logger::Builder::from_env(
|
||||||
|
env_logger::Env::default().default_filter_or("info")
|
||||||
|
).init();
|
||||||
|
|
||||||
|
info!("Starting blues");
|
||||||
|
trace!("With args: {:#?}", args);
|
||||||
|
|
||||||
|
match args.sub {
|
||||||
|
Command::Recon { throttle, parallel, timeout, limit, rand } => {
|
||||||
|
debug!("Mode is Scan/Recon");
|
||||||
|
unsafe { TIMEOUT = Some(Duration::from_millis(timeout)) };
|
||||||
|
|
||||||
|
let mut scanner = Scanner::load(&args.file);
|
||||||
|
get_rt("blues Scanner worker", args.threads).block_on(
|
||||||
|
scanner.mass_scan(throttle, parallel, limit, rand));
|
||||||
|
|
||||||
|
info!("Saving to file: {}", args.file);
|
||||||
|
IPStore::from_scanner(&scanner).save(&args.file);
|
||||||
|
},
|
||||||
|
|
||||||
|
Command::NBD { device } => {
|
||||||
|
debug!("Mode is NBD");
|
||||||
|
let store = PingStore::load_clients(&args.file);
|
||||||
|
let mut data = vec![
|
||||||
|
0x49, 0x43, 0x4d, 0x50, 0x20, 0x62, 0x61, 0x6c,
|
||||||
|
0x6c, 0x65, 0x20, 0x6e, 0x65, 0x67, 0x65, 0x72];
|
||||||
|
// get_rt("blues NBD driver", args.threads).spawn(store.run());
|
||||||
|
data.append(&mut [0x66; 64].to_vec());
|
||||||
|
// data.append(&mut [0x66; 48].to_vec());
|
||||||
|
info!("Storing some data in a few pings!");
|
||||||
|
store.write_at(&data, 0)?;
|
||||||
|
// info!("Waiting for data corruption!");
|
||||||
|
// sleep(Duration::from_millis(3000));
|
||||||
|
let mut res: [u8; 64 + 16] = [0; 64 + 16];
|
||||||
|
// let mut res: [u8; 64] = [0; 64];
|
||||||
|
store.read_at(&mut res, 0)?;
|
||||||
|
if res.to_vec() == data {
|
||||||
|
info!("SUCCESS! Recived same data as stored!");
|
||||||
|
} else {
|
||||||
|
error!("Didn't recive the same data as stored!\nExpected: {data:?}\nGot: {res:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
169
src/blocks.rs
Normal file
169
src/blocks.rs
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
use std::{
|
||||||
|
sync::{Mutex, Arc},
|
||||||
|
time::Duration,
|
||||||
|
thread::sleep,
|
||||||
|
net::IpAddr,
|
||||||
|
vec::Vec, io};
|
||||||
|
|
||||||
|
use log::{trace, warn, error};
|
||||||
|
use tokio::{
|
||||||
|
sync::mpsc,
|
||||||
|
runtime, task};
|
||||||
|
use nbd::server::Blocks;
|
||||||
|
use icmp::IcmpSocket;
|
||||||
|
use crate::{
|
||||||
|
ICMP_PACKET, IPStore,
|
||||||
|
pinger::connect,
|
||||||
|
checksum,
|
||||||
|
get_rt};
|
||||||
|
|
||||||
|
const SIZE: usize = 64; // Bits of data in each ping
|
||||||
|
static WORD_COUNT: usize = SIZE / 8; // Number of 16-bit words in each ping
|
||||||
|
|
||||||
|
type Message = (usize, usize, Vec<u8>); // Message type for the write channel
|
||||||
|
|
||||||
|
pub struct Ping {
|
||||||
|
socks: Vec<IcmpSocket>,
|
||||||
|
ips: Vec<IpAddr>,
|
||||||
|
copies: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PingStore {
|
||||||
|
pings: Arc<Mutex<Vec<Ping>>>,
|
||||||
|
size: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PingStore {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
pings: Arc::new(Mutex::new(vec![])),
|
||||||
|
size: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_clients(file: &str) -> Self {
|
||||||
|
let ips = IPStore::load(file);
|
||||||
|
let mut store = Self::new();
|
||||||
|
/*
|
||||||
|
let mut dstmap: Vec<(usize, IpAddr)> = vec![];
|
||||||
|
for dst in ips.dsts {
|
||||||
|
dstmap.push((dst.round_trip, dst.ip));
|
||||||
|
}
|
||||||
|
let sorted = dstmap.sort_by(|a, b| a.0.cmp(&b.0));
|
||||||
|
trace!("{:?}", sorted);
|
||||||
|
*/
|
||||||
|
for _ in 0..ips.dsts.len().div_floor(7) {
|
||||||
|
let offset = store.pings.lock().unwrap().len() * 7;
|
||||||
|
let mut ping = Ping::new();
|
||||||
|
for j in 0..7 {
|
||||||
|
ping.add(ips.dsts[offset + j].ip);
|
||||||
|
}
|
||||||
|
store.pings.lock().unwrap().push(ping);
|
||||||
|
store.size += 1;
|
||||||
|
}
|
||||||
|
store
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(&self, addr: usize) -> io::Result<Vec<u8>> {
|
||||||
|
trace!("Reading addr 0x{addr:x}");
|
||||||
|
let mut datas: Vec<Vec<u8>> = vec![];
|
||||||
|
let ping = &self.pings.lock().unwrap()[addr];
|
||||||
|
|
||||||
|
for i in 0..ping.copies {
|
||||||
|
let mut data: [u8; 28 + SIZE] = [0; 28 + SIZE];
|
||||||
|
ping.socks[i].recv(&mut data)?;
|
||||||
|
datas.push(data[28..(28 + SIZE)].to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
let first: [u8; SIZE] = datas[0].clone()[..].try_into().unwrap();
|
||||||
|
let mut good: [u8; SIZE] = datas[0].clone()[..].try_into().unwrap();
|
||||||
|
for data in datas[1..].iter() {
|
||||||
|
if *data != good && *data == first {
|
||||||
|
good = first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i in 0..datas.len() {
|
||||||
|
if good != *datas[i] {
|
||||||
|
warn!("Sus response data in ping from \"{}\"", ping.ips[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(good.to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn write(&self, buf: &[u8], addr: usize, off: usize, size: usize) -> io::Result<()> {
|
||||||
|
let mut packets = vec![];
|
||||||
|
for i in addr..(size.div_ceil(SIZE)) {
|
||||||
|
let loc = i * SIZE;
|
||||||
|
packets.push(buf[loc..loc+SIZE].to_vec());
|
||||||
|
}
|
||||||
|
for (pingspan, packet) in packets.iter().enumerate() {
|
||||||
|
let offset = addr + pingspan;
|
||||||
|
trace!("Writing addr 0x{offset:x}");
|
||||||
|
self.ping(offset, packet)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ping(&self, addr: usize, data: &[u8]) -> io::Result<()> {
|
||||||
|
trace!("Sending store ping with addr 0x{addr:x}");
|
||||||
|
let mut packet = ICMP_PACKET.to_vec();
|
||||||
|
packet.append(&mut data.to_vec());
|
||||||
|
let checksum = checksum(&packet);
|
||||||
|
packet[2] = checksum[0];
|
||||||
|
packet[3] = checksum[1];
|
||||||
|
packet[6] = 0;
|
||||||
|
packet[7] = 1;
|
||||||
|
let ping = &mut self.pings.lock().unwrap()[addr];
|
||||||
|
let mut res = vec![];
|
||||||
|
for i in 0..ping.copies {
|
||||||
|
res.push(ping.socks[i].send(&packet)?);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Blocks for PingStore {
|
||||||
|
fn read_at(&self, buf: &mut [u8], off: u64) -> io::Result<()> {
|
||||||
|
let addr = (off as usize).div_floor(WORD_COUNT);
|
||||||
|
let buflen = buf.len();
|
||||||
|
let mut res = vec![];
|
||||||
|
for pingspan in 0..buflen.div_ceil(SIZE) {
|
||||||
|
res.append(&mut self.read(addr + pingspan)?.to_vec());
|
||||||
|
}
|
||||||
|
//buf.copy_from_slice(&res[0..((addr * 8) - off as usize)]);
|
||||||
|
buf.copy_from_slice(&res[0..buflen]);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_at(&self, buf: &[u8], off: u64) -> io::Result<()> {
|
||||||
|
let addr = (off as usize).div_ceil(WORD_COUNT);
|
||||||
|
let buflen = buf.len();
|
||||||
|
get_rt("channel pusher", num_cpus::get().div_floor(3) + 1).block_on(
|
||||||
|
self.write(buf, addr, (addr * WORD_COUNT) - off as usize, buflen))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> io::Result<u64> {
|
||||||
|
Ok((self.pings.lock().unwrap().len() * 56) as u64)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&self) -> io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ping {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
socks: vec![],
|
||||||
|
ips: vec![],
|
||||||
|
copies: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add(&mut self, ip: IpAddr) {
|
||||||
|
self.socks.push(connect(ip));
|
||||||
|
self.ips.push(ip);
|
||||||
|
self.copies += 1;
|
||||||
|
}
|
||||||
|
}
|
33
src/lib.rs
Normal file
33
src/lib.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#![feature(bigint_helper_methods)]
|
||||||
|
#![feature(async_closure)]
|
||||||
|
#![feature(int_roundings)]
|
||||||
|
#![feature(never_type)]
|
||||||
|
#![feature(ip)]
|
||||||
|
|
||||||
|
pub mod scanner;
|
||||||
|
pub mod pinger;
|
||||||
|
pub mod blocks;
|
||||||
|
pub mod store;
|
||||||
|
pub use blocks::PingStore;
|
||||||
|
pub use scanner::Scanner;
|
||||||
|
pub use pinger::{
|
||||||
|
ICMP_PACKET, TIMEOUT, Pinger};
|
||||||
|
pub use store::IPStore;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn checksum(bytes: &[u8]) -> [u8; 2] {
|
||||||
|
let mut calc = internet_checksum::Checksum::new();
|
||||||
|
calc.add_bytes(bytes);
|
||||||
|
calc.checksum()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn get_rt(name: &str, threads: usize) -> tokio::runtime::Runtime {
|
||||||
|
match tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.worker_threads(threads)
|
||||||
|
.thread_name(name)
|
||||||
|
.build() {
|
||||||
|
Err(err) => panic!("Unable to build tokio runtime! {:?}", err),
|
||||||
|
Ok(rt) => rt,
|
||||||
|
}
|
||||||
|
}
|
164
src/pinger.rs
Normal file
164
src/pinger.rs
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
use std::{
|
||||||
|
time::{Duration, Instant},
|
||||||
|
net::{Ipv4Addr, IpAddr},
|
||||||
|
io};
|
||||||
|
|
||||||
|
use log::{trace, debug, info};
|
||||||
|
use icmp::IcmpSocket;
|
||||||
|
use rand::random;
|
||||||
|
use crate::checksum;
|
||||||
|
|
||||||
|
pub static mut TIMEOUT: Option<Duration> = None;
|
||||||
|
|
||||||
|
pub static ICMP_PACKET: [u8; 8] = [
|
||||||
|
8, 0, // 8 Echo ping request, code 0
|
||||||
|
0, 0, // Index 2 and 3 are for ICMP checksum
|
||||||
|
0xde, 0xad, // Identifier
|
||||||
|
0, 1, // Sequence numbers
|
||||||
|
];
|
||||||
|
|
||||||
|
pub struct PingResponse {
|
||||||
|
pub round_trip: usize,
|
||||||
|
pub corrupt: bool,
|
||||||
|
pub small: bool,
|
||||||
|
pub data: [u8; 84],
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type PingResult = Result<PingResponse, io::Error>;
|
||||||
|
|
||||||
|
pub struct Pinger {
|
||||||
|
pub data: [u8; 64],
|
||||||
|
pub seq: [u8; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pinger {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
data: [0; 64],
|
||||||
|
seq: [1, 0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn data(mut self, data: [u8; 64]) -> Self {
|
||||||
|
self.data = data;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ping(&mut self, ip: IpAddr) -> PingResult {
|
||||||
|
let mut pack = ICMP_PACKET.to_vec();
|
||||||
|
pack[6] = self.seq[1];
|
||||||
|
pack[7] = self.seq[0];
|
||||||
|
pack.append(&mut self.data.clone().to_vec());
|
||||||
|
|
||||||
|
let checksum = checksum(&pack);
|
||||||
|
pack[2] = checksum[0];
|
||||||
|
pack[3] = checksum[1];
|
||||||
|
|
||||||
|
let mut socket = connect(ip);
|
||||||
|
let start = Instant::now();
|
||||||
|
|
||||||
|
match socket.send(&pack) {
|
||||||
|
Err(err) => {
|
||||||
|
debug!("Was unable to ping IP {}: {:?}", ip, err);
|
||||||
|
return Err(err);
|
||||||
|
},
|
||||||
|
Ok(count) => if count != pack.len() {
|
||||||
|
debug!("Sent {} of {} bytes to {}", count, pack.len(), ip);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
self.inc_seq();
|
||||||
|
|
||||||
|
let mut response: [u8; 64 + 20] = [0; 64 + 20];
|
||||||
|
|
||||||
|
match socket.recv(&mut response) {
|
||||||
|
Err(err) => {
|
||||||
|
info!("Error scanning IP \"{}\": {:?}", ip, err);
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(size) => {
|
||||||
|
let mut result = PingResponse::new(response, start.elapsed().as_millis() as usize);
|
||||||
|
if size != 84 {
|
||||||
|
debug!("Didn't revice full response from \"{}\"", ip);
|
||||||
|
trace!("Response in question: {:?}", response);
|
||||||
|
result.corrupt = true;
|
||||||
|
}
|
||||||
|
if response[20 + 8..] != self.data {
|
||||||
|
if response[56..] == self.data[..28] {
|
||||||
|
result.small = true
|
||||||
|
} else {
|
||||||
|
debug!("Response payload was not the same as request in ping to \"{}\"", ip);
|
||||||
|
trace!("Response in question: {:?}", response);
|
||||||
|
result.corrupt = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inc_seq(&mut self) {
|
||||||
|
let mut carry = false;
|
||||||
|
(self.seq[0], carry) = self.seq[0].carrying_add(1, carry);
|
||||||
|
if carry {
|
||||||
|
carry = false;
|
||||||
|
(self.seq[1], carry) = self.seq[1].carrying_add(1, carry);
|
||||||
|
}
|
||||||
|
if carry {
|
||||||
|
self.seq = [1, 0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn connect(ip: IpAddr) -> IcmpSocket {
|
||||||
|
let sock = match IcmpSocket::connect(ip) {
|
||||||
|
Err(err) => panic!("Unable to open ICMP socket: {err:?}"),
|
||||||
|
Ok(sock) => sock,
|
||||||
|
};
|
||||||
|
let timeout = unsafe { TIMEOUT };
|
||||||
|
if timeout.is_none() {
|
||||||
|
return sock;
|
||||||
|
}
|
||||||
|
if let Err(err) = sock.set_write_timeout(timeout) {
|
||||||
|
debug!("unable to set write timeout on socket: {}", err);
|
||||||
|
};
|
||||||
|
if let Err(err) = sock.set_read_timeout(timeout) {
|
||||||
|
debug!("unable to set read timeout on socket: {}", err);
|
||||||
|
};
|
||||||
|
sock
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn rand_ip4() -> Ipv4Addr {
|
||||||
|
Ipv4Addr::new(random(), random(), random(), random())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn rand_ip() -> IpAddr {
|
||||||
|
let mut ip = rand_ip4();
|
||||||
|
while !ip.is_global() { ip = rand_ip4() };
|
||||||
|
IpAddr::V4(ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PingResponse {
|
||||||
|
fn new(data: [u8; 84], round_trip: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
round_trip,
|
||||||
|
corrupt: false,
|
||||||
|
small: false,
|
||||||
|
data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PingResponse {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new([0; 84], 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Pinger {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
110
src/scanner.rs
Normal file
110
src/scanner.rs
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
use std::{
|
||||||
|
vec::Vec, net::{Ipv4Addr, IpAddr},
|
||||||
|
time::Duration, thread::sleep};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use log::{trace, debug, info, error};
|
||||||
|
use tokio::task;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
pinger::rand_ip,
|
||||||
|
IPStore, Pinger};
|
||||||
|
|
||||||
|
//static PROBE_CHK: [u8; 2] = [0x51, 0x59];
|
||||||
|
static PROBE: [u8; 64] = [0x66; 64];
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Scanner {
|
||||||
|
pub dsts: Vec<Destination>,
|
||||||
|
pub dead: Vec<IpAddr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone)]
|
||||||
|
pub struct Destination {
|
||||||
|
pub round_trip: usize, // Round trip in ms
|
||||||
|
pub small: bool,
|
||||||
|
pub ip: IpAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type ScanResult = Result<Destination, IpAddr>;
|
||||||
|
|
||||||
|
impl Scanner {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
debug!("Initializing a blues::Scanner");
|
||||||
|
Self { dsts: vec![], dead: vec![] }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load(file: &str) -> Self {
|
||||||
|
let mut scanner = Self::new();
|
||||||
|
let store = IPStore::load(file);
|
||||||
|
scanner.dsts = store.dsts;
|
||||||
|
scanner.dead = store.dead;
|
||||||
|
scanner
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn next_ip(&self) -> IpAddr {
|
||||||
|
// let mut ip = rand_ip();
|
||||||
|
let mut ip = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 138));
|
||||||
|
while self.scanned(&ip) {
|
||||||
|
ip = rand_ip(); }
|
||||||
|
ip
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn scanned(&self, ip: &IpAddr) -> bool {
|
||||||
|
for dead in &self.dead {
|
||||||
|
if ip == dead { return true; }
|
||||||
|
}
|
||||||
|
for dst in &self.dsts {
|
||||||
|
if ip == &dst.ip {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn pop_futs(&mut self, futs: &mut Vec<task::JoinHandle<ScanResult>>) {
|
||||||
|
let fut = futs.pop().expect("Unable to pop of futures vec during mass_scan()!");
|
||||||
|
match fut.await {
|
||||||
|
Err(err) => {
|
||||||
|
error!("Joing future: {:?}", err);
|
||||||
|
},
|
||||||
|
Ok(res) => match res {
|
||||||
|
Ok(dst) => self.dsts.push(dst),
|
||||||
|
Err(ip) => self.dead.push(ip),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn mass_scan(&mut self, throttle: usize, parallel: usize, limit: usize, rand: bool) {
|
||||||
|
info!("Starting mass scan with a limit of {} and {} to randomized IP order!", limit, rand);
|
||||||
|
let duration = Duration::from_millis(throttle as u64);
|
||||||
|
let mut futs: Vec<task::JoinHandle<ScanResult>> = vec![];
|
||||||
|
|
||||||
|
for _i in 0..limit {
|
||||||
|
if futs.len() >= parallel {
|
||||||
|
self.pop_futs(&mut futs).await;
|
||||||
|
}
|
||||||
|
futs.push(task::spawn(scan(self.next_ip())));
|
||||||
|
sleep(duration);
|
||||||
|
}
|
||||||
|
while !futs.is_empty() {
|
||||||
|
self.pop_futs(&mut futs).await }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn scan(ip: IpAddr) -> Result<Destination, IpAddr> {
|
||||||
|
trace!("Scanning IP \"{}\"", ip);
|
||||||
|
if let Ok(res) = Pinger::new().data(PROBE).ping(ip) {
|
||||||
|
info!("Good result scanning IP \"{}\": {}ms", ip, res.round_trip);
|
||||||
|
if res.corrupt { return Err(ip) }
|
||||||
|
Ok(Destination { ip, small: res.small, round_trip: res.round_trip })
|
||||||
|
} else { Err(ip) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Scanner {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
87
src/store.rs
Normal file
87
src/store.rs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
use std::{
|
||||||
|
fs::OpenOptions,
|
||||||
|
io::{Write, Read},
|
||||||
|
str::from_utf8,
|
||||||
|
net::IpAddr,
|
||||||
|
vec::Vec};
|
||||||
|
|
||||||
|
use log::{trace, debug, error};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use crate::{
|
||||||
|
scanner::Destination,
|
||||||
|
Scanner};
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
pub struct IPStore {
|
||||||
|
pub dsts: Vec<Destination>,
|
||||||
|
pub dead: Vec<IpAddr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IPStore {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { dsts: vec![], dead: vec![] }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_scanner(scanner: &Scanner) -> Self {
|
||||||
|
// Self { dsts: scanner.dsts.clone(), dead: scanner.dead.clone() }
|
||||||
|
Self { dsts: scanner.dsts.clone(), dead: vec![] }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save(&self, file_name: &str) {
|
||||||
|
let mut file = OpenOptions::new()
|
||||||
|
.write(true).create(true)
|
||||||
|
.open(file_name).unwrap();
|
||||||
|
match serde_json::to_string(self) {
|
||||||
|
Err(err) => {
|
||||||
|
error!("Serializing IPStore for saving to file: {:?}", err);
|
||||||
|
},
|
||||||
|
Ok(out) => {
|
||||||
|
match file.write_all(out.as_bytes()) {
|
||||||
|
Err(err) => error!(
|
||||||
|
"Saving IPStore to file \"{}\": {:?}",
|
||||||
|
file_name, err),
|
||||||
|
Ok(ok) => ok
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load(file_name: &str) -> Self {
|
||||||
|
let mut input: Vec<u8> = vec![];
|
||||||
|
|
||||||
|
match OpenOptions::new().read(true).open(file_name) {
|
||||||
|
Err(err) => {
|
||||||
|
debug!(
|
||||||
|
"Opening IPStore save file \"{}\": {:?}",
|
||||||
|
file_name, err);
|
||||||
|
return IPStore::new();
|
||||||
|
},
|
||||||
|
|
||||||
|
Ok(mut file) => match file.read_to_end(&mut input) {
|
||||||
|
Err(err) => {
|
||||||
|
error!(
|
||||||
|
"Reading IPStore save file \"{}\": {:?}",
|
||||||
|
file_name, err);
|
||||||
|
return IPStore::new();
|
||||||
|
},
|
||||||
|
Ok(len) => trace!("Read {} bytes from IPStore save file", len),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = from_utf8(&input).unwrap();
|
||||||
|
|
||||||
|
match serde_json::from_str(data) {
|
||||||
|
Err(err) => {
|
||||||
|
error!("Loading IPStore: {:?}", err);
|
||||||
|
IPStore::new()
|
||||||
|
},
|
||||||
|
Ok(store) => store,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for IPStore {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user