Expanding on a case study started in this post, at work we came across the need for a binary agent that we can deploy on devices to help with troubleshooting and management. Because we use all types of devices, including Android, iOS, Linux, and Windows, we needed a cross-compatible solution. Golang sounded like the perfect tool.
Prototyping quickly in Go, this post is a look at what goes into building something for Mac, Windows, Android, and Linux; as well as how easy Go makes it to access system information.
First, download and install Go from source, or using your favorite package manager. On Mac, that looks like this:
brew install go
There's some wizardry required to set up the environment Go expects. Create a directory somewhere and add it to your path (I'm using ~/_go
):
# ~/.bash_profile
export GOPATH="$HOME/_go"
export GOROOT="$(brew --prefix golang)/libexec"
And grab two optional packages, for taking screenshots and compiling for mobile:
go get github.com/kbinani/screenshot
go get golang.org/x/mobile/app
Now create a directory agent
(or literally anything else) inside tthe GOAPTH
, and create a file agent.go
inside of it.
Let's pull in all of the modules we'll need. The uncommented lines are for gather system information, building the data object, and making a web request to the API. The commented lines are for taking and encoding screenshots, and for building the app for android.
// ~/_go/src/agent/agent.go
import (
"fmt"
"io/ioutil"
"bytes"
"encoding/json"
"net/http"
"net"
"os"
// "golang.org/x/mobile/app"
// "github.com/kbinani/screenshot"
// "image/png"
// "encoding/base64"
)
The data structures look very similar to the models used in the Rails API. It keeps everything easier for me personally, but go crazy here.
type Nic struct {
Addr string `json:"ip_addr"`
Mac string `json:"mac_addr"`
Name string `json:"iface_name"`
}
type Device struct {
Id string `json:"id"`
Addrs []Nic `json:"ip_addrs"`
}
//// Get IPs
func externalIP() (Device, error) {
ifaces, err := net.Interfaces()
results := []Nic{}
device := Device{} // init new Nic struct
if err != nil {
return device, err
}
for _, iface := range ifaces {
nic := Nic{} // init new Nic struct
name, error := os.Hostname()
if error != nil {
return device, error
}
device.Id = name
if iface.Flags&net.FlagUp == 0 {
continue // interface down
}
if iface.Flags&net.FlagLoopback != 0 {
continue // loopback interface
}
addrs, err := iface.Addrs()
if err != nil {
return device, err
}
for _, addr := range addrs {
fmt.Println(iface)
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if ip == nil || ip.IsLoopback() {
continue
}
ip = ip.To4()
if ip == nil {
continue // not an ipv4 address
}
nic.Name = iface.Name
nic.Addr = ip.String()
nic.Mac = iface.HardwareAddr.String()
results = append(results,nic) // Append nic{} to []results
}
}
device.Addrs = results
return device, nil
}
If building for Mac or Windows:
func main() {
url := "http://localhost:3000/devices/"
fmt.Println("URL:>", url)
// UNCOMMENT IF NOT COMPILING FOR MOBILE
ip, err := externalIP()
if err != nil {
fmt.Println(err)
}
fmt.Println(ip)
// POST request to remote endpoint
j, _ := json.Marshal(ip)
var jsonStr = []byte(j)
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
req.Header.Set("X-Custom-Header", "myvalue")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
// Print Response
fmt.Println("response Status:", resp.Status)
fmt.Println("response Headers:", resp.Header)
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println("response Body:", string(body))
}
import (
"fmt"
"io/ioutil"
"bytes"
"encoding/json"
"net/http"
"net"
"os"
)
type Nic struct {
Addr string `json:"ip_addr"`
Mac string `json:"mac_addr"`
Name string `json:"iface_name"`
}
type Device struct {
Id string `json:"id"`
Addrs []Nic `json:"ip_addrs"`
}
func main() {
url := "http://localhost:3000/devices/"
fmt.Println("URL:>", url)
// UNCOMMENT IF NOT COMPILING FOR MOBILE
ip, err := externalIP()
if err != nil {
fmt.Println(err)
}
fmt.Println(ip)
// POST request to remote endpoint
j, _ := json.Marshal(ip)
var jsonStr = []byte(j)
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
req.Header.Set("X-Custom-Header", "myvalue")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
// Print Response
fmt.Println("response Status:", resp.Status)
fmt.Println("response Headers:", resp.Header)
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println("response Body:", string(body))
}
func externalIP() (Device, error) {
ifaces, err := net.Interfaces()
results := []Nic{}
device := Device{} // init new Nic struct
if err != nil {
return device, err
}
for _, iface := range ifaces {
nic := Nic{} // init new Nic struct
name, error := os.Hostname()
if error != nil {
return device, error
}
device.Id = name
if iface.Flags&net.FlagUp == 0 {
continue // interface down
}
if iface.Flags&net.FlagLoopback != 0 {
continue // loopback interface
}
addrs, err := iface.Addrs()
if err != nil {
return device, err
}
for _, addr := range addrs {
fmt.Println(iface)
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if ip == nil || ip.IsLoopback() {
continue
}
ip = ip.To4()
if ip == nil {
continue // not an ipv4 address
}
nic.Name = iface.Name
nic.Addr = ip.String()
nic.Mac = iface.HardwareAddr.String()
results = append(results,nic) // Append nic{} to []results
}
}
device.Addrs = results
return device, nil
}