Learn in Public unlocks on Jan 1, 2026
This lesson will be public then. Admins can unlock early with a password.
Modern Port Scanning Techniques in 2026 (Rust Edition)
Understand async, burst, and low-noise port scanning in Rust—and how to tune defenses against each pattern.
Modern port scanning is fast and stealthy. Build a safe, local Rust scanner that supports burst and low-and-slow modes, validate it, and learn what defenders watch for.
What You’ll Build
- Two local open ports for testing (Python HTTP servers).
- A Rust TCP connect scanner with concurrency, jitter, and burst/slow modes.
- Validation steps and defensive signals to monitor.
Prerequisites
- macOS or Linux with Rust 1.80+.
- Python 3.10+.
- Stay on localhost; do not scan external hosts without written approval.
Safety and Legal
- Only scan assets you own or are authorized to test.
- Keep concurrency low; stop on IDS/IPS complaints.
- Tag your UA/traffic when moving beyond localhost.
Step 1) Start safe test targets
Click to view commands
python3 -m http.server 8100 > /tmp/http-8100.log 2>&1 &
python3 -m http.server 8101 > /tmp/http-8101.log 2>&1 &
Step 2) Create the Rust project
Click to view commands
cargo new rust-portscan
cd rust-portscan
Step 3) Add dependencies
Replace Cargo.toml with:
Click to view toml code
[package]
name = "rust-portscan"
version = "0.1.0"
edition = "2021"
[dependencies]
tokio = { version = "1.40", features = ["full"] }
clap = { version = "4.5", features = ["derive"] }
futures = "0.3"
rand = "0.8"
anyhow = "1.0"
Step 4) Implement burst + low-and-slow scanning
Replace src/main.rs with:
Click to view Rust code
use clap::Parser;
use futures::stream::{self, StreamExt};
use rand::{seq::SliceRandom, thread_rng};
use std::net::SocketAddr;
use std::time::Duration;
use tokio::net::TcpStream;
use tokio::time::{sleep, timeout};
#[derive(Parser, Debug)]
#[command(author, version, about)]
struct Args {
/// Host to scan (e.g., 127.0.0.1)
#[arg(long)]
host: String,
/// Ports to scan, comma-separated
#[arg(long, default_value = "22,80,443,8100,8101,9000")]
ports: String,
/// Max concurrent sockets
#[arg(long, default_value_t = 50)]
concurrency: usize,
/// Delay between tasks in ms (low-and-slow)
#[arg(long, default_value_t = 0)]
delay_ms: u64,
/// Per-connection timeout ms
#[arg(long, default_value_t = 800)]
timeout_ms: u64,
/// Shuffle ports to reduce patterning
#[arg(long, default_value_t = true)]
shuffle: bool,
}
async fn is_open(host: &str, port: u16, timeout_ms: u64) -> bool {
let addr: SocketAddr = format!("{host}:{port}").parse().unwrap();
timeout(Duration::from_millis(timeout_ms), TcpStream::connect(addr))
.await
.is_ok()
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let args = Args::parse();
let mut ports: Vec<u16> = args
.ports
.split(',')
.filter_map(|p| p.trim().parse().ok())
.collect();
if args.shuffle {
ports.shuffle(&mut thread_rng());
}
let results: Vec<(u16, bool)> = stream::iter(ports)
.map(|port| {
let host = args.host.clone();
async move {
let open = is_open(&host, port, args.timeout_ms).await;
if args.delay_ms > 0 {
sleep(Duration::from_millis(args.delay_ms)).await;
}
(port, open)
}
})
.buffer_unordered(args.concurrency)
.collect()
.await;
for (port, open) in results {
if open {
println!("OPEN {}", port);
}
}
Ok(())
}
Step 5) Run burst vs low-and-slow
- Burst (faster, noisier):
Click to view commands
cargo run --release -- --host 127.0.0.1 --ports 22,80,443,8100,8101,9000 --concurrency 100 --delay-ms 0
Click to view commands
cargo run --release -- --host 127.0.0.1 --ports 22,80,443,8100,8101,9000 --concurrency 5 --delay-ms 150
Validation: Output includes OPEN 8100 and OPEN 8101. If not, ensure the Python servers are running and timeouts are ≥800 ms.
Common fixes:
connection refusedon all ports: check target host/port list.- Slow runs: reduce
concurrencyonly after confirming the network is healthy.
Defender signals to monitor
- Fan-out: count distinct ports touched per source in short windows.
- Timing: bursts with zero delay are easy to spot; random jitter reduces signature but still shows elevated fan-out.
- Honeypot ports: add canary ports to detect scans quickly.
Cleanup
Click to view commands
cd ..
pkill -f "http.server 8100" || true
pkill -f "http.server 8101" || true
rm -rf rust-portscan
Quick Reference
- Burst for speed, low-and-slow for stealth—both must stay in authorized scope.
- Shuffle ports and cap concurrency; add jitter to avoid easy pattern detection.
- Defenders watch port fan-out and timing more than raw volume.