Compare commits

..

1 Commits

Author SHA1 Message Date
Martin/Geno
2b7bfd724d
[TASK] add respond(d) daemon - WIP 2019-01-25 14:39:44 +01:00
38 changed files with 504 additions and 859 deletions

2
.gitignore vendored
View File

@ -24,5 +24,5 @@ _testmain.go
*.prof
webroot
/config.toml
/config-respondd.toml
/vendor
/bin

View File

@ -6,7 +6,7 @@ stages:
before_script:
- curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
- mkdir -p /go/src/github.com/FreifunkBremen/
- cp -R /builds/freifunkbremen/yanic /go/src/github.com/FreifunkBremen/yanic
- cp -R /builds/FreifunkBremen/yanic /go/src/github.com/FreifunkBremen/yanic
- cd /go/src/github.com/FreifunkBremen/yanic
- dep ensure
@ -14,13 +14,9 @@ build-my-project:
stage: build
script:
- go install -ldflags "-X github.com/FreifunkBremen/yanic/cmd.VERSION=`git -C $GOPATH/src/github.com/FreifunkBremen/yanic rev-parse HEAD`" github.com/FreifunkBremen/yanic
- mkdir /builds/freifunkbremen/yanic/bin/
- cp /go/bin/yanic /builds/freifunkbremen/yanic/bin/yanic
artifacts:
paths:
- bin/yanic
- config_example.toml
- config-respondd_example.toml
- /go/bin/yanic
test-my-project:
stage: test
@ -28,7 +24,6 @@ test-my-project:
- ./.circleci/check-gofmt
- ./.circleci/check-testfiles
- go test $(go list ./... | grep -v /vendor/) -v -coverprofile .testCoverage.txt
- go tool cover -func=.testCoverage.txt
test-race-my-project:
stage: test

82
Gopkg.lock generated
View File

@ -2,28 +2,12 @@
[[projects]]
digest = "1:af6e785bedb62fc2abb81977c58a7a44e5cf9f7e41b8d3c8dd4d872edea0ce08"
digest = "1:0803645e1f57fb5271a6edc7570b9ea59bac2e5de67957075a43f3d74c8dbd97"
name = "github.com/NYTimes/gziphandler"
packages = ["."]
pruneopts = "UT"
revision = "dd0439581c7657cb652dfe5c71d7d48baf39541d"
version = "v1.1.1"
[[projects]]
digest = "1:616fe3fc0d5eb7b89324dc5e47aef8cf05f3b0aed0bfe908e62516da4c638647"
name = "github.com/bdlm/log"
packages = ["."]
pruneopts = "UT"
revision = "b8b76dd35f1d7fdf6a49497c8ac22e6554efffed"
version = "v0.1.18"
[[projects]]
branch = "master"
digest = "1:9da00d14e7f20473d129be028aa99a2f47859aed17e58cd24c5108df76ad3959"
name = "github.com/bdlm/std"
packages = ["logger"]
pruneopts = "UT"
revision = "fd3b596111c78b7d14f1e1308ebdb1153013f1a8"
revision = "2600fb119af974220d3916a5916d6e31176aac1b"
version = "v1.0.1"
[[projects]]
digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec"
@ -49,17 +33,35 @@
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
version = "v1.0"
[[projects]]
digest = "1:e0123d1a40d588fb2d5f98188927826ed5ddfa1d50e759596a6c774f71b81358"
name = "github.com/influxdata/influxdb"
packages = ["pkg/escape"]
pruneopts = "UT"
revision = "698dbc789aff13c2678357a6b93ff73dd7136571"
version = "v1.7.3"
[[projects]]
branch = "master"
digest = "1:50708c8fc92aec981df5c446581cf9f90ba9e2a5692118e0ce75d4534aaa14a2"
digest = "1:22a9593272bc74ef4c513e176608eeed2dee7e8bd7542bae40369cb62d907a3b"
name = "github.com/influxdata/influxdb1-client"
packages = [
"models",
"pkg/escape",
"v2",
]
pruneopts = "UT"
revision = "8ff2fc3824fcb533795f9a2f233275f0bb18d6c5"
revision = "e237d47d0fc87c8ba2b7c98c349cdceca9c37e27"
[[projects]]
branch = "master"
digest = "1:ac6b73796941a7ab33ec69fc32f9886a6e7506998f822e3d5205377aee77284a"
name = "github.com/influxdata/platform"
packages = [
"models",
"pkg/escape",
]
pruneopts = "UT"
revision = "98469bf07613ffae6f025893761c1e7a5e96e4aa"
[[projects]]
digest = "1:b56c589214f01a5601e0821387db484617392d0042f26234bf2da853a2f498a1"
@ -80,14 +82,6 @@
revision = "e6f5723bf2a66af014955e0888881314cf294129"
version = "v0.1.1"
[[projects]]
digest = "1:36cfc0b6facbc68c92a4d55f32eff22b872a899ab75c384ac24a08dd33bc1380"
name = "github.com/paulmach/go.geojson"
packages = ["."]
pruneopts = "UT"
revision = "a5c451da2d9c67c590ae6be92565b67698286a1a"
version = "v1.4"
[[projects]]
digest = "1:cf31692c14422fa27c83a05292eb5cbe0fb2775972e8f1f8446a71549bd8980b"
name = "github.com/pkg/errors"
@ -105,12 +99,12 @@
version = "v1.0.0"
[[projects]]
digest = "1:22799aea8fe96dd5693abdd1eaa14b1b29e3eafbdc7733fa155b3cb556c8a7ae"
digest = "1:645cabccbb4fa8aab25a956cbcbdf6a6845ca736b2c64e197ca7cbb9d210b939"
name = "github.com/spf13/cobra"
packages = ["."]
pruneopts = "UT"
revision = "67fc4837d267bc9bfd6e47f77783fcc3dffc68de"
version = "v0.0.4"
revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385"
version = "v0.0.3"
[[projects]]
digest = "1:c1b1102241e7f645bc8e0c22ae352e8f0dc6484b6cb4d132fa9f24174e0119e2"
@ -128,37 +122,15 @@
revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053"
version = "v1.3.0"
[[projects]]
branch = "master"
digest = "1:bbe51412d9915d64ffaa96b51d409e070665efc5194fcf145c4a27d4133107a4"
name = "golang.org/x/crypto"
packages = ["ssh/terminal"]
pruneopts = "UT"
revision = "22d7a77e9e5f409e934ed268692e56707cd169e5"
[[projects]]
branch = "master"
digest = "1:160e165ef13917405d1ebfde865cb9c292b8635f83655e3932662d5586d300ee"
name = "golang.org/x/sys"
packages = [
"unix",
"windows",
]
pruneopts = "UT"
revision = "c432e742b0af385916e013f6a34e9e73d139cf82"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/NYTimes/gziphandler",
"github.com/bdlm/log",
"github.com/bdlm/std/logger",
"github.com/fgrosse/graphigo",
"github.com/influxdata/influxdb1-client/models",
"github.com/influxdata/influxdb1-client/v2",
"github.com/naoina/toml",
"github.com/paulmach/go.geojson",
"github.com/pkg/errors",
"github.com/spf13/cobra",
"github.com/stretchr/testify/assert",

View File

