Skip to content

Commit

Permalink
Scan estimations for plain text output
Browse files Browse the repository at this point in the history
  • Loading branch information
Saluki committed Jun 15, 2021
1 parent cd86da7 commit 7aa9c6b
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 28 deletions.
76 changes: 57 additions & 19 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,61 +25,99 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
.version(CLI_VERSION)
.about("A minimalistic ARP scan tool written in Rust")
.arg(
Arg::with_name("profile").short("p").long("profile").takes_value(true).value_name("PROFILE_NAME").help("Scan profile")
Arg::with_name("profile").short("p").long("profile")
.takes_value(true).value_name("PROFILE_NAME")
.help("Scan profile")
)
.arg(
Arg::with_name("interface").short("i").long("interface").takes_value(true).value_name("INTERFACE_NAME").help("Network interface")
Arg::with_name("interface").short("i").long("interface")
.takes_value(true).value_name("INTERFACE_NAME")
.help("Network interface")
)
.arg(
Arg::with_name("timeout").short("t").long("timeout").takes_value(true).value_name("TIMEOUT_DURATION").help("ARP response timeout")
Arg::with_name("timeout").short("t").long("timeout")
.takes_value(true).value_name("TIMEOUT_DURATION")
.help("ARP response timeout")
)
.arg(
Arg::with_name("source_ip").short("S").long("source-ip").takes_value(true).value_name("SOURCE_IPV4").help("Source IPv4 address for requests")
Arg::with_name("source_ip").short("S").long("source-ip")
.takes_value(true).value_name("SOURCE_IPV4")
.help("Source IPv4 address for requests")
)
.arg(
Arg::with_name("destination_mac").short("M").long("dest-mac").takes_value(true).value_name("DESTINATION_MAC").help("Destination MAC address for requests")
Arg::with_name("destination_mac").short("M").long("dest-mac")
.takes_value(true).value_name("DESTINATION_MAC")
.help("Destination MAC address for requests")
)
.arg(
Arg::with_name("source_mac").long("source-mac").takes_value(true).value_name("SOURCE_MAC").help("Source MAC address for requests")
Arg::with_name("source_mac").long("source-mac")
.takes_value(true).value_name("SOURCE_MAC")
.help("Source MAC address for requests")
)
.arg(
Arg::with_name("numeric").short("n").long("numeric").takes_value(false).help("Numeric mode, no hostname resolution")
Arg::with_name("numeric").short("n").long("numeric")
.takes_value(false)
.help("Numeric mode, no hostname resolution")
)
.arg(
Arg::with_name("vlan").short("Q").long("vlan").takes_value(true).value_name("VLAN_ID").help("Send using 802.1Q with VLAN ID")
Arg::with_name("vlan").short("Q").long("vlan")
.takes_value(true).value_name("VLAN_ID")
.help("Send using 802.1Q with VLAN ID")
)
.arg(
Arg::with_name("retry_count").short("r").long("retry").takes_value(true).value_name("RETRY_COUNT").help("Host retry attempt count")
Arg::with_name("retry_count").short("r").long("retry")
.takes_value(true).value_name("RETRY_COUNT")
.help("Host retry attempt count")
)
.arg(
Arg::with_name("random").short("R").long("random").takes_value(false).help("Randomize the target list")
Arg::with_name("random").short("R").long("random")
.takes_value(false)
.help("Randomize the target list")
)
.arg(
Arg::with_name("interval").short("I").long("interval").takes_value(true).value_name("INTERVAL_DURATION").help("Milliseconds between ARP requests")
Arg::with_name("interval").short("I").long("interval")
.takes_value(true).value_name("INTERVAL_DURATION")
.help("Milliseconds between ARP requests")
)
.arg(
Arg::with_name("oui-file").long("oui-file").takes_value(true).value_name("FILE_PATH").help("Path to custom IEEE OUI CSV file")
Arg::with_name("oui-file").long("oui-file")
.takes_value(true).value_name("FILE_PATH")
.help("Path to custom IEEE OUI CSV file")
)
.arg(
Arg::with_name("list").short("l").long("list").takes_value(false).help("List network interfaces")
Arg::with_name("list").short("l").long("list")
.takes_value(false)
.help("List network interfaces")
)
.arg(
Arg::with_name("output").short("o").long("output").takes_value(true).value_name("FORMAT").help("Define output format")
Arg::with_name("output").short("o").long("output")
.takes_value(true).value_name("FORMAT")
.help("Define output format")
)
.arg(
Arg::with_name("hw_type").long("hw-type").takes_value(true).value_name("HW_TYPE").help("Custom ARP hardware field")
Arg::with_name("hw_type").long("hw-type")
.takes_value(true).value_name("HW_TYPE")
.help("Custom ARP hardware field")
)
.arg(
Arg::with_name("hw_addr").long("hw-addr").takes_value(true).value_name("ADDRESS_LEN").help("Custom ARP hardware address length")
Arg::with_name("hw_addr").long("hw-addr")
.takes_value(true).value_name("ADDRESS_LEN")
.help("Custom ARP hardware address length")
)
.arg(
Arg::with_name("proto_type").long("proto-type").takes_value(true).value_name("PROTO_TYPE").help("Custom ARP proto type")
Arg::with_name("proto_type").long("proto-type")
.takes_value(true).value_name("PROTO_TYPE")
.help("Custom ARP proto type")
)
.arg(
Arg::with_name("proto_addr").long("proto-addr").takes_value(true).value_name("ADDRESS_LEN").help("Custom ARP proto address length")
Arg::with_name("proto_addr").long("proto-addr")
.takes_value(true).value_name("ADDRESS_LEN")
.help("Custom ARP proto address length")
)
.arg(
Arg::with_name("arp_operation").long("arp-op").takes_value(true).value_name("OPERATION_ID").help("Custom ARP operation ID")
Arg::with_name("arp_operation").long("arp-op")
.takes_value(true).value_name("OPERATION_ID")
.help("Custom ARP operation ID")
)
}

