Locate Your Laptop With Go Lang Part 3


programming
guide go

Geolocation

Since we obtain client’s external IP address, we can add more useful feature such as geolocation to pinpoint the address of internet connection the laptop is connected on. In this program, we use a freely available api from freegeoip.net. It can give a rough estimate of our ‘missing’ laptop location. It can’t pinpoint to the exact address, you’d need a more comprehensive IP database (which will cost you money).

This is also a good way to introduce JSON with Go and how simple to use it. These codes are adapted from devdungeon.com

We start with a struct.

type GeoIP struct {
	// The right side is the name of the JSON variable
	Ip          string  `json:"ip"`
	CountryCode string  `json:"country_code"`
	CountryName string  `json:"country_name"`
	RegionCode  string  `json:"region_code"`
	RegionName  string  `json:"region_name"`
	City        string  `json:"city"`
	Zipcode     string  `json:"zipcode"`
	Lat         float32 `json:"latitude"`
	Lon         float32 `json:"longitude"`
	MetroCode   int     `json:"metro_code"`
	AreaCode    int     `json:"area_code"`
}

We define a struct type called GeoIP. The leftmost variable is the name of the field we are going to use in our upcoming GeoLookup function. Next, variable type. Lastly, the json data determined by the api provider.

func GeoLookup(address string) GeoIP {
	// Use freegeoip.net to get a JSON response
	// There is also /xml/ and /csv/ formats available
	response, err = http.Get("https://freegeoip.net/json/" + address)
	if err != nil {
		fmt.Println(err)
	}
	defer response.Body.Close()

	// response.Body() is a reader type. We have
	// to use ioutil.ReadAll() to read the data
	// in to a byte slice(string)
	body, err = ioutil.ReadAll(response.Body)
	if err != nil {
		fmt.Println(err)
	}

	// Unmarshal the JSON byte slice to a GeoIP struct
	err = json.Unmarshal(body, &geo)
	if err != nil {
		fmt.Println(err)
	}

	return geo
}

From runClient() function, we are going to call this GeoLookup() function by passing the client’s external IP address. The byte slice body, which is in json format, is ‘unwrapped’, matched with our GeoIP struct and referenced by geo variable (which is our return type). A simple one line err = json.Unmarshal(body, &geo) is what needed to make this happen.

Next, we need to update runClient() function to send this data to server.

...
for {
    var internalIP string = GetInternalIP()
    var externalIP string = GetExternalIP()

    geo := GeoLookup(externalIP)

    _, err := c.Write([]byte("\n\tExternal IP: " + externalIP +
        "\n\tInternal ip: " + internalIP +
        "\n\tCountry: " + geo.CountryName +
        "\n\tRegion: " + geo.RegionName +
        "\n\tCity: " + geo.City +
        "\n\tZipcode: " + geo.Zipcode +
        "\n\tLatitude: " + strconv.FormatFloat(float64(geo.Lat), 'f', -1, 64) +
        "\n\tLongitude: " + strconv.FormatFloat(float64(geo.Lon), 'f', -1, 64)))
    if err != nil {
        log.Fatal("write error:", err)
        break
    }
    time.Sleep(time.Duration(pollMinutes) * time.Minute)
}
...

Run Them

Build and run both server and client:

$ go build main.go
$ ./main

and

$ ./main -m client

And watch the client sends over our data inside log.txt

Friday, 11-Nov-16 11:01:14 AEDT 
	External IP: xxx.xxx.xxx.xxx
	Internal ip: 192.168.2.3
	Country: Australia
	Region: New South Wales
	City: xxxx
	Zipcode: xxxx
	Latitude: 99.99999999999999
	Longitude: 99.99999999999999

Full code is also available from my github account. git clone git@github.com:gmhafiz/go-locate-ip.git

package main

import (
	"fmt"
	"log"
	"net"
	"os"
	"time"
	"flag"
	"strings"
	"net/http"
	"io/ioutil"
	"encoding/json"
	"strconv"
)

const (
	CONN_TYPE = "tcp"
	CONN_PORT = ":8088" // any port >= 1024
)

var (
	newFile		*os.File
	filename	string = "log.txt"
	mode		string
	serverAddr	string
	serverPort	int = 8088
	pollMinutes	int = 10  // 10 minutes

	err      error
	geo      GeoIP
	response *http.Response
	body     []byte
)

type GeoIP struct {
	// The right side is the name of the JSON variable
	Ip          string  `json:"ip"`
	CountryCode string  `json:"country_code"`
	CountryName string  `json:"country_name"`
	RegionCode  string  `json:"region_code"`
	RegionName  string  `json:"region_name"`
	City        string  `json:"city"`
	Zipcode     string  `json:"zipcode"`
	Lat         float32 `json:"latitude"`
	Lon         float32 `json:"longitude"`
	MetroCode   int     `json:"metro_code"`
	AreaCode    int     `json:"area_code"`
}

/*
Geolocation
 */
