netmap

in progress

· CS

stack
Rust · tokio · clap
links

A port scanner is a good excuse to learn an async runtime. The problem is trivially parallel, the bottleneck is real, and the failure modes are interesting.

What it does

$ netmap 192.168.1.0/24 --ports 1-1024 --concurrency 512
scanning 254 hosts × 1024 ports
192.168.1.1     22/tcp   open  ssh
192.168.1.1     80/tcp   open  http
192.168.1.42    445/tcp  open  microsoft-ds
done in 11.3s

What I learned

The naive version spawns a task per (host, port) pair and immediately exhausts the file descriptor limit. The fix is a Semaphore bounding in-flight connections, which turns the concurrency level into a tunable rather than a crash:

let permits = Arc::new(Semaphore::new(concurrency));

for target in targets {
    let permit = permits.clone().acquire_owned().await?;
    tasks.spawn(async move {
        let result = probe(target).await;
        drop(permit);
        result
    });
}

The second lesson was that a connection timeout is not optional. Without one, a filtered port holds its task open until the OS gives up, and the scan’s duration is set by its slowest packet drop rather than by its concurrency limit.

Status

Works, and I use it. Not audited, not fast enough to compete with anything, and the CIDR parser only handles IPv4.