Network diagnostics with Go

First Round

On first entering the loop, the firstTime variable is set to true. Line 25 then returns the Pinging ... string to the caller via the ch channel, informing the caller that the test is still in progress. The Run() function in line 30 executes three pings to the network target specified in line 15 and blocks the program flow as long as the operation is running. If an error occurs, line 33 forwards it to the caller via the channel, and – after a 10-second pause – continue in line 35 starts the next round.

If there is a response to the ICMP packets sent, the network is obviously fine. The call to Statistics() in line 38 then retrieves the statistical data for the completed tests. The response times of each ping request are stored in stats.Rtts as an array slice of seconds in floating-point format. Line 39 unceremoniously bundles all three values into a string with the %v placeholder in the format string, and the same line immediately pushes this into the channel. The caller at the other end grabs the values and displays them in the graphical interface.

Connection OK?

When a WiFi client connects to the router, it is assigned an IP address, which it can display with commands like ifconfig. When you're troubleshooting, it helps to know if that worked. This is why the plugin from Listing 6 searches for local IP addresses on the network interfaces assigned by the operating system.

Listing 6

eth.go

¤¤nonumber
01 package main
02
03 import (
04  "net"
05  "sort"
06  "strings"
07  "time"
08 )
09
10 func nifs(arg ...string) chan string {
11  ch := make(chan string)
12
13  go func() {
14   for {
15    eths, err := ifconfig()
16
17    if err != nil {
18     ch <- err.Error()
19     time.Sleep(10 * time.Second)
20     continue
21    }
22
23    ch <- strings.Join(eths, ", ")
24    time.Sleep(10 * time.Second)
25   }
26  }()
27
28  return ch
29 }
30
31 func ifconfig() ([]string, error) {
32  var list []string
33  ifaces, err := net.Interfaces()
34  if err != nil {
35   return list, err
36  }
37
38  for _, iface := range ifaces {
39   addrs, err := iface.Addrs()
40   if err != nil {
41    return list, err
42   }
43
44   if len(addrs) == 0 {
45    continue
46   }
47
48   for _, addr := range addrs {
49    ip := strings.Split(addr.String(), "/")[0]
50    if net.ParseIP(ip).To4() != nil {
51     list = append(list, iface.Name+" "+ip)
52    }
53   }
54  }
55
56  sort.Strings(list)
57  return list, nil
58 }

The net package from the Go standard library offers the Interfaces() function, which returns all of the computer's network interfaces in line 33. For a laptop on a WiFi network, there are usually two interfaces: the WiFi adapter and the loopback interface. If your system is wired to the network, there are often more. Each of these interfaces, if connected, now has one or more IP addresses. Addrs() in line 39 fetches them; the for loop starting in line 48 checks them.

Hardly anyone in the US has IPv6 addresses at home. For this reason, line 50 filters out anything that doesn't look like IPv4 before appending the interface name (e.g., en0) and the IP address (without the subnet suffix) to the list array slice. Line 56 sorts all of them alphabetically, while line 57 returns it to the caller of the ifconfig() function in line 15.

The plugin works like all the others. Results such as error messages or successfully obtained IP address lists are fed into the channel as comma-separated strings, and the main program fields and displays incoming messages in the assigned table column. If there is an entry in the Ifconfig line of the terminal UI in the private IP range of 192.168.0.x, then – obviously – the connection to the router is working. If, on the other hand, only the loopback interface appears in the column, something is wrong with the assignment of the IP addresses, and you need to verify your DHCP settings.

Full Round Trip

Finally, Listing 7 provides an end-to-end test by loading the YouTube title page off the web. If this test also works, everything should be fine. Because it also measures the time taken to retrieve the page in seconds in the last line of the UI, you can guesstimate the speed of the ISP connection. Figure 1 shows that the page was loaded after 0.142 seconds in the test – perfect.

Listing 7

www.go

¤¤nonumber
01 package main
02
03 import (
04  "fmt"
05  "net/http"
06  "time"
07 )
08
09 func httpGet(arg ...string) chan string {
10  ch := make(chan string)
11
12  firstTime := true
13  go func() {
14   for {
15    if firstTime {
16     ch <- "Fetching ..."
17     firstTime = false
18    }
19
20    now := time.Now()
21    _, err := http.Get(arg[0])
22    if err != nil {
23     ch <- err.Error()
24     time.Sleep(10 * time.Second)
25     continue
26    }
27
28    dur := time.Since(now)
29    ch <- fmt.Sprintf("%.3f OK ", dur.Seconds())
30    time.Sleep(10 * time.Second)
31   }
32  }()
33
34  return ch
35 }

To obtain this number, Listing 7 in line 21 uses the Get() function to send an HTTP request; the function then blocks until the data arrives or the server returns an error. If the display in the table column gets stuck at Fetching ..., then something is wrong with the connection. In that case, the other tests should give you some clues to the cause. On the other hand, if the hostname resolution fails due to incorrect DNS configuration, line 23 pushes the error message into the provided channel, where the main program picks it up to show you the results.

If everything is working, line 28 measures how long the process took. To do this, it subtracts the start time of the request set in line 20 from the current time and pushes the resulting duration in seconds into the channel as a floating-point number. The value then appears with an OK message in the table column.

The three commands in Listing 8 create the wifi binary from the source code of the main program (Listing 4), the test plugins (Listings 5 to 7), the clock (Listing 1), and the GitHub packages and their dependencies. Calling the wifi binary starts the terminal UI and shows the network status. If needed, you can add DIY plugins following the same approach and display them in additional table rows.

Listing 8

build-wifi.sh

¤¤nonumber
$ go mod init wifi
$ go mod tidy
$ go build wifi.go clock.go eth.go ping.go www.go

Infos

  1. tview: https://github.com/rivo/tview
  2. Source code for this article: https://linuxnewmedia.thegood.cloud/s/5Rzx9tQW2FJ6N3Z
  3. Formatting date and time statements in Go: https://pkg.go.dev/time#pkg-constants

The Author

Mike Schilli works as a software engineer in the San Francisco Bay Area, California. Each month in his column, which has been running since 1997, he researches practical applications of various programming languages. If you email him at mailto:mschilli@perlmeister.com he will gladly answer any questions.

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