DEV Community

Miguel Esteves
Miguel Esteves

Posted on

Go Concurrent Port Scanner

What did I build?

A CLI tool that scans a target IP address for open TCP ports concurrently. For sysadmins, pentesters, and security researchers who need fast network reconnaissance.

How it works?

Sequential scanning is slow because each port check waits for the previous one to complete. On a remote host with filtered ports, scanning 1024 ports sequentially could take over 1000 seconds.

This scanner launches one goroutine per port. All checks run simultaneously. A sync.WaitGroup tracks when every goroutine has finished. Open ports are sent into a buffered channel instead of printed directly — this prevents race conditions where multiple goroutines writing to stdout simultaneously produce scrambled output. Once all goroutines complete and the channel is closed, one loop drains the channel and prints results cleanly.

Example:

go run main.go -ip 192.168.1.1 -start 1 -end 1024

How I built it?

package main

import (
    "flag"
    "fmt"
    "net"
    "time"
    "sync"
)

func main() {

    ip := flag.String("ip", "127.0.0.1", "Target IP adress")
    start := flag.Int("start", 1, "Target Port")
    end := flag.Int("end", 1024, "Target Port")
    flag.Parse()
    var wg sync.WaitGroup
    ch := make(chan int, 65535)

    for i := *start; i <= *end; i++ {
        wg.Add(1)
        go func(port int){
        defer wg.Done()
        address := fmt.Sprintf("%s:%d", *ip, port)
        conn, err := net.DialTimeout("tcp", address, 1*time.Second)
        if err == nil {
            conn.Close()
            ch <- port
        }
    }(i)
}
    wg.Wait()
    close(ch)
    for port := range ch {
        fmt.Printf("Port %d is open.\n", port)
        }
}
Enter fullscreen mode Exit fullscreen mode

What I learned?

Go is really expressive, you can do so much with so little; I mean it took just 37 lines to write a performant concurrent port scanner.

The variable capture bug was the first hurdle — goroutines launched inside a loop close over the loop variable, not its value. By the time the goroutine runs, the variable has already changed. Passing i as an argument to the anonymous function gives each goroutine its own copy.

The second hurdle was understanding that goroutines should not print directly. Concurrent writes to stdout produce interleaved garbage. The channel pattern — produce in goroutines, consume in one place — which solves this cleanly.

The mental model that unlocked everything: goroutines are workers, the channel is the inbox, WaitGroup is how the manager knows all workers are done.

Check it out here

Next up: I will build an SQLite uptime monitor to enforce persistence and operational thinking.

Top comments (1)

Collapse
 
pranav_gore_297555a5b7dc2 profile image
Pranav Gore

Hi, I hope you are doing well. We are a software development team. We hunt for US jobs using Us job profile. So we are looking for a senior developer who can work with us.
Your role is to take part in the job interviews and pass the interviews. If your English is fluent, we can work together. If you are interested, please kindly send me message. I will explain more detail. Thank you!
Whatsapp: +1 (351) 234-6532
Telegram: @lionking06230810