Building a Networking CLI Tool in Go (A Step-By-Step Tutorial)
Go is one of the best languages for building CLI tools — it compiles to tiny static binaries, has a rock-solid standard library, and makes concurrency effortless.
In this tutorial, we’ll build a network scanner CLI that:
- Accepts a list of hosts
- Sends a ping-like TCP check to each
- Measures latency
- Runs the checks concurrently
- Prints results in a clean, CLI-friendly table
This kind of tool is great for debugging networking issues, monitoring small clusters, or just learning Go’s networking primitives.
🚀 What We’re Building: netscan
Our CLI will:
- Accept hosts via command-line flags
- Attempt a fast TCP connection on port
80(configurable) - Time the connection
- Report whether each host is reachable
- Do it all concurrently using goroutines
Example usage:
netscan --hosts google.com,github.com,example.com --port 80
Output:
HOST STATUS LATENCY
google.com OK 32ms
github.com OK 45ms
example.com FAILED timeout
📁 Project Structure
netscan/
├── main.go
└── go.mod
🛠 Step 1: Initialise the Project
mkdir netscan
cd netscan
go mod init netscan
🌐 Step 2: Write the CLI App
Create main.go:
package main
import (
"flag"
"fmt"
"net"
"strings"
"time"
)
type ScanResult struct {
Host string
Success bool
Latency time.Duration
Err error
}
func scanHost(host string, port int, timeout time.Duration) ScanResult {
addr := fmt.Sprintf("%s:%d", host, port)
start := time.Now()
conn, err := net.DialTimeout("tcp", addr, timeout)
latency := time.Since(start)
if err != nil {
return ScanResult{Host: host, Success: false, Latency: latency, Err: err}
}
conn.Close()
return ScanResult{Host: host, Success: true, Latency: latency, Err: nil}
}
func main() {
hostsFlag := flag.String("hosts", "", "Comma-separated list of hosts")
portFlag := flag.Int("port", 80, "Port to scan")
timeoutFlag := flag.Duration("timeout", 2*time.Second, "Connection timeout (e.g. 500ms, 2s)")
flag.Parse()
if *hostsFlag == "" {
fmt.Println("Please provide hosts: --hosts host1,host2,host3")
return
}
hosts := strings.Split(*hostsFlag, ",")
results := make(chan ScanResult)
for _, host := range hosts {
h := strings.TrimSpace(host)
go func() {
results <- scanHost(h, *portFlag, *timeoutFlag)
}()
}
fmt.Printf("%-15s %-10s %-10s\n", "HOST", "STATUS", "LATENCY")
for range hosts {
r := <-results
status := "OK"
if !r.Success {
status = "FAILED"
}
lat := r.Latency.String()
if !r.Success {
lat = "timeout"
}
fmt.Printf(
"%-15s %-10s %-10s\n",
r.Host, status, lat,
)
}
}
▶️ Step 3: Run Your CLI
go run . --hosts google.com,github.com,example.com --port 80
Try scanning an unreachable host:
go run . --hosts 10.255.255.1 --timeout 500ms
✨ Feature Ideas to Level This Up
This simple CLI can turn into a full networking toolkit. Try adding:
1. Parallelism Controls
Limit concurrency with a worker pool.
2. Multiple Port Scanning
Accept a list of ports and scan all of them.
3. JSON Output
Add --json mode for scripting and automation.
4. Colourised Terminal Output
Use ANSI colours: green for OK, red for FAILED.
5. ICMP Ping Support
Requires raw sockets — a fun challenge (Go’s golang.org/x/net/icmp package helps).
6. Config File Support
Use YAML/TOML for saved scan profiles.
7. Build a TUI (Terminal UI)
Using libraries like:
tcellbubbletea
You could make a dashboard-style updater showing live scan results.
🎉 Summary
In this tutorial, you built a fast, concurrent, networking-related CLI tool in Go — the kind of thing Go absolutely excels at.
You now have hands-on experience with:
- Flag parsing
- TCP networking
- Concurrency with goroutines
- Channels
- Timeouts
- Clean CLI output