func GeoLookup(address string) GeoIP {
	// Use freegeoip.net to get a JSON response
	// There is also /xml/ and /csv/ formats available
	response, err = http.Get("https://freegeoip.net/json/" + address)
	if err != nil {
		fmt.Println(err)
	}
	defer response.Body.Close()

	// response.Body() is a reader type. We have
	// to use ioutil.ReadAll() to read the data
	// in to a byte slice(string)
	body, err = ioutil.ReadAll(response.Body)
	if err != nil {
		fmt.Println(err)
	}

	// Unmarshal the JSON byte slice to a GeoIP struct
	err = json.Unmarshal(body, &geo)
	if err != nil {
		fmt.Println(err)
	}

	return geo
}

/*
======
Client
======
 */
func runClient() {
	fmt.Println("Running on client mode.")
	fmt.Println("Connecting to " + serverAddr)

	c, err := net.Dial("tcp", serverAddr)
	if err != nil {
		panic(err)
	}
	defer c.Close()
	for {
		var internalIP string = GetInternalIP()
		var externalIP string = GetExternalIP()

		//fmt.Println(GeoLookup(externalIP).CountryName)

		geo := GeoLookup(externalIP)

		_, err := c.Write([]byte("\n\tExternal IP: " + externalIP +
			"\n\tInternal ip: " + internalIP +
			"\n\tCountry: " + geo.CountryName +
			"\n\tRegion: " + geo.RegionName +
			"\n\tCity: " + geo.City +
			"\n\tZipcode: " + geo.Zipcode +
			"\n\tLatitude: " + strconv.FormatFloat(float64(geo.Lat), 'f', -1, 64) +
			"\n\tLongitude: " + strconv.FormatFloat(float64(geo.Lon), 'f', -1, 64)))
		if err != nil {
			log.Fatal("write error:", err)
			break
		}
		//time.Sleep(pollMinutes * time.Minute)
		time.Sleep(time.Duration(pollMinutes) * time.Minute)
	}
}

func GetInternalIP() string {
	conn, err := net.Dial("udp", "8.8.8.8:80")
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	localAddr := conn.LocalAddr().String()
	idx := strings.LastIndex(localAddr, ":")

	return localAddr[0:idx]
}

func GetExternalIP() string {
	response, err := http.Get("http://ipv4bot.whatismyipaddress.com")
	if err != nil {
		log.Fatal("404 not found", err.Error())
		os.Exit(1)
	}
	defer response.Body.Close()

	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		log.Fatal("http read error", err.Error())
		os.Exit(1)
	}
	src := string(body)

	return src
}

/*
======
Server
======
 */
func runServer() {
	fmt.Println("Running on server mode")

	listen, err := net.Listen(CONN_TYPE, CONN_PORT)
	if err != nil {
		log.Fatal("listen error:", err.Error())
	}
	defer listen.Close()

	currTime := time.Now().Format(time.RFC850)
	fmt.Println(currTime + ": Listening to incoming connections...")

	for {
		incoming, err := listen.Accept()
		if err != nil {
			log.Fatal("accept error:", err.Error())
		}

		go getIPFromClient(incoming)
	}
}

func getIPFromClient(conn net.Conn) {
	for {
		buf := make([]byte, 512)
		length, err := conn.Read(buf)
		if err != nil {
			log.Fatal("Error connecting" + err.Error())
			os.Exit(1)
		}

		ip := buf[0:length]
		currTime := time.Now().Format(time.RFC850)
		data := currTime + " " + string(ip)
		println("Server got:", string(data))
		appendToLog(data)  // Write incoming message into a log file.
	}
	conn.Close()
}

/*
=========
File IO
=========
 */
func appendToLog(src string) {
	createFile(filename)
	file, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0600)
	if err != nil {

		panic(err.Error())
	}
	defer file.Close()

	if _, err = file.WriteString(src + "\n"); err != nil {
		panic(err.Error())
	}
}

func createFile(fileName string) {
	if _, err := os.Stat(fileName); os.IsNotExist(err) {
		newFile, err = os.Create(fileName)
		if err != nil {
			log.Fatal(err)
		}
		newFile.Close()
	}
}

/*
===============
Initialisation
===============
 */
func init() {
	flag.StringVar(&mode, "m", "server", "Mode: -m server|client")
	flag.StringVar(&serverAddr, "a", "0.0.0.0:8088", "Server address: -a 127.0.0.1")
	flag.IntVar(&serverPort, "p", serverPort, "Server port: -p 8088")
	flag.IntVar(&pollMinutes, "t", pollMinutes, "Poll interval (minutes): -t 10")
}

/*
============
Entry
============
 */
func main() {
	flag.Parse()
	if mode == "server" || mode == "s" {
		runServer()
		return
	} else if mode == "client" || mode == "c" {
		runClient()
		return
	} else {
		fmt.Println("No recognisable flag")
	}
}

Credits / References

comments powered by Disqus