From 30 Seconds to 2 Seconds: Building a Concurrent TCP Port Scanner in Python
I have been wanting to dive hands-on into cybersecurity for a while, and after some research, I thought the best first project was to build a basic TCP port scanner in python.
Using python's inbuilt socket library, I had a working prototype ready.
The Benchmark
The next obvious step was to benchmark my project. Testing it on localhost(127.0.0.1) would tend to be extremely fast, and so I decided to give a try to scanme.nmap.org, a dedicated site provided by the Nmap team, a target generously provided specifically for testing scanners safely and legally. (but please don't spam it!)
The version 0.01 of my script took ~30s (!!) to scan only a few hundred ports. It performed a full TCP Connect handshake on a single port, waited for a response or timeout, and only then moved on to the next. Sitting idle while waiting for network timeouts was killing performance, which was very slow and so in a attempt to improve it, I decided to check out how concurrency would work in python.
Concurrency
Using concurrent.futures I was able to whip up a quick refactor (v0.02) of my script, so that there were (by default) 100 workers, each would handle a port, and it was all concurrent.
# main concurrency logic
with ThreadPoolExecutor(max_workers=max_workers) as executor:
results = executor.map(lambda p: scan_port(target_host, p), ports_to_scan)
This is the main part of the rewrite. Instead of idling while waiting for a single port to time out, the script kept all 100 threads busy, bringing the scan time down from half a minute to under two seconds.
| Metric | Sequential (v0.01) | Multithreaded (v0.02) |
|---|---|---|
| Execution Time | ~30.0s | <2.0s |
| Concurrency Strategy | Single-threaded | 100 Worker Threads |
| Relative Speedup | Baseline (1x) | ~15x Faster |
Key Takeaways & Trade-offs
The speed improvement was massive. That said, using full TCP connect scans with basic threading isn't without its drawbacks. Some drawbacks were:
- Resource Usage: Spinning up hundreds of OS threads creates overhead and doesn't scale infinitely.
- Network Noise: Standard TCP Connect scans are "loud." Firewalls and intrusion detection systems (IDS) will easily flag this traffic.
- Rate-Limiting: High-concurrency scans can easily get throttled by basic network defenses or overwhelm local sockets.
Next Plans
This current version is a great start, but there is still plenty of room to improve both performance and stealth. Here is what I am planning to tackle in the next few iteration:
Stealthier Scans: Swapping full TCP connections for raw SYN (half-open) packet crafting using scapy to reduce network noise.
Banner Grabbing: Probing open ports to read service headers and automatically identify running software (like SSH, HTTP, or FTP).
asyncioRefactor: Benchmarking Python's single-threaded asyncio event loop against ThreadPoolExecutor to see which handles high concurrency better.CLI & Export Tools: Adding
argparsesupport for custom port ranges and outputting scan results directly to JSON.
Source Code & Repository
You can check out the full source code, test it out, or follow along with future updates over on GitHub: