Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add host resolution #152

Merged
merged 2 commits into from
Aug 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
103 changes: 90 additions & 13 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ use rlimit::Resource;
use rlimit::{getrlimit, setrlimit};
use std::collections::HashMap;
use std::process::Command;
use std::{net::IpAddr, time::Duration};
use std::str::FromStr;
use std::{net::IpAddr, net::ToSocketAddrs, time::Duration};
use structopt::{clap::arg_enum, StructOpt};

extern crate colorful;
Expand Down Expand Up @@ -47,9 +48,9 @@ arg_enum! {
/// - Discord https://discord.gg/GFrQsGy
/// - GitHub https://github.com/RustScan/RustScan
struct Opts {
/// A list of comma separated IP addresses to be scanned.
#[structopt(use_delimiter = true, parse(try_from_str), required = true)]
ips: Vec<IpAddr>,
/// A list of comma separated IP addresses or hosts to be scanned.
#[structopt(use_delimiter = true, required = true)]
ips_or_hosts: Vec<String>,

///Quiet mode. Only output the ports. No Nmap. Useful for grep or outputting to a file.
#[structopt(short, long)]
Expand Down Expand Up @@ -103,11 +104,18 @@ fn main() {
print_opening();
}

let ips: Vec<IpAddr> = parse_ips(&opts);

if ips.is_empty() {
warning!("No IPs could be resolved, aborting scan.", false);
std::process::exit(1);
}

let ulimit: rlimit::rlim = adjust_ulimit_size(&opts);
let batch_size: u16 = infer_batch_size(&opts, ulimit);

let scanner = Scanner::new(
&opts.ips,
&ips,
batch_size,
Duration::from_millis(opts.timeout.into()),
opts.quiet,
Expand All @@ -124,7 +132,7 @@ fn main() {
.push(socket.port());
}

for ip in opts.ips {
for ip in ips {
if ports_per_ip.contains_key(&ip) {
continue;
}
Expand Down Expand Up @@ -229,6 +237,25 @@ fn build_nmap_arguments<'a>(
arguments
}

fn parse_ips(opts: &Opts) -> Vec<IpAddr> {
let mut ips: Vec<IpAddr> = Vec::new();

for ip_or_host in &opts.ips_or_hosts {
match IpAddr::from_str(ip_or_host) {
Ok(ip) => ips.push(ip),
_ => match format!("{}:{}", &ip_or_host, 80).to_socket_addrs() {
Ok(mut iter) => ips.push(iter.nth(0).unwrap().ip()),
_ => {
let failed_to_resolve = format!("Host {:?} could not be resolved.", ip_or_host);
warning!(failed_to_resolve, opts.quiet);
}
},
}
}

ips
}

fn adjust_ulimit_size(opts: &Opts) -> rlimit::rlim {
if opts.ulimit.is_some() {
let limit: rlimit::rlim = opts.ulimit.unwrap();
Expand Down Expand Up @@ -289,13 +316,12 @@ fn infer_batch_size(opts: &Opts, ulimit: rlimit::rlim) -> u16 {

#[cfg(test)]
mod tests {
use crate::{adjust_ulimit_size, infer_batch_size, print_opening, Opts, ScanOrder};
use std::{net::IpAddr, str::FromStr};
use crate::{adjust_ulimit_size, infer_batch_size, parse_ips, print_opening, Opts, ScanOrder};

#[test]
fn batch_size_lowered() {
let opts = Opts {
ips: vec![IpAddr::from_str("127.0.0.1").unwrap()],
ips_or_hosts: vec!["127.0.0.1".to_owned()],
quiet: true,
batch_size: 50_000,
timeout: 1_000,
Expand All @@ -312,7 +338,7 @@ mod tests {
#[test]
fn batch_size_lowered_average_size() {
let opts = Opts {
ips: vec![IpAddr::from_str("127.0.0.1").unwrap()],
ips_or_hosts: vec!["127.0.0.1".to_owned()],
quiet: true,
batch_size: 50_000,
timeout: 1_000,
Expand All @@ -330,7 +356,7 @@ mod tests {
// because ulimit and batch size are same size, batch size is lowered
// to ULIMIT - 100
let opts = Opts {
ips: vec![IpAddr::from_str("127.0.0.1").unwrap()],
ips_or_hosts: vec!["127.0.0.1".to_owned()],
quiet: true,
batch_size: 50_000,
timeout: 1_000,
Expand All @@ -347,7 +373,7 @@ mod tests {
fn batch_size_adjusted_2000() {
// ulimit == batch_size
let opts = Opts {
ips: vec![IpAddr::from_str("127.0.0.1").unwrap()],
ips_or_hosts: vec!["127.0.0.1".to_owned()],
quiet: true,
batch_size: 50_000,
timeout: 1_000,
Expand All @@ -369,7 +395,7 @@ mod tests {
#[test]
fn test_high_ulimit_no_quiet_mode() {
let opts = Opts {
ips: vec![IpAddr::from_str("127.0.0.1").unwrap()],
ips_or_hosts: vec!["127.0.0.1".to_owned()],
quiet: false,
batch_size: 10,
timeout: 1_000,
Expand All @@ -383,4 +409,55 @@ mod tests {

assert!(1 == 1);
}

#[test]
fn parse_correct_ips_or_hosts() {
let opts = Opts {
ips_or_hosts: vec!["127.0.0.1".to_owned(), "google.com".to_owned()],
quiet: true,
batch_size: 10,
timeout: 1_000,
ulimit: Some(2_000),
command: Vec::new(),
accessible: false,
scan_order: ScanOrder::Serial,
};
let ips = parse_ips(&opts);

assert_eq!(2, ips.len());
}

#[test]
fn parse_correct_and_incorrect_ips_or_hosts() {
let opts = Opts {
ips_or_hosts: vec!["127.0.0.1".to_owned(), "im_wrong".to_owned()],
quiet: true,
batch_size: 10,
timeout: 1_000,
ulimit: Some(2_000),
command: Vec::new(),
accessible: false,
scan_order: ScanOrder::Serial,
};
let ips = parse_ips(&opts);

assert_eq!(1, ips.len());
}

#[test]
fn parse_incorrect_ips_or_hosts() {
let opts = Opts {
ips_or_hosts: vec!["im_wrong".to_owned(), "300.10.1.1".to_owned()],
quiet: true,
batch_size: 10,
timeout: 1_000,
ulimit: Some(2_000),
command: Vec::new(),
accessible: false,
scan_order: ScanOrder::Serial,
};
let ips = parse_ips(&opts);

assert_eq!(0, ips.len());
}
}
24 changes: 12 additions & 12 deletions src/scanner/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ use std::{
};

/// The class for the scanner
/// Host is data type IpAddr and is the host address
/// IP is data type IpAddr and is the IP address
/// start & end is where the port scan starts and ends
/// batch_size is how many ports at a time should be scanned
/// Timeout is the time RustScan should wait before declaring a port closed. As datatype Duration.
/// Quiet is whether or not RustScan should print things, or wait until the end to print only open ports.
#[cfg(not(tarpaulin_include))]
pub struct Scanner {
hosts: Vec<IpAddr>,
ips: Vec<IpAddr>,
batch_size: u16,
timeout: Duration,
quiet: bool,
Expand All @@ -28,7 +28,7 @@ pub struct Scanner {

impl Scanner {
pub fn new(
hosts: &[IpAddr],
ips: &[IpAddr],
batch_size: u16,
timeout: Duration,
quiet: bool,
Expand All @@ -39,7 +39,7 @@ impl Scanner {
timeout,
quiet,
port_strategy,
hosts: hosts.iter().map(|host| host.to_owned()).collect(),
ips: ips.iter().map(|ip| ip.to_owned()).collect(),
}
}

Expand All @@ -48,10 +48,10 @@ impl Scanner {
/// Returns all open ports as Vec<u16>
pub async fn run(&self) -> Vec<SocketAddr> {
let ports: Vec<u16> = self.port_strategy.order();
let batch_per_host: usize = self.batch_size as usize / self.hosts.len();
let batch_per_ip: usize = self.batch_size as usize / self.ips.len();
let mut open_sockets: Vec<SocketAddr> = Vec::new();

for batch in ports.chunks(batch_per_host) {
for batch in ports.chunks(batch_per_ip) {
let mut sockets = self.scan_ports(batch).await;
open_sockets.append(&mut sockets);
}
Expand All @@ -64,8 +64,8 @@ impl Scanner {
async fn scan_ports(&self, ports: &[u16]) -> Vec<SocketAddr> {
let mut ftrs = FuturesUnordered::new();
for port in ports {
for host in &self.hosts {
ftrs.push(self.scan_socket(SocketAddr::new(*host, *port)));
for ip in &self.ips {
ftrs.push(self.scan_socket(SocketAddr::new(*ip, *port)));
}
}

Expand All @@ -91,7 +91,7 @@ impl Scanner {
///
/// self.scan_port(10:u16)
///
/// Note: `self` must contain `self.host`.
/// Note: `self` must contain `self.ip`.
async fn scan_socket(&self, socket: SocketAddr) -> io::Result<SocketAddr> {
match self.connect(socket).await {
Ok(x) => {
Expand Down Expand Up @@ -119,9 +119,9 @@ impl Scanner {
/// # Example
///
/// let port: u16 = 80
/// // Host is an IpAddr type
/// let host = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1));
/// let socket = SocketAddr::new(host, port);
/// // ip is an IpAddr type
/// let ip = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1));
/// let socket = SocketAddr::new(ip, port);
/// self.connect(socket)
/// // returns Result which is either Ok(stream) for port is open, or Er for port is closed.
/// // Timeout occurs after self.timeout seconds
Expand Down