Build a Network Port Scanner

Build a TCP port scanner in Python that discovers open ports and services.

Introduction

30 Second Summary

Every computer has thousands of virtual "ports" that let different programs communicate over a network. Knowing which ports are open on a machine is one of the first things security professionals check when assessing a system's exposure.

In this project, you will build a command-line port scanner in Python that discovers open ports on your own machine, identifies which services are running, and generates a professional security report.

What You'll Build

A Python script that scans TCP ports on localhost, resolves port numbers to human-readable service names, and outputs a formatted security report to a text file.

By the end of this project, you'll have:

  • A working port scanning function using Python's built-in socket module to test whether ports are open or closed.
  • Formatted terminal output showing scan progress, timing, and a summary of discovered services.
  • A security report file summarizing all findings with the target host, port range, timestamps, and statistics.
  • Secret Mission: Add banner grabbing to detect service versions running on open ports.

Are there any prerequisites?

None. This guide includes full setup instructions for Python, VS Code, and everything else you need. You will only scan your own machine (localhost), which is safe and legal.

Set Up Your Python Environment

Before you can build a port scanner, you need the right tools installed on your machine.

In this step, you'll install Python and VS Code, create a project folder, and verify everything works by running a simple test file.

In this step, get ready to:
  • Install and verify Python 3.12+ on your machine.
  • Set up VS Code and create your project folder.
  • Create and run your first Python file.
Install and verify Python

Python is the programming language you'll use to build the entire port scanner. You need version 3.12 or higher installed.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal and press Enter to open it.
  • Check your Python version by running this command:
python3 --version

✔️ I see version 3.12 or higher

Your Python installation is ready. Move on to the next substep below.

ⓧ I see an older version

Your machine has an older Python installed. macOS often ships with an outdated version by default. Upgrade through Homebrew by running this command:

brew install python
  • After installation completes, close and reopen your terminal.
  • Run python3 --version again to confirm you see version 3.12 or higher.

Still seeing the old version?

If python3 --version still shows an old version after reinstalling, your terminal may be using the system Python instead of the Homebrew version. Try running python3.12 --version or python3.14 --version to access the Homebrew-installed version directly.

help me fix my Python version on macOS after installing with Homebrew

ⓧ Command not found

Python isn't installed yet. You'll install it using Homebrew, a package manager for macOS.

  • First, check if Homebrew is installed by running:
brew --version

If you see a version number, skip to the Python install command below. If brew is also not found, install Homebrew first by running:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Follow the prompts in your terminal to complete the Homebrew installation. You may need to enter your computer password.

  • Once Homebrew is ready, install Python by running:
brew install python
  • Close and reopen your terminal.
  • Verify Python installed successfully by running:
python3 --version

You should now see a version number of 3.12 or higher.

Installation failing?

