diff --git a/Cargo.toml b/Cargo.toml
index 0831030..8fde212 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "portnut"
-version = "0.1.0"
+version = "0.1.1"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
diff --git a/README.md b/README.md
index 01d1d63..b71ca71 100644
--- a/README.md
+++ b/README.md
@@ -34,16 +34,17 @@ Stressing 127.0.0.1 using 100 concurrent threads per port for 30s
See the help :
```
-1648 justine@portnut > target/release/portnut -h
+justine@portnut > target/release/portnut -h
A simple TCP port scanner / stresser. If stressing, you can pass a payload via a pipe
Usage: portnut [OPTIONS] --address
Options:
- -a, --address IP address to scan
+ -a, --address IP address or hostname to scan
-t, --timeout Timeout for each connection in seconds [default: 1]
-w, --wait Number of milliseconds to wait in between scans when scanning OR duration of stress when stress testing (in seconds) [default: 30]
-p, --ports ... Ports to stress / scan, separated by commas (22,80)
+ -r, --range Set this flag to treat the ports as a range rather than a list
-s, --stress Set this flag to stress the ports instead of scanning them
-c, --cthreads How many threads per port when stressing (Concurrent Threads) [default: 5]
-h, --help Print help
diff --git a/src/main.rs b/src/main.rs
index e867fd3..033d664 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,10 +1,11 @@
use clap::Parser;
use std::io::prelude::*;
use std::io::{self, Read};
-use std::net::{SocketAddr, TcpStream};
+use std::net::{SocketAddr, TcpStream, ToSocketAddrs};
use std::time::{Duration, Instant};
use std::thread;
use atty::Stream;
+use std::io::BufReader;
fn main() -> std::io::Result<()> {
@@ -12,7 +13,7 @@ fn main() -> std::io::Result<()> {
let address = args.address;
let timeout = Duration::from_secs(args.timeout);
let sleep = Duration::from_millis(args.wait);
- let ports = args.ports;
+ let ports = handle_ports(args.ports.clone(), &args.range);
let threads = args.cthreads;
//Reading the payload from stdin (if applicable)
@@ -34,17 +35,33 @@ fn main() -> std::io::Result<()> {
}
+//Returns a correct list of ports, according to range.
+//If range is true, we send a range of ports. Otherwise
+//we send the original back
+fn handle_ports(ports: Vec, range: &bool) -> Vec {
+ if *range {
+ if ports.iter().count() != 2 {
+ panic!("A range must be given only a start and an end value.");
+ }
+ let ret: Vec = (ports[0]..ports[1]).collect();
+ ret
+ } else {
+ ports
+ }
+}
+
+
+
///Scan the ports on address by attempting a tcp connection.
fn tcp_scan(address: String, timeout: Duration, sleep: Duration, ports: Vec) -> std::io::Result<()> {
println!("Scanning {}", address);
for i in ports {
- let socket = format!("{}:{}", &address, i);
- let socket = socket.parse::().unwrap();
+ let socket = resolve_add(&address.as_str(), &i);
match TcpStream::connect_timeout(&socket, timeout) {
- Ok(mut s) => {
+ Ok(mut _s) => {
println!("TCP/{} => ACCEPT", i);
- s.write(b"Some padding")?;
+ //s.write(b"Some padding")?;
}
Err(_) => {
println!("TCP/{} => REJECT", i);
@@ -58,8 +75,18 @@ fn tcp_scan(address: String, timeout: Duration, sleep: Duration, ports: Vec
Ok(())
}
-///Try to stress the remote machine by attempting to every given port at once and sending random
-///data
+///Takes either an hostname or an IP address and port, and resolve to a socket address.
+///DNS recordings can lead to multiple entries : we take the first one.
+fn resolve_add(host: &str, port: &u32) -> SocketAddr {
+ let details = format!("{}:{}", host, port);
+ let server: Vec<_> = details
+ .to_socket_addrs()
+ .expect("Can't resolve your address")
+ .collect();
+ return server[0];
+}
+
+///Try to stress the remote machine by attempting to every given port at once and sending payload
fn tcp_stress(payload: String, address: String, timeout: Duration, interval: Duration, ports: Vec, threads: u32) -> std::io::Result<()> {
let mut handles = vec![];
@@ -72,20 +99,33 @@ fn tcp_stress(payload: String, address: String, timeout: Duration, interval: Dur
let pay = payload.clone();
let int = interval * 1000;
let end_time = Instant::now() + int;
+ //pay.push_str("\r\n");
handles.push(thread::spawn( move || {
- let socket = format!("{}:{}", &add, &port);
- let socket = socket.parse::().unwrap();
+ let socket = resolve_add(&add.as_str(), &port);
loop {
//Check if it is time to stop
if Instant::now() >= end_time { break; }
match TcpStream::connect_timeout(&socket, timeout) {
Ok(mut s) => {
- s.write(pay.as_bytes()).unwrap();
+ //Remote port answers
+ //Writing
+ s.write_all(pay.as_bytes()).unwrap();
s.flush().unwrap();
- //s.read(&mut [0; 128]).unwrap();
+
+ //Reading
+ s.set_read_timeout(Some(Duration::from_millis(500))).unwrap();
+ let mut reader = BufReader::new(s.try_clone().unwrap());
+ let mut answer = String::new();
+ let count = reader.read_line(&mut answer).unwrap_or(0);
+ if count > 0 {
+ println!("Read {} bytes for port {} -> {}", count, &port, answer);
+ }
+
+ //Sleeping
thread::sleep(Duration::from_millis(10));
+
},
Err(_) => (),
}
@@ -107,7 +147,7 @@ fn tcp_stress(payload: String, address: String, timeout: Duration, interval: Dur
#[derive(Debug, Parser)]
#[command(author, version, about, long_about = None)]
struct Args {
- ///IP address to scan
+ ///IP address or hostname to scan
#[arg(short, long)]
address: String,
///Timeout for each connection in seconds
@@ -120,6 +160,9 @@ struct Args {
///Ports to stress / scan, separated by commas (22,80)
#[arg(short, long, value_parser, num_args=1.., value_delimiter=',')]
ports: Vec,
+ ///Set this flag to treat the ports as a range rather than a list
+ #[arg(long, short, action, default_value_t = false)]
+ range: bool,
///Set this flag to stress the ports instead of scanning them
#[arg(long, short, action, default_value_t = false)]
stress: bool,