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.