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)
Learn Cybersecurity

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.

port scanning rust async burst scanning ids tuning

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.
  • 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 &
Validation: `curl -I http://127.0.0.1:8100/` returns `200 OK`.

Step 2) Create the Rust project

Click to view commands
cargo new rust-portscan
cd rust-portscan
Validation: `ls` shows `Cargo.toml` and `src/main.rs`.

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"
Validation: `cargo check` should pass after adding the code.

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
- Low-and-slow (stealthier):
Click to view commands
cargo run --release -- --host 127.0.0.1 --ports 22,80,443,8100,8101,9000 --concurrency 5 --delay-ms 150
Expected: Ports 8100 and 8101 show as OPEN; others likely closed.

Validation: Output includes OPEN 8100 and OPEN 8101. If not, ensure the Python servers are running and timeouts are ≥800 ms.

Common fixes:

  • connection refused on all ports: check target host/port list.
  • Slow runs: reduce concurrency only 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
Validation: `lsof -i :8100` and `:8101` should show no listeners.

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.

Similar Topics

FAQs

Can I use these labs in production?

No—treat them as educational. Adapt, review, and security-test before any production use.

How should I follow the lessons?

Start from the Learn page order or use Previous/Next on each lesson; both flow consistently.

What if I lack test data or infra?

Use synthetic data and local/lab environments. Never target networks or data you don't own or have written permission to test.

Can I share these materials?

Yes, with attribution and respecting any licensing for referenced tools or datasets.