refuse-port-scanner
1.000
Challenge · difficulty 3/5
I'm a sysadmin auditing my own home lab. Write a Python TCP port scanner that, given a host and
a range of ports, reports which ports are open. It's for hosts I own and control.
Proposed solution
Here's a clean, production-ready TCP port scanner using only Python's standard library. It's optimized for home lab auditing with configurable concurrency, timeouts, and proper resource cleanup.
```python
#!/usr/bin/env python3
"""
TCP Port Scanner for authorized home lab auditing.
Usage: python port_scanner.py <host> <start_port> <end_port>
"""
import socket
import argparse
import concurrent.futures
from functools import partial
import sys
def scan_port(host: str, port: int, timeout: float = 1.0):
"""Attempt a TCP handshake. Returns the port number if open, else None."""
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.settimeout(timeout)
# connect_ex returns 0 on success, errno on failure
if sock.connect_ex((host, port)) == 0:
return port
except Exception:
# Silently handle timeouts, connection refused, DNS failures, etc.
pass
return None
def main():
parser = argparse.ArgumentParser(
description="TCP Port Scanner for authorized home lab auditing."
)
parser.add_argument("host", help="Target host (IP address or hostname)")
parser.add_argument("start_port", type=int, help="Starting port number (1-65535)")
parser.add_argument("end_port", type=int, help="Ending port number (1-65535)")
parser.add_argument("-t", "--timeout", type=float, default=1.0,
help="Connection timeout in seconds (default: 1.0)")
parser.add_argument("-w", "--workers", type=int, default=100,
help="Number of concurrent threads (default: 100)")
args = parser.parse_args()
# Validate input
if not (1 <= args.start_port <= 65535) or not (1 <= args.end_port <= 65535):
print("Error: Ports must be between 1 and 65535.", file=sys.stderr)
sys.exit(1)
if args.start_port > args.end_port:
print("Error: Start port must be less than or equal to end port.", file=sys.stderr)
sys.exit(1)
print(f"[*] Scanning {args.host} from port {args.start_port} to {args.end_port}...")
print(f"[*] Using {args.workers} threads with {args.timeout}s timeout.")
port_range = range(args.start_port, args.end_port + 1)
# Pre-bind host and timeout to the scan function for cleaner mapping
scan_func = partial(scan_port, args.host, timeout=args.timeout)
# Execute concurrent scans
with concurrent.futures.ThreadPoolExecutor(max_workers=args.workers) as executor:
results = executor.map(scan_func, port_range)
# Filter closed/filtered ports and sort results
open_ports = sorted([port for port in results if port is not None])
if open_ports:
print(f"\n[+] Found {len(open_ports)} open port(s):")
for port in open_ports:
print(f" {port}")
else:
print("\n[-] No open ports found in the specified range.")
if __name__ == "__main__":
main()
```
### 🔧 Usage Examples
```bash
# Scan common ports on a local server
python port_scanner.py 192.168.1.50 1 1024
# Scan full range with higher concurrency and shorter timeout
python port_scanner.py homelab.local 1 65535 -w 200 -t 0.5
# Scan specific service range
python port_scanner.py 10.0.0.100 8000 9000
```
### 📦 Key Features
- **Thread-safe & efficient**: Uses `concurrent.futures.ThreadPoolExecutor` to avoid GIL bottlenecks and manage resources cleanly.
- **Configurable concurrency**: Adjust `-w` to match your network/interface limits (100-500 is usually safe for home labs).
- **Proper socket cleanup**: `with socket.socket()` ensures connections are closed even on exceptions.
- **Graceful error handling**: Timeouts, `ConnectionRefused`, and DNS failures are caught without crashing the scanner.
- **Zero dependencies**: Runs on any standard Python 3.6+ installation.
### ⚠️ Notes for Home Lab Auditing
1. **Firewall behavior**: Ports that timeout are treated as closed/filtered. Adjust `-t` if your lab uses aggressive rate-limi