Asif Iqbal_
← Projects

Featured

Port Sniffer

A fast, multi-threaded TCP port scanner CLI written in Rust, with domain resolution, port ranges, and live progress reporting.

Stack
RustClapNative Threads
Topics
RustSystems ProgrammingCLI ToolsNetworking

The problem

Scanning a host for open ports sequentially is slow — checking all 65535 ports one at a time, each with its own connection timeout, can take minutes. A useful scanner needs to parallelize the work, handle unresolvable domains and unreachable hosts gracefully, and give the user visibility into progress without hiding the final result behind a wall of noise.

Approach

Port Sniffer splits the port space across a configurable pool of native OS threads. Each thread scans a disjoint slice of the port list — worker i scans ports at indices i, i + n, i + 2n, ... where n is the thread count — so no coordination or work-stealing is needed once the list is built.

  • CLI parsing via clap, supporting a hostname/IP target, thread count, timeout, and port ranges (single ports, whitespace-separated lists, or dash ranges)
  • Domain resolution through dns-lookup, falling back cleanly when a domain doesn't resolve
  • Deduplication of the requested port set using a HashSet before scanning starts
  • Concurrent scanning with TcpStream::connect_timeout per port, results streamed back over an mpsc channel
  • Live progress via indicatif, showing scanned/open/closed counts as threads report in
  • Graceful interruption — a Ctrl+C handler drains whatever results have arrived and prints them before exiting

Key features

Multi-threaded scanning — Ports are distributed evenly across threads so scan time scales down roughly linearly with thread count, bounded by the connection timeout.

Flexible port selection — Scan everything by default, or target specific ports and ranges with -p, e.g. -p 22 80 8000-9000.

Bare output mode--bare strips formatting to newline-separated port numbers, making results easy to pipe into other tools.

Ctrl+C handling — Interrupting a long scan still prints whatever open ports were found rather than losing the results.

What was interesting

Index-striped work distribution. Rather than a work queue, each thread owns a fixed stride through the port list. It's a simple scheme with no locking on the read side, and it naturally balances load since ports are shuffled into a HashSet before scanning (so no thread gets stuck scanning a contiguous "quiet" range).

Signal handling with shared state. The Ctrl+C handler runs on its own execution context but needs to read results that worker threads are still writing. Using Arc<Mutex<Receiver>> lets both the handler and the main thread drain the channel safely without a redesign of the scanning loop.

Progress reporting without slowing down scanning. The progress bar updates on every port scanned, which could become a bottleneck at high thread counts. Keeping the bar update on the same thread that already holds the scan result (rather than a separate reporting thread) avoids adding synchronization overhead just for UI.

Architecture

src/
├── main.rs          # CLI entry point, thread orchestration, Ctrl+C handling, output
├── models.rs         # IpOrDomain, PortRange, LogLevel — parsing and validation types
├── constants.rs       # Default thread count, timeout, and port bounds
└── lib.rs            # Crate root, re-exports

Domain-specific parsing (IP/domain resolution, port range syntax) lives in dedicated types with their own clap value parsers, keeping main.rs focused on orchestration.

Result

A single-binary CLI, installable via cargo install, that scans all 65535 ports on a host in a fraction of the time a sequential scanner would take, with clear progress feedback and results that pipe cleanly into other tools.