Getty Images

How to build a Python port scanner

Python offers beginning coders a lot of flexibility and is a novel way to build tools designed to probe port performance across your network.

Python is powerful, flexible and popular, in part because it's an interpretive, scripted programming language that doesn't require code to be compiled into machine language. Instead, Python code runs through an interpreter to interact with your computer, making it relatively easy to learn and work with. Python is a great place to start with programming or when exploring options for building your own administrative tools.

One tool that can be easily built with Python is a port scanner. Follow along for steps on how to build one here. You need to install Python 3. Many Linux distributions include Python by default, as does macOS, and you can also add it to Windows. You might want to find a Python integrated development environment, too. I'm using a combination of Visual Studio Code and Vim on a Mac for this tutorial.

This article assumes some knowledge of Python and port scanners, tools that help identify potentially vulnerable open ports on remote systems.

You can write this simple Python program yourself. Another option is to pull the source code from another developer's project -- this is common. You can find many programming projects on repository sites, such as GitHub. I've provided the bare-bones code in the next section.

Regardless of your goal, creating an application in Python is rarely a waste of time.

So, why create a Python port scanner yourself rather than using an existing tool like Nmap? The main reason is to familiarize yourself with the language. Doing so also provides a little more insight into how a basic port scanner operates. You can also add unique functionalities or features to assemble a tool that works well for your specific purpose. Regardless of your goal, creating an application in Python is rarely a waste of time

Basic structure of the program

Let's use the following code sample for this article. I break down the sections to explain their purpose and suggest modifications you could make.

# PART ONE
# Import Python modules
import sys
import socket
from datetime import datetime

# PART TWO
# Define a target
if len(sys.argv) == 2:
    # Translate hostname to IPv4
    target = socket.gethostbyname(sys.argv[1])
else:
    print("Please add a target hostname or IP address")

# PART THREE
# Show scan info
print("=" * 45)
print("Scan Target: " + target)
print("Scanning started: " + str(datetime.now()))
print("=" * 45)

# PART FOUR
# Run the scan
try:
    # Scans specified ports. Adjustable but recommend the well-known ports of 1-1023)
    for port in range(1,1023):
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        socket.setdefaulttimeout(1)

        # Scan results
        result = s.connect_ex((target,port))
        if result ==0:
            print("Port number {} is open".format(port))
        s.close()

# PART FIVE
# Interrupt a scan
except KeyboardInterrupt:
        print("\n Scan halted by user")
        sys.exit()

Understand the Python code

I've used comments to divide the code into five sections for easier reference in the explanations below. Python comments are delineated using the # character.

  • # PART ONE: Importing modules.
  • # PART TWO: Defining a target.
  • # PART THREE: Show scan info.
  • # PART FOUR: Scan the target.
  • # PART FIVE: Exceptions.

# Part one: Importing modules

Python modules contain functionality, saved in a file, which can be imported into the Python interpreter. Modules permit Python script authors to call functionality each time the program runs. You can create your own modules or import ones made by other developers. Pre-created modules typically have functionality commonly used by many developers. Using these generic, readily available modules keeps developers from having to recreate modules repeatedly.

Take a look at # PART ONE. For this activity, import a module by writing the instructions in the Python file. Notice the lines at the top of the file with the import statement. These instruct the system to pull the specified modules for use. The sys module provides interpreter components. The socket module enables access to the system's network interface.

The line reading datetime import datetime is a bit different. It pulls the datetime class from the datetime module without importing the entire module. It's a way to pull a specific piece of functionality from a module. In this case, it pulls the datetime class to display the system's current date and time. Other datetime module classes include time zone information and time delta results. Our Python script doesn't need this functionality.

Screenshot of the first step in creating and using a port scanner with Python
# Part one: Importing modules

# Part two: Defining a target

The initial if else statement in # PART TWO defines the target you're scanning. Enter the target name or IP address at the command prompt as an argument when you run the script. The sys.argv entry accepts command-line arguments into the script.

Translating a hostname into an IPv4 address is handled by the line target = socket.gethostbyname(sys.argv[1]). This is a socket module function that resolves a hostname to an IP address. It accepts a hostname as an argument using sys.argv when running the script. You can also enter an IP address as the argument.

I suggest working with network devices by IP address rather than hostname. Doing so removes name resolution as a source of potential problems. Feel free to use example.com as the target of these scans.

Notice the script sets target as a variable at this point. Variables are stored values that can be called later in the code. If you skip down to the scan results section of the code, you see a reference to target, pointing back to this variable setting, which is the hostname you specified.

