Building a Port Scanner with Python

Many cybersecurity incidents can be traced to open ports that should definitely not be open. How would someone know for example if they had port 23 (Telnet) open? This is one of many ports that should always be closed as a best practice. A port scanner is a tool that can be used to check all available ports and determine whether or not they are open. The following Python script is a custom port scanner that can be run from the command line and it will scan a range of ports on a given IP address. It will even validate the IP address to ensure that it was entered correctly.

#!/bin/python3

import sys
import socket
from datetime import datetime

# add some input validation for the ip address argument
def validate_ip_address(ip_address):
	try:
		socket.inet_aton(sys.argv[1])
		return True
	except socket.error:
		return False

# determine if there is an argument provided		
if len(sys.argv) == 2:
	ip_address = sys.argv[1]
else:
	print("Invalid amount of arguments.")
	print("Syntax: python3 scanner.py <ip>")
	exit()

# determine if ip address is valid and define target
if validate_ip_address(ip_address):
	target = ip_address
else:
	print("Not a valid IP address")
	exit()
	
#add a banner
print("-" * 50)
print("Scanning target " + target)
print("Time started: "+str(datetime.now()))
print("-" * 50)

try:
	for port in range(50,85):
		s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		socket.setdefaulttimeout(1)
		result = s.connect_ex((target,port))
		if result == 0:
			print(f"Port {port} is open.")
		s.close()

except KeyboardInterrupt:
	print("\nExiting program.")
	sys.exit()
	
except socket.error:
	print("Could not connect to the server.")
	sys.exit()

I will explain the basic functioning of each block of code to help you understand how it works.

import sys
import socket
from datetime import datetime

With this block of code the necessary modules are being imported. The sys module will allow us to pass arguments to the script from the command line.

The socket module is imported and allows us to connect two nodes together. The script in total is going to search for open ports on an IP address and allow us to connect to those ports if they are open using the various methods from the socket module.

The datetime class is being imported from the datetime module and will allow us to print to screen the exact date and time that our script is being run.

def validate_ip_address(ip_address):
	try:
		socket.inet_aton(sys.argv[1])
		return True
	except socket.error:
		return False

This block of code defines the function validate_ip_address and will determine if the IP address provided is in fact a valid IP address. The IP address is passed into the function as an argument and then it will try to determine if this IP address is valid by using the function inet_aton from the socket module. It does this by attempting to convert the IP address to a 32-bit packed binary format. If it is successful then it will return ‘True’. If it is not successful then the function will return an error, but with the ‘except’ statement being used it will return ‘False’ instead.

if len(sys.argv) == 2:
	ip_address = sys.argv[1]
else:
	print("Invalid amount of arguments.")
	print("Syntax: python3 scanner.py <ip>")
	exit()

This block of code will determine if the proper number of arguments have been provided by the user. With sys.argv arguments can be provided from the command line and the first argument will be the script itself. The second argument comes after the script, and this is where the IP address needs to be set. If there are two arguments provided, the variable ip_address will be set to the second argument. If only one argument is provided, or more than two arguments are provided, the error messages will be printed to the screen and the program will exit.

if validate_ip_address(ip_address):
	target = ip_address
else:
	print("Not a valid IP address")
	exit()

This block of code takes the variable ip_address that was set in the previous block and validates whether it is a valid IP address using the function validate_ip_address which was defined earlier. If the function returns ‘True’ then the variable target will be set to the user provided IP address. If the function returns ‘False’ then the error will be printed to the screen and the program will exit.

print("-" * 50)
print("Scanning target " + target)
print("Time started: "+str(datetime.now()))
print("-" * 50)

This block of code will add a nice format to display on the screen stating what IP is being scanned and at what time using the datetime.now function.

try:
	for port in range(50,85):
		s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		socket.setdefaulttimeout(1)
		result = s.connect_ex((target,port))
		if result == 0:
			print(f"Port {port} is open.")
		s.close()

This block of code is going to use the try command to check if the following code works, and if not, there will be exceptions coming subsequently to handle any errors that might arise. The for loop is going to loop through whatever ports are set in the range. This example will check ports 50 through 85.

For each iteration of the for loop the variable s is being set to gather the IPv4 address with the AF_INET function and also gather the port that the current iteration is attempting to connect to with the SOCK_STREAM function.

The setdefaulttimeout function is set to 1 so that if there is no response back within one second it will drop whatever port it is currently scanning and move on so that it doesn’t take too long to run the entire script.

The variable result is being set by using the connect_ex function which is an error indicator. If the port for the given IP is open the error indicator will return 0, and if it is closed it will return 1.

The if statement will print out the give string notifying that the port is open based on the value of the result variable. If the port is not open it will close out that socket connection and move on to the next iteration in the for loop.

except KeyboardInterrupt:
	print("\nExiting program.")
	sys.exit()
	
except socket.error:
	print("Could not connect to the server.")
	sys.exit()

A couple exceptions are required in case any errors occur during the for loop in the try statement.

If the user types ctrl-c during the loop it will exit the loop and print the given string and exit the script.

If no connection can be established to the provided IP address a socket error will occur and the given string will be printed and then the script will exit.

Testing the script with the IP 1.1.1.1 shows that it is working properly and it returns the open ports for the given range provided.