Port Scanner

GoNetworkingTCPConcurrencyCLI

Port Scanner: A Simple Guide

If you think of a computer as a large apartment building, "ports" are like the doors to the different apartments. Some doors are meant to be open for the public (like a business reception), while others should be securely locked. A Port Scanner is simply a tool that walks down the hallway, knocking on every door to see which ones open.

In this project, I built a fast, lightweight port scanner using the Go programming language. Let's break down how it works, step by step.

Foundations of Networking (Knocking on Doors)

Goal: Figure out how to "knock" on a port.

When computers talk over the internet, they mostly use a polite protocol called TCP. When it wants to connect, it does a "3-way handshake":

  1. "Hello, can we talk?"
  2. "Yes, I'm here. Can we talk?"
  3. "Great, let's talk!"

If the computer on the other end responds, we know the port (the door) is open. If it ignores us or says "go away," the port is closed.

In our code, we use a timeout. This means if we knock and nobody answers after a second, we assume nobody is home and move on, rather than waiting forever.

// We knock on the door, but only wait a short time for an answer
conn, err := net.DialTimeout("tcp", address, timeout)
if err == nil {
    conn.Close() // They answered! We say goodbye.
    // Port is OPEN
}

The Sequential Scanner (Checking One by One)

Goal: Check a whole row of doors.

Now that we know how to check one door, we need to check many. Some common doors you might use every day without knowing it:

  • Port 80 – Regular websites
  • Port 443 – Secure websites (the padlock in your browser)

There are over 65,000 possible ports! If we write a simple loop to check them one after another, it works, but it's incredibly slow.

// Checking doors one by one
for port := startPort; port <= endPort; port++ {
    if scanPort(host, port, timeout) {
        fmt.Printf("Port %d is OPEN\n", port)
    }
}

The problem? If a door is closed, we stand there waiting for a response before moving to the next one. Checking 1,000 doors like this could take over 15 minutes!

Input Handling & User Options

Goal: Let the user tell the scanner what to do.

A good tool lets the user make choices. We want to let the user specify which building (IP address) to check, and which doors (ports) to knock on. We use simple text commands for this:

// Setting up the options users can type
hostFlag := flag.String("host", "127.0.0.1", "Target host IP")
portFlag := flag.String("ports", "1-1024", "Port range to check")
threadFlag := flag.Int("threads", 100, "How many workers to use")

Now, someone can run the program and easily say, "Check doors 20 through 100 on this specific computer."

Performance with Concurrency (Bringing Friends)

Goal: Make it fast!

Checking doors one by one is slow. But what if we brought 100 friends, and we all checked different doors at the exact same time? This is called Concurrency.

The Go programming language is famous for making this easy using things called goroutines (our "friends") and channels (a to-do list they all share).

We create a team of workers. They all grab a port number from the list, knock on the door, report back if it's open, and then grab the next port number.

func worker(ports <-chan int, results chan<- int) {
    for port := range ports {
        // Knock on the door
        conn, err := net.DialTimeout("tcp", address, timeout)
        if err == nil {
            conn.Close()
            results <- port // Tell everyone it's open!
        }
    }
}

With 100 workers checking at the same time, scanning 1,000 ports goes from taking 15 minutes to taking just a few seconds!

Service Identification (Asking "Who are you?")

Goal: Find out who lives behind the open door.

When a door opens, it's helpful to know who is behind it. In networking, when you connect to an open port, the computer often sends an automatic welcoming message called a banner.

By grabbing this banner, we can tell if the open door leads to a web server, an email server, or a database.

if err == nil {
    // We connected! Let's listen for their greeting.
    banner := make([]byte, 1024)
    conn.Read(banner)
    fmt.Printf("Greeting from port: %s\n", string(banner))
}

Wrapping It Up

The final tool is a fast, lightweight application. It takes user instructions, launches hundreds of simultaneous checks, and neatly prints out which ports are open.

Scanning 127.0.0.1 on ports 1 to 1024 using 100 threads...
Port 22 is OPEN
Port 80 is OPEN
Scan complete! Open ports: [22 80]

⚠️ Important Note: Just like you shouldn't go around checking the locks on random people's houses, you should never scan computers or networks you don't own or have explicit permission to test!

Building this was a fantastic way to understand the fundamentals of internet communication and see how modern programming can make slow tasks incredibly fast.