Compare commits

..
Author SHA1 Message Date
bee cb1d3ffeae run just 2025-03-04 21:15:56 +00:00
bee 74a9e3e89e Add custom script directory 2025-03-04 21:12:33 +00:00
28 changed files with 994 additions and 776 deletions

No files matched your search

+17 -17
View File
@@ -37,11 +37,11 @@ jobs:
path: target/aarch64-unknown-linux-gnu/release/rustscan
pkg_config_path: /usr/lib/x86_64-linux-gnu/pkgconfig
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v4
- name: Cache cargo & target directories
uses: Swatinem/rust-cache@v2
- name: Build binary
uses: houseabsolute/actions-rust-cross@v1
uses: houseabsolute/actions-rust-cross@v0
with:
command: build
target: ${{ matrix.target }}
@@ -52,11 +52,11 @@ jobs:
if: matrix.type == 'ubuntu-x64'
run: |
tar czf ${{ matrix.name }}.tar.gz -C target/x86_64-unknown-linux-musl/release rustscan
- uses: actions/upload-artifact@v7
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.name }}
path: ${{ matrix.path }}
- uses: actions/upload-artifact@v7
- uses: actions/upload-artifact@v4
if: matrix.type == 'ubuntu-x64'
with:
name: ${{ matrix.name }}.tar.gz
@@ -67,7 +67,7 @@ jobs:
env:
IN_PIPELINE: true
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v4
- name: Install cargo-deb
run: cargo install -f cargo-deb
- uses: awalsh128/cache-apt-pkgs-action@v1
@@ -79,7 +79,7 @@ jobs:
- name: Deb Build
run: cargo deb --target=x86_64-unknown-linux-musl
- name: Upload Deb Artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v4
with:
name: rustscan.deb
path: ./target/x86_64-unknown-linux-musl/debian/*
@@ -90,11 +90,11 @@ jobs:
runs-on: macos-latest
if: github.ref == 'refs/heads/master'
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v4
- name: Cache cargo & target directories
uses: Swatinem/rust-cache@v2
- name: Build binary
uses: houseabsolute/actions-rust-cross@v1
uses: houseabsolute/actions-rust-cross@v0
with:
command: build
target: x86_64-apple-darwin
@@ -104,11 +104,11 @@ jobs:
- name: Build tar.gz for homebrew installs
run: |
tar czf x86_64-macos-rustscan.tar.gz -C target/x86_64-apple-darwin/release rustscan
- uses: actions/upload-artifact@v7
- uses: actions/upload-artifact@v4
with:
name: x86_64-macos-rustscan
path: target/x86_64-apple-darwin/release/rustscan
- uses: actions/upload-artifact@v7
- uses: actions/upload-artifact@v4
with:
name: x86_64-macos-rustscan.tar.gz
path: x86_64-macos-rustscan.tar.gz
@@ -119,11 +119,11 @@ jobs:
runs-on: macos-latest
if: github.ref == 'refs/heads/master'
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v4
- name: Cache cargo & target directories
uses: Swatinem/rust-cache@v2
- name: Build binary
uses: houseabsolute/actions-rust-cross@v1
uses: houseabsolute/actions-rust-cross@v0
with:
command: build
target: aarch64-apple-darwin
@@ -133,11 +133,11 @@ jobs:
- name: Build tar.gz for homebrew installs
run: |
tar czf aarch64-macos-rustscan.tar.gz -C target/aarch64-apple-darwin/release rustscan
- uses: actions/upload-artifact@v7
- uses: actions/upload-artifact@v4
with:
name: aarch64-macos-rustscan
path: target/aarch64-apple-darwin/release/rustscan
- uses: actions/upload-artifact@v7
- uses: actions/upload-artifact@v4
with:
name: aarch64-macos-rustscan.tar.gz
path: aarch64-macos-rustscan.tar.gz
@@ -162,18 +162,18 @@ jobs:
name: x86-windows-rustscan.exe
path: target\i686-pc-windows-msvc\release\rustscan.exe
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v4
- name: Cache cargo & target directories
uses: Swatinem/rust-cache@v2
- name: Build binary
uses: houseabsolute/actions-rust-cross@v1
uses: houseabsolute/actions-rust-cross@v0
with:
command: build
target: ${{ matrix.target }}
args: "--locked --release"
strip: true
toolchain: stable
- uses: actions/upload-artifact@v7
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.name }}
path: ${{ matrix.path }}
+5
View File
@@ -1,4 +1,9 @@
name: Dependabot Auto-Merge
on: pull_request
permissions:
contents: write
pull-requests: write
jobs:
dependabot:
+21
View File
@@ -0,0 +1,21 @@
name: Check Markdown links
on:
push:
branches:
- master
paths-ignore:
- '.github/**'
schedule:
# Run everyday at 9:00 AM (See https://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html#tag_20_25_07)
- cron: "0 9 * * *"
jobs:
markdown-link-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: gaurav-nelson/github-action-markdown-link-check@v1
with:
use-quiet-mode: 'yes'
config-file: './.github/workflows/mlc_config.json'
+2 -2
View File
@@ -15,7 +15,7 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v7
uses: actions/checkout@v4
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
@@ -28,7 +28,7 @@ jobs:
- uses: Swatinem/rust-cache@v2
- name: Install Just
uses: extractions/setup-just@v4
uses: extractions/setup-just@v2
- name: Run just
run: just test
+1
View File
@@ -17,6 +17,7 @@ jobs:
script:
- cargo build
- cargo test
- "ulimit -n 5000; cargo test timelimits:: -- --ignored --test-threads=1 --show-output"
- cargo fmt -- --check
after_failure:
- wget https://raw.githubusercontent.com/DiscordHooks/travis-ci-discord-webhook/master/send.sh
Generated
+196 -504
View File
File diff suppressed because it is too large. Load diff
+16 -15
View File
@@ -17,32 +17,33 @@ exclude = [
]
[dependencies]
clap = { version = "4.6.1", features = ["derive", "wrap_help"] }
colored = "3.1.1"
async-std = "1.13.2"
clap = { version = "4.5.31", features = ["derive", "wrap_help"] }
colored = "3.0.0"
async-std = "1.13.0"
futures = "0.3"
rlimit = "0.11.0"
log = "0.4.32"
env_logger = "0.11.10"
anstream = "=1.0.0"
dirs = "7.0.0"
rlimit = "0.10.2"
log = "0.4.25"
env_logger = "0.11.6"
anstream = "=0.6.18"
dirs = "6.0.0"
gcd = "2.0.1"
rand = "0.10.1"
rand = "0.9.0"
colorful = "0.3.2"
ansi_term = "0.12.1"
toml = "1.1.2"
toml = "0.8.20"
serde = "1.0.124"
serde_derive = "1.0.116"
cidr-utils = "0.7.1"
itertools = "0.15.0"
cidr-utils = "0.6.1"
itertools = "0.14.0"
hickory-resolver = { version = "0.24.3", features = ["dns-over-rustls"] }
anyhow = "1.0.40"
text_placeholder = { version = "0.5", features = ["struct_context"] }
once_cell = "1.21.4"
once_cell = "1.20.3"
[dev-dependencies]
parameterized = "2.0.0"
criterion = { version = "0.8", features = ["html_reports"] }
wait-timeout = "0.2"
criterion = { version = "0.5", features = ["html_reports"] }
[package.metadata.deb]
depends = "$auto, nmap"
@@ -62,5 +63,5 @@ path = "src/main.rs"
unexpected_cfgs = { level = "warn", check-cfg = ["cfg(tarpaulin_include)"] }
[[bench]]
name = "benchmark_helpers"
name = "benchmark_portscan"
harness = false
+3 -3
View File
@@ -40,7 +40,7 @@ MacOS:
Arch:
```
pacman -S rustscan
yay rustscan
```
# ✨ Features
@@ -55,7 +55,7 @@ Arch:
| <!--Installation Guide--> | <!--Documentation--> | <!--Discord--> |
| :----------------------------------------: | :------------------------------------: | :-------------------------: |
| :book: [Installation Guide][toc-install] | :books: [Documentation][links-table-2] | :parrot: [Discord][discord] |
| :book: [Installation Guide][links-table-1] | :books: [Documentation][links-table-2] | :parrot: [Discord][discord] |
## 🙋 Table of Contents
@@ -209,7 +209,7 @@ This project follows the [all-contributors](https://github.com/all-contributors/
[speed-3]: https://github.com/RustScan/RustScan/wiki/Increasing-Speed-&-Accuracy "Increasing Speed & Accuracy"
[toc-community]: https://github.com/RustScan/RustScan#-community "Community"
[links-table-1]: https://github.com/RustScan/RustScan#-full-installation-guide "Full installation guide"
[links-table-2]: https://github.com/bee-san/RustScan/wiki "Rustscan"
[links-table-2]: https://rustscan.github.io/RustScan/ "Rustscan"
[discord]: http://discord.skerritt.blog "Discord blog"
[toc-install]: https://github.com/RustScan/RustScan/wiki/Installation-Guide "Installation Guide Wiki"
[toc-docker-usage]: https://github.com/RustScan/RustScan/wiki/Installation-Guide#docker- "Docker Installation Guide Wiki"
-52
View File
@@ -1,52 +0,0 @@
use criterion::{criterion_group, criterion_main, Criterion};
use rustscan::input::{Opts, PortRange, ScanOrder};
use rustscan::port_strategy::PortStrategy;
use std::net::IpAddr;
use std::time::Duration;
fn bench_address() {
let _addrs = ["127.0.0.1".parse::<IpAddr>().unwrap()];
}
fn bench_port_strategy() {
let range = PortRange {
start: 1,
end: 1_000,
};
let _strategy = PortStrategy::pick(&Some(range.clone()), None, ScanOrder::Serial);
}
fn bench_address_parsing() {
let opts = Opts {
addresses: vec![
"127.0.0.1".to_owned(),
"10.2.0.1".to_owned(),
"192.168.0.0/24".to_owned(),
],
exclude_addresses: Some(vec![
"10.0.0.0/8".to_owned(),
"172.16.0.0/12".to_owned(),
"192.168.0.0/16".to_owned(),
"172.16.0.1".to_owned(),
]),
..Default::default()
};
let _ips = rustscan::address::parse_addresses(&opts);
}
fn criterion_benchmark(c: &mut Criterion) {
// Benching helper functions
c.bench_function("parse address", |b| b.iter(bench_address));
c.bench_function("port strategy", |b| b.iter(bench_port_strategy));
let mut address_group = c.benchmark_group("address parsing");
address_group.measurement_time(Duration::from_secs(10));
address_group.bench_function("parse addresses with exclusions", |b| {
b.iter(bench_address_parsing)
});
address_group.finish();
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
+80
View File
@@ -0,0 +1,80 @@
use async_std::task::block_on;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use rustscan::input::{PortRange, ScanOrder};
use rustscan::port_strategy::PortStrategy;
use rustscan::scanner::Scanner;
use std::net::IpAddr;
use std::time::Duration;
fn portscan_tcp(scanner: &Scanner) {
let _scan_result = block_on(scanner.run());
}
fn portscan_udp(scanner: &Scanner) {
let _scan_result = block_on(scanner.run());
}
fn bench_address() {
let _addrs = ["127.0.0.1".parse::<IpAddr>().unwrap()];
}
fn bench_port_strategy() {
let range = PortRange {
start: 1,
end: 1_000,
};
let _strategy = PortStrategy::pick(&Some(range.clone()), None, ScanOrder::Serial);
}
fn criterion_benchmark(c: &mut Criterion) {
let addrs = vec!["127.0.0.1".parse::<IpAddr>().unwrap()];
let range = PortRange {
start: 1,
end: 1_000,
};
let strategy_tcp = PortStrategy::pick(&Some(range.clone()), None, ScanOrder::Serial);
let strategy_udp = PortStrategy::pick(&Some(range.clone()), None, ScanOrder::Serial);
let scanner_tcp = Scanner::new(
&addrs,
10,
Duration::from_millis(10),
1,
false,
strategy_tcp,
true,
vec![],
false,
);
c.bench_function("portscan tcp", |b| {
b.iter(|| portscan_tcp(black_box(&scanner_tcp)))
});
let scanner_udp = Scanner::new(
&addrs,
10,
Duration::from_millis(10),
1,
false,
strategy_udp,
true,
vec![],
true,
);
let mut udp_group = c.benchmark_group("portscan udp");
udp_group.measurement_time(Duration::from_secs(20));
udp_group.bench_function("portscan udp", |b| {
b.iter(|| portscan_udp(black_box(&scanner_udp)))
});
udp_group.finish();
// Benching helper functions
c.bench_function("parse address", |b| b.iter(bench_address));
c.bench_function("port strategy", |b| b.iter(bench_port_strategy));
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
+1 -1
View File
@@ -141,7 +141,7 @@ fn ports_v(fp_map: &BTreeMap<i32, String>) -> BTreeMap<i32, Vec<u16>> {
} else if !segment.is_empty() {
match segment.parse::<u16>() {
Ok(port) => port_list.push(port),
Err(_) => println!("Error parsing port: {segment}"),
Err(_) => println!("Error parsing port: {}", segment),
}
}
}
+1 -3
View File
@@ -1,7 +1,5 @@
# Dockerfile for RustScan development environment
# Provides a containerized setup with Rust, nmap, and development tools
FROM rust
# Install nmap first.
RUN apt-get update -qy && apt-get install -qy nmap
# Then install rustfmt for code formatting and clippy for linting.
# Then install rustfmt and clippy for cargo.
RUN rustup component add rustfmt clippy
-5
View File
@@ -67,8 +67,3 @@ root@container:/rustscan# cargo fmt
root@container:/rustscan# cargo clippy
root@container:/rustscan# cargo test
```
Automated tests and benchmarks must run without network traffic or external script
execution. Keep coverage focused on parsing, configuration, port ordering, socket
address iteration, and timing helpers. Do not add tests that run TCP/UDP scans
(including localhost), resolve hostnames, or launch Nmap or other scripts.
+2 -2
View File
@@ -4,7 +4,7 @@
#ports_separator = ","
#call_format = "perl {{script}} {{ip}} {{port}}"
# The Scriptfile parser stops parsing at the first blank line.
# Sriptfile parser stops at the first blank line with parsing.
# This script will run itself as an argument with the system installed perl interpreter, ports will be concatenated with "," .
# Unused field: trigger_port = "80"
# get total arg passed to this script
@@ -20,4 +20,4 @@ print "Total args passed to $scriptname : $total\n";
foreach my $a(@ARGV) {
print "Arg # $counter : $a\n";
$counter++;
}
}
+2 -2
View File
@@ -4,10 +4,10 @@
#trigger_port = "80"
#call_format = "python3 {{script}} {{ip}} {{port}}"
# The Scriptfile parser stops parsing at the first blank line.
# Sriptfile parser stops at the first blank line with parsing.
# This script will run itself as an argument with the system installed python interpreter, only scanning port 80.
# Unused filed: ports_separator = ","
import sys
print('Python script ran with arguments', str(sys.argv))
print('Python script ran with arguments', str(sys.argv))
+2 -2
View File
@@ -4,9 +4,9 @@
#ports_separator = ","
#call_format = "bash {{script}} {{ip}} {{port}}"
# The Scriptfile parser stops parsing at the first blank line.
# Sriptfile parser stops at the first blank line with parsing.
# This script will run itself as an argument with the system installed bash interpreter, scanning all ports concatenated with "," .
# Unused filed: trigger_port = "80"
# print all arguments passed to the script
echo $@
echo $@
+2 -2
View File
@@ -4,6 +4,6 @@
#ports_separator = ","
#call_format = "nmap -vvv -p {{port}} {{ip}}"
# The Scriptfile parser stops parsing at the first blank line.
# Scriptfile parser stops at the first blank line with parsing.
# This script will run the system installed nmap, ports will be concatenated with "," .
# Unused field: trigger_port = "80"
# Unused field: trigger_port = "80"
+1 -1
View File
@@ -1,5 +1,5 @@
test:
cargo test
cargo nextest run
cargo clippy -- --deny warnings
cargo clippy --tests -- --deny warnings
cargo fmt --check
+112 -64
View File
@@ -6,7 +6,7 @@ use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
use std::path::Path;
use std::str::FromStr;
use cidr_utils::cidr::{IpCidr, IpInet};
use cidr_utils::cidr::IpCidr;
use hickory_resolver::{
config::{NameServerConfig, Protocol, ResolverConfig, ResolverOpts},
Resolver,
@@ -69,11 +69,17 @@ pub fn parse_addresses(input: &Opts) -> Vec<IpAddr> {
}
}
let excluded_cidrs = parse_excluded_networks(&input.exclude_addresses, &backup_resolver);
// Finally, craft a list of addresses to be excluded from the scan.
let mut excluded_ips: BTreeSet<IpAddr> = BTreeSet::new();
if let Some(exclude_addresses) = &input.exclude_addresses {
for addr in exclude_addresses {
excluded_ips.extend(parse_address(addr, &backup_resolver));
}
}
// Remove duplicated/excluded IPs.
let mut seen = BTreeSet::new();
ips.retain(|ip| seen.insert(*ip) && !excluded_cidrs.iter().any(|cidr| cidr.contains(ip)));
ips.retain(|ip| seen.insert(*ip) && !excluded_ips.contains(ip));
ips
}
@@ -92,21 +98,16 @@ pub fn parse_addresses(input: &Opts) -> Vec<IpAddr> {
/// let ips = parse_address("127.0.0.1", &Resolver::default().unwrap());
/// ```
pub fn parse_address(address: &str, resolver: &Resolver) -> Vec<IpAddr> {
if let Ok(addr) = IpAddr::from_str(address) {
// `address` is an IP string
vec![addr]
} else if let Ok(net_addr) = IpInet::from_str(address) {
// `address` is a CIDR string
net_addr.network().into_iter().addresses().collect()
} else {
// `address` is a hostname or DNS name
// attempt default DNS lookup
match format!("{address}:80").to_socket_addrs() {
Ok(mut iter) => vec![iter.next().unwrap().ip()],
// default lookup didn't work, so try again with the dedicated resolver
Err(_) => resolve_ips_from_host(address, resolver),
}
}
IpCidr::from_str(address)
.map(|cidr| cidr.iter().map(|c| c.address()).collect())
.ok()
.or_else(|| {
format!("{}:80", &address)
.to_socket_addrs()
.ok()
.map(|mut iter| vec![iter.next().unwrap().ip()])
})
.unwrap_or_else(|| resolve_ips_from_host(address, resolver))
}
/// Uses DNS to get the IPS associated with host
@@ -124,46 +125,6 @@ fn resolve_ips_from_host(source: &str, backup_resolver: &Resolver) -> Vec<IpAddr
ips
}
/// Parses excluded networks from a list of addresses.
///
/// This function handles three types of inputs:
/// 1. CIDR notation (e.g. "192.168.0.0/24")
/// 2. Single IP addresses (e.g. "192.168.0.1")
/// 3. Hostnames that need to be resolved (e.g. "example.com")
///
/// ```rust
/// # use rustscan::address::parse_excluded_networks;
/// # use hickory_resolver::Resolver;
/// let resolver = Resolver::default().unwrap();
/// let excluded = parse_excluded_networks(&Some(vec!["192.168.0.0/24".to_owned()]), &resolver);
/// ```
pub fn parse_excluded_networks(
exclude_addresses: &Option<Vec<String>>,
resolver: &Resolver,
) -> Vec<IpCidr> {
exclude_addresses
.iter()
.flatten()
.flat_map(|addr| parse_single_excluded_address(addr, resolver))
.collect()
}
/// Parses a single address into an IpCidr, handling CIDR notation, IP addresses, and hostnames.
fn parse_single_excluded_address(addr: &str, resolver: &Resolver) -> Vec<IpCidr> {
if let Ok(cidr) = IpCidr::from_str(addr) {
return vec![cidr];
}
if let Ok(ip) = IpAddr::from_str(addr) {
return vec![IpCidr::new_host(ip)];
}
resolve_ips_from_host(addr, resolver)
.into_iter()
.map(IpCidr::new_host)
.collect()
}
/// Derive a DNS resolver.
///
/// 1. if the `resolver` parameter has been set:
@@ -236,7 +197,7 @@ fn read_ips_from_file(
#[cfg(test)]
mod tests {
use super::{parse_addresses, Opts};
use super::{get_resolver, parse_addresses, Opts};
use std::net::Ipv4Addr;
#[test]
@@ -318,6 +279,81 @@ mod tests {
);
}
#[test]
fn parse_correct_host_addresses() {
let opts = Opts {
addresses: vec!["google.com".to_owned()],
..Default::default()
};
let ips = parse_addresses(&opts);
assert_eq!(ips.len(), 1);
}
#[test]
fn parse_correct_and_incorrect_addresses() {
let opts = Opts {
addresses: vec!["127.0.0.1".to_owned(), "im_wrong".to_owned()],
..Default::default()
};
let ips = parse_addresses(&opts);
assert_eq!(ips, [Ipv4Addr::new(127, 0, 0, 1),]);
}
#[test]
fn parse_incorrect_addresses() {
let opts = Opts {
addresses: vec!["im_wrong".to_owned(), "300.10.1.1".to_owned()],
..Default::default()
};
let ips = parse_addresses(&opts);
assert!(ips.is_empty());
}
#[test]
fn parse_hosts_file_and_incorrect_hosts() {
// Host file contains IP, Hosts, incorrect IPs, incorrect hosts
let opts = Opts {
addresses: vec!["fixtures/hosts.txt".to_owned()],
..Default::default()
};
let ips = parse_addresses(&opts);
assert_eq!(ips.len(), 3);
}
#[test]
fn parse_empty_hosts_file() {
// Host file contains IP, Hosts, incorrect IPs, incorrect hosts
let opts = Opts {
addresses: vec!["fixtures/empty_hosts.txt".to_owned()],
..Default::default()
};
let ips = parse_addresses(&opts);
assert_eq!(ips.len(), 0);
}
#[test]
fn parse_naughty_host_file() {
// Host file contains IP, Hosts, incorrect IPs, incorrect hosts
let opts = Opts {
addresses: vec!["fixtures/naughty_string.txt".to_owned()],
..Default::default()
};
let ips = parse_addresses(&opts);
assert_eq!(ips.len(), 0);
}
#[test]
fn parse_duplicate_cidrs() {
let opts = Opts {
@@ -331,15 +367,27 @@ mod tests {
}
#[test]
fn parse_overspecific_cidr() {
// a canonical CIDR string has 0 in all host bits, but we want to treat any CIDR-like string as CIDR
fn resolver_default_cloudflare() {
let opts = Opts::default();
let resolver = get_resolver(&opts.resolver);
let lookup = resolver.lookup_ip("www.example.com.").unwrap();
assert!(opts.resolver.is_none());
assert!(lookup.iter().next().is_some());
}
#[test]
fn resolver_args_google_dns() {
// https://developers.google.com/speed/public-dns
let opts = Opts {
addresses: vec!["192.128.1.1/24".to_owned()],
resolver: Some("8.8.8.8,8.8.4.4".to_owned()),
..Default::default()
};
let ips = parse_addresses(&opts);
let resolver = get_resolver(&opts.resolver);
let lookup = resolver.lookup_ip("www.example.com.").unwrap();
assert_eq!(ips.len(), 256);
assert!(lookup.iter().next().is_some());
}
}
+7 -5
View File
@@ -42,11 +42,13 @@ impl Benchmark {
let mut summary = String::from("\nRustScan Benchmark Summary");
for timer in &self.named_timers {
if let Some(start) = timer.start {
if let Some(end) = timer.end {
let runtime_secs = end.saturating_duration_since(start).as_secs_f32();
summary.push_str(&format!("\n{0: <10} | {1: <10}s", timer.name, runtime_secs));
}
if timer.start.is_some() && timer.end.is_some() {
let runtime_secs = timer
.end
.unwrap()
.saturating_duration_since(timer.start.unwrap())
.as_secs_f32();
summary.push_str(&format!("\n{0: <10} | {1: <10}s", timer.name, runtime_secs));
}
}
summary
+13 -26
View File
@@ -1,6 +1,7 @@
//! Provides a means to read, parse and hold configuration options for scans.
use clap::{Parser, ValueEnum};
use serde_derive::Deserialize;
use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;
@@ -113,7 +114,7 @@ pub struct Opts {
/// it will do every port at the same time. Although, your OS may not
/// support this.
#[arg(short, long, default_value = "4500")]
pub batch_size: usize,
pub batch_size: u16,
/// The timeout in milliseconds before a port is assumed to be closed.
#[arg(short, long, default_value = "1500")]
@@ -126,7 +127,7 @@ pub struct Opts {
/// Automatically ups the ULIMIT with the value you provided.
#[arg(short, long)]
pub ulimit: Option<usize>,
pub ulimit: Option<u64>,
/// The order of scanning to be performed. The "serial" option will
/// scan ports in ascending order while the "random" option will scan
@@ -200,7 +201,7 @@ impl Opts {
merge_required!(
addresses, greppable, accessible, batch_size, timeout, tries, scan_order, scripts,
command, udp, no_banner
command, udp
);
}
@@ -217,7 +218,11 @@ impl Opts {
// Only use top ports when the user asks for them
if self.top && config.ports.is_some() {
self.ports = config.ports.clone();
let mut ports: Vec<u16> = Vec::with_capacity(config.ports.as_ref().unwrap().len());
for entry in config.ports.as_ref().unwrap().keys() {
ports.push(entry.parse().unwrap());
}
self.ports = Some(ports);
}
merge_optional!(range, resolver, ulimit, exclude_ports, exclude_addresses);
@@ -258,14 +263,14 @@ impl Default for Opts {
#[derive(Debug, Deserialize)]
pub struct Config {
addresses: Option<Vec<String>>,
ports: Option<Vec<u16>>,
ports: Option<HashMap<String, u16>>,
range: Option<PortRange>,
greppable: Option<bool>,
accessible: Option<bool>,
batch_size: Option<usize>,
batch_size: Option<u16>,
timeout: Option<u32>,
tries: Option<u8>,
ulimit: Option<usize>,
ulimit: Option<u64>,
resolver: Option<String>,
scan_order: Option<ScanOrder>,
command: Option<Vec<String>>,
@@ -273,7 +278,6 @@ pub struct Config {
exclude_ports: Option<Vec<u16>>,
exclude_addresses: Option<Vec<String>>,
udp: Option<bool>,
no_banner: Option<bool>,
}
#[cfg(not(tarpaulin_include))]
@@ -294,14 +298,7 @@ impl Config {
///
pub fn read(custom_config_path: Option<PathBuf>) -> Self {
let mut content = String::new();
let config_path = custom_config_path.unwrap_or_else(|| {
let path = default_config_path();
match path.exists() {
true => path,
false => old_default_config_path(),
}
});
let config_path = custom_config_path.unwrap_or_else(default_config_path);
if config_path.exists() {
content = match fs::read_to_string(config_path) {
Ok(content) => content,
@@ -323,15 +320,6 @@ impl Config {
/// Constructs default path to config toml
pub fn default_config_path() -> PathBuf {
let Some(mut config_path) = dirs::config_dir() else {
panic!("Could not infer config file path.");
};
config_path.push(".rustscan.toml");
config_path
}
/// Returns the deprecated home directory config path used for backwards compatibility.
pub fn old_default_config_path() -> PathBuf {
let Some(mut config_path) = dirs::home_dir() else {
panic!("Could not infer config file path.");
};
@@ -365,7 +353,6 @@ mod tests {
exclude_ports: None,
exclude_addresses: None,
udp: Some(false),
no_banner: None,
}
}
}
+1 -3
View File
@@ -7,9 +7,7 @@
//! [`Scanner`](crate::scanner::Scanner) which in turn requires a
//! [`PortStrategy`](crate::port_strategy::PortStrategy):
//!
//! This example is compiled but never executed by the test suite.
//!
//! ```no_run
//! ```rust
//! use async_std::task::block_on;
//! use std::{net::IpAddr, time::Duration};
//!
+26 -35
View File
@@ -23,9 +23,9 @@ extern crate dirs;
// Average value for Ubuntu
#[cfg(unix)]
const DEFAULT_FILE_DESCRIPTORS_LIMIT: usize = 8000;
const DEFAULT_FILE_DESCRIPTORS_LIMIT: u64 = 8000;
// Safest batch size based on experimentation
const AVERAGE_BATCH_SIZE: usize = 3000;
const AVERAGE_BATCH_SIZE: u16 = 3000;
#[macro_use]
extern crate log;
@@ -46,7 +46,7 @@ fn main() {
let config = Config::read(opts.config_path.clone());
opts.merge(&config);
debug!("Main() `opts` arguments are {opts:?}");
debug!("Main() `opts` arguments are {:?}", opts);
let scripts_to_run: Vec<ScriptFile> = match init_scripts(&opts.scripts) {
Ok(scripts_to_run) => scripts_to_run,
@@ -60,7 +60,7 @@ fn main() {
}
};
debug!("Scripts initialized {scripts_to_run:?}");
debug!("Scripts initialized {:?}", &scripts_to_run);
if !opts.greppable && !opts.accessible && !opts.no_banner {
print_opening(&opts);
@@ -78,10 +78,10 @@ fn main() {
}
#[cfg(unix)]
let batch_size: usize = infer_batch_size(&opts, adjust_ulimit_size(&opts));
let batch_size: u16 = infer_batch_size(&opts, adjust_ulimit_size(&opts));
#[cfg(not(unix))]
let batch_size: usize = AVERAGE_BATCH_SIZE;
let batch_size: u16 = AVERAGE_BATCH_SIZE;
let scanner = Scanner::new(
&ips,
@@ -94,7 +94,7 @@ fn main() {
opts.exclude_ports.unwrap_or_default(),
opts.udp,
);
debug!("Scanner finished building: {scanner:?}");
debug!("Scanner finished building: {:?}", scanner);
let mut portscan_bench = NamedTimer::start("Portscan");
let scan_result = block_on(scanner.run());
@@ -136,7 +136,7 @@ fn main() {
// if option scripts is none, no script will be spawned
if opts.greppable || opts.scripts == ScriptsRequired::None {
println!("{ip} -> [{ports_str}]");
println!("{} -> [{}]", &ip, ports_str);
continue;
}
detail!("Starting Script(s)", opts.greppable, opts.accessible);
@@ -146,7 +146,7 @@ fn main() {
// This part allows us to add commandline arguments to the Script call_format, appending them to the end of the command.
if !opts.command.is_empty() {
let user_extra_args = &opts.command.join(" ");
debug!("Extra args vec {user_extra_args:?}");
debug!("Extra args vec {:?}", user_extra_args);
if script_f.call_format.is_some() {
let mut call_f = script_f.call_format.unwrap();
call_f.push(' ');
@@ -156,7 +156,7 @@ fn main() {
opts.greppable,
opts.accessible
);
debug!("Call format {call_f}");
debug!("Call format {}", call_f);
script_f.call_format = Some(call_f);
}
}
@@ -173,7 +173,7 @@ fn main() {
);
match script.run() {
Ok(script_result) => {
detail!(script_result.clone(), opts.greppable, opts.accessible);
detail!(script_result.to_string(), opts.greppable, opts.accessible);
}
Err(e) => {
warning!(&format!("Error {e}"), opts.greppable, opts.accessible);
@@ -187,7 +187,7 @@ fn main() {
benchmarks.push(script_bench);
rustscan_bench.end();
benchmarks.push(rustscan_bench);
debug!("Benchmarks raw {benchmarks:?}");
debug!("Benchmarks raw {:?}", benchmarks);
info!("{}", benchmarks.summary());
}
@@ -219,26 +219,13 @@ The Modern Day Port Scanner."#;
opts.greppable,
opts.accessible
);
if opts.config_path.is_none() {
let old_config_path = input::old_default_config_path();
detail!(
format!(
"For backwards compatibility, the config file may also be at {old_config_path:?}"
),
opts.greppable,
opts.accessible
);
}
}
#[cfg(unix)]
fn adjust_ulimit_size(opts: &Opts) -> usize {
fn adjust_ulimit_size(opts: &Opts) -> u64 {
use rlimit::Resource;
use std::convert::TryInto;
if let Some(limit) = opts.ulimit {
let limit = limit as u64;
if Resource::NOFILE.set(limit, limit).is_ok() {
detail!(
format!("Automatically increasing ulimit value to {limit}."),
@@ -255,12 +242,14 @@ fn adjust_ulimit_size(opts: &Opts) -> usize {
}
let (soft, _) = Resource::NOFILE.get().unwrap();
soft.try_into().unwrap_or(usize::MAX)
soft
}
#[cfg(unix)]
fn infer_batch_size(opts: &Opts, ulimit: usize) -> usize {
let mut batch_size = opts.batch_size;
fn infer_batch_size(opts: &Opts, ulimit: u64) -> u16 {
use std::convert::TryInto;
let mut batch_size: u64 = opts.batch_size.into();
// Adjust the batch size when the ulimit value is lower than the desired batch size
if ulimit < batch_size {
@@ -271,7 +260,7 @@ fn infer_batch_size(opts: &Opts, ulimit: usize) -> usize {
// When the OS supports high file limits like 8000, but the user
// selected a batch size higher than this we should reduce it to
// a lower number.
if ulimit < AVERAGE_BATCH_SIZE {
if ulimit < AVERAGE_BATCH_SIZE.into() {
// ulimit is smaller than aveage batch size
// user must have very small ulimit
// decrease batch size to half of ulimit
@@ -280,7 +269,7 @@ fn infer_batch_size(opts: &Opts, ulimit: usize) -> usize {
batch_size = ulimit / 2;
} else if ulimit > DEFAULT_FILE_DESCRIPTORS_LIMIT {
info!("Batch size is now average batch size");
batch_size = AVERAGE_BATCH_SIZE;
batch_size = AVERAGE_BATCH_SIZE.into();
} else {
batch_size = ulimit - 100;
}
@@ -293,6 +282,8 @@ fn infer_batch_size(opts: &Opts, ulimit: usize) -> usize {
}
batch_size
.try_into()
.expect("Couldn't fit the batch size into a u16.")
}
#[cfg(test)]
@@ -322,7 +313,7 @@ mod tests {
};
let batch_size = infer_batch_size(&opts, 9_000);
assert_eq!(batch_size, 3_000);
assert!(batch_size == 3_000);
}
#[test]
#[cfg(unix)]
@@ -335,7 +326,7 @@ mod tests {
};
let batch_size = infer_batch_size(&opts, 5_000);
assert_eq!(batch_size, 4_900);
assert!(batch_size == 4_900);
}
#[test]
#[cfg(unix)]
@@ -348,7 +339,7 @@ mod tests {
};
let batch_size = adjust_ulimit_size(&opts);
assert_eq!(batch_size, 2_000);
assert!(batch_size == 2_000);
}
#[test]
@@ -362,7 +353,7 @@ mod tests {
let batch_size = infer_batch_size(&opts, 1_000_000);
assert_eq!(batch_size, opts.batch_size);
assert!(batch_size == opts.batch_size);
}
#[test]
+1 -1
View File
@@ -1,5 +1,5 @@
use gcd::Gcd;
use rand::RngExt;
use rand::Rng;
use std::convert::TryInto;
pub struct RangeIterator {
+237 -14
View File
@@ -29,7 +29,7 @@ use std::{
#[derive(Debug)]
pub struct Scanner {
ips: Vec<IpAddr>,
batch_size: usize,
batch_size: u16,
timeout: Duration,
tries: NonZeroU8,
greppable: bool,
@@ -44,7 +44,7 @@ pub struct Scanner {
impl Scanner {
pub fn new(
ips: &[IpAddr],
batch_size: usize,
batch_size: u16,
timeout: Duration,
tries: u8,
greppable: bool,
@@ -94,7 +94,7 @@ impl Scanner {
debug!("Start scanning sockets. \nBatch size {}\nNumber of ip-s {}\nNumber of ports {}\nTargets all together {} ",
self.batch_size,
self.ips.len(),
ports.len(),
&ports.len(),
(self.ips.len() * ports.len()));
while let Some(result) = ftrs.next().await {
@@ -112,8 +112,8 @@ impl Scanner {
}
}
}
debug!("Typical socket connection errors {errors:?}");
debug!("Open Sockets found: {:?}", open_sockets);
debug!("Typical socket connection errors {:?}", errors);
debug!("Open Sockets found: {:?}", &open_sockets);
open_sockets
}
@@ -144,13 +144,16 @@ impl Scanner {
for nr_try in 1..=tries {
match self.connect(socket).await {
Ok(tcp_stream) => {
debug!("Connection was successful, shutting down stream {}", socket);
debug!(
"Connection was successful, shutting down stream {}",
&socket
);
if let Err(e) = tcp_stream.shutdown(Shutdown::Both) {
debug!("Shutdown stream error {}", e);
debug!("Shutdown stream error {}", &e);
}
self.fmt_ports(socket);
debug!("Return Ok after {nr_try} tries");
debug!("Return Ok after {} tries", nr_try);
return Ok(socket);
}
Err(e) => {
@@ -161,7 +164,7 @@ impl Scanner {
if nr_try == tries {
error_string.push(' ');
error_string.push_str(&socket.ip().to_string());
return Err(io::Error::other(error_string));
return Err(io::Error::new(io::ErrorKind::Other, error_string));
}
}
};
@@ -190,9 +193,10 @@ impl Scanner {
}
}
Err(io::Error::other(format!(
"UDP scan timed-out for all tries on socket {socket}"
)))
Err(io::Error::new(
io::ErrorKind::Other,
format!("UDP scan timed-out for all tries on socket {}", socket),
))
}
/// Performs the connection to the socket with timeout
@@ -271,7 +275,7 @@ impl Scanner {
match io::timeout(wait, udp_socket.recv(&mut buf)).await {
Ok(size) => {
debug!("Received {size} bytes");
debug!("Received {} bytes", size);
self.fmt_ports(socket);
Ok(true)
}
@@ -285,7 +289,7 @@ impl Scanner {
}
}
Err(e) => {
println!("Err E binding sock {e:?}");
println!("Err E binding sock {:?}", e);
Err(e)
}
}
@@ -302,3 +306,222 @@ impl Scanner {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::input::{PortRange, ScanOrder};
use async_std::task::block_on;
use std::{net::IpAddr, time::Duration};
#[test]
fn scanner_runs() {
// Makes sure the program still runs and doesn't panic
let addrs = vec!["127.0.0.1".parse::<IpAddr>().unwrap()];
let range = PortRange {
start: 1,
end: 1_000,
};
let strategy = PortStrategy::pick(&Some(range), None, ScanOrder::Random);
let scanner = Scanner::new(
&addrs,
10,
Duration::from_millis(100),
1,
true,
strategy,
true,
vec![9000],
false,
);
block_on(scanner.run());
// if the scan fails, it wouldn't be able to assert_eq! as it panicked!
assert_eq!(1, 1);
}
#[test]
fn ipv6_scanner_runs() {
// Makes sure the program still runs and doesn't panic
let addrs = vec!["::1".parse::<IpAddr>().unwrap()];
let range = PortRange {
start: 1,
end: 1_000,
};
let strategy = PortStrategy::pick(&Some(range), None, ScanOrder::Random);
let scanner = Scanner::new(
&addrs,
10,
Duration::from_millis(100),
1,
true,
strategy,
true,
vec![9000],
false,
);
block_on(scanner.run());
// if the scan fails, it wouldn't be able to assert_eq! as it panicked!
assert_eq!(1, 1);
}
#[test]
fn quad_zero_scanner_runs() {
let addrs = vec!["0.0.0.0".parse::<IpAddr>().unwrap()];
let range = PortRange {
start: 1,
end: 1_000,
};
let strategy = PortStrategy::pick(&Some(range), None, ScanOrder::Random);
let scanner = Scanner::new(
&addrs,
10,
Duration::from_millis(100),
1,
true,
strategy,
true,
vec![9000],
false,
);
block_on(scanner.run());
assert_eq!(1, 1);
}
#[test]
fn google_dns_runs() {
let addrs = vec!["8.8.8.8".parse::<IpAddr>().unwrap()];
let range = PortRange {
start: 400,
end: 445,
};
let strategy = PortStrategy::pick(&Some(range), None, ScanOrder::Random);
let scanner = Scanner::new(
&addrs,
10,
Duration::from_millis(100),
1,
true,
strategy,
true,
vec![9000],
false,
);
block_on(scanner.run());
assert_eq!(1, 1);
}
#[test]
fn infer_ulimit_lowering_no_panic() {
// Test behaviour on MacOS where ulimit is not automatically lowered
let addrs = vec!["8.8.8.8".parse::<IpAddr>().unwrap()];
// mac should have this automatically scaled down
let range = PortRange {
start: 400,
end: 600,
};
let strategy = PortStrategy::pick(&Some(range), None, ScanOrder::Random);
let scanner = Scanner::new(
&addrs,
10,
Duration::from_millis(100),
1,
true,
strategy,
true,
vec![9000],
false,
);
block_on(scanner.run());
assert_eq!(1, 1);
}
#[test]
fn udp_scan_runs() {
// Makes sure the program still runs and doesn't panic
let addrs = vec!["127.0.0.1".parse::<IpAddr>().unwrap()];
let range = PortRange {
start: 1,
end: 1_000,
};
let strategy = PortStrategy::pick(&Some(range), None, ScanOrder::Random);
let scanner = Scanner::new(
&addrs,
10,
Duration::from_millis(100),
1,
true,
strategy,
true,
vec![9000],
true,
);
block_on(scanner.run());
// if the scan fails, it wouldn't be able to assert_eq! as it panicked!
assert_eq!(1, 1);
}
#[test]
fn udp_ipv6_runs() {
// Makes sure the program still runs and doesn't panic
let addrs = vec!["::1".parse::<IpAddr>().unwrap()];
let range = PortRange {
start: 1,
end: 1_000,
};
let strategy = PortStrategy::pick(&Some(range), None, ScanOrder::Random);
let scanner = Scanner::new(
&addrs,
10,
Duration::from_millis(100),
1,
true,
strategy,
true,
vec![9000],
true,
);
block_on(scanner.run());
// if the scan fails, it wouldn't be able to assert_eq! as it panicked!
assert_eq!(1, 1);
}
#[test]
fn udp_quad_zero_scanner_runs() {
let addrs = vec!["0.0.0.0".parse::<IpAddr>().unwrap()];
let range = PortRange {
start: 1,
end: 1_000,
};
let strategy = PortStrategy::pick(&Some(range), None, ScanOrder::Random);
let scanner = Scanner::new(
&addrs,
10,
Duration::from_millis(100),
1,
true,
strategy,
true,
vec![9000],
true,
);
block_on(scanner.run());
assert_eq!(1, 1);
}
#[test]
fn udp_google_dns_runs() {
let addrs = vec!["8.8.8.8".parse::<IpAddr>().unwrap()];
let range = PortRange {
start: 100,
end: 150,
};
let strategy = PortStrategy::pick(&Some(range), None, ScanOrder::Random);
let scanner = Scanner::new(
&addrs,
10,
Duration::from_millis(100),
1,
true,
strategy,
true,
vec![9000],
true,
);
block_on(scanner.run());
assert_eq!(1, 1);
}
}
+81 -15
View File
@@ -109,7 +109,7 @@ pub fn init_scripts(scripts: &ScriptsRequired) -> Result<Vec<ScriptFile>> {
}
ScriptsRequired::Custom => {
let script_config = ScriptConfig::read_config()?;
debug!("Script config \n{script_config:?}");
debug!("Script config \n{:?}", script_config);
let script_dir_base = if let Some(config_directory) = &script_config.directory {
PathBuf::from(config_directory)
@@ -118,10 +118,10 @@ pub fn init_scripts(scripts: &ScriptsRequired) -> Result<Vec<ScriptFile>> {
};
let script_paths = find_scripts(script_dir_base)?;
debug!("Scripts paths \n{script_paths:?}");
debug!("Scripts paths \n{:?}", script_paths);
let parsed_scripts = parse_scripts(script_paths);
debug!("Scripts parsed \n{parsed_scripts:?}");
debug!("Scripts parsed \n{:?}", parsed_scripts);
// Only Scripts that contain all the tags found in ScriptConfig will be selected.
if let Some(config_hashset) = script_config.tags {
@@ -135,14 +135,14 @@ pub fn init_scripts(scripts: &ScriptsRequired) -> Result<Vec<ScriptFile>> {
} else {
debug!(
"\nScript tags does not match config tags {:?} {}",
script_hashset,
&script_hashset,
script.path.unwrap().display()
);
}
}
}
}
debug!("\nScript(s) to run {scripts_to_run:?}");
debug!("\nScript(s) to run {:?}", scripts_to_run);
}
}
@@ -152,7 +152,7 @@ pub fn init_scripts(scripts: &ScriptsRequired) -> Result<Vec<ScriptFile>> {
pub fn parse_scripts(scripts: Vec<PathBuf>) -> Vec<ScriptFile> {
let mut parsed_scripts: Vec<ScriptFile> = Vec::with_capacity(scripts.len());
for script in scripts {
debug!("Parsing script {}", script.display());
debug!("Parsing script {}", &script.display());
if let Some(script_file) = ScriptFile::new(script) {
parsed_scripts.push(script_file);
}
@@ -224,7 +224,7 @@ impl Script {
// Some variables get changed before read, and compiler throws warning on warn(unused_assignments)
#[allow(unused_assignments)]
pub fn run(self) -> Result<String> {
debug!("run self {:?}", self);
debug!("run self {:?}", &self);
let separator = self.ports_separator.unwrap_or_else(|| ",".into());
@@ -269,14 +269,14 @@ impl Script {
};
to_run = default_template.fill_with_struct(&exec_parts)?;
}
debug!("\nScript format to run {to_run}");
debug!("\nScript format to run {}", to_run);
execute_script(&to_run)
}
}
#[cfg(not(tarpaulin_include))]
fn execute_script(script: &str) -> Result<String> {
debug!("\nScript arguments {script}");
debug!("\nScript arguments {}", script);
let (cmd, arg) = if cfg!(unix) {
("sh", "-c")
@@ -314,7 +314,7 @@ fn execute_script(script: &str) -> Result<String> {
Ok(String::from_utf8_lossy(&output.stdout).into_owned())
}
Err(error) => {
debug!("Command error {error}",);
debug!("Command error {}", error.to_string());
Err(anyhow!(error.to_string()))
}
}
@@ -322,7 +322,7 @@ fn execute_script(script: &str) -> Result<String> {
pub fn find_scripts(path: PathBuf) -> Result<Vec<PathBuf>> {
if path.is_dir() {
debug!("Scripts folder found {}", path.display());
debug!("Scripts folder found {}", &path.display());
let mut files_vec: Vec<PathBuf> = Vec::new();
for entry in fs::read_dir(path)? {
let entry = entry?;
@@ -360,20 +360,20 @@ impl ScriptFile {
}
}
} else {
debug!("Failed to read file: {}", real_path.display());
debug!("Failed to read file: {}", &real_path.display());
return None;
}
debug!("ScriptFile {} lines\n{}", real_path.display(), lines_buf);
debug!("ScriptFile {} lines\n{}", &real_path.display(), &lines_buf);
match toml::from_str::<ScriptFile>(&lines_buf) {
Ok(mut parsed) => {
debug!("Parsed ScriptFile{} \n{:?}", real_path.display(), parsed);
debug!("Parsed ScriptFile{} \n{:?}", &real_path.display(), &parsed);
parsed.path = Some(real_path);
// parsed_scripts.push(parsed);
Some(parsed)
}
Err(e) => {
debug!("Failed to parse ScriptFile headers {e}");
debug!("Failed to parse ScriptFile headers {}", e.to_string());
None
}
}
@@ -406,6 +406,20 @@ impl ScriptConfig {
mod tests {
use super::*;
// Function for testing only, it inserts static values into ip and open_ports
// Doesn't use impl in case it's implemented in the super module at some point
fn into_script(script_f: ScriptFile) -> Script {
Script::build(
script_f.path,
"127.0.0.1".parse().unwrap(),
vec![80, 8080],
script_f.port,
script_f.ports_separator,
script_f.tags,
script_f.call_format,
)
}
#[test]
fn find_and_parse_scripts() {
let scripts = find_scripts("fixtures/.rustscan_scripts".into()).unwrap();
@@ -426,6 +440,26 @@ mod tests {
.unwrap();
}
#[test]
#[should_panic]
fn open_script_file_invalid_call_format() {
let mut script_f =
ScriptFile::new("fixtures/.rustscan_scripts/test_script.txt".into()).unwrap();
script_f.call_format = Some("qwertyuiop".to_string());
let script: Script = into_script(script_f);
let _output = script.run().unwrap();
}
#[test]
#[should_panic]
fn open_script_file_missing_call_format() {
let mut script_f =
ScriptFile::new("fixtures/.rustscan_scripts/test_script.txt".into()).unwrap();
script_f.call_format = None;
let script: Script = into_script(script_f);
let _output = script.run().unwrap();
}
#[test]
#[should_panic]
fn open_nonexisting_script_file() {
@@ -454,6 +488,38 @@ mod tests {
);
}
#[test]
#[cfg(unix)]
fn run_bash_script() {
let script_f = ScriptFile::new("fixtures/.rustscan_scripts/test_script.sh".into()).unwrap();
let script: Script = into_script(script_f);
let output = script.run().unwrap();
// output has a newline at the end by default, .trim() trims it
assert_eq!(output.trim(), "127.0.0.1 80,8080");
}
#[test]
fn run_python_script() {
let script_f = ScriptFile::new("fixtures/.rustscan_scripts/test_script.py".into()).unwrap();
let script: Script = into_script(script_f);
let output = script.run().unwrap();
// output has a newline at the end by default, .trim() trims it
assert_eq!(
output.trim(),
"Python script ran with arguments ['fixtures/.rustscan_scripts/test_script.py', '127.0.0.1', '80,8080']"
);
}
#[test]
#[cfg(unix)]
fn run_perl_script() {
let script_f = ScriptFile::new("fixtures/.rustscan_scripts/test_script.pl".into()).unwrap();
let script: Script = into_script(script_f);
let output = script.run().unwrap();
// output has a newline at the end by default, .trim() trims it
assert_eq!(output.trim(), "Total args passed to fixtures/.rustscan_scripts/test_script.pl : 2\nArg # 1 : 127.0.0.1\nArg # 2 : 80,8080");
}
#[test]
fn test_custom_directory_config() {
// Create test config
+1 -2
View File
@@ -43,7 +43,7 @@ macro_rules! output {
($name:expr) => {
println!(
"{} {}",
ansi_term::Colour::RGB(0, 255, 9).bold().paint("[>]"),
RGansi_term::Colour::RGB(0, 255, 9).bold().paint("[>]"),
$name
);
};
@@ -97,7 +97,6 @@ macro_rules! funny_opening {
"RustScan: Exploring the digital landscape, one IP at a time.",
"TreadStone was here 🚀",
"With RustScan, I scan ports so fast, even my firewall gets whiplash 💨",
"Scanning ports so fast, even the internet got a speeding ticket!",
];
let random_quote = quotes.choose(&mut rand::rng()).unwrap();
+163
View File
@@ -0,0 +1,163 @@
/*
* Test rustscan against different targets with a time limit.
* The tests assumes target/debug/rustscan has already been built.
*
* The tests are #[ignore] to avoid running them during normal development.
*
* Their tests in the timelimits module are run by travis during CI.
*/
use std::process::Command;
use std::time::Duration;
use wait_timeout::ChildExt;
const TIMEOUT_MARGIN: u32 = 3;
#[cfg(not(tarpaulin_include))]
fn run_rustscan_with_timeout(args: &[&str], timeout: Duration) {
println!("Running: target/debug/rustscan: {}", args.join(" "));
use std::time::Instant;
let start = Instant::now();
let mut child = Command::new("target/debug/rustscan")
.args(args)
.spawn()
.unwrap();
let mut tries = TIMEOUT_MARGIN;
loop {
match child.wait_timeout(timeout).unwrap() {
Some(_status) => break,
None => {
tries -= 1;
if tries == 0 {
// child hasn't exited yet
child.kill().unwrap();
child.wait().unwrap().code().unwrap();
panic!("Timeout while running command");
}
}
}
}
let end = Instant::now();
let duration = end.saturating_duration_since(start).as_secs_f32();
println!("time: {:1.1}s", duration);
}
mod timelimits {
#[test]
#[ignore]
fn scan_localhost() {
let timeout = super::Duration::from_secs(25);
super::run_rustscan_with_timeout(&["--greppable", "--no-nmap", "127.0.0.1"], timeout);
}
#[test]
#[ignore]
fn scan_google_com() {
super::run_rustscan_with_timeout(
&[
"--greppable",
"--no-nmap",
"-u",
"5000",
"-b",
"2500",
"google.com",
],
super::Duration::from_secs(28),
);
}
#[test]
#[ignore]
fn scan_example_com() {
super::run_rustscan_with_timeout(
&[
"--greppable",
"--no-nmap",
"-u",
"5000",
"-b",
"2500",
"example.com",
],
super::Duration::from_secs(28),
);
}
#[test]
#[ignore]
fn scan_rustscan_cmnatic_co_uk() {
super::run_rustscan_with_timeout(
&[
"--greppable",
"--no-nmap",
"-u",
"5000",
"-b",
"2500",
"rustscan.cmnatic.co.uk",
],
super::Duration::from_secs(26),
);
}
#[test]
#[ignore]
fn udp_scan_localhost() {
let timeout = super::Duration::from_secs(25);
super::run_rustscan_with_timeout(&["--greppable", "127.0.0.1", "--udp"], timeout);
}
#[test]
#[ignore]
fn udp_scan_google_com() {
super::run_rustscan_with_timeout(
&[
"--udp",
"--greppable",
"-u",
"5000",
"-b",
"2500",
"google.com",
],
super::Duration::from_secs(28),
);
}
#[test]
#[ignore]
fn udp_scan_example_com() {
super::run_rustscan_with_timeout(
&[
"--udp",
"--greppable",
"-u",
"5000",
"-b",
"2500",
"example.com",
],
super::Duration::from_secs(28),
);
}
#[test]
#[ignore]
fn udp_scan_rustscan_cmnatic_co_uk() {
super::run_rustscan_with_timeout(
&[
"--udp",
"--greppable",
"-u",
"5000",
"-b",
"2500",
"rustscan.cmnatic.co.uk",
],
super::Duration::from_secs(26),
);
}
}