Make sure you have an active internet connection. If the Homebrew install script asks for your password, type your macOS login password (you won't see characters appear as you type, which is normal).

help me troubleshoot installing Python with Homebrew on macOS

Set up VS Code and create your project folder

VS Code is the code editor where you'll write your Python script. You also need a dedicated folder to keep your project files organized.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Visual Studio Code and press Enter to open it.

✔️ VS Code opened

VS Code is ready to go. Continue with the folder creation below.

ⓧ I don't have VS Code yet

  • Download VS Code from code.visualstudio.com/download.
  • Open the downloaded file and follow the installer instructions.
  • Once installed, open VS Code using the search bar as described above.

Now create your project folder. Switch back to your terminal (it should still be open from the Python step).

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir port-scanner
cd port-scanner
  • Confirm the folder exists by running:
ls ~/Desktop

You should see port-scanner listed in the output.

  • In VS Code, click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop, select the port-scanner folder, and click Open.

What should I see?

VS Code's Explorer sidebar on the left should show the folder name PORT-SCANNER at the top. The folder is empty for now.

Don't see the folder?

Make sure you ran mkdir port-scanner while inside the ~/Desktop directory. If you skipped the cd ~/Desktop step, the folder may have been created in a different location.

help me find where my port-scanner folder was created

Create and run your first Python file

With your project folder open in VS Code, you'll create a Python file and confirm that Python can run it successfully.

  • In the VS Code Explorer sidebar, hover over the PORT-SCANNER heading.
  • Click the New File icon (it looks like a page with a plus sign).
  • Type scanner.py as the filename and press Enter.

The file opens in the editor. This is where you'll write all your port scanner code throughout the project.

  • Add this code to scanner.py:
print("Hello, port scanner!")

What does this code do?

print() is a built-in Python function that outputs text to the terminal. This simple one-liner confirms that Python can find and execute your file correctly.

  • Save the file by pressing Cmd+S (macOS) or Ctrl+S (Windows).
  • Open the VS Code terminal by clicking Terminal in the top menu bar, then New Terminal.
  • Run your script by entering this command:
python3 scanner.py

You should see Hello, port scanner! printed in the terminal.

Seeing an error?

If you see No such file or directory, make sure your VS Code terminal is inside the port-scanner folder. Type pwd to check your current location. If you're not in the right folder, run cd ~/Desktop/port-scanner first.

If you see SyntaxError, double-check that the quotation marks and parentheses in your code match the example exactly.

help me fix an error when running my Python file

✔️ Awesome, I've got everything!

Great. Double check you've saved your file.

ⓧ I'd like to double check the full code

print("Hello, port scanner!")

Your environment is ready. Next up, you'll write your first port scanning function using Python's socket module.

Write Your First Port Scanning Function

Your project folder is open in VS Code and scanner.py is ready to go. Now it's time to write the core function that makes your scanner actually scan.

Port scanning works by attempting to open a TCP connection to a specific port on a machine. If the connection succeeds, the port is open and a service is listening there. If it fails, the port is closed.

In this step, get ready to:
  • Import Python's socket module and write a port scanning function.
  • Test the function by scanning port 80 on your own machine.
  • Handle connection errors gracefully with try/except.
Create the scan_port function

Python's built-in socket module lets you create network connections at the lowest level. You'll use it to attempt a TCP connection to each port and check whether it succeeds or not.

  • In VS Code, open scanner.py from the Explorer sidebar.
  • Delete the existing print("Hello, port scanner!") line.
  • Replace the entire file contents with the following code:
import socket


def scan_port(host, port, timeout=0.5):
    """Scan a single port on the target host.
    Returns True if the port is open, False otherwise."""
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(timeout)
        result = sock.connect_ex((host, port))
        sock.close()
        return result == 0
    except (socket.error, OSError):
        return False


if __name__ == "__main__":
    print(scan_port('127.0.0.1', 80))

What does this code do?

The import socket line loads Python's built-in networking module. This gives you access to low-level TCP/IP operations without any external dependencies.

  • socket.socket(socket.AF_INET, socket.SOCK_STREAM) creates a new TCP socket using IPv4 addressing.
  • sock.settimeout(timeout) limits how long the connection attempt waits (0.5 seconds by default) before giving up.
  • sock.connect_ex((host, port)) tries to connect and returns 0 if successful, or an error code if not. This is faster than connect() because it avoids raising exceptions on every closed port.
  • The try/except block catches unexpected network errors (like the network being unreachable) and returns False instead of crashing.
  • The if __name__ == "__main__" block at the bottom runs the function as a quick test when you execute the script directly.
  • Save scanner.py.
  • Open your terminal in VS Code by pressing Ctrl+` (backtick key, top-left of your keyboard).
  • Run the scanner by entering this command:
python3 scanner.py

You should see either True or False printed to the terminal. Most machines don't have a web server running on port 80 by default, so False is the expected result.

Seeing an error instead of True or False?

  • Check that you saved the file before running it. Unsaved changes won't execute.
  • Make sure you're running python3 scanner.py from inside your port-scanner folder. If you get a "No such file or directory" message, run cd ~/Desktop/port-scanner first.
  • Look for any red underlines in VS Code indicating a typo in the code. Python is indentation-sensitive, so make sure the try block lines are indented with spaces, not tabs.

Still stuck? Help me debug my scan_port function. I'm getting an error when I run python3 scanner.py.

Why connect_ex instead of connect?

The regular connect() method raises an exception every time a port is closed. Since most ports will be closed, that means thousands of exceptions per scan.

connect_ex() returns an error code instead, which is much faster when you're checking hundreds of ports in a row.

✔️ Awesome, I've got everything!

Great. Double check you've saved scanner.py and confirm the output shows True or False.

ⓧ I'd like to double check the full code

Here's the complete scanner.py as it should look at the end of this step:

import socket


def scan_port(host, port, timeout=0.5):
    """Scan a single port on the target host.
    Returns True if the port is open, False otherwise."""
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(timeout)
        result = sock.connect_ex((host, port))
        sock.close()
        return result == 0
    except (socket.error, OSError):
        return False


if __name__ == "__main__":
    print(scan_port('127.0.0.1', 80))

Your scanner can now check whether a single port is open or closed. Next up, you'll wrap this function in a loop to scan an entire range of ports at once.

Scan a Range of Ports with a Loop

Your scan_port() function can test a single port. That is useful for spot checks, but real security scanning means sweeping through hundreds of ports to find every open service on a machine.

In this step, you will write a new function that loops through ports 1 to 1024, calls scan_port() on each one, and collects the results. You will also add formatted output so you can watch the scan's progress in real time, plus timing so you know how long the full sweep takes.

In this step, get ready to:
  • Write a scan_ports() function that loops through a range of ports and collects open ones into a list.
  • Add formatted terminal output with f-strings showing scan progress and a summary.
  • Add timing with the time module to measure how long the scan takes.
Write a scan_ports() function

The scan_ports() function ties everything together. It prints a header banner, loops through each port in the range, calls your existing scan_port() function, and tracks which ports are open.

  • In scanner.py, find the line that says import socket at the very top. Add the following two imports directly below it:
import time
from datetime import datetime
  • Now scroll down past the scan_port() function (after its final return False line). Add the following new function below it:
def scan_ports(host, start_port=1, end_port=1024):
    """Scan a range of ports on the target host.
    Returns a list of dictionaries with port info."""
    print(f"\n{'='*50}")
    print(f"  Port Scanner - Scanning {host}")
    print(f"  Port range: {start_port}-{end_port}")
    print(f"  Started at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    print(f"{'='*50}\n")

    start_time = time.time()
    results = []

    for port in range(start_port, end_port + 1):
        if port % 100 == 0:
            print(f"  Scanning port {port}...")

        if scan_port(host, port):
            results.append(port)
            print(f"  [OPEN] Port {port}")

    end_time = time.time()
    duration = end_time - start_time

    print(f"\n{'='*50}")
    print(f"  Scan complete!")
    print(f"  Duration: {duration:.2f} seconds")
    print(f"  Open ports found: {len(results)}")
    print(f"{'='*50}\n")

    return results, duration

What does this function do?

The header banner uses f-strings to print a clear start message with the host, port range, and current timestamp. This tells you exactly what the scanner is doing.

The for loop iterates through every port in the range using range(start_port, end_port + 1). For each port, it calls scan_port() and appends open ports to the results list.

The progress indicator (if port % 100 == 0) prints a status message every 100 ports so you know the scan has not frozen. The timing code uses time.time() to record when the scan starts and ends, then calculates the total duration in seconds.

Update your main block to call scan_ports()

Your existing __main__ block still calls scan_port() on a single port. You need to replace it so it calls your new scan_ports() function across the full range.

  • At the bottom of scanner.py, find this existing code:
if __name__ == "__main__":
    print(scan_port('127.0.0.1', 80))
  • Replace the entire if __name__ block with this updated version:
if __name__ == "__main__":
    target_host = "127.0.0.1"
    start = 1
    end = 1024
    results, duration = scan_ports(target_host, start, end)

What changed here?

Instead of printing a single True/False result, the main block now defines the target host and port range as variables. It calls scan_ports() with those values and captures both the list of open ports and the scan duration.

  • Save scanner.py.
Run the port scanner

Your scanner is ready to sweep all 1024 well-known ports on your own machine. This will take anywhere from a few seconds to a couple of minutes depending on how many ports respond.

  • Run the scanner by entering this command in your terminal:
python3 scanner.py

You should see the header banner appear immediately, followed by progress messages every 100 ports. When the scan finishes, a summary shows the total duration and how many open ports were found.

What should I see?

Your output will start with a banner showing the target host and port range. As the scan runs, you will see lines like Scanning port 100... and [OPEN] Port 80 for any open ports. The exact open ports depend on what services are running on your machine.

The final summary prints the duration (e.g. Duration: 12.34 seconds) and the count of open ports found. If no ports are open, you will still see the complete banner and summary with 0 open ports.

Scan seems stuck or takes too long?

The scan checks 1024 ports with a 0.5 second timeout each. If many ports are unresponsive (neither accepting nor refusing connections), the scan can take several minutes. This is normal.

If you want a faster test while developing, temporarily change end = 1024 to end = 100 in your main block. Change it back to 1024 once you confirm the scanner works.

help me figure out why my port scanner is running slowly or not producing output

✔️ Awesome, I've got everything!

Great. Double check you have saved scanner.py before moving on.

ⓧ I'd like to double check the full code

Here is the complete scanner.py as it should look at the end of this step:

import socket
import time
from datetime import datetime


def scan_port(host, port, timeout=0.5):
    """Scan a single port on the target host.
    Returns True if the port is open, False otherwise."""
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(timeout)
        result = sock.connect_ex((host, port))
        sock.close()
        return result == 0
    except (socket.error, OSError):
        return False


def scan_ports(host, start_port=1, end_port=1024):
    """Scan a range of ports on the target host.
    Returns a list of dictionaries with port info."""
    print(f"\n{'='*50}")
    print(f"  Port Scanner - Scanning {host}")
    print(f"  Port range: {start_port}-{end_port}")
    print(f"  Started at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    print(f"{'='*50}\n")

    start_time = time.time()
    results = []

    for port in range(start_port, end_port + 1):
        if port % 100 == 0:
            print(f"  Scanning port {port}...")

        if scan_port(host, port):
            results.append(port)
            print(f"  [OPEN] Port {port}")

    end_time = time.time()
    duration = end_time - start_time

    print(f"\n{'='*50}")
    print(f"  Scan complete!")
    print(f"  Duration: {duration:.2f} seconds")
    print(f"  Open ports found: {len(results)}")
    print(f"{'='*50}\n")

    return results, duration


if __name__ == "__main__":
    target_host = "127.0.0.1"
    start = 1
    end = 1024
    results, duration = scan_ports(target_host, start, end)

Your scanner now sweeps through a full range of ports and reports what it finds. Next up, you will resolve those port numbers into human-readable service names so your results tell you exactly what is running on each open port.

Resolve Service Names and Display Results

Your scanner can find open ports, but a raw number like 443 does not tell you much at a glance. Every port number maps to a well-known service name in the system's services database.

In this step, you will translate port numbers into human-readable service names like "http" or "ssh". You will also upgrade your results from a flat list of numbers to structured dictionaries that carry richer information about each open port.

In this step, get ready to:
  • Write a helper function that resolves port numbers to service names.
  • Store scan results as dictionaries containing port, status, and service.
  • Display service names alongside open ports in your terminal output.
Write the service name lookup function

Your operating system maintains a database that maps port numbers to service names. Python's socket module can query this database directly.

  • In scanner.py, add the following function between your scan_port() function and your scan_ports() function:
def get_service_name(port):
    """Look up the service name for a given port number."""
    try:
        return socket.getservbyport(port, "tcp")
    except OSError:
        return "unknown"

What does this code do?

socket.getservbyport(port, "tcp") queries your system's services database (/etc/services on macOS) and returns the registered name for that port number.

Not every port has a registered name. When a port is unregistered, getservbyport() raises an OSError. The try/except block catches that error and returns "unknown" as a safe fallback.

  • Save scanner.py.

Seeing a syntax error?

Make sure the new function is placed after scan_port() ends and before scan_ports() begins. There should be two blank lines separating each function definition.

help me fix a syntax error in my port scanner after adding the get_service_name function

Update scan_ports() to store dictionaries with service names

Right now your scan_ports() function appends bare port numbers to the results list. A dictionary for each port lets you carry structured information that the report generator will use later.

  • In scanner.py, scroll down to the scan_ports() function and find these two lines inside the if scan_port(host, port): block:
            results.append(port)
            print(f"  [OPEN] Port {port}")
  • Replace those two lines with the following code:
            service = get_service_name(port)
            results.append({
                "port": port,
                "status": "open",
                "service": service
            })
            print(f"  [OPEN] Port {port} - {service}")

What does this code do?

get_service_name(port) calls your new helper to resolve the port number into a name like "http" or "ssh".

Instead of appending a bare number, you now append a dictionary with three keys: port, status, and service. This structured format makes it easy to generate a formatted report in the next step.

  • Save scanner.py.
  • Run your scanner by entering this command in your terminal:
python3 scanner.py

You should see open port lines now include a service name, for example: [OPEN] Port 443 - https.

Seeing a TypeError or AttributeError?

If you see TypeError, check that you replaced both the results.append(port) line and the print line together. Leaving the old append while updating only the print (or vice versa) causes a mismatch.

help me debug my scan_ports function after adding dictionary results and service names

✔️ Awesome, I've got everything!

Great. Make sure you have saved scanner.py before moving on.

ⓧ I'd like to double check the full code

Here is the full scanner.py file as it should look at the end of this step:

import socket
import time
from datetime import datetime


def scan_port(host, port, timeout=0.5):
    """Scan a single port on the target host.
    Returns True if the port is open, False otherwise."""
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(timeout)
        result = sock.connect_ex((host, port))
        sock.close()
        return result == 0
    except (socket.error, OSError):
        return False


def get_service_name(port):
    """Look up the service name for a given port number."""
    try:
        return socket.getservbyport(port, "tcp")
    except OSError:
        return "unknown"


def scan_ports(host, start_port=1, end_port=1024):
    """Scan a range of ports on the target host.
    Returns a list of dictionaries with port info."""
    print(f"\n{'='*50}")
    print(f"  Port Scanner - Scanning {host}")
    print(f"  Port range: {start_port}-{end_port}")
    print(f"  Started at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    print(f"{'='*50}\n")

    start_time = time.time()
    results = []

    for port in range(start_port, end_port + 1):
        if port % 100 == 0:
            print(f"  Scanning port {port}...")

        if scan_port(host, port):
            service = get_service_name(port)
            results.append({
                "port": port,
                "status": "open",
                "service": service
            })
            print(f"  [OPEN] Port {port} - {service}")

    end_time = time.time()
    duration = end_time - start_time

    print(f"\n{'='*50}")
    print(f"  Scan complete!")
    print(f"  Duration: {duration:.2f} seconds")
    print(f"  Open ports found: {len(results)}")
    print(f"{'='*50}\n")

    return results, duration


if __name__ == "__main__":
    target_host = "127.0.0.1"
    start = 1
    end = 1024
    results, duration = scan_ports(target_host, start, end)

Your scanner now translates raw port numbers into meaningful service names. Next up, you will write all of these results into a professional security report file.

Generate a Security Report File

Your scanner now identifies open ports and displays their service names in the terminal. Terminal output disappears when you close the window.

In this step, you'll write a function that saves all your scan findings to a permanent text file. This is how security professionals document their reconnaissance results.

In this step, get ready to:
  • Write a generate_report() function that creates a formatted security report.
  • Save scan results to a scan_report.txt file with headers, summaries, and findings.
  • Run the full scanner and verify the generated report file.
Write the report generation function

Python's built-in open() function lets you create and write to files. You'll use it to build a structured report with multiple sections.

  • In scanner.py, find the if __name__ == "__main__": block at the bottom of the file.
  • Add the following function directly above that block:
def generate_report(host, start_port, end_port, results, duration):
    """Generate a security report and save it to a file."""
    report_filename = "scan_report.txt"
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    total_scanned = end_port - start_port + 1
    open_count = len(results)
    closed_count = total_scanned - open_count

    with open(report_filename, "w") as report_file:
        report_file.write("=" * 50 + "\n")
        report_file.write("  NETWORK PORT SCAN REPORT\n")
        report_file.write("=" * 50 + "\n\n")

        report_file.write("SCAN DETAILS\n")
        report_file.write("-" * 30 + "\n")
        report_file.write(f"Target Host: {host}\n")
        report_file.write(f"Port Range: {start_port}-{end_port}\n")
        report_file.write(f"Scan Date: {timestamp}\n")
        report_file.write(f"Duration: {duration:.2f} seconds\n\n")

        report_file.write("SUMMARY\n")
        report_file.write("-" * 30 + "\n")
        report_file.write(f"Total Ports Scanned: {total_scanned}\n")
        report_file.write(f"Open Ports: {open_count}\n")
        report_file.write(f"Closed Ports: {closed_count}\n\n")

        report_file.write("FINDINGS\n")
        report_file.write("-" * 30 + "\n")

        if results:
            report_file.write(f"{'Port':<10}{'Status':<10}{'Service':<20}\n")
            report_file.write("-" * 40 + "\n")
            for entry in results:
                report_file.write(
                    f"{entry['port']:<10}{entry['status']:<10}{entry['service']:<20}\n"
                )
        else:
            report_file.write("No open ports found.\n")

        report_file.write("\n" + "=" * 50 + "\n")
        report_file.write("  End of Report\n")
        report_file.write("=" * 50 + "\n")

    print(f"Report saved to: {report_filename}")

What does this code do?

This function takes all the data from your scan and writes it to a structured text file with three sections:

  • report_filename, timestamp, and the count variables set up the metadata for the report header.
  • open(report_filename, "w") creates the file for writing. The with keyword automatically closes the file when the block finishes.
  • The SCAN DETAILS section records what was scanned and when.
  • The SUMMARY section calculates totals so a reader can see the overall picture at a glance.
  • The FINDINGS section uses f-string formatting with :<10 to left-align columns into a readable table. If no ports were open, it writes a fallback message instead.

What is file I/O?

File I/O (Input/Output) is how programs read from and write to files on disk. The "w" mode creates a new file or overwrites an existing one. Each time you run the scanner, you get a fresh report.

  • Save scanner.py.

Seeing a syntax error?

  • Check that the function is placed above the if __name__ == "__main__": block, not inside it.
  • Make sure the f-strings with curly braces {} are inside f"..." strings. Missing the f prefix means variables won't be substituted.
  • Verify the indentation inside the with open(...) block is consistent (4 spaces per level).

Help me fix a syntax error in my generate_report function

Call the report generator from your main block

The function exists but nothing calls it yet. You need to hook it into the main execution flow so it runs after the scan completes.

  • In scanner.py, scroll down to the if __name__ == "__main__": block at the bottom of the file.
  • Find this line:
    results, duration = scan_ports(target_host, start, end)
  • Add the following line directly below it:
    generate_report(target_host, start, end, results, duration)

What does this line do?

This passes all the scan data into your report function. It reuses the same target_host, start, end, results, and duration variables so the report accurately reflects what was scanned.

  • Save scanner.py.
Run the scanner and check your report

Time to run the complete scanner and verify it generates your security report file.

  • Run the scanner by executing this command in your terminal:
python3 scanner.py
  • Wait for the scan to complete. You should see the familiar scan output followed by a new line: Report saved to: scan_report.txt.
  • Confirm the report file was created by running:
ls
  • You should see scan_report.txt listed alongside scanner.py in the output.
  • Open the report to read its contents by running:
cat scan_report.txt
  • You should see a formatted report with a header, scan details, a summary of open and closed ports, and a findings table listing each discovered service.

Don't see scan_report.txt?

  • Make sure you added the generate_report() call inside the if __name__ == "__main__": block. If it's outside that block, it won't run.
  • Check that your terminal is in the port-scanner folder. The file is created in whatever directory you run the script from.
  • If you see an error about generate_report not being defined, make sure the function definition appears above the __main__ block in the file.

Help me debug why my report file isn't being generated

✔️ Awesome, I've got everything!

Great. Double check you've saved scanner.py and confirmed that scan_report.txt exists in your port-scanner folder.

ⓧ I'd like to double check the full code

Here's the complete scanner.py file for comparison:

import socket
import time
from datetime import datetime


def scan_port(host, port, timeout=0.5):
    """Scan a single port on the target host.
    Returns True if the port is open, False otherwise."""
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(timeout)
        result = sock.connect_ex((host, port))
        sock.close()
        return result == 0
    except (socket.error, OSError):
        return False


def get_service_name(port):
    """Look up the service name for a given port number."""
    try:
        return socket.getservbyport(port, "tcp")
    except OSError:
        return "unknown"


def scan_ports(host, start_port=1, end_port=1024):
    """Scan a range of ports on the target host.
    Returns a list of dictionaries with port info."""
    print(f"\n{'='*50}")
    print(f"  Port Scanner - Scanning {host}")
    print(f"  Port range: {start_port}-{end_port}")
    print(f"  Started at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    print(f"{'='*50}\n")

    start_time = time.time()
    results = []

    for port in range(start_port, end_port + 1):
        if port % 100 == 0:
            print(f"  Scanning port {port}...")

        if scan_port(host, port):
            service = get_service_name(port)
            results.append({
                "port": port,
                "status": "open",
                "service": service
            })
            print(f"  [OPEN] Port {port} - {service}")

    end_time = time.time()
    duration = end_time - start_time

    print(f"\n{'='*50}")
    print(f"  Scan complete!")
    print(f"  Duration: {duration:.2f} seconds")
    print(f"  Open ports found: {len(results)}")
    print(f"{'='*50}\n")

    return results, duration


def generate_report(host, start_port, end_port, results, duration):
    """Generate a security report and save it to a file."""
    report_filename = "scan_report.txt"
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    total_scanned = end_port - start_port + 1
    open_count = len(results)
    closed_count = total_scanned - open_count

    with open(report_filename, "w") as report_file:
        report_file.write("=" * 50 + "\n")
        report_file.write("  NETWORK PORT SCAN REPORT\n")
        report_file.write("=" * 50 + "\n\n")

        report_file.write("SCAN DETAILS\n")
        report_file.write("-" * 30 + "\n")
        report_file.write(f"Target Host: {host}\n")
        report_file.write(f"Port Range: {start_port}-{end_port}\n")
        report_file.write(f"Scan Date: {timestamp}\n")
        report_file.write(f"Duration: {duration:.2f} seconds\n\n")

        report_file.write("SUMMARY\n")
        report_file.write("-" * 30 + "\n")
        report_file.write(f"Total Ports Scanned: {total_scanned}\n")
        report_file.write(f"Open Ports: {open_count}\n")
        report_file.write(f"Closed Ports: {closed_count}\n\n")

        report_file.write("FINDINGS\n")
        report_file.write("-" * 30 + "\n")

        if results:
            report_file.write(f"{'Port':<10}{'Status':<10}{'Service':<20}\n")
            report_file.write("-" * 40 + "\n")
            for entry in results:
                report_file.write(
                    f"{entry['port']:<10}{entry['status']:<10}{entry['service']:<20}\n"
                )
        else:
            report_file.write("No open ports found.\n")

        report_file.write("\n" + "=" * 50 + "\n")
        report_file.write("  End of Report\n")
        report_file.write("=" * 50 + "\n")

    print(f"Report saved to: {report_filename}")


if __name__ == "__main__":
    target_host = "127.0.0.1"
    start = 1
    end = 1024

    results, duration = scan_ports(target_host, start, end)
    generate_report(target_host, start, end, results, duration)

Secret mission

Add Banner Grabbing

Your scanner knows which ports are open, but what software is actually running behind them? Banner grabbing connects to open ports and reads the service's greeting message, revealing version numbers and software names that security professionals use to identify vulnerabilities.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely on your local machine, so there are no ongoing costs. Decide whether to keep your files for future reference or delete them to free up disk space.

Resources you used:

  • The port-scanner project folder (containing scanner.py and scan_report.txt).
  • Python 3.12+ (installed via Homebrew).
  • VS Code.

Keep everything running

No action needed. Choose this if you want to keep building on your port scanner or reference the code later.

  • Your port-scanner folder stays on your Desktop.
  • Python and VS Code remain installed and available for other projects.

Pause - I'll come back to this later

Since this project is entirely local, there is nothing to pause. Your files sit on disk and consume no resources while idle.

  • Close VS Code to free up memory.
  • Your project folder and installed tools stay in place for when you return.

Delete - I don't want to use this again

Remove all project files and optionally uninstall the tools you set up.

  • Delete the project folder by running this command in your terminal:
rm -rf ~/Desktop/port-scanner
  • To uninstall Python (installed via Homebrew), run:
brew uninstall python
  • To uninstall VS Code on macOS, open Finder, go to Applications, right-click Visual Studio Code, and select Move to Trash.
  • To uninstall VS Code on Windows, open Add or Remove Programs, find Visual Studio Code in the list, and click Uninstall.

Nice Work!

Nice Work!

You just built a network port scanner from scratch using Python. This is the same foundational technique used by professional security tools like Nmap.

What you learned:

  • Built a TCP port scanning function using Python's socket module to discover open ports on a target machine.
  • Used loops, lists, and dictionaries to scan a range of ports, organize results, and resolve port numbers to human-readable service names.
  • Generated a formatted security report saved to a text file, summarizing all findings with timestamps, statistics, and detailed port information.
  • Secret Mission: Added banner grabbing to detect service versions running on open ports.

Ready to quiz yourself?