Simple Concurrent Port Scanner in Go


When learning network programming or cybersecurity basics, building a port scanner is a classic, hands-on project. It’s a practical way to explore sockets, concurrency, and command-line interface (CLI) development. In this post, we’ll break down a lightweight port scanner implemented in Go, showing how its concurrency model makes large-scale scanning both simple and fast.
What is a Port Scanner?
A port scanner is a tool that probes a target machine for open ports. Each open port could represent a running service, like SSH (22), HTTP (80), or custom applications. Port scanning is essential for network diagnostics and is foundational in security assessments.
Why Go for Port Scanning?
Go (or Golang) offers unique advantages for this task:
Goroutines enable lightweight concurrent execution.
Channels and WaitGroups help coordinate parallel tasks with ease.
Robust networking support built into the standard library.
These features allow us to write highly scalable port scanners with minimal code.
Breaking Down the Port Scanner
Let's examine the most important parts and why they matter.
1. Project Structure
main.go: Entry point. Sets up flags, concurrency, and result reporting.
scanner/scanner.go: Contains the scanning logic.
2. Command-Line Flags
The user can specify a target host via the -host
flag (defaults to localhost
). The scanner covers the entire range of possible TCP ports (1 to 65535), but you could easily extend this.
gohost := flag.String("host", "localhost", "Target host to scan")
flag.Parse()
3. Concurrency with Goroutines and WaitGroups
The heart of the scanner is a loop that kicks off a goroutine ("lightweight thread") for each port. All goroutines share a common WaitGroup for graceful shutdown and a channel to report results.
gofor port := startPort; port <= endPort; port++ {
wg.Add(1)
go func(p int) {
ps.CheckPort(p, openPorts, &wg)
}(port)
}
4. The Port Checking Logic
Under the hood, CheckPort
tries to open a TCP connection to the target host and port.
goaddress := net.JoinHostPort(ps.Host, strconv.Itoa(port))
conn, err := net.DialTimeout("tcp", address, 1*time.Second)
if err == nil {
conn.Close()
ch <- port // Report open ports over the channel
}
A timeout ensures the scanner doesn’t hang on unresponsive ports.
5. Collecting Results Asynchronously
As each scanner goroutine discovers open ports, it sends the port number to the main goroutine via a channel. The main code reads from this channel and prints results as they come:
gofor port := range openPorts {
fmt.Printf("Port %d is open\n", port)
}
The result: you see open ports displayed live as they’re found, even while other scans continue in the background.
Why This Pattern Rocks
High Performance: Thanks to Go’s concurrency primitives, you can scan all 65,535 ports very quickly (although in practice, for network friendliness, you may want to limit concurrency in real-world use).
Simplicity: The code is short, clear, and idiomatic; robust error handling and CLI parsing are easy to add.
Extensible: You can easily add features like custom port ranges, configurable timeouts, or service banner grabbing.
Possible Enhancements
Limit concurrent goroutines to avoid overwhelming the host or your own machine.
Support for port ranges or port list input instead of always scanning all ports.
Add result output in formats like JSON or CSV.
Try UDP scanning by using
"udp"
withnet.DialTimeout
.Include verbose logging for closed/filtered ports, if desired.
Final Thoughts
This simple scanner highlights Go’s power for network programming. With just a couple dozen lines, we get a highly concurrent, responsive tool that’s a great starting point for anyone looking to learn about sockets, protocols, or Go’s concurrency story.
Try extending the code—experiment with new features, or run it against test servers in your environment!
Sample Usage:
bashgo run main.go -host example.com
You’ll see output like:
textPort 22 is open
Port 80 is open
Port 443 is open
Happy scanning—use responsibly
Subscribe to my newsletter
Read articles from Pravesh Vyas directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
