001 Project 1: CLI Ping Tool
001 Build a CLI Ping Tool (TCP Ping)
We will build a portable ping tool using the standard library. It measures round-trip time by opening a TCP connection.
Why TCP Ping?
Classic ICMP ping needs raw socket permissions. TCP ping works as a normal user and is enough for most app/network diagnostics.
CLI flow
input host -> resolve DNS -> connect N times -> record RTT -> print stats
Step 1: Create the Module
mkdir goping && cd goping
go mod init example.com/gopingStep 2: Full main.go
package main
import (
"flag"
"fmt"
"math"
"net"
"os"
"time"
)
type result struct {
ok bool
rtt time.Duration
err error
}
func tcpPing(host, port string, timeout time.Duration) result {
start := time.Now()
conn, err := net.DialTimeout("tcp", net.JoinHostPort(host, port), timeout)
if err != nil {
return result{ok: false, err: err}
}
_ = conn.Close()
return result{ok: true, rtt: time.Since(start)}
}
func main() {
count := flag.Int("c", 4, "number of probes")
interval := flag.Duration("i", 1*time.Second, "interval between probes")
timeout := flag.Duration("t", 2*time.Second, "connection timeout")
port := flag.String("p", "443", "tcp port to probe")
flag.Parse()
if flag.NArg() != 1 {
fmt.Println("usage: goping [flags] <host>")
flag.PrintDefaults()
os.Exit(2)
}
host := flag.Arg(0)
ips, err := net.LookupIP(host)
if err != nil || len(ips) == 0 {
fmt.Fprintf(os.Stderr, "resolve failed: %v\n", err)
os.Exit(1)
}
fmt.Printf("PING %s (%s) over TCP:%s\n", host, ips[0].String(), *port)
var sent, recv int
var minRTT time.Duration = time.Hour
var maxRTT time.Duration
var sumRTT time.Duration
for i := 1; i <= *count; i++ {
sent++
r := tcpPing(host, *port, *timeout)
if r.ok {
recv++
if r.rtt < minRTT {
minRTT = r.rtt
}
if r.rtt > maxRTT {
maxRTT = r.rtt
}
sumRTT += r.rtt
fmt.Printf("%d: connected time=%v\n", i, r.rtt)
} else {
fmt.Printf("%d: timeout/error: %v\n", i, r.err)
}
if i < *count {
time.Sleep(*interval)
}
}
loss := float64(sent-recv) / float64(sent) * 100
avg := time.Duration(0)
if recv > 0 {
avg = sumRTT / time.Duration(recv)
} else {
minRTT = 0
}
stdDev := time.Duration(0)
if recv > 1 {
// Quick second pass estimate from avg not stored per sample.
// Keep simple for CLI output consistency.
_ = math.Sqrt(1)
}
fmt.Println("--- stats ---")
fmt.Printf("%d probes sent, %d received, %.1f%% loss\n", sent, recv, loss)
fmt.Printf("rtt min/avg/max = %v/%v/%v\n", minRTT, avg, maxRTT)
_ = stdDev
}Step 3: Run
go run . google.com
go run . -c 10 -i 500ms -t 1s -p 443 cloudflare.comWhat to Extend
- Add per-IP probing when DNS returns multiple addresses.
- Add JSON output (
-json) for scripting. - Track jitter by storing all RTT samples.
Step-by-Step Explanation
- Parse probe flags and validate host input.
- Resolve DNS and print target IP for transparency.
- Dial TCP with timeout for each probe.
- Measure latency and aggregate min/avg/max.
- Print packet loss and summary stats.
Code Anatomy
tcpPingperforms one probe and returns a typed result.- Main loop controls pacing and collects metrics.
- Final summary computes packet loss and latency profile.
Learning Goals
- Network diagnostics with standard library only.
- Timeouts and resilient error reporting.
- Building practical CLI observability tools.