Network diagnostics with Go
Dr. Wireless
Why is the WiFi not working? Instead of always typing the same steps to diagnose the problem, Mike Schilli writes a tool in Go that puts the wireless network through its paces and helps isolate the cause.
Imagine you've just arrived at your vacation resort, and the WiFi isn't working. Is the router's DHCP server failing to assign an IP address to your laptop? Is it DNS? Or is it just that the throughput is so poor that everything seems to be stalling?
You can diagnose all of these issues by running various command-line tools, but it is tedious and annoying to have to repeat the procedure every time. How about a tool that repeatedly runs these steps at regular intervals, visualizes the results, and hopefully zeroes in on the root cause?
I will use the tview [1] library from GitHub as the terminal user interface (UI) for my wifi
diagnostic tool. After all, some well-known projects, such as Kubernetes, also use it for their command-line tools. With just a few lines of code, tview switches the current terminal to raw mode and displays simple graphical elements such as tables or forms in a retro white on black background style0 (Figure 1). It accepts keyboard input in raw mode, and applications can use it to control actions on the interface.
Called at the command line, the readily compiled Go program wifi
from the source code in this article [2] runs four different tests simultaneously and displays the results in a table. Every 10 seconds, it runs the tests again and thus dynamically reflects what is changing in the network. If everything is working as desired, the tool displays the measured results (Figure 1). If the tests fail, the program shows you helpful error messages to narrow down the cause (Figure 2). Pressing Ctrl+C terminates the wifi
tool, switches the terminal back to normal mode, and lets it jump back to the shell prompt.
Parallel Test
The first two tests run by wifi
send ping requests to the Google server; both to the hostname www.google.com and to the IP address of Google's well-known DNS server (8.8.8.8). If both tests fail, the connection to the Internet is probably completely severed. However, if only the host is not found, but the IP ping succeeds, the problem is more likely related to DNS settings.
In the third test, labeled Ifconfig
, wifi
searches for all client IP addresses assigned to the computer by the network's DHCP server. If the test finds nothing, the router or the WLAN connection is probably to blame. In the fourth test, the tool sends an HTTP request to the YouTube server; if successful, it displays the round-trip time in milliseconds. This test can diagnose a lame Internet service provider (ISP).
Getting Close
As an example of what the tview library can do, Listing 1 implements a running stopwatch. Its current time arrives every second as a string via a Go channel. It is then dynamically refreshed in a TextView
type widget in the terminal interface.
Listing 1
clock-main.go
¤¤nonumber 01 package main 02 03 import ( 04 "fmt" 05 "github.com/rivo/tview" 06 ) 07 08 func main() { 09 app := tview.NewApplication() 10 tv := tview.NewTextView() 11 tv.SetBorder(true).SetTitle("Test Clock") 12 ch := clock() 13 14 go func() { 15 for { 16 select { 17 case val := <-ch: 18 app.QueueUpdateDraw(func() { 19 tv.Clear() 20 fmt.Fprintf(tv, "%s ", val) 21 }) 22 } 23 } 24 }() 25 26 err := app.SetRoot(tv, true).Run() 27 if err != nil { 28 panic(err) 29 } 30 }
To do this, the code pulls in the tview framework from GitHub in line 5. Line 9 creates a new terminal application and stores a reference to it in the app
variable. The TextView
widget is used as the clock's window content: This is stored in the tv
variable and is shown with a border in the terminal because of the SetBorder(true)
setting. SetTitle()
adds a header.
The call to the clock()
function in line 12 starts the actual stopwatch. The function not only triggers the timer and keeps it running in the background, but it also creates a channel that it passes back to the caller. The current stopwatch readings then arrive as formatted strings via this channel every second, and the caller picks them up to update the graphical display.
In the main program, the goroutine starting in line 14 concurrently uses a select
statement to intercept incoming strings from the channel in an infinite loop starting in line 15. As soon as a new value arrives in line 17, the program notifies the terminal UI by calling app.QueueUpdateDraw()
and tells the framework to first clear the clock display with tv.Clear()
before calling Fprintf()
to write the new, current value to the TextView
widget.
This completes setting up the UI's graphical elements. All that remains is to inject the TextView
widget into the application window by calling app.SetRoot()
in line 26 and to start the UI with Run()
. It keeps running from this moment on (Figure 3). If you press Ctrl+C, it folds away neatly, freeing up the terminal for the shell again.
Tick-Tock
The actual stopwatch is implemented by Listing 2 with the clock()
function, which accepts an optional string argument. The stopwatch doesn't actually use this, but I want the function's interface to be able to handle more complex actions for the UI later. That is why the code implements the function as a variadic function. In Go, the three dots between the name of the parameter and its type (arg
and string
in this case) indicate that you can either call the function entirely without arguments or with one or more arguments of the specified type.
Listing 2
clock.go
¤¤nonumber 01 package main 02 03 import ( 04 "time" 05 ) 06 07 func clock(arg ...string) chan string { 08 ch := make(chan string) 09 start := time.Now() 10 11 go func() { 12 for { 13 z := time.Unix(0, 0).UTC() 14 ch <- z.Add(time.Since(start)).Format("15:04:05") 15 time.Sleep(1 * time.Second) 16 } 17 }() 18 19 return ch 20 }
In line 8, clock()
creates the channel, which the function later passes back to the main program, to hook it up for periodic clock updates.
Listing 2 uses an interesting trick to display the time elapsed since the start time in hours, minutes, and seconds: The time.Since()
function in line 14 obtains the time elapsed since the start time in start
as a value of the time.Duration
type. However, Go does not provide elegant formatting as a string for this type. The time.Time
type for absolute time values, on the other hand, supports the Format()
function, which formats the internal time format in a human-readable way. To get free formatting for the Duration
type, Listing 2 simply converts it to absolute time by adding it to the beginning of time at zero Unix seconds.
In case you are wondering about the strange string 15:04:05
as an argument for the formatter: Go expects the format of hours, minutes, and seconds as numeric placeholders. Other programming languages specify such a format using a template string like HH:MM:SS
. Go, on the other hand, chooses the strange approach of using the magic time at 15:04:05
on Monday
, 2/1/2006
as a reference [3].
Line 14 pushes the current state of the stopwatch as a formatted string into the ch
channel. The calling main program listens at the other end of the channel and keeps refreshing its screen display with the incoming information.
To generate the binary from the source code, the three commands from Listing 3 retrieve the code of the dependent libraries from GitHub, compile the whole enchilada, and finally generate a clock-main
binary. If you start the result at the command line, the terminal is painted black and the stopwatch is drawn, ticking away the moments that make up a dull day, refreshing dynamically every second, inside a framed box (Figure 3). But be careful: The tview library requires at least Go 1.18. If you are still running an older version, you need to upgrade beforehand.
Listing 3
build-clock.sh
¤¤nonumber go mod init clock-main go mod tidy go build clock-main.go clock.go
Buy this article as PDF
(incl. VAT)
Buy Linux Magazine
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Support Our Work
Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.
News
-
Latest Cinnamon Desktop Releases with a Bold New Look
Just in time for the holidays, the developer of the Cinnamon desktop has shipped a new release to help spice up your eggnog with new features and a new look.
-
Armbian 24.11 Released with Expanded Hardware Support
If you've been waiting for Armbian to support OrangePi 5 Max and Radxa ROCK 5B+, the wait is over.
-
SUSE Renames Several Products for Better Name Recognition
SUSE has been a very powerful player in the European market, but it knows it must branch out to gain serious traction. Will a name change do the trick?
-
ESET Discovers New Linux Malware
WolfsBane is an all-in-one malware that has hit the Linux operating system and includes a dropper, a launcher, and a backdoor.
-
New Linux Kernel Patch Allows Forcing a CPU Mitigation
Even when CPU mitigations can consume precious CPU cycles, it might not be a bad idea to allow users to enable them, even if your machine isn't vulnerable.
-
Red Hat Enterprise Linux 9.5 Released
Notify your friends, loved ones, and colleagues that the latest version of RHEL is available with plenty of enhancements.
-
Linux Sees Massive Performance Increase from a Single Line of Code
With one line of code, Intel was able to increase the performance of the Linux kernel by 4,000 percent.
-
Fedora KDE Approved as an Official Spin
If you prefer the Plasma desktop environment and the Fedora distribution, you're in luck because there's now an official spin that is listed on the same level as the Fedora Workstation edition.
-
New Steam Client Ups the Ante for Linux
The latest release from Steam has some pretty cool tricks up its sleeve.
-
Gnome OS Transitioning Toward a General-Purpose Distro
If you're looking for the perfectly vanilla take on the Gnome desktop, Gnome OS might be for you.