Building a Custom Port Scanner with Python: Step-by-Step
This guide walks through building a simple, effective TCP port scanner in Python. It’s intended for educational use (network troubleshooting, asset discovery on networks you own or have permission to test). Do not scan networks without authorization.
What you’ll build
A command-line Python script that:
- Accepts a target IP or hostname
- Scans a range of TCP ports (configurable)
- Returns open, closed, and filtered status (basic)
- Runs scans concurrently for speed
Requirements
- Python 3.8+
- Modules: socket, argparse, concurrent.futures, datetime (All are in the standard library; no external packages required.)
Step 1 — Script outline
Create a file named scan.py and structure it with:
- argument parsing
- a worker function to test one port
- a concurrent executor to run many workers
- result aggregation and simple reporting
Step 2 — Argument parsing
Use argparse to accept:
- target (positional): IP or hostname
- –start (optional, default 1): start port
- –end (optional, default 1024): end port
- –timeout (optional, default 1.0): socket timeout in seconds
- –workers (optional, default 100): concurrency level
Example code:
python
#!/usr/bin/env python3 import argparse def parse_args(): p = argparse.ArgumentParser(description=“Simple TCP port scanner”) p.add_argument(“target”, help=“IP address or hostname to scan”) p.add_argument(”–start”, type=int, default=1, help=“Start port (default 1)”) p.add_argument(”–end”, type=int, default=1024, help=“End port (default 1024)”) p.add_argument(”–timeout”, type=float, default=1.0, help=“Socket timeout seconds”) p.add_argument(”–workers”, type=int, default=100, help=“Concurrent workers”) return p.parse_args()
Step 3 — Port test worker
Use socket.createconnection to attempt a TCP connect. Return a simple status string.
python
import socket def scan_port(target, port, timeout): try: with socket.createconnection((target, port), timeout=timeout): return port, “open” except socket.timeout: return port, “filtered” except ConnectionRefusedError: return port, “closed” except Exception: return port, “filtered”
Notes:
- ConnectionRefused usually means closed.
- Timeout or other network errors can indicate filtered/unreachable.
- This is a basic heuristic — advanced scanners use TCP/IP stack tricks.
Step 4 — Concurrency
Use ThreadPoolExecutor for I/O-bound tasks to scan many ports quickly.
python
from concurrent.futures import ThreadPoolExecutor, as_completed def run_scan(target, start, end, timeout, workers): results = [] with ThreadPoolExecutor(max_workers=workers) as exe: futures = {exe.submit(scan_port, target, port, timeout): port for port in range(start, end + 1)} for fut in ascompleted(futures): results.append(fut.result()) return sorted(results, key=lambda x: x[0])
Step 5 — DNS resolution and validation
Resolve hostname to IP and validate port range before scanning.
python
import socket import sys def resolvetarget(target): try: return socket.gethostbyname(target) except socket.gaierror as e: print(f”DNS resolution failed: {e}“) sys.exit(1)
Step 6 — Reporting
Print a concise summary including elapsed time and each open port.
python
from datetime import datetime def report(results, target, ip, start, end, elapsed): open_ports = [p for p, s in results if s == “open”] print(f”Scan of {target} ({ip}) ports {start}-{end} completed in {elapsed:.2f}s”) if open_ports: print(“Open ports:”) for p in openports: print(f” - {p}“) else: print(“No open ports found.”)
Step 7 — Main function
Put it together and run.
python
def main(): args = parse_args() ip = resolve_target(args.target) start, end = args.start, args.end if start < 1 or end > 65535 or start > end: print(“Invalid port range.”) return t0 = datetime.now() results = run_scan(ip, start, end, args.timeout, args.workers) elapsed = (datetime.now() - t0).total_seconds() report(results, args.target, ip, start, end, elapsed) if name == “main”: main()
Step 8 — Usage examples
- Scan common ports on example.com: python3 scan.py example.com –start 1 –end 1024
- Faster scan with higher concurrency and shorter timeout: python3 scan.py 192.0.2.10 –start 1 –end 5000 –workers 200 –timeout 0.5
Security & legal reminders
- Only scan systems you own or have explicit permission to test.
- Frequent or broad scans can trigger intrusion detection systems and may violate policies or laws.
Next steps / improvements
- Add UDP scanning (requires raw sockets or external libs).
- Implement SYN scan using raw sockets (needs root and more complex handling).
- Parse service banners for identified open ports.
- Output in formats (CSV, JSON) for integration with asset inventories.
This script provides a practical, extensible foundation for building more advanced scanning tools.
Leave a Reply