@ -5,7 +5,7 @@
### Install
```sh
cd /usr/local/
wget https://dl.google.com/go/go1.13.1.linux-amd64.tar.gz -O go-release-linux-amd64.tar.gz
wget https://storage.googleapis.com/golang/go1.9.1.linux-amd64.tar.gz -O go-release-linux-amd64.tar.gz
tar xvf go-release-linux-amd64.tar.gz
rm go-release-linux-amd64.tar.gz
```
@ -62,15 +62,7 @@ the same directory as the `dataPath`. Change the path in the section
```sh
cp /opt/go/src/github.com/FreifunkBremen/yanic/contrib/init/linux-systemd/yanic.service /lib/systemd/system/yanic.service
systemctl daemon-reload
```
Before start, you should configure yanic by the file `/etc/yanic.conf`:
```sh
systemctl start yanic
```
Enable to start on boot:
```sh
systemctl enable yanic
```

View File

@ -19,25 +19,25 @@ Yet another node info collector
* Provide a little webserver for a standalone installation with a meshviewer
## How it works
In the first step Yanic sends a multicast message to the group `ff05::2:1001` and port `1001`.
In the first step Yanic sends a multicast message to the group `ff02:0:0:0:0:0:2:1001` and port `1001`.
Recently seen nodes that does not reply are requested via a unicast message.
## Documentation
Take a look at the [git](https://github.com/FreifunkBremen/yanic/blob/master/SUMMARY.md) or [Gitbook](https://freifunkbremen.gitbooks.io/yanic/content/)
# Installation
Take a look into the Documentation (see above) or for Quick Overview in [INSTALL.md](INSTALL.md).
If you like Docker you may want to take a look [here](https://github.com/christf/docker-yanic).
## Configuration
Read comments in [config_example.toml](config_example.toml) for more information.
## Running
Yanic provides several commands:
### Usage
Run Yanic without any arguments to get the usage information:
```
Usage:
yanic [command]
@ -49,72 +49,58 @@ Available Commands:
serve Runs the yanic server
Flags:
-h, --help help for yanic
--loglevel uint32 Show log message starting at level (default 40)
--timestamps Enables timestamps for log output
-h, --help help for yanic
--timestamps Enables timestamps for log output
Use "yanic [command] --help" for more information about a command.
```
#### Serve
Runs the yanic server
```
Usage:
yanic serve [flags]
Examples:
yanic serve --config /etc/yanic.toml
yanic serve --config /etc/yanic.toml
Flags:
-c, --config string Path to configuration file (default "config.toml")
-h, --help help for serve
Global Flags:
--loglevel uint32 Show log message starting at level (default 40)
--timestamps Enables timestamps for log output
```
#### Query
Sends a query on the interface to the destination and waits for a response
```
Usage:
yanic query <interfaces> <destination> [flags]
Examples:
yanic query "eth0,wlan0" "fe80::eade:27ff:dead:beef"
Flags:
-h, --help help for query
--ip string ip address which is used for sending (optional - without definition used the link-local address)
--port int define a port to listen (if not set or set to 0 the kernel will use a random free port at its own)
--wait int Seconds to wait for a response (default 1)
Global Flags:
--loglevel uint32 Show log message starting at level (default 40)
--timestamps Enables timestamps for log output
```
#### Import
Imports global statistics from the given RRD files (ffmap-backend).
```
Usage:
yanic import <file.rrd> <site> <domain> [flags]
yanic import <file.rrd> [flags]
Examples:
yanic import --config /etc/yanic.toml olddata.rrd global global
yanic import --config /etc/yanic.toml olddata.rrd
Flags:
-c, --config string Path to configuration file (default "config.toml")
-h, --help help for import
Global Flags:
--loglevel uint32 Show log message starting at level (default 40)
--timestamps Enables timestamps for log output
```
#### Query
```
Usage:
yanic query <interface> <destination> [flags]
Examples:
yanic query wlan0 "fe80::eade:27ff:dead:beef"
Flags:
-h, --help help for query
--wait int Seconds to wait for a response (default 1)
```
## Communities using Yanic
* **Freifunk Bremen** uses InfluxDB, [Grafana](https://grafana.bremen.freifunk.net), and [Meshviewer](https://map.bremen.freifunk.net) with a patch to show state-version of `nodes.json`.
* **Freifunk Nord** uses [hopglass](https://github.com/hopglass/hopglass) (commit 587740a) as frontend: https://mesh.freifunknord.de/
* **Freifunk Kiel** uses [Meshviewer](https://github.com/ffrgb/meshviewer/) as frontend: https://map.freifunk.in-kiel.de/
@ -122,6 +108,7 @@ Global Flags:
Do you know someone else using Yanic? Create a [pull request](https://github.com/FreifunkBremen/yanic/issues/new?template=community.md&title=Mention+community+$name)!
## Related projects
Collecting data from respondd:
* [HopGlass Server](https://github.com/plumpudding/hopglass-server) written in Node.js
@ -131,4 +118,5 @@ Respondd for servers:
## License
This software is licensed under the terms of the [AGPL v3 License](LICENSE).

View File

@ -1,54 +1,31 @@
package cmd
import (
"fmt"
"io/ioutil"
"os"
"github.com/naoina/toml"
"github.com/FreifunkBremen/yanic/database"
"github.com/FreifunkBremen/yanic/respond"
"github.com/FreifunkBremen/yanic/runtime"
"github.com/FreifunkBremen/yanic/webserver"
)
// Config represents the whole configuration
type Config struct {
Respondd respond.Config
Webserver webserver.Config
Nodes runtime.NodesConfig
Database database.Config
}
var (
configPath string
collector *respond.Collector
nodes *runtime.Nodes
)
func loadConfig() *Config {
config, err := ReadConfigFile(configPath)
if err != nil {
fmt.Fprintln(os.Stderr, "unable to load config file:", err)
os.Exit(2)
}
return config
}
// ReadConfigFile reads a config model from path of a yml file
func ReadConfigFile(path string) (config *Config, err error) {
config = &Config{}
func ReadConfigFile(path string, config interface{}) error {
file, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
return err
}
err = toml.Unmarshal(file, config)
if err != nil {
return nil, err
return err
}
return
return nil
}

View File

@ -10,7 +10,9 @@ import (
func TestReadConfig(t *testing.T) {
assert := assert.New(t)
config, err := ReadConfigFile("../config_example.toml")
config := &ServeConfig{}
err := ReadConfigFile("../config_example.toml", config)
assert.NoError(err)
assert.NotNil(config)
@ -25,7 +27,7 @@ func TestReadConfig(t *testing.T) {
assert.Contains(config.Respondd.Sites["ffhb"].Domains, "city")
// Test output plugins
assert.Len(config.Nodes.Output, 4)
assert.Len(config.Nodes.Output, 3)
outputs := config.Nodes.Output["meshviewer"].([]interface{})
assert.Len(outputs, 1)
meshviewer := outputs[0]
@ -40,11 +42,11 @@ func TestReadConfig(t *testing.T) {
},
}, meshviewer)
_, err = ReadConfigFile("testdata/config_invalid.toml")
err = ReadConfigFile("testdata/config_invalid.toml", config)
assert.Error(err, "not unmarshalable")
assert.Contains(err.Error(), "invalid TOML syntax")
_, err = ReadConfigFile("testdata/adsa.toml")
err = ReadConfigFile("testdata/adsa.toml", config)
assert.Error(err, "not found able")
assert.Contains(err.Error(), "no such file or directory")
}

View File

@ -19,7 +19,10 @@ var importCmd = &cobra.Command{
path := args[0]
site := args[1]
domain := args[2]
config := loadConfig()
config := &ServeConfig{}
if err := ReadConfigFile(configPath, config); err != nil {
log.Panicf("unable to load config file: %s", err)
}
err := allDatabase.Start(config.Database)
if err != nil {

40
cmd/respondd.go Normal file
View File

@ -0,0 +1,40 @@
package cmd
import (
"os"
"os/signal"
"syscall"
"github.com/bdlm/log"
"github.com/spf13/cobra"
"github.com/FreifunkBremen/yanic/respond/daemon"
)
// serveCmd represents the serve command
var responddCMD = &cobra.Command{
Use: "respondd",
Short: "Runs a respond daemon",
Example: "yanic respondd --config /etc/respondd.toml",
Run: func(cmd *cobra.Command, args []string) {
daemon := &respondd.Daemon{}
if err := ReadConfigFile(configPath, daemon); err != nil {
log.Panicf("unable to load config file: %s", err)
}
go daemon.Start()
log.Info("respondd daemon started")
// Wait for INT/TERM
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
sig := <-sigs
log.Infof("received %s", sig)
},
}
func init() {
RootCmd.AddCommand(responddCMD)
responddCMD.Flags().StringVarP(&configPath, "config", "c", "config-respondd.toml", "Path to configuration file")
}

View File

@ -9,6 +9,7 @@ import (
"github.com/bdlm/log"
"github.com/spf13/cobra"
"github.com/FreifunkBremen/yanic/database"
allDatabase "github.com/FreifunkBremen/yanic/database/all"
allOutput "github.com/FreifunkBremen/yanic/output/all"
"github.com/FreifunkBremen/yanic/respond"
@ -16,16 +17,26 @@ import (
"github.com/FreifunkBremen/yanic/webserver"
)
// Config represents the whole configuration
type ServeConfig struct {
Respondd respond.Config
Webserver webserver.Config
Nodes runtime.NodesConfig
Database database.Config
}
// serveCmd represents the serve command
var serveCmd = &cobra.Command{
Use: "serve",
Short: "Runs the yanic server",
Example: "yanic serve --config /etc/yanic.toml",
Run: func(cmd *cobra.Command, args []string) {
config := loadConfig()
config := &ServeConfig{}
if err := ReadConfigFile(configPath, config); err != nil {
log.Panicf("unable to load config file: %s", err)
}
err := allDatabase.Start(config.Database)
if err != nil {
if err := allDatabase.Start(config.Database); err != nil {
log.Panicf("could not connect to database: %s", err)
}
defer allDatabase.Close()
@ -33,8 +44,7 @@ var serveCmd = &cobra.Command{
nodes = runtime.NewNodes(&config.Nodes)
nodes.Start()
err = allOutput.Start(nodes, config.Nodes)
if err != nil {
if err := allOutput.Start(nodes, config.Nodes); err != nil {
log.Panicf("error on init outputs: %s", err)
}
defer allOutput.Close()

View File

@ -0,0 +1,15 @@
# how ofter the cache respond of a respondd request is calculated
data_interval = "3m"
# if set true, respond will contain data from batman interface
multi_instance = false
[[listen]]
address = "ff02::2:1001"
interface = ""
port = 1001
# manuelle data for respond
[data.nodeinfo.location]
latitude = 53.112446246
longitude = 8.734087944

View File

@ -1,6 +1,6 @@
# This is the config file for Yanic written in "Tom's Obvious, Minimal Language."
# syntax: https://github.com/toml-lang/toml
# (if you need something multiple times, checkout out the [[array of table]] section)
# (if you need somethink multiple times, checkout out the [[array of table]] section)
# Send respondd request to update information
[respondd]
@ -23,14 +23,14 @@ domains = ["city"]
# name of interface on which this collector is running
ifname = "br-ffhb"
# ip address which is used for sending
# (optional - without definition used a address of ifname - preferred link local)
# (optional - without definition used a address of ifname)
ip_address = "fd2f:5119:f2d::5"
# disable sending multicast respondd request
# (for receiving only respondd packages e.g. database respondd)
#send_no_request = false
# multicast address to destination of respondd
# (optional - without definition used default ff05::2:1001)
#multicast_address = "ff02::2:1001"
# (optional - without definition used batman default ff02::2:1001)
multicast_address = "ff05::2:1001"
# define a port to listen
# if not set or set to 0 the kernel will use a random free port at its own
#port = 10001
@ -93,11 +93,6 @@ offline_after = "10m"
#longitude_max = 39.72
# outputs all nodes as points into nodes.geojson
[[nodes.output.geojson]]
enable = true
path = "/var/www/html/meshviewer/data/nodes.geojson"
# definition for the new more compressed meshviewer.json
[[nodes.output.meshviewer-ffrgb]]
enable = true

View File

@ -0,0 +1,13 @@
[Unit]
Description=yanic
[Service]
Type=simple
User=yanic
ExecStart=/opt/go/bin/yanic respondd --config /etc/respondd.conf
Restart=always
RestartSec=5s
Environment=PATH=/usr/bin:/usr/local/bin
[Install]
WantedBy=multi-user.target

View File

@ -2,7 +2,7 @@ package data
// ResponseData struct
type ResponseData struct {
Neighbours *Neighbours `json:"neighbours"`
Nodeinfo *Nodeinfo `json:"nodeinfo"`
Statistics *Statistics `json:"statistics"`
Nodeinfo *Nodeinfo `json:"nodeinfo" toml:"nodeinfo"`
Statistics *Statistics `json:"statistics" toml:"statistics"`
Neighbours *Neighbours `json:"neighbours" toml:"neighbours"`
}

View File

@ -30,36 +30,25 @@ func (c *Connection) InsertNode(node *runtime.Node) {
fields = append(fields, graphigo.Metric{Name: node_prefix + "." + name, Value: value})
}
vpnInterfaces := make(map[string]bool)
for _, mIface := range nodeinfo.Network.Mesh {
for _, tunnel := range mIface.Interfaces.Tunnel {
vpnInterfaces[tunnel] = true
}
}
if neighbours := node.Neighbours; neighbours != nil {
vpn := 0
if meshvpn := stats.MeshVPN; meshvpn != nil {
for _, group := range meshvpn.Groups {
for _, link := range group.Peers {
if link != nil && link.Established > 1 {
vpn++
}
}
}
}
addField("neighbours.vpn", vpn)
// protocol: Batman Advance
batadv := 0
for mac, batadvNeighbours := range neighbours.Batadv {
for _, batadvNeighbours := range neighbours.Batadv {
batadv += len(batadvNeighbours.Neighbours)
if _, ok := vpnInterfaces[mac]; ok {
vpn += len(batadvNeighbours.Neighbours)
}
}
addField("neighbours.batadv", batadv)
// protocol: Babel
babel := 0
for _, babelNeighbours := range neighbours.Babel {
babel += len(babelNeighbours.Neighbours)
if _, ok := vpnInterfaces[babelNeighbours.LinkLocalAddress]; ok {
vpn += len(babelNeighbours.Neighbours)
}
}
addField("neighbours.babel", babel)
// protocol: LLDP
lldp := 0
for _, lldpNeighbours := range neighbours.LLDP {
@ -67,10 +56,8 @@ func (c *Connection) InsertNode(node *runtime.Node) {
}
addField("neighbours.lldp", lldp)
addField("neighbours.vpn", vpn)
// total is the sum of all protocols
addField("neighbours.total", batadv+babel+lldp)
addField("neighbours.total", batadv+lldp)
}
if t := stats.Traffic.Rx; t != nil {

View File

@ -48,15 +48,7 @@ func (conn *Connection) InsertNode(node *runtime.Node) {
"memory.available": stats.Memory.Available,
}
vpnInterfaces := make(map[string]bool)
if nodeinfo := node.Nodeinfo; nodeinfo != nil {
for _, mIface := range nodeinfo.Network.Mesh {
for _, tunnel := range mIface.Interfaces.Tunnel {
vpnInterfaces[tunnel] = true
}
}
tags.SetString("hostname", nodeinfo.Hostname)
if nodeinfo.System.SiteCode != "" {
tags.SetString("site", nodeinfo.System.SiteCode)
@ -83,30 +75,28 @@ func (conn *Connection) InsertNode(node *runtime.Node) {
}
}
if neighbours := node.Neighbours; neighbours != nil {
// VPN Neighbours are Neighbours but includet in one protocol
vpn := 0
if meshvpn := stats.MeshVPN; meshvpn != nil {
for _, group := range meshvpn.Groups {
for _, link := range group.Peers {
if link != nil && link.Established > 1 {
vpn++
}
}
}
}
fields["neighbours.vpn"] = vpn
// protocol: Batman Advance
batadv := 0
for mac, batadvNeighbours := range neighbours.Batadv {
for _, batadvNeighbours := range neighbours.Batadv {
batadv += len(batadvNeighbours.Neighbours)
if _, ok := vpnInterfaces[mac]; ok {
vpn += len(batadvNeighbours.Neighbours)
}
}
fields["neighbours.batadv"] = batadv
// protocol: Babel
babel := 0
for _, babelNeighbours := range neighbours.Babel {
babel += len(babelNeighbours.Neighbours)
if _, ok := vpnInterfaces[babelNeighbours.LinkLocalAddress]; ok {
vpn += len(babelNeighbours.Neighbours)
}
}
fields["neighbours.babel"] = babel
// protocol: LLDP
lldp := 0
for _, lldpNeighbours := range neighbours.LLDP {
@ -114,11 +104,8 @@ func (conn *Connection) InsertNode(node *runtime.Node) {
}
fields["neighbours.lldp"] = lldp
// vpn wait for babel
fields["neighbours.vpn"] = vpn
// total is the sum of all protocols
fields["neighbours.total"] = batadv + babel + lldp
fields["neighbours.total"] = batadv + lldp
}
if procstat := stats.ProcStats; procstat != nil {
fields["stat.cpu.user"] = procstat.CPU.User

View File

@ -39,6 +39,18 @@ func TestToInflux(t *testing.T) {
MgmtTx: &data.Traffic{Packets: 2327},
MgmtRx: &data.Traffic{Bytes: 2331},
},
MeshVPN: &data.MeshVPN{
Groups: map[string]*data.MeshVPNPeerGroup{
"ffhb": {
Peers: map[string]*data.MeshVPNPeerLink{
"vpn01": {Established: 3},
"vpn02": {},
"trash": nil,
"vpn03": {Established: 0},
},
},
},
},
},
Nodeinfo: &data.Nodeinfo{
NodeID: "deadbeef",
@ -55,17 +67,6 @@ func TestToInflux(t *testing.T) {
},
Network: data.Network{
Mac: "DEADMAC",
Mesh: map[string]*data.NetworkInterface{
"bat0": {
Interfaces: struct {
Wireless []string `json:"wireless,omitempty"`
Other []string `json:"other,omitempty"`
Tunnel []string `json:"tunnel,omitempty"`
}{
Tunnel: []string{"a-interface-mac", "fe80::1"},
},
},
},
},
Software: data.Software{
Autoupdater: struct {
@ -80,7 +81,7 @@ func TestToInflux(t *testing.T) {
Neighbours: &data.Neighbours{
NodeID: "deadbeef",
Batadv: map[string]data.BatadvNeighbours{
"a-interface-mac": {
"a-interface": {
Neighbours: map[string]data.BatmanLink{
"BAFF1E5": {
Tq: 204,
@ -88,18 +89,8 @@ func TestToInflux(t *testing.T) {
},
},
},
Babel: map[string]data.BabelNeighbours{
"wg-01": {
LinkLocalAddress: "fe80::1",
Neighbours: map[string]data.BabelLink{
"fe80::2": {
Cost: 0,
},
},
},
},
LLDP: map[string]data.LLDPNeighbours{
"b-interface-mac": {},
"b-interface": {},
},
},
}
@ -153,10 +144,9 @@ func TestToInflux(t *testing.T) {
assert.EqualValues("city", tags["domain"])
assert.EqualValues(0.5, fields["load"])
assert.EqualValues(0, fields["neighbours.lldp"])
assert.EqualValues(1, fields["neighbours.babel"])
assert.EqualValues(1, fields["neighbours.batadv"])
assert.EqualValues(2, fields["neighbours.vpn"])
assert.EqualValues(2, fields["neighbours.total"])
assert.EqualValues(1, fields["neighbours.vpn"])
assert.EqualValues(1, fields["neighbours.total"])
assert.EqualValues(uint32(3), fields["wireless.txpower24"])
assert.EqualValues(uint32(5500), fields["airtime11a.frequency"])
@ -175,7 +165,7 @@ func TestToInflux(t *testing.T) {
assert.EqualValues("link", nPoint.Name())
assert.EqualValues(map[string]string{
"source.id": "deadbeef",
"source.addr": "a-interface-mac",
"source.addr": "a-interface",
"target.id": "foobar",
"target.addr": "BAFF1E5",
}, tags)

View File

@ -52,7 +52,7 @@ synchronize = "1m"
{% method %}
How often send request per respondd.
It will send UDP packets with multicast address `ff05::2:1001` and port `1001`.
It will send UDP packets with multicast address `ff02::2:1001` and port `1001`.
If a node does not answer after the half time, it will request with the last know address under the port `1001`.
{% sample lang="toml" %}
```toml
@ -110,7 +110,7 @@ ifname = "br-ffhb"
{% method %}
ip address is the own address which is used for sending.
If not set or set with empty string it will take an address of ifname.
(It prefers the link local address, so at babel mesh-network it should be configurated)
(If `multicast_address` is not set the link local address otherwise a global unicast address)
{% sample lang="toml" %}
```toml
ip_address = "fe80::..."
@ -130,8 +130,8 @@ send_no_request = true
### multicast_address
{% method %}
Multicast address to destination of respondd.
If not set or set with empty string it will take the batman default multicast address `ff05::2:1001`
(Needed to set for legacy `ff02::2:1001`)
If not set or set with empty string it will take the batman default multicast address `ff02::2:1001`
(Needed in babel for a mesh-network wide routeable multicast addreess `ff05::2:1001`)
{% sample lang="toml" %}
```toml
multicast_address = "ff02::2:1001"
@ -387,32 +387,6 @@ longitude_max = 39.72
## [[nodes.output.geojson]]
{% method %}
The geojson output produces a geojson file which contains the location data of all monitored nodes to be used to visualize the location of the nodes.
It is optimized to be used with [UMap](https://github.com/umap-project/umap) but should work with other tools as well.
Here is a public demo provided by Freifunk Muenchen: http://u.osmfr.org/m/328494/
{% sample lang="toml" %}
```toml
[[nodes.output.geojson]]
enable = true
path = "/var/www/html/meshviewer/data/nodes.geojson"
```
{% endmethod %}
### path
{% method %}
The path, where to store nodes.geojson
{% sample lang="toml" %}
```toml
path = "/var/www/html/meshviewer/data/nodes.geojson"
```
{% endmethod %}
## [[nodes.output.meshviewer-ffrgb]]
{% method %}
The new json file format for the [meshviewer](https://github.com/ffrgb/meshviewer) developed in Regensburg.
@ -521,21 +495,6 @@ path = "/var/www/html/meshviewer/data/nodelist.json"
```
{% endmethod %}
## [[nodes.output.raw]]
{% method %}
This output takes the respondd response as sent by the node and includes it in a JSON document.
{% endmethod %}
### path
{% method %}
The path, where to store raw.json
{% sample lang="toml" %}
```toml
path = "/var/www/html/meshviewer/data/raw.json"
```
{% endmethod %}
## [database]

View File

@ -5,13 +5,14 @@
### Install
```sh
cd /usr/local/
wget https://dl.google.com/go/go1.13.1.linux-amd64.tar.gz -O go-release-linux-amd64.tar.gz
wget https://storage.googleapis.com/golang/go1.9.1.linux-amd64.tar.gz -O go-release-linux-amd64.tar.gz
tar xvf go-release-linux-amd64.tar.gz
rm go-release-linux-amd64.tar.gz
```
### Configure go
Add these lines in your root shell startup file (e.g. `/root/.bashrc`):
```sh
export GOPATH=/opt/go
export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin
@ -33,12 +34,14 @@ systemctl daemon-reload
```
Before start, you should configure yanic by the file `/etc/yanic.conf`:
```sh
```
systemctl start yanic
```
Enable to start on boot:
```sh
```
systemctl enable yanic
```

View File

@ -7,7 +7,7 @@ A little overview of yanic in connection with other software:
It sends the `gluon-neighbour-info` request and collects the answers.
It will send UDP packets with multicast address `ff05:0:0:0:0:0:2:1001` and port `1001`.
It will send UDP packets with multicast address `ff02:0:0:0:0:0:2:1001` and port `1001`.
If a node does not answer, it will request with the last know address under the port `1001`.

View File

@ -9,9 +9,9 @@ import (
"github.com/FreifunkBremen/yanic/cmd"
)
type hook struct{}
type Hook struct{}
func (h *hook) Fire(entry *log.Entry) error {
func (hook *Hook) Fire(entry *log.Entry) error {
switch entry.Level {
case log.PanicLevel:
entry.Logger.Out = os.Stderr
@ -31,12 +31,12 @@ func (h *hook) Fire(entry *log.Entry) error {
return nil
}
func (h *hook) Levels() []stdLogger.Level {
func (hook *Hook) Levels() []stdLogger.Level {
return log.AllLevels
}
func main() {
log.AddHook(&hook{})
log.AddHook(&Hook{})
cmd.Execute()
}

View File

@ -1,9 +1,7 @@
package all
import (
_ "github.com/FreifunkBremen/yanic/output/geojson"
_ "github.com/FreifunkBremen/yanic/output/meshviewer"
_ "github.com/FreifunkBremen/yanic/output/meshviewer-ffrgb"
_ "github.com/FreifunkBremen/yanic/output/nodelist"
_ "github.com/FreifunkBremen/yanic/output/raw"
)

View File

@ -1,89 +0,0 @@
package geojson
import (
"strconv"
"strings"
"github.com/FreifunkBremen/yanic/runtime"
"github.com/paulmach/go.geojson"
)
const (
POINT_UMAP_CLASS = "Circle"
POINT_UMAP_ONLINE_COLOR = "Green"
POINT_UMAP_OFFLINE_COLOR = "Red"
)
func newNodePoint(n *runtime.Node) (point *geojson.Feature) {
nodeinfo := n.Nodeinfo
location := nodeinfo.Location
point = geojson.NewPointFeature([]float64{
location.Longitude,
location.Latitude,
})
point.Properties["id"] = nodeinfo.NodeID
point.Properties["name"] = nodeinfo.Hostname
point.Properties["online"] = n.Online
var description strings.Builder
if n.Online {
description.WriteString("Online\n")
if statistics := n.Statistics; statistics != nil {
point.Properties["clients"] = statistics.Clients.Total
description.WriteString("Clients: " + strconv.Itoa(int(statistics.Clients.Total)) + "\n")
}
} else {
description.WriteString("Offline\n")
}
if nodeinfo.Hardware.Model != "" {
point.Properties["model"] = nodeinfo.Hardware.Model
description.WriteString("Model: " + nodeinfo.Hardware.Model + "\n")
}
if fw := nodeinfo.Software.Firmware; fw.Release != "" {
point.Properties["firmware"] = fw.Release
description.WriteString("Firmware: " + fw.Release + "\n")
}
if nodeinfo.System.SiteCode != "" {
point.Properties["site"] = nodeinfo.System.SiteCode
description.WriteString("Site: " + nodeinfo.System.SiteCode + "\n")
}
if nodeinfo.System.DomainCode != "" {
point.Properties["domain"] = nodeinfo.System.DomainCode
description.WriteString("Domain: " + nodeinfo.System.DomainCode + "\n")
}
if owner := nodeinfo.Owner; owner != nil && owner.Contact != "" {
point.Properties["contact"] = owner.Contact
description.WriteString("Contact: " + owner.Contact + "\n")
}
point.Properties["description"] = description.String()
point.Properties["_umap_options"] = getUMapOptions(n)
return
}
func getUMapOptions(n *runtime.Node) map[string]string {
result := map[string]string{
"iconClass": POINT_UMAP_CLASS,
}
if n.Online {
result["color"] = POINT_UMAP_ONLINE_COLOR
} else {
result["color"] = POINT_UMAP_OFFLINE_COLOR
}
return result
}
func transform(nodes *runtime.Nodes) *geojson.FeatureCollection {
nodelist := geojson.NewFeatureCollection()
for _, n := range nodes.List {
if n.Nodeinfo == nil || n.Nodeinfo.Location == nil {
continue
}
point := newNodePoint(n)
if point != nil {
nodelist.Features = append(nodelist.Features, point)
}
}
return nodelist
}

View File

@ -1,129 +0,0 @@
package geojson
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/FreifunkBremen/yanic/data"
"github.com/FreifunkBremen/yanic/runtime"
)
const (
testNodeDescription string = "Online\nClients: 42\nModel: TP-Link 841\n" +
"Site: mysite\nDomain: domain_42\n"
)
func TestTransform(t *testing.T) {
testNodes := createTestNodes()
nodes := transform(testNodes)
assert := assert.New(t)
assert.Len(testNodes.List, 4)
assert.Len(nodes.Features, 3)
node := testNodes.List["abcdef012425"]
umap := getUMapOptions(node)
assert.Len(umap, 2)
nodePoint := newNodePoint(node)
assert.Equal(
"abcdef012425",
nodePoint.Properties["id"],
)
assert.Equal(
"TP-Link 841",
nodePoint.Properties["model"],
)
assert.Equal(
uint32(42),
nodePoint.Properties["clients"],
)
assert.Equal(
testNodeDescription,
nodePoint.Properties["description"],
)
}
func createTestNodes() *runtime.Nodes {
nodes := runtime.NewNodes(&runtime.NodesConfig{})
nodes.AddNode(&runtime.Node{
Online: true,
Statistics: &data.Statistics{
Clients: data.Clients{
Total: 42,
},
},
Nodeinfo: &data.Nodeinfo{
NodeID: "abcdef012425",
Hardware: data.Hardware{
Model: "TP-Link 841",
},
Location: &data.Location{
Latitude: 24,
Longitude: 2,
},
System: data.System{
SiteCode: "mysite",
DomainCode: "domain_42",
},
},
})
nodeData := &runtime.Node{
Online: true,
Statistics: &data.Statistics{
Clients: data.Clients{
Total: 23,
},
},
Nodeinfo: &data.Nodeinfo{
NodeID: "abcdef012345",
Hardware: data.Hardware{
Model: "TP-Link 842",
},
System: data.System{
SiteCode: "mysite",
DomainCode: "domain_42",
},
},
}
nodeData.Nodeinfo.Software.Firmware.Release = "2019.1~exp42"
nodes.AddNode(nodeData)
nodes.AddNode(&runtime.Node{
Statistics: &data.Statistics{
Clients: data.Clients{
Total: 2,
},
},
Nodeinfo: &data.Nodeinfo{
NodeID: "112233445566",
Hardware: data.Hardware{
Model: "TP-Link 843",
},
Location: &data.Location{
Latitude: 23,
Longitude: 2,
},
},
})
nodes.AddNode(&runtime.Node{
Nodeinfo: &data.Nodeinfo{
NodeID: "0xdeadbeef0x",
VPN: true,
Hardware: data.Hardware{
Model: "Xeon Multi-Core",
},
Location: &data.Location{
Latitude: 23,
Longitude: 22,
},
},
})
return nodes
}

View File

@ -1,46 +0,0 @@
package geojson
import (
"errors"
"github.com/FreifunkBremen/yanic/output"
"github.com/FreifunkBremen/yanic/runtime"
)
type Output struct {
output.Output
path string
}
type Config map[string]interface{}
func (c Config) Path() string {
if path, ok := c["path"]; ok {
return path.(string)
}
return ""
}
func init() {
output.RegisterAdapter("geojson", Register)
}
func Register(configuration map[string]interface{}) (output.Output, error) {
var config Config
config = configuration
if path := config.Path(); path != "" {
return &Output{
path: path,
}, nil
}
return nil, errors.New("no path given")
}
func (o *Output) Save(nodes *runtime.Nodes) {
nodes.RLock()
defer nodes.RUnlock()
runtime.SaveJSON(transform(nodes), o.path)
}

View File

@ -1,28 +0,0 @@
package geojson
import (
"os"
"testing"
"github.com/FreifunkBremen/yanic/runtime"
"github.com/stretchr/testify/assert"
)
func TestOutput(t *testing.T) {
assert := assert.New(t)
out, err := Register(map[string]interface{}{})
assert.Error(err)
assert.Nil(out)
out, err = Register(map[string]interface{}{
"path": "/tmp/nodes.geojson",
})
os.Remove("/tmp/nodes.geojson")
assert.NoError(err)
assert.NotNil(out)
out.Save(&runtime.Nodes{})
_, err = os.Stat("/tmp/nodes.geojson")
assert.NoError(err)
}

View File

@ -69,7 +69,7 @@ func TestTransform(t *testing.T) {
},
},
Neighbours: &data.Neighbours{
NodeID: "node_c",
NodeID: "node_b",
Batadv: map[string]data.BatadvNeighbours{
"node:c:mac:lan": {
Neighbours: map[string]data.BatmanLink{

View File

@ -1,46 +0,0 @@
package raw
import (
"errors"
"github.com/FreifunkBremen/yanic/output"
"github.com/FreifunkBremen/yanic/runtime"
)
type Output struct {
output.Output
path string
}
type Config map[string]interface{}
func (c Config) Path() string {
if path, ok := c["path"]; ok {
return path.(string)
}
return ""
}
func init() {
output.RegisterAdapter("raw", Register)
}
func Register(configuration map[string]interface{}) (output.Output, error) {
var config Config
config = configuration
if path := config.Path(); path != "" {
return &Output{
path: path,
}, nil
}
return nil, errors.New("no path given")
}
func (o *Output) Save(nodes *runtime.Nodes) {
nodes.RLock()
defer nodes.RUnlock()
runtime.SaveJSON(transform(nodes), o.path)
}

View File

@ -1,28 +0,0 @@
package raw
import (
"os"
"testing"
"github.com/FreifunkBremen/yanic/runtime"
"github.com/stretchr/testify/assert"
)
func TestOutput(t *testing.T) {
assert := assert.New(t)
out, err := Register(map[string]interface{}{})
assert.Error(err)
assert.Nil(out)
out, err = Register(map[string]interface{}{
"path": "/tmp/raw.json",
})
os.Remove("/tmp/raw.json")
assert.NoError(err)
assert.NotNil(out)
out.Save(&runtime.Nodes{})
_, err = os.Stat("/tmp/raw.json")
assert.NoError(err)
}

View File

@ -1,45 +0,0 @@
package raw
import (
"github.com/FreifunkBremen/yanic/data"
"github.com/FreifunkBremen/yanic/lib/jsontime"
"github.com/FreifunkBremen/yanic/runtime"
)
// Node struct
type RawNode struct {
Firstseen jsontime.Time `json:"firstseen"`
Lastseen jsontime.Time `json:"lastseen"`
Online bool `json:"online"`
Statistics *data.Statistics `json:"statistics"`
Nodeinfo *data.Nodeinfo `json:"nodeinfo"`
Neighbours *data.Neighbours `json:"neighbours"`
}
type NodeList struct {
Version string `json:"version"`
Timestamp jsontime.Time `json:"updated_at"` // Timestamp of the generation
List []*RawNode `json:"nodes"`
}
func transform(nodes *runtime.Nodes) *NodeList {
nodelist := &NodeList{
Version: "1.0.0",
Timestamp: jsontime.Now(),
}
for _, nodeOrigin := range nodes.List {
if nodeOrigin != nil {
node := &RawNode{
Firstseen: nodeOrigin.Firstseen,
Lastseen: nodeOrigin.Lastseen,
Online: nodeOrigin.Online,
Statistics: nodeOrigin.Statistics,
Nodeinfo: nodeOrigin.Nodeinfo,
Neighbours: nodeOrigin.Neighbours,
}
nodelist.List = append(nodelist.List, node)
}
}
return nodelist
}

View File

@ -1,67 +0,0 @@
package raw
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/FreifunkBremen/yanic/data"
"github.com/FreifunkBremen/yanic/runtime"
)
func TestTransform(t *testing.T) {
nodes := transform(createTestNodes())
assert := assert.New(t)
assert.Len(nodes.List, 3)
}
func createTestNodes() *runtime.Nodes {
nodes := runtime.NewNodes(&runtime.NodesConfig{})
nodeData := &runtime.Node{
Statistics: &data.Statistics{
Clients: data.Clients{
Total: 23,
},
},
Nodeinfo: &data.Nodeinfo{
NodeID: "abcdef012345",
Hardware: data.Hardware{
Model: "TP-Link 841",
},
},
}
nodeData.Nodeinfo.Software.Firmware.Release = "2016.1.6+entenhausen1"
nodes.AddNode(nodeData)
nodes.AddNode(&runtime.Node{
Statistics: &data.Statistics{
Clients: data.Clients{
Total: 2,
},
},
Nodeinfo: &data.Nodeinfo{
NodeID: "112233445566",
Hardware: data.Hardware{
Model: "TP-Link 841",
},
Location: &data.Location{
Latitude: 23,
Longitude: 2,
},
},
})
nodes.AddNode(&runtime.Node{
Nodeinfo: &data.Nodeinfo{
NodeID: "0xdeadbeef0x",
VPN: true,
Hardware: data.Hardware{
Model: "Xeon Multi-Core",
},
},
})
return nodes
}

View File

@ -1,9 +1,6 @@
package respond
import (
"bytes"
"compress/flate"
"encoding/json"
"fmt"
"net"
"time"
@ -66,7 +63,7 @@ func (coll *Collector) listenUDP(iface InterfaceConfig) {
if iface.IPAddress != "" {
addr = net.ParseIP(iface.IPAddress)
} else {
addr, err = getUnicastAddr(iface.InterfaceName)
addr, err = getUnicastAddr(iface.InterfaceName, iface.MulticastAddress == "")
if err != nil {
log.WithField("iface", iface.InterfaceName).Panic(err)
}
@ -86,7 +83,7 @@ func (coll *Collector) listenUDP(iface InterfaceConfig) {
if err != nil {
log.Panic(err)
}
conn.SetReadBuffer(maxDataGramSize)
conn.SetReadBuffer(MaxDataGramSize)
coll.connections = append(coll.connections, multicastConn{
Conn: conn,
@ -99,7 +96,7 @@ func (coll *Collector) listenUDP(iface InterfaceConfig) {
}
// Returns a unicast address of given interface (linklocal or global unicast address)
func getUnicastAddr(ifname string) (net.IP, error) {
func getUnicastAddr(ifname string, linklocal bool) (net.IP, error) {
iface, err := net.InterfaceByName(ifname)
if err != nil {
return nil, err
@ -116,7 +113,7 @@ func getUnicastAddr(ifname string) (net.IP, error) {
if !ok {
continue
}
if (ip == nil && ipnet.IP.IsGlobalUnicast()) || ipnet.IP.IsLinkLocalUnicast() {
if (!linklocal && ipnet.IP.IsGlobalUnicast()) || (linklocal && ipnet.IP.IsLinkLocalUnicast()) {
ip = ipnet.IP
}
}
@ -245,18 +242,6 @@ func (coll *Collector) parser() {
}
}
func (res *Response) parse() (*data.ResponseData, error) {
// Deflate
deflater := flate.NewReader(bytes.NewReader(res.Raw))
defer deflater.Close()
// Unmarshal
rdata := &data.ResponseData{}
err := json.NewDecoder(deflater).Decode(rdata)
return rdata, err
}
func (coll *Collector) saveResponse(addr *net.UDPAddr, res *data.ResponseData) {
// Search for NodeID
var nodeID string
@ -308,7 +293,7 @@ func (coll *Collector) saveResponse(addr *net.UDPAddr, res *data.ResponseData) {
}
func (coll *Collector) receiver(conn *net.UDPConn) {
buf := make([]byte, maxDataGramSize)
buf := make([]byte, MaxDataGramSize)
for {
n, src, err := conn.ReadFromUDP(buf)

65
respond/daemon/data.go Normal file
View File

@ -0,0 +1,65 @@
package respondd
import (
"io/ioutil"
"os"
"strings"
"github.com/FreifunkBremen/yanic/data"
)
func trim(s string) string {
return strings.TrimSpace(strings.Trim(s, "\n"))
}
func (d *Daemon) updateData() {
nodeID := ""
// Nodeinfo
if d.Data.Nodeinfo == nil {
d.Data.Nodeinfo = &data.Nodeinfo{}
} else {
nodeID = d.Data.Nodeinfo.NodeID
}
if d.Data.Nodeinfo.Hostname == "" {
d.Data.Nodeinfo.Hostname, _ = os.Hostname()
}
// Statistics
if d.Data.Statistics == nil {
d.Data.Statistics = &data.Statistics{}
} else if nodeID == "" {
nodeID = d.Data.Statistics.NodeID
}
// Neighbours
if d.Data.Neighbours == nil {
d.Data.Neighbours = &data.Neighbours{}
} else if nodeID == "" {
nodeID = d.Data.Neighbours.NodeID
}
if nodeID == "" && !d.MultiInstance {
if v, err := ioutil.ReadFile("/etc/machine-id"); err == nil {
nodeID = trim(string(v))[:12]
}
}
d.Data.Nodeinfo.NodeID = nodeID
d.Data.Statistics.NodeID = nodeID
d.Data.Neighbours.NodeID = nodeID
for _, data := range d.dataByInterface {
data.Nodeinfo = d.Data.Nodeinfo
}
}
func (d *Daemon) getData(iface string) *data.ResponseData {
if !d.MultiInstance {
return d.Data
}
if data, ok := d.dataByInterface[iface]; ok {
return data
}
d.dataByInterface[iface] = &data.ResponseData{}
d.updateData()
return d.dataByInterface[iface]
}

91
respond/daemon/handler.go Normal file
View File

@ -0,0 +1,91 @@
package respondd
import (
"encoding/json"
"net"
"reflect"
"github.com/bdlm/log"
"github.com/FreifunkBremen/yanic/respond"
)
func (d *Daemon) handler(socket *net.UDPConn) {
socket.SetReadBuffer(respond.MaxDataGramSize)
// Loop forever reading from the socket
for {
buf := make([]byte, respond.MaxDataGramSize)
n, src, err := socket.ReadFromUDP(buf)
if err != nil {
log.Errorf("ReadFromUDP failed: %s", err)
}
raw := make([]byte, n)
copy(raw, buf)
get := string(raw)
data := d.getData(src.Zone)
log.WithFields(map[string]interface{}{
"bytes": n,
"data": get,
"src": src.String(),
}).Debug("recieve request")
if get[:3] == "GET" {
res, err := respond.NewRespone(data, src)
if err != nil {
log.Errorf("Decode failed: %s", err)
continue
}
n, err = socket.WriteToUDP(res.Raw, res.Address)
if err != nil {
log.Errorf("WriteToUDP failed: %s", err)
continue
}
log.WithFields(map[string]interface{}{
"bytes": n,
"dest": res.Address.String(),
}).Debug("send respond")
continue
}
found := false
t := reflect.TypeOf(data).Elem()
v := reflect.ValueOf(data).Elem()
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
fv := v.FieldByName(f.Name)
if f.Tag.Get("json") == get {
log.WithFields(map[string]interface{}{
"param": get,
"dest": src.String(),
}).Debug("found")
raw, err = json.Marshal(fv.Interface())
found = true
break
}
}
if !found {
log.WithFields(map[string]interface{}{
"param": get,
"dest": src.String(),
}).Debug("not found")
raw = []byte("ressource not found")
}
n, err = socket.WriteToUDP(raw, src)
if err != nil {
log.Errorf("WriteToUDP failed: %s", err)
continue
}
log.WithFields(map[string]interface{}{
"bytes": n,
"dest": src.String(),
}).Debug("send respond")
}
}

71
respond/daemon/main.go Normal file
View File

@ -0,0 +1,71 @@
package respondd
import (
"net"
"time"
"github.com/bdlm/log"
"github.com/FreifunkBremen/yanic/data"
"github.com/FreifunkBremen/yanic/lib/duration"
)
type Daemon struct {
MultiInstance bool `toml:"multi_instance"`
DataInterval duration.Duration `toml:"data_interval"`
Listen []struct {
Address string `toml:"address"`
Interface string `toml:"interface"`
Port int `toml:"port"`
} `toml:"listen"`
Data *data.ResponseData `toml:"data"`
dataByInterface map[string]*data.ResponseData
}
func (d *Daemon) Start() {
if d.Data == nil {
d.Data = &data.ResponseData{}
}
d.updateData()
go d.updateWorker()
for _, listen := range d.Listen {
var socket *net.UDPConn
var err error
addr := net.ParseIP(listen.Address)
if addr.IsMulticast() {
var iface *net.Interface
if listen.Interface != "" {
iface, err = net.InterfaceByName(listen.Interface)
if err != nil {
log.Fatal(err)
}
}
if socket, err = net.ListenMulticastUDP("udp6", iface, &net.UDPAddr{
IP: addr,
Port: listen.Port,
}); err != nil {
log.Fatal(err)
}
} else {
if socket, err = net.ListenUDP("udp6", &net.UDPAddr{
IP: addr,
Port: listen.Port,
}); err != nil {
log.Fatal(err)
}
}
go d.handler(socket)
}
log.Debug("all listener started")
}
func (d *Daemon) updateWorker() {
c := time.Tick(d.DataInterval.Duration)
for range c {
d.updateData()
}
}

View File

@ -1,18 +1,23 @@
package respond
import (
"bytes"
"compress/flate"
"encoding/json"
"net"
"github.com/FreifunkBremen/yanic/data"
)
const (
// default multicast group used by announced
multicastAddressDefault = "ff05:0:0:0:0:0:2:1001"
multicastAddressDefault = "ff02:0:0:0:0:0:2:1001"
// default udp port used by announced
port = 1001
// maximum receivable size
maxDataGramSize = 8192
MaxDataGramSize = 8192
)
// Response of the respond request
@ -20,3 +25,35 @@ type Response struct {
Address *net.UDPAddr
Raw []byte
}
func NewRespone(res *data.ResponseData, addr *net.UDPAddr) (*Response, error) {
buf := new(bytes.Buffer)
flater, err := flate.NewWriter(buf, flate.BestCompression)
if err != nil {
return nil, err
}
defer flater.Close()
if err = json.NewEncoder(flater).Encode(res); err != nil {
return nil, err
}
err = flater.Flush()
return &Response{
Raw: buf.Bytes(),
Address: addr,
}, err
}
func (res *Response) parse() (*data.ResponseData, error) {
// Deflate
deflater := flate.NewReader(bytes.NewReader(res.Raw))
defer deflater.Close()
// Unmarshal
rdata := &data.ResponseData{}
err := json.NewDecoder(deflater).Decode(rdata)
return rdata, err
}

View File

@ -55,8 +55,8 @@ func TestLoadAndSave(t *testing.T) {
// not autoload without StatePath
NewNodes(config)
// Test unmarshalable
config.StatePath = "testdata/nodes-broken.json"
// Test unmarshalable /dev/null - autolead with StatePath
config.StatePath = "/dev/null"
nodes := NewNodes(config)
// Test unopen able
config.StatePath = "/root/nodes.json"
@ -71,8 +71,8 @@ func TestLoadAndSave(t *testing.T) {
os.Remove(tmpfile.Name())
assert.Panics(func() {
SaveJSON(nodes, "/proc/a")
// "open /proc/a.tmp: permission denied",
SaveJSON(nodes, "/dev/null")
// "open /dev/null.tmp: permission denied",
})
tmpfile, _ = ioutil.TempFile("/tmp", "nodes")
@ -162,14 +162,6 @@ func TestLinksNodes(t *testing.T) {
}
assert.Len(nodes.List, 0)
nodes.Update("f4f26dd7a300", &data.ResponseData{
Nodeinfo: &data.Nodeinfo{
NodeID: "f4f26dd7a300",
Network: data.Network{
Mac: "f4:f2:6d:d7:a3:00",
},
},
})
nodes.Update("f4f26dd7a30a", &data.ResponseData{
Nodeinfo: &data.Nodeinfo{
NodeID: "f4f26dd7a30a",
@ -177,37 +169,11 @@ func TestLinksNodes(t *testing.T) {
Mac: "f4:f2:6d:d7:a3:0a",
},
},
Neighbours: &data.Neighbours{
NodeID: "f4f26dd7a30a",
Babel: map[string]data.BabelNeighbours{
"vx_mesh_lan": {
LinkLocalAddress: "fe80::2",
Neighbours: map[string]data.BabelLink{
"fe80::1337": {
Cost: 26214,
},
},
},
},
},
})
nodes.Update("f4f26dd7a30b", &data.ResponseData{
Nodeinfo: &data.Nodeinfo{
NodeID: "f4f26dd7a30b",
Network: data.Network{
Mesh: map[string]*data.NetworkInterface{
"babel": {
Interfaces: struct {
Wireless []string `json:"wireless,omitempty"`
Other []string `json:"other,omitempty"`
Tunnel []string `json:"tunnel,omitempty"`
}{
Other: []string{"fe80::1337"},
},
},
},
},
},
Neighbours: &data.Neighbours{
NodeID: "f4f26dd7a30b",
@ -223,35 +189,21 @@ func TestLinksNodes(t *testing.T) {
},
})
// no neighbours nodeid
node := nodes.List["f4f26dd7a300"]
node := nodes.List["f4f26dd7a30a"]
assert.NotNil(node)
links := nodes.NodeLinks(node)
assert.Len(links, 0)
// babel link
node = nodes.List["f4f26dd7a30a"]
assert.NotNil(node)
links = nodes.NodeLinks(node)
assert.Len(links, 1)
link := links[0]
assert.Equal("f4f26dd7a30a", link.SourceID)
assert.Equal("fe80::2", link.SourceAddress)
assert.Equal("f4f26dd7a30b", link.TargetID)
assert.Equal("fe80::1337", link.TargetAddress)
assert.Equal(float32(0.6), link.TQ)
// batman link
node = nodes.List["f4f26dd7a30b"]
assert.NotNil(node)
links = nodes.NodeLinks(node)
assert.Len(links, 1)
link = links[0]
assert.Equal("f4f26dd7a30b", link.SourceID)
assert.Equal("f4:f2:6d:d7:a3:0b", link.SourceAddress)
assert.Equal("f4f26dd7a30a", link.TargetID)
assert.Equal("f4:f2:6d:d7:a3:0a", link.TargetAddress)
assert.Equal(float32(0.8), link.TQ)
link := links[0]
assert.Equal(link.SourceID, "f4f26dd7a30b")
assert.Equal(link.SourceAddress, "f4:f2:6d:d7:a3:0b")
assert.Equal(link.TargetID, "f4f26dd7a30a")
assert.Equal(link.TargetAddress, "f4:f2:6d:d7:a3:0a")
assert.Equal(link.TQ, float32(0.8))
nodeid := nodes.GetNodeIDbyAddress("f4:f2:6d:d7:a3:0a")
assert.Equal("f4f26dd7a30a", nodeid)

View File

@ -1,4 +0,0 @@
{
// make it invalid by this comment
"affe": 3
}