The Ultimate Guide to Port Scanning: From Zero to Hero with Nmap and Python

The CyberSec Guru

The Ultimate Guide to Port Scanning From Zero to Hero with Nmap and Python

If you like this post, then please share it:

Buy me A Coffee!

Support The CyberSec Guru’s Mission

🔐 Fuel the cybersecurity crusade by buying me a coffee! Why your support matters: Zero paywalls: Keep the main content 100% free for learners worldwide.

“Your coffee keeps the servers running and the knowledge flowing in our fight against cybercrime.”☕ Support My Work

Buy Me a Coffee Button

Welcome.

You’ve landed on the most comprehensive guide to port scanning on the internet. This isn’t just a quick tutorial. This is the ultimate guide.

By the time you finish this article, you will go from an absolute beginner with zero networking knowledge to someone who can not only use the most powerful network scanning tool in the world (Nmap) but also build your own custom port scanners from scratch using Python.

We’re going to pull back the curtain on a topic that forms the very foundation of ethical hacking, penetration testing, and network defense. This is the #1 skill every security professional must know, and we’re covering all of it. No external guides needed. Everything is right here.

Let’s get started.

The Absolute Foundations (Networking 101 You Can’t Skip)

Before you can scan a port, you need to understand what a port even is. And to understand that, you need to know what an IP address is. And to understand that… you get the idea.

Most guides skip this. We’re not. Let’s build your foundation from the ground up.

What is a Network? (The 5-Sec Version)

Imagine a network as a giant, digital city. In this city, you have homes (computers, servers, phones) and roads (cables, Wi-Fi) connecting them. When your computer wants to visit google.com, it sends a tiny car (called a “packet”) out onto the roads to Google’s “home” to fetch the website.

This system of sending and receiving packets is the core of all networking.

What is an IP Address? (Your Digital Street Address)

An IP (Internet Protocol) Address is the unique street address for every single home in the digital city. It’s how your “packet” knows exactly where to go.

📬 Stay Ahead of Cyber Threats

Get the latest cybersecurity news, critical vulnerabilities, threat intelligence, tutorials, and exclusive giveaways delivered straight to your inbox. No spam. Unsubscribe anytime.

Subscribe to the Newsletter →
IP Address
IP Address

You’ll see two types:

  1. IPv4 (e.g., 172.217.14.228): The classic, most common type. It’s a 32-bit number, which gives us about 4.3 billion unique addresses. (We’re running out!)
  2. IPv6 (e.g., 2001:0db8:85a3:0000:0000:8a2e:0370:7334): The new, much longer version. It’s 128-bit, which provides an astronomical number of addresses (340 undecillion—that’s 340 with 36 zeroes).

Key Takeaway: Every device connected to a network (like the internet or your home Wi-Fi) has an IP address. It’s the “who” and “where.”

What is a Port? (The Digital Apartment Door)

This is the most important concept you will learn today.

If an IP address is the street address of a giant apartment building (a server), a port is the specific apartment number.

A single server (one IP address) can run many different services at the same time. It might be a web server, an email server, and a file server all at once. How does your computer know which service it wants to talk to?

It uses a port number.

  • You want to visit the website? Your computer sends its packet to Port 443 (for secure HTTPS web traffic).
  • You want to send an email? Your email client connects to Port 25 (for SMTP).
  • You want to log in securely? You connect to Port 22 (for SSH).

A computer has 65,535 available ports (numbered 0 to 65535). They are split into categories:

  • Well-Known Ports (0-1023): These are reserved for standard, common services. You’ll memorize these.
    • Port 21: FTP (File Transfer Protocol)
    • Port 22: SSH (Secure Shell) – For secure remote login.
    • Port 25: SMTP (Simple Mail Transfer Protocol) – For sending email.
    • Port 53: DNS (Domain Name System) – For translating google.com into an IP address.
    • Port 80: HTTP (Hypertext Transfer Protocol) – The insecure web.
    • Port 443: HTTPS (HTTP Secure) – The secure web.
  • Registered Ports (1024-49151): Used for specific applications (e.g., databases like MySQL on 3306, or Microsoft SQL on 1433).
  • Dynamic/Private Ports (49152-65535): Your computer grabs one of these temporary ports for its side of the conversation.

Key Takeaway: A port scanner’s job is to go to one IP address and metaphorically knock on all 65,535 “apartment doors” to see which ones are open.

Network Ports
Network Ports

The Two Giants: TCP vs. UDP

Your computer “knocks” on these doors using one of two main protocols. Understanding the difference is critical to understanding scanning.

1. TCP (Transmission Control Protocol)

Think of TCP as a reliable, registered phone call.