Expand Down
15 changes: 6 additions & 9 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ use std::sync::Arc;
use std::time::Duration;
use std::sync::atomic::{AtomicBool, Ordering};

use ipnetwork::NetworkSize;
use pnet::datalink;
use rand::prelude::*;

Expand Down Expand Up @@ -85,7 +84,7 @@ fn main() {
// local network.

let channel_config = datalink::Config {
read_timeout: Some(Duration::from_millis(500)),
read_timeout: Some(Duration::from_millis(network::DATALINK_RCV_TIMEOUT)),
..datalink::Config::default()
};

Expand All @@ -112,15 +111,13 @@ fn main() {
let cloned_options = Arc::clone(&scan_options);
let arp_responses = thread::spawn(move || network::receive_arp_responses(&mut rx, cloned_options, cloned_timed_out, &mut vendor_list));

let network_size: u128 = match ip_network.size() {
NetworkSize::V4(ipv4_network_size) => ipv4_network_size.into(),
NetworkSize::V6(_) => {
eprintln!("IPv6 networks are not supported by the ARP protocol");
process::exit(1);
}
};
let network_size = utils::compute_network_size(&ip_network);

if scan_options.is_plain_output() {

let estimations = network::compute_scan_estimation(network_size, &scan_options);
println!("Estimated scan time {}ms (sending {} bytes, {} bytes/s)", estimations.duration_ms, estimations.request_size, estimations.bandwidth);

println!("Sending {} ARP requests (waiting at least {}ms, {}ms request interval)", network_size, scan_options.timeout_ms, scan_options.interval_ms);
}

Expand Down
44 changes: 44 additions & 0 deletions src/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::collections::HashMap;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::io::ErrorKind::TimedOut;
use std::convert::TryInto;

use dns_lookup::lookup_addr;
use ipnetwork::IpNetwork;
Expand All @@ -17,13 +18,25 @@ use pnet::packet::vlan::{ClassOfService, MutableVlanPacket};
use crate::args::ScanOptions;
use crate::vendor::Vendor;

pub const DATALINK_RCV_TIMEOUT: u64 = 500;

const VLAN_QOS_DEFAULT: u8 = 1;
const ARP_PACKET_SIZE: usize = 28;
const VLAN_PACKET_SIZE: usize = 32;

const ETHERNET_STD_PACKET_SIZE: usize = 42;
const ETHERNET_VLAN_PACKET_SIZE: usize = 46;

/**
* Contains scan estimation records. This will be computed before the scan
* starts and should give insights about the scan.
*/
pub struct ScanEstimation {
pub duration_ms: u128,
pub request_size: u128,
pub bandwidth: u128
}

/**
* Gives high-level details about the scan response. This may include Ethernet
* details (packet count, size, ...) and other technical network aspects.
Expand All @@ -46,6 +59,37 @@ pub struct TargetDetails {
pub vendor: Option<String>
}

/**
* Based on the network size and given scan options, this function performs an
* estimation of the scan impact (timing, bandwidth, ...). Keep in mind that
* this is only an estimation, real results may vary based on the network.
*/
pub fn compute_scan_estimation(network_size: u128, options: &Arc<ScanOptions>) -> ScanEstimation {

let interval: u128 = options.interval_ms.into();
let timeout: u128 = options.timeout_ms.into();
let packet_size: u128 = match options.has_vlan() {
true => ETHERNET_VLAN_PACKET_SIZE.try_into().unwrap(),
false => ETHERNET_STD_PACKET_SIZE.try_into().unwrap()
};
let retry_count: u128 = options.retry_count.try_into().unwrap();

let avg_arp_request_ms = 3;
let avg_resolve_ms = 500;

let request_duration_ms: u128 = (network_size * (interval+avg_arp_request_ms)) * retry_count;
let duration_ms = request_duration_ms + timeout + avg_resolve_ms;
let request_size: u128 = network_size * packet_size;

let bandwidth = (request_size / request_duration_ms) * 1000;

ScanEstimation {
duration_ms,
request_size,
bandwidth
}
}

/**
* Send a single ARP request - using a datalink-layer sender, a given network
* interface and a target IPv4 address. The ARP request will be broadcasted to
Expand Down
12 changes: 12 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use pnet::datalink::{self, NetworkInterface};
use ipnetwork::{IpNetwork, NetworkSize};
use serde::Serialize;
use std::process;

Expand Down Expand Up @@ -60,6 +61,17 @@ pub fn select_default_interface() -> Option<NetworkInterface> {
})
}

pub fn compute_network_size(ip_network: &IpNetwork) -> u128 {

match ip_network.size() {
NetworkSize::V4(ipv4_network_size) => ipv4_network_size.into(),
NetworkSize::V6(_) => {
eprintln!("IPv6 networks are not supported by the ARP protocol");
process::exit(1);
}
}
}

/**
* Display the scan results on stdout with a table. The 'final_result' vector
* contains all items that will be displayed.
Expand Down

0 comments on commit 7aa9c6b

Please sign in to comment.