This script is a foundation for network reconnaissance and vulnerability assessment.
- What problem does it solve? It efficiently determines which TCP ports are open on one or more target hosts. Crucially, it goes beyond a simple open/closed check by attempting to retrieve a service banner (the initial data a service sends upon connection) and then performs a basic analysis (fingerprinting) to identify the application running on that port (e.g., HTTP, SSH, MySQL).
- Who benefits from using it?
- Ethical Hackers / Penetration Testers: For quickly enumerating open services on a target to narrow down potential attack vectors.
- System Administrators: For auditing their own network security posture, ensuring only intended ports are accessible.
- Beginner Developers / Cybersecurity Students: The script is heavily commented and uses
asyncio, providing a practical, high-performance example of concurrent network programming.
The script executes a concurrent TCP port scan across a defined list of hosts and ports, reporting open ports, the grabbed banner, and the likely service running.
- Input:
- Targets (
--hostsor--hosts-file): A list of IP addresses or domain names. - Ports (
--ports): A list or range of TCP ports to check (e.g.,22,80,443,1000-1024). - Parameters: Scan configuration (e.g.,
--concurrency,--timeout).
- Targets (
- Process:
- It uses
asyncioto launch many connection attempts simultaneously, limited by the--concurrencysetting. - For each open port, it attempts a basic banner grab. If no banner is immediately received, it attempts simple probes (e.g., an HTTP
HEADrequest or a newline\r\n) to provoke a response. - The collected banner is analyzed using pre-defined regular expression (regex) rules to identify the service (e.g., finding "SSH-2.0" identifies it as SSH). If no banner is available, it falls back to a standard port-to-service mapping (e.g., port 22 is SSH).
- It uses
- Output / Side Effects:
- Standard Output (stdout): A human-readable, single-line summary for every open port found.
- Optional JSON File (
--output): A comprehensive JSON file containing the full structured results for all ports scanned (open and closed).
| Library | Purpose |
|---|---|
argparse |
Used to parse command-line arguments (e.g., --hosts, --ports) for configuring the scan. |
asyncio |
The core library for concurrent and asynchronous I/O operations, enabling the scanner to handle hundreds of connections simultaneously without waiting for one to finish before starting the next. |
json |
Used to format and output the scan results into a structured JSON file. |
logging |
Provides flexible event logging (info/debug messages) to help the user monitor the scanner's internal operations. |
re |
Regular Expressions are used for the fingerprinting logic to match patterns within the raw service banners. |
datetime |
Used to timestamp the scan results. |
| Function/Method | Purpose | Parameters & Returns |
|---|---|---|
parse_port_list |
Parses a string like "22,80,8000-8010" into a sorted list of integer port numbers. |
Params: port_string (str). Returns: List[int]. |
_fingerprint |
Analyzes a raw banner and/or port number to determine the likely service(s) running. It prioritizes regex matches on the banner. | Params: port (int), banner (bytes). Returns: List[str] of service names. |
AsyncPortScanner.__init__ |
Initializes the scanner with concurrency limits, timeout, and banner read size. | Params: timeout, concurrency, read_bytes. Returns: None (constructor). |
AsyncPortScanner._grab_banner |
Attempts to read data from a socket stream within the defined timeout. | Params: reader (asyncio.StreamReader). Returns: bytes (the raw banner). |
AsyncPortScanner.scan_port |
The core asynchronous scan logic. Attempts to connect, grabs the banner (with optional probes), closes the connection, and structures the result. Handles all connection errors and timeouts. | Params: host (str), port (int). Returns: Dict[str, Any] (structured result for one port). |
AsyncPortScanner.scan_multiple |
Creates a list of scan_port tasks for all combinations of hosts and ports, runs them concurrently using asyncio.gather, and collects all results. |
Params: hosts (List[str]), ports (List[int]). Returns: List[Dict[str, Any]]. |
cli |
The command-line interface entry point. Parses CLI arguments, sets up the logger, executes the scan by calling asyncio.run(_run()), and prints/saves the final results. |
Params: argv (Optional[List[str]]). Returns: int (exit code). |
The script's execution flow is managed by the standard Python entry point:
if __name__ == "__main__":- It calls
SystemExit(cli()), which immediately transfers control to thecli()function.
- It calls
cli()Function Execution:- Argument Parsing:
argparseprocesses the command-line inputs (hosts, ports, settings). - Host/Port Collection: Host lists are generated from the provided arguments (
--hostsor--hosts-file). Ports are parsed usingparse_port_list. - Scanner Instantiation: An instance of
AsyncPortScanneris created, passing the configuredtimeout,concurrency, andread_bytessettings. - Asynchronous Kickoff: The
_runinner asynchronous function is defined, which simply calls the core scanning logic:scanner.scan_multiple(hosts, ports). - Blocking Start: The line
results = asyncio.run(_run())is the pivotal step. It starts theasyncioevent loop and waits for the entire scan (_run) to complete before continuing. - Result Reporting: Once the scan is complete, the
cli()function iterates over theresultslist. It prints a summary to the console for every port where"open": Truewas found. - Output File: If the
--outputargument was provided, the completeresultslist is serialized and saved as a JSON file. - Exit: The function returns
0, signaling a successful exit to the operating system.
- Argument Parsing:
This diagram illustrates the main flow of a scan operation, from user input to final output.
sequenceDiagram
participant User
participant CLI as async-port-scanner CLI (cli)
participant ASC as AsyncPortScanner
participant Hosts as Target Host(s)
User->>CLI: Execute script with args (--hosts, --ports)
CLI->>CLI: Parse arguments and load hosts/ports
CLI->>ASC: new AsyncPortScanner(concurrency, timeout, read_bytes)
CLI->>CLI: Define _run() (calls scan_multiple)
CLI->>CLI: asyncio.run(_run()) (Starts Event Loop)
loop for each Host and Port combination
ASC->>ASC: Acquire Semaphore (limit concurrency)
ASC->>Hosts: asyncio.open_connection(host, port)
alt Connection Successful (Port Open)
Hosts-->>ASC: Return (reader, writer)
ASC->>ASC: Call _grab_banner(reader)
ASC->>Hosts: Attempt initial read/probes (e.g., HEAD /)
Hosts-->>ASC: Return banner data
ASC->>ASC: _fingerprint(port, banner)
ASC->>ASC: Close writer/connection
ASC->>CLI: Return structured result (open: true, banner, services)
else Connection Refused / Timeout
ASC->>CLI: Return structured result (open: false, error)
end
ASC->>ASC: Release Semaphore
end
CLI->>CLI: Process results (print open ports to stdout)
alt Output File Requested
CLI->>CLI: Write all results to --output JSON file
end
CLI-->>User: Display output and Exit (0)