When you make a phone call, you (1) dial the number, (2) someone picks up and says “Hello?” (3) you say “Hi, is this Bob?” and (4) they say “Yes!” Now you have a confirmed, two-way connection and can start talking.

TCP does this digitally with a Three-Way Handshake. This is vital. Memorize it.

  1. SYN: Your computer sends a packet with a “SYN” (Synchronize) flag to the server’s port (e.g., port 443). This is you dialing and saying “Hello, are you there?”
  2. SYN-ACK: If the port is open, the server replies with a “SYN-ACK” (Synchronize-Acknowledge) packet. This is the server picking up and saying “Yes, I’m here and I hear you!”
  3. ACK: Your computer sends a final “ACK” (Acknowledge) packet back. This is you saying, “Great! I’ll start sending the data.”
TCP Three-Way Handshake
TCP Three-Way Handshake

The connection is now established. It’s reliable, and data is guaranteed to arrive in order. This is used for web browsing, email, and anything where data integrity is crucial.

2. UDP (User Datagram Protocol)

Think of UDP as a postcard.

When you send a postcard, you (1) write the address, (2) put it in the mailbox, and… that’s it. You just hope it gets there. You get no confirmation, no error message if it’s lost, and if you send 10 postcards, they might arrive out of order.

UDP is “connectionless.” You just fire packets and forget. It’s fast, but unreliable. This is used for things where speed matters more than perfect accuracy, like online gaming, video streaming, or DNS lookups.

Key Takeaway: Scanning TCP ports is easy—you just look for the SYN-ACK response. Scanning UDP ports is hard—you send a packet and usually get nothing back, making it difficult to know if the port is open or just ignoring you.

The Three Port States: Open, Closed, and Filtered

A port scanner will report one of three (or four) states. This is the “result” of your scan.

  1. OPEN: The best-case scenario! The server responded with a SYN-ACK (for TCP) or a service-specific response (for UDP). This means a service is actively listening on this port, and you can connect to it. A web server is open on port 443.
  2. CLOSED: The server’s operating system responded, but not with a “yes.” For TCP, it sends back a RST (Reset) packet. This is an abrupt “Connection Refused.” It’s like knocking, and someone immediately yelling “Go away!” from behind the locked door. This is useful information! It confirms the computer (the IP) is on and reachable, but that specific port (the “door”) is not in use.
  3. FILTERED: The most frustrating state. You send your SYN packet… and get nothing. No SYN-ACK. No RST. Just silence.This doesn’t mean the computer is off. It almost always means a firewall is in the way. The firewall sees your “knock,” doesn’t like the look of you, and just drops your packet on the floor. It doesn’t even bother to send a “Go away.” This silence tells you that you’re being actively blocked.
Network Port States Visualized
Network Port States Visualized

Sometimes, Nmap will report open|filtered. This is common in UDP scanning, where (as we learned) “no response” is the norm. Nmap can’t tell if the port is open (and just ignoring the UDP packet) or filtered (and the firewall is dropping it).

Now, you have the complete theoretical knowledge. Let’s get our hands dirty.

The “Easy Button” – Become an Nmap God

You don’t need to reinvent the wheel. The single most powerful, famous, and essential port scanner has already been built. It’s called Nmap (Network Mapper).

Nmap is the “Swiss Army Knife” of network security. It’s been featured in movies like The Matrix, and it’s the first tool any real-world pentester runs. Learning it is not optional.

How to Install Nmap on Any OS

This is easy. Nmap is free and open-source.

  • On Linux (Debian/Ubuntu):sudo apt update sudo apt install nmap
  • On Linux (Red Hat/Fedora):sudo dnf install nmap
  • On macOS (using Homebrew):brew install nmap
  • On Windows: Go to the official Nmap download page (nmap.org/download.html) and grab the “Zenmap” installer. Zenmap is Nmap with a nice graphical user interface (GUI), and the installer will also add the command-line nmap tool, which is what we’ll be using.

To check if it’s installed, open a terminal (or cmd.exe on Windows) and type:

nmap -v

If you see a version number, you’re ready.

Your First Scan: The “Loud” TCP Connect Scan (-sT)

Let’s start with the most basic scan. This scan performs the full TCP Three-Way Handshake we just learned about. It’s “loud” because the target server’s logs will clearly show a full connection was made from your IP. It’s not stealthy, but it’s reliable and doesn’t require special permissions.

The command is -sT.

Let’s scan a special domain Nmap provides for testing: scanme.nmap.org.

nmap -sT scanme.nmap.org

Wait a few seconds, and you’ll get a result like this:

Nmap Sample Scan
Nmap Sample Scan
Starting Nmap 7.92 ( [https://nmap.org](https://nmap.org) )
Nmap scan report for scanme.nmap.org (45.33.32.156)
Host is up (0.16s latency).
Not shown: 995 closed ports
PORT      STATE  SERVICE
22/tcp    open   ssh
80/tcp    open   http
135/tcp   filtered msrpc
139/tcp   filtered netbios-ssn
445/tcp   filtered microsoft-ds
9929/tcp  open   nping-echo
31337/tcp open   Elite

Nmap done: 1 IP address (1 host up) scanned in 11.53 seconds

Look at that! We see ssh, http, nping-echo, and Elite are all open. We also see a few filtered ports, meaning a firewall is blocking them. And Nmap tells us 995 closed ports aren’t shown (to save space).

You just completed your first port scan.

The Stealth Master: TCP SYN Scan (-sS)

This is the “pro” scan. It’s the default scan Nmap runs if you have root/administrator privileges (i.e., you use sudo).

Why is it better? It’s a “half-open” scan.

It starts the 3-Way Handshake:

  1. Your computer sends SYN.
  2. The target server, if the port is open, replies with SYN-ACK.
  3. …and that’s it. Your computer, having received the “open” confirmation, immediately sends an RST (Reset) packet instead of the final ACK.

This tears down the connection before it’s fully established. The benefit? Many older logging systems would only log a “connection” if the full handshake completed. This scan was “stealthy” because it got the answer without ever “officially” connecting.

This is the default and preferred scan type. On Linux/macOS, just use sudo:

sudo nmap scanme.nmap.org

On Windows, you need to be in an Administrator command prompt. The command is -sS. If you don’t have admin rights, you must use -sT.

# This forces a SYN scan, just to be explicit
sudo nmap -sS scanme.nmap.org

The output will look identical to the -sT scan, but it was faster and stealthier.

The UDP Slog: UDP Scanning (-sU)

Now for the painful one. As we discussed, UDP is connectionless. How do you scan it?

Nmap sends a UDP packet to a port.

  • If it gets an ICMP “Port Unreachable” error back, Nmap knows the port is CLOSED.
  • If it gets a UDP-based response, Nmap knows the port is OPEN.
  • If it gets nothing (which happens all the time), Nmap marks it as open|filtered.

UDP scanning is INCREDIBLY SLOW because many systems limit the rate of ICMP errors they send, so Nmap has to wait to avoid being rate-limited.

Let’s scan for the top 50 most common UDP ports on scanme.nmap.org:

# -sU for UDP scan
# --top-ports 50 to make this finish before next year
sudo nmap -sU --top-ports 50 scanme.nmap.org

The output might look like this:

Starting Nmap 7.92 ( [https://nmap.org](https://nmap.org) )
Nmap scan report for scanme.nmap.org (45.33.32.156)
Host is up (0.16s latency).
PORT      STATE         SERVICE
53/udp    open          domain
67/udp    open|filtered dhcpserver
68/udp    open|filtered dhcpc
69/udp    open|filtered tftp
123/udp   open|filtered ntp
137/udp   open|filtered netbios-ns
...

Nmap done: 1 IP address (1 host up) scanned in 25.18 seconds

Notice all the open|filtered states? Nmap just isn’t sure. This is the reality of UDP scanning.

Nmap Command Cheatsheet: Building the Perfect Scan

Nmap’s true power comes from combining options. Here are the building blocks you must know.

1. Target Specification (The “Who”)

  • Single IP/Host: nmap 192.168.1.1 or nmap scanme.nmap.org
  • CIDR Range: nmap 192.168.1.0/24 (This scans all 256 IPs from 192.168.1.0 to 192.168.1.255. This is how you scan your whole network).
  • List from File: nmap -iL targets.txt (Put a list of IPs in a text file).

2. Port Specification (The “Where”)

By default, Nmap only scans the top 1,000 most common TCP ports.

  • Single Port: -p 80 (Scan only port 80).
  • Range of Ports: -p 1-100 (Scan ports 1 through 100).
  • Specific Ports: -p 22,80,443 (Scan only these three ports).
  • All Ports: -p- (Scan all 65,535 ports. This takes a while!)
  • Top Ports: --top-ports 100 (Scan the 100 most common ports).
  • Fast Scan: -F (Same as --top-ports 100, just shorter).

3. Timing and Performance (The “How Fast”)

Nmap’s timing templates (-T) control how aggressive it is. It’s a scale from 0 to 5.

  • -T0: Paranoid (Extremely slow, for IDS evasion)
  • -T1: Sneaky
  • -T2: Polite (Slower than default, uses less bandwidth)
  • -T3: Normal (The default, a good balance)
  • -T4: Aggressive (Assumes a fast, reliable network)
  • -T5: Insane (Very fast, but you might miss ports or crash weak targets)

For most scans, -T4 is what you want.

# A fast scan
nmap -T4 scanme.nmap.org

4. The Real Magic: Service, Version, and OS Detection

This is what separates a script kiddie from a pro. Knowing port 22 is open is good. Knowing it’s running OpenSSH version 6.6.1 on Ubuntu is game-changing. That’s how you find vulnerabilities.

  • Service & Version Detection (-sV): After finding an open port, Nmap will “interrogate” it, sending a series of probes to figure out exactly what software is running and what version it is.
  • OS Detection (-O): Nmap will analyze the low-level TCP/IP stack behavior (packet window sizes, flag orders, etc.) to make an educated guess at the host’s Operating System.
  • The “Aggressive” Flag (-A): This is the magic “do-it-all” flag. It’s equivalent to -sV -O -sC --traceroute. It’s my personal favorite.

Let’s run an aggressive scan:

sudo nmap -A -T4 scanme.nmap.org

The output is now much richer:

Nmap Aggressive Scan
Nmap Aggressive Scan
Starting Nmap 7.92 ( [https://nmap.org](https://nmap.org) )
Nmap scan report for scanme.nmap.org (45.33.32.156)
Host is up (0.16s latency).
Not shown: 995 closed ports
PORT      STATE  SERVICE     VERSION
22/tcp    open   ssh         OpenSSH 6.6.1p1 Ubuntu 2ubuntu2.13 (Ubuntu Linux; protocol 2.0)
80/tcp    open   http        Apache httpd 2.4.7 ((Ubuntu))
...
9929/tcp  open   nping-echo  Nping echo
31337/tcp open   tcpwrapped
Device type: general purpose
Running: Linux 3.X|4.X
OS CPE: cpe:/o:linux:linux_kernel:3 cpe:/o:linux:linux_kernel:4
OS details: Linux 3.10 - 4.11
...
Nmap done: 1 IP address (1 host up) scanned in 34.21 seconds

Look at that data! We know it’s OpenSSH 6.6.1p1 on Ubuntu. We know it’s Apache 2.4.7. We know it’s a Linux 3.X or 4.X kernel. Now you can go to a site like Exploit-DB and search for vulnerabilities in those specific versions. This is how hacking actually starts.

5. The Ultimate Power: The Nmap Scripting Engine (NSE)

If Nmap is a Swiss Army Knife, the NSE is a collection of 600+ other tools you can plug into it. These are scripts (written in the Lua language) that can do almost anything.

  • --script=default or -sC: Runs a default, safe set of scripts. This is included in -A.
  • --script=vuln: Runs all scripts tagged as “vulnerability.” This is amazing.
  • --script=<script-name>: Runs one specific script.

Let’s try to find the homepage title for scanme.nmap.org‘s web server:

nmap -p 80 --script=http-title scanme.nmap.org

Output:

Nmap Scripting Engine
Nmap Scripting Engine
PORT   STATE SERVICE
80/tcp open  http
| http-title: Go ahead and ScanMe!
|_  And help yourself to a cookie: ...

It found the title! This proves the script ran and interrogated the web server.

My “Go-To” Nmap Scan Command

When I’m doing a serious assessment, this is the command I run 90% of the time.

sudo nmap -A -T4 -p- -oA my_scan_results scanme.nmap.org

Let’s break that down:

  • sudo: Run as root to enable the default SYN (-sS) scan.
  • -A: Aggressive mode. Does OS detection (-O), Version detection (-sV), and default Scripts (-sC).
  • -T4: Run fast.
  • -p-: Scan ALL 65,535 ports, not just the top 1,000.
  • -oA my_scan_results: Output the results in All 3 major formats (.nmap, .gnmap, and .xml) so I can review them later.
  • scanme.nmap.org: The target.

This one command gives you almost everything you need to know about a target.

You are now in the top 10% of Nmap users. But what if Nmap is too “canned” for you? What if you want to build your own tool?

The “DIY” Path – Build Your Own Scanner in Python

Welcome to the most exciting part of this guide. You’re going to learn how to build your own port scanners from scratch. This is a massive resume-builder and will teach you exactly how these tools work under the hood.

We’re going to use Python, the #1 language for security and automation.

Why Python?

The socket library. Python has a built-in library called socket that lets you interact with the network at a very low level. It’s the “digital hands” you’ll use to knock on ports.

Setting Up Your Environment

  1. Make sure you have Python 3 installed (python3 --version).
  2. Create a new folder for your project (e.g., pyscanner).
  3. It’s good practice to use a virtual environment:python3 -m venv venv source venv/bin/activate # On Windows: venv\Scripts\activate

Your First TCP Connect Scanner (The Simple Way)

This is the Python equivalent of Nmap’s -sT scan. We will try to perform a full TCP connection on each port.

Create a new file, scanner.py.

import socket

# Define the target
target_ip = "45.33.32.156"  # This is scanme.nmap.org

# Define the port we want to scan
port = 80

# 1. Create a new socket object
#    AF_INET means we are using IPv4
#    SOCK_STREAM means we are using TCP
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # 2. Set a timeout
    #    This is CRITICAL. If a port is filtered, the connection
    #    will just hang forever. We'll give it 1 second.
    s.settimeout(1.0)

    # 3. Try to connect
    #    connect_ex() is the key. It returns 0 if the connection
    #    was successful (port is OPEN). It returns an error code
    #    if it fails (port is CLOSED or FILTERED).
    result = s.connect_ex((target_ip, port))

    if result == 0:
        print(f"Port {port} is OPEN")
    else:
        print(f"Port {port} is CLOSED or FILTERED")

    # 4. Close the socket
    s.close()

except socket.error as e:
    print(f"Error: {e}")

Run this file: python3 scanner.py

Output: Port 80 is OPEN

Now try changing port = 80 to port = 81.

Output: Port 81 is CLOSED or FILTERED

It works! You’ve built a port scanner.

Making a Real Scanner (Multiple Ports)

That was great, but we want to scan more than one port. Let’s make a loop. And let’s make it scan a hostname instead of just an IP.

import socket
import sys

def scan_port(ip, port):
    """
    Tries to connect to a specific IP and port.
    Returns True if open, False if closed/filtered.
    """
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.settimeout(0.5)  # Shorter timeout for faster scanning
        
        # connect_ex returns 0 on success
        if s.connect_ex((ip, port)) == 0:
            s.close()
            return True
        else:
            s.close()
            return False
            
    except socket.error:
        return False

def main():
    target_host = "scanme.nmap.org"
    
    # 1. Translate hostname to IP address
    try:
        target_ip = socket.gethostbyname(target_host)
        print(f"Scanning target: {target_host} ({target_ip})")
    except socket.gaierror:
        print(f"Error: Could not resolve hostname {target_host}")
        sys.exit()

    # Define the port range
    start_port = 1
    end_port = 100

    print(f"Scanning ports from {start_port} to {end_port}...")
    
    open_ports = []
    
    # 2. Loop through every port in the range
    for port in range(start_port, end_port + 1):
        if scan_port(target_ip, port):
            print(f"[+] Port {port} is OPEN")
            open_ports.append(port)
        else:
            # This is too noisy, let's just print open ports
            pass
            
    print("\nScan complete.")
    print(f"Found {len(open_ports)} open ports: {open_ports}")

if __name__ == "__main__":
    main()

Run it: python3 scanner.py

Output:

Scanning target: scanme.nmap.org (45.33.32.156)
Scanning ports from 1 to 100...
[+] Port 22 is OPEN
[+] Port 80 is OPEN

Scan complete.
Found 2 open ports: [22, 80]

This is awesome. But… it’s slow. It’s scanning one port at a time. To scan ports 1-1000, it would take a long time. How do we make it fast like Nmap?

Making it Fast with Multi-Threading

We’re going to use threading. This lets our program do multiple things at once. Instead of scanning ports 1, 2, 3… sequentially, we’ll create 100 “workers” and have them scan 100 ports simultaneously.

This is an advanced topic, but it’s the key to performance.

We’ll use Python’s queue module to create a “to-do list” of ports. The threads will grab a port from the queue, scan it, and go back for another.

import socket
import sys
import threading
from queue import Queue

# A thread-safe lock to prevent printing from jumbling
print_lock = threading.Lock()

def scan_port(ip, port):
    """
    Tries to connect to a specific IP and port.
    Returns True if open, False if closed/filtered.
    """
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.settimeout(0.5)
        if s.connect_ex((ip, port)) == 0:
            s.close()
            return True
        else:
            s.close()
            return False
    except (socket.error, socket.timeout):
        return False

# This is our "worker" thread.
def worker_thread(target_ip, port_queue):
    """
    This is the function our threads will run.
    It will keep grabbing ports from the queue until it's empty.
    """
    while not port_queue.empty():
        port = port_queue.get()
        
        if scan_port(target_ip, port):
            # Use the lock to print safely
            with print_lock:
                print(f"[+] Port {port} is OPEN")
        
        # Tell the queue that this task is done
        port_queue.task_done()

def main():
    target_host = "scanme.nmap.org"
    
    try:
        target_ip = socket.gethostbyname(target_host)
        print(f"Scanning target: {target_host} ({target_ip})")
    except socket.gaierror:
        print(f"Error: Could not resolve hostname {target_host}")
        sys.exit()

    # 1. Create our "to-do list" queue
    port_queue = Queue()
    
    # Let's scan the top 1024 ports
    for port in range(1, 1025):
        port_queue.put(port)

    # 2. Define how many workers (threads) we want
    num_threads = 100
    
    print(f"Spawning {num_threads} threads to scan 1024 ports...")

    # 3. Create and start the threads
    for _ in range(num_threads):
        t = threading.Thread(target=worker_thread, args=(target_ip, port_queue))
        t.daemon = True  # Allows program to exit even if threads are running
        t.start()

    # 4. Wait for all ports to be scanned
    #    This blocks until the queue is empty.
    port_queue.join()
    
    print("\nScan complete.")

if __name__ == "__main__":
    main()

Run this: python3 scanner.py

It will fly. It will scan 1024 ports in just a few seconds, with the open ports printing as they’re found. You’ve just built an incredibly fast, multi-threaded port scanner. This is a serious piece of code.

The “Pro” Scanner with Scapy (Python SYN Scan)

All our scanners so far have been TCP Connect (-sT) scanners. To build a real stealthy SYN (-sS) scanner, we need to go deeper. We need to build our own IP and TCP packets from scratch.

This requires:

  1. Root/Administrator privileges (to send raw packets).
  2. A new library: Scapy.

Scapy is a packet-crafting masterpiece. Install it: pip install scapy

Now, let’s build a SYN scanner. This is advanced, but you’re ready.

import sys
from scapy.all import IP, TCP, sr1, ICMP

def syn_scan(target_ip, port):
    """
    Performs a TCP SYN scan on a single port.
    Returns 'OPEN', 'CLOSED', or 'FILTERED'.
    """
    
    # 1. Craft the SYN packet
    #    flags="S" means the SYN flag is set
    syn_packet = IP(dst=target_ip) / TCP(dport=port, flags="S")

    # 2. Send the packet and wait 1 second for a response
    #    sr1() means "send and receive 1 packet"
    #    verbose=0 turns off Scapy's noisy output
    response = sr1(syn_packet, timeout=1, verbose=0)

    # 3. Analyze the response
    if response is None:
        # No response at all
        return "FILTERED"
    
    elif response.haslayer(TCP):
        # We got a TCP packet back
        tcp_layer = response.getlayer(TCP)
        
        # Check the flags
        # 0x12 is SYN-ACK (SYN=0x02, ACK=0x10)
        if tcp_layer.flags == 0x12:
            # Got a SYN-ACK! Port is OPEN.
            # Send an RST to tear down the connection stealthily.
            rst_packet = IP(dst=target_ip) / TCP(dport=port, flags="R")
            sr1(rst_packet, timeout=1, verbose=0) # Fire-and-forget
            return "OPEN"
        
        # 0x14 is RST-ACK (RST=0x04, ACK=0x10)
        elif tcp_layer.flags == 0x14:
            # Got an RST-ACK. Port is CLOSED.
            return "CLOSED"
            
    elif response.haslayer(ICMP):
        # We got an ICMP error (e.g., from a firewall)
        icmp_layer = response.getlayer(ICMP)
        if icmp_layer.type == 3 and icmp_layer.code in [1, 2, 3, 9, 10, 13]:
            # These are "Destination Unreachable" codes
            return "FILTERED"
            
    return "UNKNOWN"


def main():
    target_host = "scanme.nmap.org"
    
    try:
        target_ip = socket.gethostbyname(target_host)
        print(f"SYN Scanning target: {target_host} ({target_ip})")
    except socket.gaierror:
        print(f"Error: Could not resolve hostname {target_host}")
        sys.exit()

    # Let's just check a few interesting ports
    ports_to_scan = [21, 22, 23, 25, 80, 135, 443, 445, 9929]
    
    print("This requires root/administrator privileges to run.")
    print("-" * 30)
    
    for port in ports_to_scan:
        status = syn_scan(target_ip, port)
        print(f"Port {port:5d} is {status}")
        
    print("-" * 30)
    print("SYN Scan complete.")


if __name__ == "__main__":
    main()

To run this, you must use sudo:

sudo python3 scanner.py

Output:

SYN Scanning target: scanme.nmap.org (45.33.32.156)
This requires root/administrator privileges to run.
------------------------------
Port    21 is CLOSED
Port    22 is OPEN
Port    23 is CLOSED
Port    25 is CLOSED
Port    80 is OPEN
Port   135 is FILTERED
Port   443 is CLOSED
Port   445 is FILTERED
Port  9929 is OPEN
------------------------------
SYN Scan complete.

You have just built a stealthy SYN scanner from scratch. You are now in the top 1% of this field. You understand exactly how Nmap’s -sS scan works because you just built it.

The “Final” Python Tool (Using argparse)

Let’s combine these ideas into a “real” command-line tool that can take arguments, just like Nmap. We’ll use the fast, multi-threaded TCP Connect scanner as our base.

We’ll use argparse to parse commands like -t <target> and -p <ports>.

import socket
import sys
import threading
from queue import Queue
import argparse  # Import the argparse library

print_lock = threading.Lock()
open_ports = []

def scan_port(ip, port):
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.settimeout(0.5)
        if s.connect_ex((ip, port)) == 0:
            s.close()
            return True
        else:
            s.close()
            return False
    except (socket.error, socket.timeout):
        return False

def worker_thread(target_ip, port_queue):
    while not port_queue.empty():
        port = port_queue.get()
        if scan_port(target_ip, port):
            with print_lock:
                print(f"[+] Port {port} is OPEN")
                open_ports.append(port)
        port_queue.task_done()

def main():
    # 1. Setup our argument parser
    parser = argparse.ArgumentParser(description="A fast, multi-threaded port scanner.")
    parser.add_argument("target", help="The target host or IP address to scan.")
    parser.add_argument("-p", "--ports", default="1-1024", help="Port range to scan (e.g., '1-1024', '80,443', '20-25,80').")
    parser.add_argument("-t", "--threads", type=int, default=100, help="Number of threads to use (default: 100).")
    
    args = parser.parse_args()
    
    target_host = args.target
    port_range = args.ports
    num_threads = args.threads

    try:
        target_ip = socket.gethostbyname(target_host)
        print(f"Scanning target: {target_host} ({target_ip})")
    except socket.gaierror:
        print(f"Error: Could not resolve hostname {target_host}")
        sys.exit()
    
    port_queue = Queue()
    
    # 2. Parse the complex port range
    try:
        for part in port_range.split(','):
            if '-' in part:
                start, end = map(int, part.split('-'))
                for port in range(start, end + 1):
                    port_queue.put(port)
            else:
                port_queue.put(int(part))
    except ValueError:
        print(f"Error: Invalid port range '{port_range}'.")
        sys.exit()

    port_count = port_queue.qsize()
    print(f"Spawning {num_threads} threads to scan {port_count} ports...")
    
    for _ in range(num_threads):
        t = threading.Thread(target=worker_thread, args=(target_ip, port_queue))
        t.daemon = True
        t.start()

    port_queue.join()
    
    print("\nScan complete.")
    print(f"Found {len(open_ports)} open ports: {sorted(open_ports)}")

if __name__ == "__main__":
    main()

Now you can run your tool from the command line like a pro:

python3 scanner.py scanme.nmap.org python3 scanner.py 192.168.1.1 -p 1-65535 -t 500 python3 scanner.py google.com -p 80,443

You have built a genuinely useful, fast, and powerful security tool.

The “Must-Read” Chapter – Legality & Ethics

This is the most important section of this entire guide.

Read it. Do not skip it.

THE BIG QUESTION: IS PORT SCANNING LEGAL?

The answer is a massive, dangerous gray area.

Disclaimer: I am not a lawyer. This is not legal advice. This is guidance for ethical hacking and network defense.

  • The Analogy: Port scanning is often compared to walking down a street and jiggling the doorknobs on every house. Or knocking on every door. Or walking up a driveway and peering in the windows.
  • The Problem: Is that “trespassing”? In the digital world, the laws are catching up. In some jurisdictions, an “unauthorized port scan” can be interpreted as the first step of an attack and could be considered a violation of laws like the CFAA (Computer Fraud and Abuse Act) in the United States.
  • The Rule: The only thing that matters is PERMISSION.

Here are the rules you must live by:

  1. NEVER, EVER, EVER SCAN A TARGET YOU DO NOT OWN.
    • Scanning google.com? Illegal.
    • Scanning your university’s server? Illegal (and will get you expelled).
    • Scanning a random website? Illegal.
    • Scanning your own home router (192.168.1.1)? Perfectly legal and a great idea.
    • Scanning your own web server that you pay for? Legal.
    • Scanning a target as part of a bug bounty program (like HackerOne) where they explicitly give you permission in the “Rules of Engagement”? Legal and you might get paid for it!
Network Access Permissions
Network Access Permissions
  1. WHAT IS “OWNERSHIP”?
    • If you pay the bill for the internet or the server, you own it.
    • If you are on a “guest” Wi-Fi (like at Starbucks or an airport), you DO NOT own it. Scanning their network is illegal.
    • If you are at work, you DO NOT own the corporate network. Do not scan it unless it is explicitly your job as a network administrator or security tester, and you have written permission.

When people get in trouble for port scanning, it’s not because they scanned their home network. It’s because they scanned a bank, or a government website, or a corporation, and that company’s IDS (Intrusion Detection System) flagged the scan, reported the IP address to their ISP, and they got a knock on the door.

Use your new powers for good. Use them for defense. Scan your network. Find out what you’ve left open. Close the ports you don’t need. That’s the entire point of “ethical” hacking.

Beyond the Basics (Advanced Evasion)

This is a bonus section. Now that you know how to scan, you need to know how network defenders catch you.

Modern firewalls and IDS/IPS (Intrusion Prevention Systems) are not stupid. They are designed to detect scans. If an IP address suddenly “knocks” on 1,000 ports in 2 seconds, that’s not normal traffic. That’s a scan.

Nmap has features to try and bypass this.

  • Fragmented Packets (-f): This splits your SYN packet into tiny, 8-byte pieces. A simple firewall waiting for a “SYN packet” might not see it, as it’s just getting tiny fragments. (Most modern firewalls reassemble packets and catch this anyway).
  • Decoy Scans (-D <decoy1,decoy2,ME>): This is brilliant. Nmap will send your real scan packet, but it will also send scan packets that look like they’re coming from other IP addresses (decoys). The target’s logs will show that 10 different IPs are scanning them, and they won’t know which one is the real one. (Note: this is a good way to get other people in trouble, so be careful).
  • Source Port Spoofing (-g or --source-port): Some old, misconfigured firewalls might have a rule that says “Allow all traffic from port 53 (DNS).” Nmap can send its packet from source port 53, trying to trick the firewall into thinking it’s a legitimate DNS reply.
  • Idle Scan (-sI <zombie_ip>): This is the most complex, brilliant, and truly “stealth” scan. It’s too complex to fully detail here, but the concept is:
    1. You find a “zombie” server on the internet that is idle (not busy).You send packets to the zombie to measure its IP ID (a number that increments).You spoof a packet so it looks like it’s from the zombie and send it to your target.If the target’s port is open, it will send a SYN-ACK back to the zombie.The zombie, which never started a connection, will send an RST. This increments its IP ID.You re-check the zombie’s IP ID. If it has incremented, you know the target’s port is OPEN.
  • In this entire exchange, you never sent a single packet to the target from your real IP. You used a zombie as a blind intermediary. It’s genius, and Nmap automates it.

Conclusion: You’re Now a Port Scanning Expert

If you’ve read this far, give yourself a massive pat on the back.

You started with “what is an IP address?” and “what is a port?” You learned the deep, technical difference between TCP (the phone call) and UDP (the postcard). You mastered the TCP Three-Way Handshake (SYN, SYN-ACK, ACK), which is the key to everything.

You then became an Nmap master. You know the difference between -sT (connect) and -sS (SYN stealth). You know how to find versions (-sV), guess operating systems (-O), and run the all-powerful -A aggressive scan. You’ve even seen the power of the Nmap Scripting Engine.

And then, you did what most people only dream of. You built your own tools. You wrote a single-threaded scanner, a blazingly-fast multi-threaded scanner, and even a low-level, stealthy SYN scanner using Scapy. You topped it off by building a real CLI tool with argparse.

You now understand this topic at a level most IT professionals don’t.

Your journey isn’t over. It’s just beginning. Take these skills and use them for defense. Run nmap -A -T4 192.168.1.0/24 on your own home network. (Check your router’s IP first, it might be 192.168.0.1 or 10.0.0.1).

Are there any strange ports open? Is your “Smart” TV or printer listening on something it shouldn’t be?

You now have the power to find out.

Go be curious. Go be ethical. And go secure your world.

Buy me A Coffee!

Support The CyberSec Guru’s Mission

🔐 Fuel the cybersecurity crusade by buying me a coffee! Your contribution powers free tutorials, hands-on labs, and security resources.

Why your support matters:
  • Writeup Access: Get complete writeup access within 12 hours
  • Zero paywalls: Keep the main content 100% free for learners worldwide

Perks for one-time supporters:
☕️ $5: Shoutout in Buy Me a Coffee
🛡️ $8: Fast-track Access to Live Webinars
💻 $10: Vote on future tutorial topics + exclusive AMA access

“Your coffee keeps the servers running and the knowledge flowing in our fight against cybercrime.”☕ Support My Work

Buy Me a Coffee Button

If you like this post, then please share it:

Projects

Discover more from The CyberSec Guru

Subscribe to get the latest posts sent to your email!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Discover more from The CyberSec Guru

Subscribe now to keep reading and get access to the full archive.

Continue reading