Sonde réseaux en Go

Utilisation d'une base de donnée temporelle pour enregistrer des pings en Go.

Sonde réseaux en Go

Cet article vous présente le développement d'une sonde réseaux (ping vers des hosts) et la sauvegarde des résultats dans une base de donnée temporelle : Influx DB.

InfluxDB est le I dans l'offre TICK de InfluxData. Le T est l'outil d'acquisition des données, le I est pour le stockage, le C pour la restitution graphique (que nous utiliserons également) et le K pour la gestion des alertes.

1. Préparation de la base de donnée

1.1 Installation

Nous procédons à l'installation via Docker par le script ci-dessous:

#!/bin/sh

# Create a Network if not already done, by
# docker network create influxdb

# The first time, generate the default config file by running
# docker run --rm influxdb influxd config > conf/influxdb.conf

docker run      -d \
                -p 8086:8086 \
                --net=influxdb \
                -v $PWD/data:/var/lib/influxdb \
                -v $PWD/conf/influxdb.conf:/etc/influxdb/influxdb.conf:ro \
                --name influxdb \
                influxdb -config /etc/influxdb/influxdb.conf

Démarrez le script (après un chmod u+x) et verifiez que le script a bien tourné via $ docker logs -f influxdb et vous devriez avoir

8888888           .d888 888                   8888888b.  888888b.
   888            d88P"  888                   888  "Y88b 888  "88b
   888            888    888                   888    888 888  .88P
   888   88888b.  888888 888 888  888 888  888 888    888 8888888K.
   888   888 "88b 888    888 888  888  Y8bd8P' 888    888 888  "Y88b
   888   888  888 888    888 888  888   X88K   888    888 888    888
   888   888  888 888    888 Y88b 888 .d8""8b. 888  .d88P 888   d88P
 8888888 888  888 888    888  "Y88888 888  888 8888888P"  8888888P"

[I] 2017-12-19T12:15:54Z InfluxDB starting, version 1.4.2, branch 1.4, commit 6d2685d1738277a1c2672fc58df7994627769be6
(...)
[I] 2017-12-19T12:15:54Z Listening for signals

Si vous souhaitez garder la base de donnée après un restart, ajoutez dans le script de démarrage --restart=unless-stopped \.

1.2 Configuration

Nous créons maintenant un utilisateur et une base pour recuillir nos données.
On se connecte à la base de donnée en mode CLI :

$ docker exec -it influxdb bash
root@725a5f3e929c:/# influx
Connected to http://localhost:8086 version 1.4.2
InfluxDB shell version: 1.4.2
> show databases
name: databases
name
----
_internal

Il n'y a que la base de donnée interne.
Tapez alors

> CREATE DATABASE sonde
> CREATE USER usersonde WITH PASSWORD 'supermotdepasse'
> GRANT ALL ON sonde to usersonde

Placez vous alors dans la base créée et gardez une console ouverte que nous utiliserons pour vérifier les résultats.

> use sonde
Using database sonde
> show measurements
>

qui est vide pour l'instant.

Nous ajountons également une règle sur le temps de conservation des données à 30j. Les données plus vieilles seront automatiquement supprimées.

> create retention policy quatre_semaines on sonde duration 4w replication 1 default
> show retention policies
name            duration shardGroupDuration replicaN default
----            -------- ------------------ -------- -------
autogen         0s       168h0m0s           1        false
quatre_semaines 672h0m0s 24h0m0s            1        true

2. Développement du client InfluxDB en Golang

Il faut évidemment que votre environnement Go soit opérationnel.

2.1 Connexion à la base de donnée

Nous créons un exemple de client pour tester la connection à la base de donnée. Dans un fichier sonde.go

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/influxdata/influxdb/client/v2"
)

const (
	database = "sonde"
	username = "usersonde"
	password = "supermotdepasse"
)

type pingResult struct {
	fromLabel string
	toIP      string
	toLabel   string
	value     float64
}

func main() {
	// Test values
	pr := pingResult{"localhost", "8.8.8.8", "DNS Google", 1.25}

	// Create a new database connection
	c := influxDBClient()

	// prepare the batch point
	bp, err := client.NewBatchPoints(client.BatchPointsConfig{
		Database:  database,
		Precision: "s",
	})
	if err != nil {
		log.Fatalln(err)
	}

	tags := map[string]string{
		"fromLabel": pr.fromLabel,
		"toIP":      pr.toIP,
		"toLabel":   pr.toLabel,
	}
	fields := map[string]interface{}{"ping": pr.value}

	pt, err := client.NewPoint("ping", tags, fields, time.Now())
	bp.AddPoint(pt)

	if err := c.Write(bp); err != nil {
		log.Fatalln(err)
	}

	fmt.Println("Done")
}

func influxDBClient() client.Client {
	c, err := client.NewHTTPClient(client.HTTPConfig{
		Addr:     "http://localhost:8086",
		Username: username,
		Password: password,
	})
	if err != nil {
		log.Fatalln(err)
	}
	return c
}

Testons ensuite le bon fonctionnenment en executant le code via $ go run sonde.go. Il doit simplement afficher "Done".
Dans la console de la base de donnée, nous vérifions que les données sont bien présentes:

> show measurements
name: measurements
name
----
ping
> select * from ping
name: ping
time                fromLabel ping toIP    toLabel
----                --------- ---- ----    -------
1513687798000000000 localhost 1.25 8.8.8.8 DNS Google

Si vous ne savez pas faire la translation par coeur des dates et temps depuis le 1er Janvier 1970, vous pouvez relancer la console en ajoutant -precision rfc3339

root@725a5f3e929c:/# influx -precision rfc3339
Connected to http://localhost:8086 version 1.4.2
InfluxDB shell version: 1.4.2
> use sonde
Using database sonde
> select * from ping
name: ping
time                 fromLabel ping toIP    toLabel
----                 --------- ---- ----    -------
2017-12-19T12:49:58Z localhost 1.25 8.8.8.8 DNS Google

2.2 Fonction ping

Nous allons d'abord déplacer le code de la connexion dans un fichier à part pour plus de visibilité.
Copiez le fichier sonde en tant que pingdb.go par exemple et remplacez la fonction main() par

// Save a pingResult struct into InfluxDB
func Save(pr pingResult) {
	// Create a new database connection
	c := influxDBClient()
    ...

et nous créons également un fichier ping.go.
Nous allons utiliser la librairie fastping que l'on installe via $ go get github.com/tatsushid/go-fastping

Et dans le fichier ping.go, nous créons une fonction Ping qui prend une ipv4 en argument et qui retourne le temps de l'aller/retour (RTT):

package main

import (
	"fmt"
	"log"
	"net"
	"time"

	"github.com/tatsushid/go-fastping"
)

// Ping send a ping to the ip address
func Ping(ipAddToPing string) time.Duration {
	var res *time.Duration
	p := fastping.NewPinger()
	p.Network("ip")
	ra, err := net.ResolveIPAddr("ip4:icmp", ipAddToPing)
	if err != nil {
		log.Fatalln(err)
	}
	p.AddIPAddr(ra)

	p.OnRecv = func(addr *net.IPAddr, rtt time.Duration) {
		// fmt.Printf("IP Addr: %s receive, RTT: %v\n", addr.String(), rtt)
		res = &rtt
	}

	p.OnIdle = func() {
		// fmt.Println("finish")
	}
	err = p.Run()
	if err != nil {
		fmt.Println(err)
	}

	return *res
}

et voilà vos pings sauvegardés dans une base temporelle.

Resources