The else part of the statement prints a message if you didn't add a target. It reads, "Please add a target hostname or IP address," if you called the script without an argument.

Screenshot of the second step in creating and using a port scanner with Python
# Part two: Defining a target to scan
Screenshot of Python asking for a target hostname or IP address if one was not specified
# Part two: A message prompting for a target

# Part three: Show scan info

Port numbers represent application layer protocols. Humans prefer easy-to-remember names, while computers require a difficult-to-remember number.

Following is a few common ports.

Port number Protocol and description
22 SSH, remote administration
25 SMTP, email transfers
80 HTTP, web browsing
123 NTP, time synchronization
443 HTTPS, secure web browsing

Our program scans these port numbers. The results indicate which network services are running on a remote system. This information is valuable for security audits, penetration testing or other cybersecurity activities. It also lets you know the target system's role on the network. If ports 80 and 443 are open, it's probably a web server. If port 25 is open, it probably hosts email services.

There are 65,535 port numbers. The first 1,023 port numbers are associated with specific services, as seen in the table. The remaining are used for various network functions but do not generally equate to particular services. Scanning for these can be useful, but typically, you can limit your scans to the well-known ports.

Enter the ports you want to scan in the for port range (1,1023): statement. You can edit these to whatever you want. I selected the well-known ports previously discussed.

The # PART THREE section is optional but makes the script much more user-friendly. It uses print to display information about the current scan and when it began. The information is bracketed between === characters for clarity.

Screenshot of the Python port scanner code displaying port scan information
# Part three: Displaying scan information

# Part four: Scan the target

In # PART FOUR, Python uses try and except statements to allow programs to exit smoothly if there's a failure (exception).

The try statement tells Python what to do. Notice that the try statement includes formatting for expected results using print ("Port number {} is open".format(port)) to display open ports.

Screenshot of the Python port scanner running the scan
# Part four: Running the scan

# Part five: Exceptions

The except statement in # PART FIVE applies if the try fails. You can edit the related print statement to display whatever wording is useful to you.

Exceptions occur if the program fails or cannot conduct the scan -- for example, if you interrupt the scan while it's running by selecting Ctrl-C. The except entry at the bottom of the script shows this configuration with the phrase, "Scan halted by user."

Screenshot of the Python port scanner being interrupted manually
# Part five: In this exception, the scan being interrupted manually
Screenshot of the Python port scanner being interrupted manually
# Part five: A second example of the Python port scanner being interrupted manually

Run the scan

Run the scan by calling Python 3 and the code file, as well as specifying the IP address you want scanned. Here's an example.

$ python3 scan.py 10.0.0.1

The runtime varies depending on the number of ports specified.

Screenshot of the results of a Python port scan
A successful scan showing three open ports

The 10.0.0.1 device has ports 53 (DNS), 80 (HTTP) and 443 (HTTPS) open.

The 10.0.0.236 device has different ports open, as shown here:

Screenshot of the results of a Python port scan
A successful scan showing five open ports

Research the five open ports on the 10.0.0.236 device. Based on the scan results, can you guess what type of device this is?

Modifications

Following are three additional ideas to enhance your Python port scanner:

  1. Add a banner. Import the pyfiglet module to add an ASCII-based banner to the scanner. This adds a bit of personalization but doesn't provide direct functionality to the user.
  2. Adjust the timeout. Try adjusting the timeout for better performance. The original sample file includes a one-second timeout for the connection attempts using socket.setdefaulttimeout(1). This is probably much longer than necessary. Try editing the following line and rerunning your scan: socket.setdefaulttimeout(0.01). It should be much quicker.
  3. Write results to a file. You might want to save the results of your scan to a text file. There are several ways to accomplish this. You could use standard Bash redirectors -- > and >> -- or the tee command. Another alternative is adding a Python with open statement to your code.

Creating a Python port scanner is a great way to learn more about Python programming. It also exercises your knowledge of networking and security, helping you see how services are exposed on the network. You can construct plenty of other security tools using Python, among them network traffic analysis for security and performance, automation for consistency and availability, and Nmap interoperability with the python-nmap module. Another common use of Python is parsing and organizing data, including log files and security audit results.

Use this guide as a jumping-off point for future Python projects.

Damon Garn owns Cogspinner Coaction and provides freelance IT writing and editing services. He has written multiple CompTIA study guides, including the Linux+, Cloud Essentials+ and Server+ guides, and contributes extensively to TechTarget Editorial, The New Stack and CompTIA Blogs.

Dig Deeper on Network security