Skip to content

Commit c05abc6

Browse files
committed
feat: Implement multi-threaded TCP port scanner with command-line interface
1 parent 284d505 commit c05abc6

3 files changed

Lines changed: 415 additions & 0 deletions

File tree

__init__.py

Whitespace-only changes.

main.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import argparse
2+
import threading
3+
4+
# Import the PortScanner class from the scanner module
5+
from scanner import PortScanner
6+
7+
8+
def main():
9+
"""
10+
Main function to run the port scanner application.
11+
It handles command-line argument parsing, initializes the scanner,
12+
and starts the scanning process.
13+
"""
14+
15+
# 1. Argument Parsing Setup
16+
# --------------------------------------------------------------------------
17+
# Create an ArgumentParser object. This object will hold all the information
18+
# needed to parse the command line into Python data types.
19+
# The 'description' argument provides a brief help message shown when
20+
# you runs the script with -h or --help.
21+
parser = argparse.ArgumentParser(description="A multi-threaded TCP Port Scanner Tool")
22+
23+
# Add arguments that the script will accept from the command line.
24+
25+
# --target / -t: Specifies the host (IP address or hostname) to scan.
26+
# 'type=str' ensures the input is treated as a string.
27+
# 'default="localhost"' sets a default value if the argument is not provided.
28+
# 'help' provides a description for the argument in the help message.
29+
parser.add_argument(
30+
"-t", "--target", type=str, default="localhost",
31+
help="Target host to scan (e.g., 'example.com' or '192.168.1.1'). Defaults to 'localhost'."
32+
)
33+
34+
# --start-port / -sp: Defines the starting port for the scan range.
35+
# 'type=int' ensures the input is an integer.
36+
parser.add_argument(
37+
"-sp", "--start-port", type=int, default=1,
38+
help="Starting port for the scan range. Defaults to 1."
39+
)
40+
41+
# --end-port / -ep: Defines the ending port for the scan range.
42+
# 'type=int' ensures the input is an integer.
43+
parser.add_argument(
44+
"-ep", "--end-port", type=int, default=1024,
45+
help="Ending port for the scan range (inclusive). Defaults to 1024."
46+
)
47+
48+
# --max-connections / -mc: Limits the number of concurrent connections (threads).
49+
# This prevents overwhelming the target or the scanning machine.
50+
# 'type=int' for an integer value.
51+
parser.add_argument(
52+
"-mc", "--max-connections", type=int, default=100,
53+
help="The maximum number of concurrent connections (threads) to use during scanning. Defaults to 100."
54+
)
55+
56+
# --verbose / -v: Enables verbose output, showing status for all ports.
57+
# 'action='store_true'' means this argument is a boolean flag. If it's present
58+
# on the command line, args.verbose will be True; otherwise, False.
59+
parser.add_argument(
60+
"-v", "--verbose", action='store_true',
61+
help="Enable verbose output. Prints the status of every port (open, closed, or filtered) as it's scanned."
62+
)
63+
64+
# --output / -o: Specifies an output file for scan results.
65+
# 'type=str': Expects a string value (the filename).
66+
# 'nargs='?'': This is crucial. It means the argument is optional.
67+
# - If `-o filename.txt` is given, args.output will be "filename.txt".
68+
# - If just `-o` is given (without a filename), `const` value is used.
69+
# 'const='auto_generate'': The value assigned to `args.output` if `-o` is present
70+
# but no value is provided (e.g., `python main.py -o`).
71+
# 'default=None': The value assigned to `args.output` if `-o` is not present at all.
72+
parser.add_argument(
73+
"-o", "--output", type=str, nargs='?', const='auto_generate', default=None,
74+
help="Specify an output file to save results (e.g., results.txt). If just '-o' is used without a filename, a timestamped file will be created automatically in a 'port-scanner_results' directory."
75+
)
76+
77+
# Parse the arguments provided by you from the command line.
78+
args = parser.parse_args()
79+
80+
# 2. PortScanner Initialization
81+
# --------------------------------------------------------------------------
82+
# Create an instance of the PortScanner class.
83+
# Pass the parsed target host, verbose flag, and output file path to its constructor.
84+
# The PortScanner will resolve the hostname to an IP address during its initialization.
85+
scanner = PortScanner(args.target, args.verbose, args.output)
86+
87+
# Check if the target host was successfully resolved to an IP address.
88+
# The resolve_host method in PortScanner returns an empty string if resolution fails.
89+
if not scanner.target_ip:
90+
print("[!] Exiting: Target host could not be resolved. Please check the hostname or IP address.")
91+
return # Exit the script if the target is invalid.
92+
93+
# 3. Configure Scanner Parameters
94+
# --------------------------------------------------------------------------
95+
# Set the port range and maximum concurrent connections based on the parsed arguments.
96+
# These properties are set directly on the scanner instance.
97+
scanner.start_port = args.start_port
98+
scanner.end_port = args.end_port
99+
scanner.max_connections = args.max_connections
100+
101+
# Crucial: Re-initialize the semaphore in the scanner.
102+
# The semaphore is responsible for limiting concurrent threads.
103+
# It must be re-initialized here because 'max_connections' might have been
104+
# changed by the command-line argument, overriding its default value set in __init__.
105+
scanner.scan_semaphore = threading.Semaphore(scanner.max_connections)
106+
107+
# 4. Start the Scan
108+
# --------------------------------------------------------------------------
109+
# Call the scan_range method to begin the port scanning process.
110+
# This method orchestrates the creation of threads and manages the scan.
111+
scanner.scan_range()
112+
113+
114+
# 5. Script Entry Point
115+
# --------------------------------------------------------------------------
116+
# This block ensures that the main() function is called only when the script
117+
# is executed directly (not when it's imported as a module into another script).
118+
if __name__ == "__main__":
119+
main()

0 commit comments

Comments
 (0)