Network diagnostics with Go

Dr. Wireless

© Lead Image © Ewa Walicka, Fotolia.com

© Lead Image © Ewa Walicka, Fotolia.com

Article from Issue 275/2023
Author(s):

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.

Figure 1: The diagnostic wifi tool shows a working network.
Figure 2: In case of a network problem, wifi helps to isolate the cause.

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.

Figure 3: A stopwatch built with tview.

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

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • Housecleaning

    This month, Mike Schilli writes a quick terminal UI in Go to help track down space hogs on his hard disk.

  • At a Glance

    Using extensions in Go and Ruby, Mike Schilli adapts the WTF terminal dashboard tool to meet his personal needs.

  • Let's Go!

    Released back in 2012, Go flew under the radar for a long time until showcase projects such as Docker pushed its popularity. Today, Go has become the language of choice of many system programmers.

  • DIY Scoreboard

    We look at a broadcast video system network that uses Python code to control a video router and check out another program that creates a scoreboard.

  • Finding Processes

    How do you find a process running on a Linux system by start time? The question sounds trivial, but the answer is trickier than it first appears.

comments powered by Disqus
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.

Learn More

News