[TASK] make yanic babel compatible (#104)

This commit is contained in:
Martin Geno 2017-12-05 23:17:49 +01:00 committed by Geno
parent 950ad8457f
commit c660e175f0
22 changed files with 345 additions and 163 deletions

View File

@ -15,7 +15,7 @@ func TestReadConfig(t *testing.T) {
assert.NotNil(config) assert.NotNil(config)
assert.True(config.Respondd.Enable) assert.True(config.Respondd.Enable)
assert.Equal([]string{"br-ffhb"}, config.Respondd.Interfaces) assert.Equal("br-ffhb", config.Respondd.Interfaces[0].InterfaceName)
assert.Equal(time.Minute, config.Respondd.CollectInterval.Duration) assert.Equal(time.Minute, config.Respondd.CollectInterval.Duration)
assert.Equal(time.Hour*24*7, config.Nodes.PruneAfter.Duration) assert.Equal(time.Hour*24*7, config.Nodes.PruneAfter.Duration)
assert.Equal(time.Hour*24*7, config.Database.DeleteAfter.Duration) assert.Equal(time.Hour*24*7, config.Database.DeleteAfter.Duration)

View File

@ -1,8 +1,10 @@
package cmd package cmd
import ( import (
"encoding/json"
"log" "log"
"net" "net"
"strings"
"time" "time"
"github.com/FreifunkBremen/yanic/respond" "github.com/FreifunkBremen/yanic/respond"
@ -10,32 +12,55 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var wait int var (
wait int
port int
ipAddress string
)
// queryCmd represents the query command // queryCmd represents the query command
var queryCmd = &cobra.Command{ var queryCmd = &cobra.Command{
Use: "query <interface> <destination>", Use: "query <interfaces> <destination>",
Short: "Sends a query on the interface to the destination and waits for a response", Short: "Sends a query on the interface to the destination and waits for a response",
Example: `yanic query wlan0 "fe80::eade:27ff:dead:beef"`, Example: `yanic query "eth0,wlan0" "fe80::eade:27ff:dead:beef"`,
Args: cobra.ExactArgs(2), Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
iface := args[0] ifaces := strings.Split(args[0], ",")
dstAddress := net.ParseIP(args[1]) dstAddress := net.ParseIP(args[1])
log.Printf("Sending request address=%s iface=%s", dstAddress, iface) log.Printf("Sending request address=%s ifaces=%s", dstAddress, ifaces)
var ifacesConfigs []respond.InterfaceConfig
for _, iface := range ifaces {
ifaceConfig := respond.InterfaceConfig{
InterfaceName: iface,
Port: port,
IPAddress: ipAddress,
}
ifacesConfigs = append(ifacesConfigs, ifaceConfig)
}
nodes := runtime.NewNodes(&runtime.NodesConfig{}) nodes := runtime.NewNodes(&runtime.NodesConfig{})
sitesDomains := make(map[string][]string) sitesDomains := make(map[string][]string)
collector := respond.NewCollector(nil, nodes, sitesDomains, ifacesConfigs)
collector := respond.NewCollector(nil, nodes, sitesDomains, []string{iface}, 0)
defer collector.Close() defer collector.Close()
collector.SendPacket(dstAddress) collector.SendPacket(dstAddress)
time.Sleep(time.Second * time.Duration(wait)) time.Sleep(time.Second * time.Duration(wait))
for id, data := range nodes.List { for id, data := range nodes.List {
jq, err := json.Marshal(data)
if err != nil {
log.Printf("%s: %+v", id, data) log.Printf("%s: %+v", id, data)
} else {
jqNeighbours, err := json.Marshal(data.Neighbours)
if err != nil {
log.Printf("%s: %s neighbours: %+v", id, string(jq), data.Neighbours)
} else {
log.Printf("%s: %s neighbours: %s", id, string(jq), string(jqNeighbours))
}
}
} }
}, },
} }
@ -43,4 +68,6 @@ var queryCmd = &cobra.Command{
func init() { func init() {
RootCmd.AddCommand(queryCmd) RootCmd.AddCommand(queryCmd)
queryCmd.Flags().IntVar(&wait, "wait", 1, "Seconds to wait for a response") queryCmd.Flags().IntVar(&wait, "wait", 1, "Seconds to wait for a response")
queryCmd.Flags().IntVar(&port, "port", 0, "define a port to listen (if not set or set to 0 the kernel will use a random free port at its own)")
queryCmd.Flags().StringVar(&ipAddress, "ip", "", "ip address which is used for sending (optional - without definition used the link-local address)")
} }

View File

@ -54,7 +54,7 @@ var serveCmd = &cobra.Command{
time.Sleep(delay) time.Sleep(delay)
} }
collector = respond.NewCollector(allDatabase.Conn, nodes, config.Respondd.SitesDomains(), config.Respondd.Interfaces, config.Respondd.Port) collector = respond.NewCollector(allDatabase.Conn, nodes, config.Respondd.SitesDomains(), config.Respondd.Interfaces)
collector.Start(config.Respondd.CollectInterval.Duration) collector.Start(config.Respondd.CollectInterval.Duration)
defer collector.Close() defer collector.Close()
} }

View File

@ -9,11 +9,6 @@ enable = true
synchronize = "1m" synchronize = "1m"
# how often request per multicast # how often request per multicast
collect_interval = "1m" collect_interval = "1m"
# interface that has an IP in your mesh network
interfaces = ["br-ffhb"]
# 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
# table of a site to save stats for (not exists for global only) # table of a site to save stats for (not exists for global only)
#[respondd.sites.example] #[respondd.sites.example]
@ -23,6 +18,20 @@ interfaces = ["br-ffhb"]
[respondd.sites.ffhb] [respondd.sites.ffhb]
domains = ["city"] domains = ["city"]
# interface that has an IP in your mesh network
[[respondd.interfaces]]
# 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)
ip_address = "fd2f:5119:f2d::5"
# multicast address to destination of respondd
# (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
# A little build-in webserver, which statically serves a directory. # A little build-in webserver, which statically serves a directory.
# This is useful for testing purposes or for a little standalone installation. # This is useful for testing purposes or for a little standalone installation.
[webserver] [webserver]

View File

@ -3,6 +3,7 @@ package data
// Neighbours struct // Neighbours struct
type Neighbours struct { type Neighbours struct {
Batadv map[string]BatadvNeighbours `json:"batadv"` Batadv map[string]BatadvNeighbours `json:"batadv"`
Babel map[string]BabelNeighbours `json:"babel"`
LLDP map[string]LLDPNeighbours `json:"lldp"` LLDP map[string]LLDPNeighbours `json:"lldp"`
//WifiNeighbours map[string]WifiNeighbours `json:"wifi"` //WifiNeighbours map[string]WifiNeighbours `json:"wifi"`
NodeID string `json:"node_id"` NodeID string `json:"node_id"`
@ -27,11 +28,27 @@ type LLDPLink struct {
Description string `json:"descr"` Description string `json:"descr"`
} }
// BabelLink struct
type BabelLink struct {
// How need this:
RXCost int `json:"rxcost"`
TXCost int `json:"txcost"`
Cost int `json:"cost"`
Reachability int `json:"reachability"`
}
// BatadvNeighbours struct // BatadvNeighbours struct
type BatadvNeighbours struct { type BatadvNeighbours struct {
Neighbours map[string]BatmanLink `json:"neighbours"` Neighbours map[string]BatmanLink `json:"neighbours"`
} }
// BabelNeighbours struct
type BabelNeighbours struct {
Protocol string `json:"protocol"`
LinkLocalAddress string `json:"ll-addr"`
Neighbours map[string]BabelLink `json:"neighbours"`
}
// WifiNeighbours struct // WifiNeighbours struct
type WifiNeighbours struct { type WifiNeighbours struct {
Neighbours map[string]WifiLink `json:"neighbours"` Neighbours map[string]WifiLink `json:"neighbours"`

View File

@ -14,8 +14,8 @@ type NodeInfo struct {
Wireless *Wireless `json:"wireless,omitempty"` Wireless *Wireless `json:"wireless,omitempty"`
} }
// BatInterface struct // NetworkInterface struct
type BatInterface struct { type NetworkInterface struct {
Interfaces struct { Interfaces struct {
Wireless []string `json:"wireless,omitempty"` Wireless []string `json:"wireless,omitempty"`
Other []string `json:"other,omitempty"` Other []string `json:"other,omitempty"`
@ -24,7 +24,7 @@ type BatInterface struct {
} }
// Addresses returns a flat list of all MAC addresses // Addresses returns a flat list of all MAC addresses
func (iface *BatInterface) Addresses() []string { func (iface *NetworkInterface) Addresses() []string {
return append(append(iface.Interfaces.Other, iface.Interfaces.Tunnel...), iface.Interfaces.Wireless...) return append(append(iface.Interfaces.Other, iface.Interfaces.Tunnel...), iface.Interfaces.Wireless...)
} }
@ -32,7 +32,8 @@ func (iface *BatInterface) Addresses() []string {
type Network struct { type Network struct {
Mac string `json:"mac"` Mac string `json:"mac"`
Addresses []string `json:"addresses"` Addresses []string `json:"addresses"`
Mesh map[string]*BatInterface `json:"mesh"` Mesh map[string]*NetworkInterface `json:"mesh"`
// still used in gluon?
MeshInterfaces []string `json:"mesh_interfaces"` MeshInterfaces []string `json:"mesh_interfaces"`
} }
@ -64,6 +65,9 @@ type Software struct {
Version string `json:"version,omitempty"` Version string `json:"version,omitempty"`
Compat int `json:"compat,omitempty"` Compat int `json:"compat,omitempty"`
} `json:"batman-adv,omitempty"` } `json:"batman-adv,omitempty"`
Babeld struct {
Version string `json:"version,omitempty"`
} `json:"babeld,omitempty"`
Fastd struct { Fastd struct {
Enabled bool `json:"enabled,omitempty"` Enabled bool `json:"enabled,omitempty"`
Version string `json:"version,omitempty"` Version string `json:"version,omitempty"`

View File

@ -8,7 +8,7 @@ import (
func TestNodeinfoBatAddresses(t *testing.T) { func TestNodeinfoBatAddresses(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
batIface := &BatInterface{ iface := &NetworkInterface{
Interfaces: struct { Interfaces: struct {
Wireless []string `json:"wireless,omitempty"` Wireless []string `json:"wireless,omitempty"`
Other []string `json:"other,omitempty"` Other []string `json:"other,omitempty"`
@ -20,7 +20,7 @@ func TestNodeinfoBatAddresses(t *testing.T) {
}, },
} }
addr := batIface.Addresses() addr := iface.Addresses()
assert.NotNil(addr) assert.NotNil(addr)
assert.Equal([]string{"aa:aa:aa:aa:aa", "aa:aa:aa:aa:ab"}, addr) assert.Equal([]string{"aa:aa:aa:aa:aa", "aa:aa:aa:aa:ab"}, addr)
} }

View File

@ -11,9 +11,9 @@ import (
func (conn *Connection) InsertLink(link *runtime.Link, t time.Time) { func (conn *Connection) InsertLink(link *runtime.Link, t time.Time) {
tags := models.Tags{} tags := models.Tags{}
tags.SetString("source.id", link.SourceID) tags.SetString("source.id", link.SourceID)
tags.SetString("source.mac", link.SourceMAC) tags.SetString("source.addr", link.SourceAddress)
tags.SetString("target.id", link.TargetID) tags.SetString("target.id", link.TargetID)
tags.SetString("target.mac", link.TargetMAC) tags.SetString("target.addr", link.TargetAddress)
conn.addPoint(MeasurementLink, tags, models.Fields{"tq": float32(link.TQ) / 2.55}, t) conn.addPoint(MeasurementLink, tags, models.Fields{"tq": link.TQ * 100}, t)
} }

View File

@ -159,9 +159,9 @@ func TestToInflux(t *testing.T) {
assert.EqualValues("link", nPoint.Name()) assert.EqualValues("link", nPoint.Name())
assert.EqualValues(map[string]string{ assert.EqualValues(map[string]string{
"source.id": "deadbeef", "source.id": "deadbeef",
"source.mac": "a-interface", "source.addr": "a-interface",
"target.id": "foobar", "target.id": "foobar",
"target.mac": "BAFF1E5", "target.addr": "BAFF1E5",
}, tags) }, tags)
assert.EqualValues(80, fields["tq"]) assert.EqualValues(80, fields["tq"])

View File

@ -14,10 +14,15 @@ Group for configuration of respondd request.
enable = true enable = true
# synchronize = "1m" # synchronize = "1m"
collect_interval = "1m" collect_interval = "1m"
interfaces = ["br-ffhb"]
#port = 10001
#[respondd.sites.example] #[respondd.sites.example]
#domains = ["city"] #domains = ["city"]
[[respondd.interfaces]]
ifname = "br-ffhb"
#ip_address = "fe80::..."
#multicast_address = "ff02::2:1001"
#port = 10001
``` ```
{% endmethod %} {% endmethod %}
@ -46,7 +51,7 @@ synchronize = "1m"
{% method %} {% method %}
How often send request per respondd. How often send request per respondd.
It will send UDP packets with multicast group `ff02::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`. 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" %} {% sample lang="toml" %}
```toml ```toml
@ -55,26 +60,16 @@ collect_interval = "1m"
{% endmethod %} {% endmethod %}
### interfaces ### sites
{% method %} {% method %}
Interface that has an IP in your mesh network List of sites to save stats for (empty for global only)
{% sample lang="toml" %} {% sample lang="toml" %}
```toml ```toml
interfaces = ["br-ffhb"] sites = ["ffhb"]
``` ```
{% endmethod %} {% endmethod %}
### port
{% method %}
Define a port to listen and send the respondd packages.
If not set or set to 0 the kernel will use a random free port at its own.
{% sample lang="toml" %}
```toml
port = 10001
```
{% endmethod %}
### [respondd.sites.example] ### [respondd.sites.example]
{% method %} {% method %}
Tables of sites to save stats for (not exists for global only). Tables of sites to save stats for (not exists for global only).
@ -85,6 +80,7 @@ Here is the site _ffhb_.
domains = ["city"] domains = ["city"]
``` ```
{% endmethod %} {% endmethod %}
#### domains #### domains
{% method %} {% method %}
list of domains on this site to save stats for (empty for global only) list of domains on this site to save stats for (empty for global only)
@ -95,6 +91,60 @@ domains = ["city"]
{% endmethod %} {% endmethod %}
### [[respondd.interfaces]]
{% method %}
Interface that has an ip address in your mesh network.
It is possible to have multiple interfaces, just add this group again with new parameters (see toml [[array of table]]).
{% sample lang="toml" %}
```toml
[[respondd.interfaces]]
ifname = "br-ffhb"
#ip_address = "fe80::..."
#multicast_address = "ff02::2:1001"
#port = 10001
```
{% endmethod %}
### ifname
{% method %}
name of interface on which this collector is running.
{% sample lang="toml" %}
```toml
ifname = "br-ffhb"
```
{% endmethod %}
### ip_address
{% 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.
{% sample lang="toml" %}
```toml
ip_address = "fe80::..."
```
{% endmethod %}
### 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 `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"
```
{% endmethod %}
### port
{% method %}
Define a port to listen and send the respondd packages.
If not set or set to 0 the kernel will use a random free port at its own.
{% sample lang="toml" %}
```toml
port = 10001
```
{% endmethod %}
## [webserver] ## [webserver]
{% method %} {% method %}

View File

@ -5,7 +5,7 @@ cp /opt/go/src/github.com/FreifunkBremen/yanic/config_example.toml /etc/yanic.co
``` ```
# Quick configuration # Quick configuration
For an easy startup you only need to edit the `interfaces` in section For an easy startup you only need to edit the `[[respondd.interfaces]]` in section
`[respondd]` in file `/etc/yanic.conf`. `[respondd]` in file `/etc/yanic.conf`.
Then create the following files and folders: Then create the following files and folders:

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 sends the `gluon-neighbour-info` request and collects the answers.
It will send UDP packets with multicast group `ff02: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`. If a node does not answer, it will request with the last know address under the port `1001`.

View File

@ -9,6 +9,12 @@ import (
"github.com/FreifunkBremen/yanic/runtime" "github.com/FreifunkBremen/yanic/runtime"
) )
const (
LINK_TYPE_WIRELESS = "wifi"
LINK_TYPE_TUNNEL = "vpn"
LINK_TYPE_FALLBACK = "other"
)
func transform(nodes *runtime.Nodes) *Meshviewer { func transform(nodes *runtime.Nodes) *Meshviewer {
meshviewer := &Meshviewer{ meshviewer := &Meshviewer{
@ -34,11 +40,11 @@ func transform(nodes *runtime.Nodes) *Meshviewer {
if nodeinfo := nodeOrigin.Nodeinfo; nodeinfo != nil { if nodeinfo := nodeOrigin.Nodeinfo; nodeinfo != nil {
if meshes := nodeinfo.Network.Mesh; meshes != nil { if meshes := nodeinfo.Network.Mesh; meshes != nil {
for _, mesh := range meshes { for _, mesh := range meshes {
for _, mac := range mesh.Interfaces.Wireless { for _, addr := range mesh.Interfaces.Wireless {
typeList[mac] = "wifi" typeList[addr] = LINK_TYPE_WIRELESS
} }
for _, mac := range mesh.Interfaces.Tunnel { for _, addr := range mesh.Interfaces.Tunnel {
typeList[mac] = "vpn" typeList[addr] = LINK_TYPE_TUNNEL
} }
} }
} }
@ -47,52 +53,70 @@ func transform(nodes *runtime.Nodes) *Meshviewer {
for _, linkOrigin := range nodes.NodeLinks(nodeOrigin) { for _, linkOrigin := range nodes.NodeLinks(nodeOrigin) {
var key string var key string
// keep source and target in the same order // keep source and target in the same order
switchSourceTarget := strings.Compare(linkOrigin.SourceMAC, linkOrigin.TargetMAC) > 0 switchSourceTarget := strings.Compare(linkOrigin.SourceAddress, linkOrigin.TargetAddress) > 0
if switchSourceTarget { if switchSourceTarget {
key = fmt.Sprintf("%s-%s", linkOrigin.SourceMAC, linkOrigin.TargetMAC) key = fmt.Sprintf("%s-%s", linkOrigin.SourceAddress, linkOrigin.TargetAddress)
} else { } else {
key = fmt.Sprintf("%s-%s", linkOrigin.TargetMAC, linkOrigin.SourceMAC) key = fmt.Sprintf("%s-%s", linkOrigin.TargetAddress, linkOrigin.SourceAddress)
} }
if link := links[key]; link != nil { if link := links[key]; link != nil {
linkType, linkTypeFound := typeList[linkOrigin.SourceAddress]
if !linkTypeFound {
linkType, linkTypeFound = typeList[linkOrigin.TargetAddress]
}
if switchSourceTarget { if switchSourceTarget {
link.TargetTQ = float32(linkOrigin.TQ) / 255.0 link.TargetTQ = linkOrigin.TQ
if link.Type == "other" {
link.Type = typeList[linkOrigin.TargetMAC] linkType, linkTypeFound = typeList[linkOrigin.TargetAddress]
} else if link.Type != typeList[linkOrigin.TargetMAC] { if !linkTypeFound {
log.Printf("different linktypes %s:%s current: %s source: %s target: %s", linkOrigin.SourceMAC, linkOrigin.TargetMAC, link.Type, typeList[linkOrigin.SourceMAC], typeList[linkOrigin.TargetMAC]) linkType, linkTypeFound = typeList[linkOrigin.SourceAddress]
} }
} else { } else {
link.SourceTQ = float32(linkOrigin.TQ) / 255.0 link.SourceTQ = linkOrigin.TQ
if link.Type == "other" { }
link.Type = typeList[linkOrigin.SourceMAC]
} else if link.Type != typeList[linkOrigin.SourceMAC] { if linkTypeFound && linkType != link.Type {
log.Printf("different linktypes %s:%s current: %s source: %s target: %s", linkOrigin.SourceMAC, linkOrigin.TargetMAC, link.Type, typeList[linkOrigin.SourceMAC], typeList[linkOrigin.TargetMAC]) if link.Type == LINK_TYPE_FALLBACK {
link.Type = linkType
} else {
log.Printf("different linktypes for '%s' - '%s' prev: '%s' new: '%s' source: '%s' target: '%s'", linkOrigin.SourceAddress, linkOrigin.TargetAddress, link.Type, linkType, typeList[linkOrigin.SourceAddress], typeList[linkOrigin.TargetAddress])
} }
} }
if link.Type == "" {
link.Type = "other"
}
continue continue
} }
tq := float32(linkOrigin.TQ) / 255.0
link := &Link{ link := &Link{
Type: typeList[linkOrigin.SourceMAC],
Source: linkOrigin.SourceID, Source: linkOrigin.SourceID,
SourceMAC: linkOrigin.SourceMAC, SourceAddress: linkOrigin.SourceAddress,
Target: linkOrigin.TargetID, Target: linkOrigin.TargetID,
TargetMAC: linkOrigin.TargetMAC, TargetAddress: linkOrigin.TargetAddress,
SourceTQ: tq, SourceTQ: linkOrigin.TQ,
TargetTQ: tq, TargetTQ: linkOrigin.TQ,
} }
linkType, linkTypeFound := typeList[linkOrigin.SourceAddress]
if !linkTypeFound {
linkType, linkTypeFound = typeList[linkOrigin.TargetAddress]
}
if switchSourceTarget { if switchSourceTarget {
link.Type = typeList[linkOrigin.TargetMAC]
link.Source = linkOrigin.TargetID link.Source = linkOrigin.TargetID
link.SourceMAC = linkOrigin.TargetMAC link.SourceAddress = linkOrigin.TargetAddress
link.Target = linkOrigin.SourceID link.Target = linkOrigin.SourceID
link.TargetMAC = linkOrigin.SourceMAC link.TargetAddress = linkOrigin.SourceAddress
linkType, linkTypeFound = typeList[linkOrigin.TargetAddress]
if !linkTypeFound {
linkType, linkTypeFound = typeList[linkOrigin.SourceAddress]
} }
if link.Type == "" { }
link.Type = "other"
if linkTypeFound {
link.Type = linkType
} else {
link.Type = LINK_TYPE_FALLBACK
} }
links[key] = link links[key] = link
meshviewer.Links = append(meshviewer.Links, link) meshviewer.Links = append(meshviewer.Links, link)

View File

@ -18,7 +18,7 @@ func TestTransform(t *testing.T) {
NodeID: "node_a", NodeID: "node_a",
Network: data.Network{ Network: data.Network{
Mac: "node:a:mac", Mac: "node:a:mac",
Mesh: map[string]*data.BatInterface{ Mesh: map[string]*data.NetworkInterface{
"bat0": { "bat0": {
Interfaces: struct { Interfaces: struct {
Wireless []string `json:"wireless,omitempty"` Wireless []string `json:"wireless,omitempty"`
@ -55,7 +55,7 @@ func TestTransform(t *testing.T) {
NodeID: "node_c", NodeID: "node_c",
Network: data.Network{ Network: data.Network{
Mac: "node:c:mac", Mac: "node:c:mac",
Mesh: map[string]*data.BatInterface{ Mesh: map[string]*data.NetworkInterface{
"bat0": { "bat0": {
Interfaces: struct { Interfaces: struct {
Wireless []string `json:"wireless,omitempty"` Wireless []string `json:"wireless,omitempty"`
@ -85,7 +85,7 @@ func TestTransform(t *testing.T) {
NodeID: "node_b", NodeID: "node_b",
Network: data.Network{ Network: data.Network{
Mac: "node:b:mac", Mac: "node:b:mac",
Mesh: map[string]*data.BatInterface{ Mesh: map[string]*data.NetworkInterface{
"bat0": { "bat0": {
Interfaces: struct { Interfaces: struct {
Wireless []string `json:"wireless,omitempty"` Wireless []string `json:"wireless,omitempty"`
@ -121,7 +121,7 @@ func TestTransform(t *testing.T) {
NodeID: "node_d", NodeID: "node_d",
Network: data.Network{ Network: data.Network{
Mac: "node:d:mac", Mac: "node:d:mac",
Mesh: map[string]*data.BatInterface{ Mesh: map[string]*data.NetworkInterface{
"bat0": { "bat0": {
Interfaces: struct { Interfaces: struct {
Wireless []string `json:"wireless,omitempty"` Wireless []string `json:"wireless,omitempty"`
@ -159,27 +159,27 @@ func TestTransform(t *testing.T) {
assert.Len(links, 3) assert.Len(links, 3)
for _, link := range links { for _, link := range links {
switch link.SourceMAC { switch link.SourceAddress {
case "node:a:mac:lan": case "node:a:mac:lan":
assert.Equal("other", link.Type) assert.Equal("other", link.Type)
assert.Equal("node:b:mac:lan", link.TargetMAC) assert.Equal("node:b:mac:lan", link.TargetAddress)
assert.Equal(float32(0.2), link.SourceTQ) assert.Equal(float32(0.2), link.SourceTQ)
assert.Equal(float32(0.2), link.TargetTQ) assert.Equal(float32(0.2), link.TargetTQ)
break break
case "node:a:mac:wifi": case "node:a:mac:wifi":
assert.Equal("wifi", link.Type) assert.Equal("wifi", link.Type)
assert.Equal("node:b:mac:wifi", link.TargetMAC) assert.Equal("node:b:mac:wifi", link.TargetAddress)
assert.Equal(float32(0.6), link.SourceTQ) assert.Equal(float32(0.6), link.SourceTQ)
assert.Equal(float32(0.8), link.TargetTQ) assert.Equal(float32(0.8), link.TargetTQ)
case "node:b:mac:lan": case "node:b:mac:lan":
assert.Equal("other", link.Type) assert.Equal("other", link.Type)
assert.Equal("node:c:mac:lan", link.TargetMAC) assert.Equal("node:c:mac:lan", link.TargetAddress)
assert.Equal(float32(0.8), link.SourceTQ) assert.Equal(float32(0.8), link.SourceTQ)
assert.Equal(float32(0.4), link.TargetTQ) assert.Equal(float32(0.4), link.TargetTQ)
break break
default: default:
assert.False(true, "invalid link.SourceMAC found") assert.False(true, "invalid link.SourceAddress found")
} }
} }
} }

View File

@ -69,8 +69,8 @@ type Link struct {
Target string `json:"target"` Target string `json:"target"`
SourceTQ float32 `json:"source_tq"` SourceTQ float32 `json:"source_tq"`
TargetTQ float32 `json:"target_tq"` TargetTQ float32 `json:"target_tq"`
SourceMAC string `json:"source_mac"` SourceAddress string `json:"source_addr"`
TargetMAC string `json:"target_mac"` TargetAddress string `json:"target_addr"`
} }
func NewNode(nodes *runtime.Nodes, n *runtime.Node) *Node { func NewNode(nodes *runtime.Nodes, n *runtime.Node) *Node {
@ -134,15 +134,15 @@ func NewNode(nodes *runtime.Nodes, n *runtime.Node) *Node {
} }
node.Uptime = jsontime.Now().Add(time.Duration(statistic.Uptime) * -time.Second) node.Uptime = jsontime.Now().Add(time.Duration(statistic.Uptime) * -time.Second)
node.GatewayNexthop = nodes.GetNodeIDbyMAC(statistic.GatewayNexthop) node.GatewayNexthop = nodes.GetNodeIDbyAddress(statistic.GatewayNexthop)
if node.GatewayNexthop == "" { if node.GatewayNexthop == "" {
node.GatewayNexthop = statistic.GatewayNexthop node.GatewayNexthop = statistic.GatewayNexthop
} }
node.GatewayIPv4 = nodes.GetNodeIDbyMAC(statistic.GatewayIPv4) node.GatewayIPv4 = nodes.GetNodeIDbyAddress(statistic.GatewayIPv4)
if node.GatewayIPv4 == "" { if node.GatewayIPv4 == "" {
node.GatewayIPv4 = statistic.GatewayIPv4 node.GatewayIPv4 = statistic.GatewayIPv4
} }
node.GatewayIPv6 = nodes.GetNodeIDbyMAC(statistic.GatewayIPv6) node.GatewayIPv6 = nodes.GetNodeIDbyAddress(statistic.GatewayIPv6)
if node.GatewayIPv6 == "" { if node.GatewayIPv6 == "" {
node.GatewayIPv6 = statistic.GatewayIPv6 node.GatewayIPv6 = statistic.GatewayIPv6
} }

View File

@ -17,8 +17,7 @@ import (
// Collector for a specificle respond messages // Collector for a specificle respond messages
type Collector struct { type Collector struct {
connections []*net.UDPConn // UDP sockets connections []multicastConn // UDP sockets
ifaceToConn map[string]*net.UDPConn // map from interface name to UDP socket
port int port int
queue chan *Response // received responses queue chan *Response // received responses
@ -29,17 +28,20 @@ type Collector struct {
stop chan interface{} stop chan interface{}
} }
type multicastConn struct {
Conn *net.UDPConn
MulticastAddress net.IP
}
// NewCollector creates a Collector struct // NewCollector creates a Collector struct
func NewCollector(db database.Connection, nodes *runtime.Nodes, sitesDomains map[string][]string, ifaces []string, port int) *Collector { func NewCollector(db database.Connection, nodes *runtime.Nodes, sitesDomains map[string][]string, ifaces []InterfaceConfig) *Collector {
coll := &Collector{ coll := &Collector{
db: db, db: db,
nodes: nodes, nodes: nodes,
sitesDomains: sitesDomains, sitesDomains: sitesDomains,
port: port,
queue: make(chan *Response, 400), queue: make(chan *Response, 400),
stop: make(chan interface{}), stop: make(chan interface{}),
ifaceToConn: make(map[string]*net.UDPConn),
} }
for _, iface := range ifaces { for _, iface := range ifaces {
@ -55,35 +57,47 @@ func NewCollector(db database.Connection, nodes *runtime.Nodes, sitesDomains map
return coll return coll
} }
func (coll *Collector) listenUDP(iface string) { func (coll *Collector) listenUDP(iface InterfaceConfig) {
if _, found := coll.ifaceToConn[iface]; found {
log.Panicf("can not listen twice on %s", iface) var addr net.IP
}
linkLocalAddr, err := getLinkLocalAddr(iface) var err error
if iface.IPAddress != "" {
addr = net.ParseIP(iface.IPAddress)
} else {
addr, err = getUnicastAddr(iface.InterfaceName)
if err != nil { if err != nil {
log.Panic(err) log.Panic(err)
} }
}
multicastAddress := multicastAddressDefault
if iface.MulticastAddress != "" {
multicastAddress = iface.MulticastAddress
}
// Open socket // Open socket
conn, err := net.ListenUDP("udp", &net.UDPAddr{ conn, err := net.ListenUDP("udp", &net.UDPAddr{
IP: linkLocalAddr, IP: addr,
Port: coll.port, Port: iface.Port,
Zone: iface, Zone: iface.InterfaceName,
}) })
if err != nil { if err != nil {
log.Panic(err) log.Panic(err)
} }
conn.SetReadBuffer(maxDataGramSize) conn.SetReadBuffer(maxDataGramSize)
coll.ifaceToConn[iface] = conn coll.connections = append(coll.connections, multicastConn{
coll.connections = append(coll.connections, conn) Conn: conn,
MulticastAddress: net.ParseIP(multicastAddress),
})
// Start receiver // Start receiver
go coll.receiver(conn) go coll.receiver(conn)
} }
// Returns the first link local unicast address for the given interface name // Returns a unicast address of given interface (prefer global unicast address over link local address)
func getLinkLocalAddr(ifname string) (net.IP, error) { func getUnicastAddr(ifname string) (net.IP, error) {
iface, err := net.InterfaceByName(ifname) iface, err := net.InterfaceByName(ifname)
if err != nil { if err != nil {
return nil, err return nil, err
@ -93,13 +107,23 @@ func getLinkLocalAddr(ifname string) (net.IP, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
var ip net.IP
for _, addr := range addresses { for _, addr := range addresses {
if ipnet := addr.(*net.IPNet); ipnet.IP.IsLinkLocalUnicast() { ipnet, ok := addr.(*net.IPNet)
return ipnet.IP, nil if !ok {
continue
}
if ipnet.IP.IsGlobalUnicast() {
ip = ipnet.IP
} else if ipnet.IP.IsLinkLocalUnicast() && ip == nil {
ip = ipnet.IP
} }
} }
return nil, fmt.Errorf("unable to find link local unicast address for %s", ifname) if ip != nil {
return ip, nil
}
return nil, fmt.Errorf("unable to find a unicast address for %s", ifname)
} }
// Start Collector // Start Collector
@ -122,7 +146,7 @@ func (coll *Collector) Start(interval time.Duration) {
func (coll *Collector) Close() { func (coll *Collector) Close() {
close(coll.stop) close(coll.stop)
for _, conn := range coll.connections { for _, conn := range coll.connections {
conn.Close() conn.Conn.Close()
} }
close(coll.queue) close(coll.queue)
} }
@ -139,7 +163,7 @@ func (coll *Collector) sendOnce() {
func (coll *Collector) sendMulticast() { func (coll *Collector) sendMulticast() {
log.Println("sending multicasts") log.Println("sending multicasts")
for _, conn := range coll.connections { for _, conn := range coll.connections {
coll.sendPacket(conn, multiCastGroup) coll.sendPacket(conn.Conn, conn.MulticastAddress)
} }
} }
@ -153,21 +177,29 @@ func (coll *Collector) sendUnicasts(seenBefore jsontime.Time) {
}) })
// Send unicast packets // Send unicast packets
log.Printf("sending unicast to %d nodes", len(nodes)) count := 0
for _, node := range nodes { for _, node := range nodes {
conn := coll.ifaceToConn[node.Address.Zone] send := 0
if conn == nil { for _, conn := range coll.connections {
log.Printf("unable to find connection for %s", node.Address.Zone) if node.Address.Zone != "" && conn.Conn.LocalAddr().(*net.UDPAddr).Zone != node.Address.Zone {
continue continue
} }
coll.sendPacket(conn, node.Address.IP) coll.sendPacket(conn.Conn, node.Address.IP)
time.Sleep(10 * time.Millisecond) send++
} }
if send == 0 {
log.Printf("unable to find connection for %s", node.Address.Zone)
} else {
time.Sleep(10 * time.Millisecond)
count += send
}
}
log.Printf("sending %d unicast pkg for %d nodes", count, len(nodes))
} }
// SendPacket sends a UDP request to the given unicast or multicast address on the first UDP socket // SendPacket sends a UDP request to the given unicast or multicast address on the first UDP socket
func (coll *Collector) SendPacket(destination net.IP) { func (coll *Collector) SendPacket(destination net.IP) {
coll.sendPacket(coll.connections[0], destination) coll.sendPacket(coll.connections[0].Conn, destination)
} }
// sendPacket sends a UDP request to the given unicast or multicast address on the given UDP socket // sendPacket sends a UDP request to the given unicast or multicast address on the given UDP socket
@ -201,7 +233,7 @@ func (coll *Collector) sender() {
func (coll *Collector) parser() { func (coll *Collector) parser() {
for obj := range coll.queue { for obj := range coll.queue {
if data, err := obj.parse(); err != nil { if data, err := obj.parse(); err != nil {
log.Println("unable to decode response from", obj.Address.String(), err, "\n", string(obj.Raw)) log.Println("unable to decode response from", obj.Address.String(), err)
} else { } else {
coll.saveResponse(obj.Address, data) coll.saveResponse(obj.Address, data)
} }

View File

@ -17,7 +17,7 @@ const (
func TestCollector(t *testing.T) { func TestCollector(t *testing.T) {
nodes := runtime.NewNodes(&runtime.NodesConfig{}) nodes := runtime.NewNodes(&runtime.NodesConfig{})
collector := NewCollector(nil, nodes, map[string][]string{SITE_TEST: {DOMAIN_TEST}}, []string{}, 10001) collector := NewCollector(nil, nodes, map[string][]string{SITE_TEST: {DOMAIN_TEST}}, []InterfaceConfig{})
collector.Start(time.Millisecond) collector.Start(time.Millisecond)
time.Sleep(time.Millisecond * 10) time.Sleep(time.Millisecond * 10)
collector.Close() collector.Close()

View File

@ -5,9 +5,8 @@ import "github.com/FreifunkBremen/yanic/lib/duration"
type Config struct { type Config struct {
Enable bool `toml:"enable"` Enable bool `toml:"enable"`
Synchronize duration.Duration `toml:"synchronize"` Synchronize duration.Duration `toml:"synchronize"`
Interfaces []string `toml:"interfaces"` Interfaces []InterfaceConfig `toml:"interfaces"`
Sites map[string]SiteConfig `toml:"sites"` Sites map[string]SiteConfig `toml:"sites"`
Port int `toml:"port"`
CollectInterval duration.Duration `toml:"collect_interval"` CollectInterval duration.Duration `toml:"collect_interval"`
} }
@ -22,3 +21,10 @@ func (c *Config) SitesDomains() (result map[string][]string) {
type SiteConfig struct { type SiteConfig struct {
Domains []string `toml:"domains"` Domains []string `toml:"domains"`
} }
type InterfaceConfig struct {
InterfaceName string `toml:"ifname"`
IPAddress string `toml:"ip_address"`
MulticastAddress string `toml:"multicast_address"`
Port int `toml:"port"`
}

View File

@ -4,10 +4,10 @@ import (
"net" "net"
) )
// default multicast group used by announced
var multiCastGroup = net.ParseIP("ff02:0:0:0:0:0:2:1001")
const ( const (
// default multicast group used by announced
multicastAddressDefault = "ff02:0:0:0:0:0:2:1001"
// default udp port used by announced // default udp port used by announced
port = 1001 port = 1001

View File

@ -21,10 +21,10 @@ type Node struct {
// Link represents a link between two nodes // Link represents a link between two nodes
type Link struct { type Link struct {
SourceID string SourceID string
SourceMAC string SourceAddress string
TargetID string TargetID string
TargetMAC string TargetAddress string
TQ int TQ float32
} }
// IsGateway returns whether the node is a gateway // IsGateway returns whether the node is a gateway

View File

@ -100,8 +100,8 @@ func (nodes *Nodes) Select(f func(*Node) bool) []*Node {
return result return result
} }
func (nodes *Nodes) GetNodeIDbyMAC(mac string) string { func (nodes *Nodes) GetNodeIDbyAddress(addr string) string {
return nodes.ifaceToNodeID[mac] return nodes.ifaceToNodeID[addr]
} }
// NodeLinks returns a list of links to known neighbours // NodeLinks returns a list of links to known neighbours
@ -117,10 +117,23 @@ func (nodes *Nodes) NodeLinks(node *Node) (result []Link) {
if neighbourID := nodes.ifaceToNodeID[neighbourMAC]; neighbourID != "" { if neighbourID := nodes.ifaceToNodeID[neighbourMAC]; neighbourID != "" {
result = append(result, Link{ result = append(result, Link{
SourceID: neighbours.NodeID, SourceID: neighbours.NodeID,
SourceMAC: sourceMAC, SourceAddress: sourceMAC,
TargetID: neighbourID, TargetID: neighbourID,
TargetMAC: neighbourMAC, TargetAddress: neighbourMAC,
TQ: link.Tq, TQ: float32(link.Tq) / 255.0,
})
}
}
}
for _, iface := range neighbours.Babel {
for neighbourIP, link := range iface.Neighbours {
if neighbourID := nodes.ifaceToNodeID[neighbourIP]; neighbourID != "" {
result = append(result, Link{
SourceID: neighbours.NodeID,
SourceAddress: iface.LinkLocalAddress,
TargetID: neighbourID,
TargetAddress: neighbourIP,
TQ: 1.0 - (float32(link.Cost) / 65535.0),
}) })
} }
} }
@ -179,19 +192,19 @@ func (nodes *Nodes) readIfaces(nodeinfo *data.NodeInfo) {
addresses := []string{network.Mac} addresses := []string{network.Mac}
for _, batinterface := range network.Mesh { for _, iface := range network.Mesh {
addresses = append(addresses, batinterface.Addresses()...) addresses = append(addresses, iface.Addresses()...)
} }
for _, mac := range addresses { for _, addr := range addresses {
if mac == "" { if addr == "" {
continue continue
} }
if oldNodeID, _ := nodes.ifaceToNodeID[mac]; oldNodeID != nodeID { if oldNodeID, _ := nodes.ifaceToNodeID[addr]; oldNodeID != nodeID {
if oldNodeID != "" { if oldNodeID != "" {
log.Printf("override nodeID from %s to %s on MAC address %s", oldNodeID, nodeID, mac) log.Printf("override nodeID from %s to %s on MAC address %s", oldNodeID, nodeID, addr)
} }
nodes.ifaceToNodeID[mac] = nodeID nodes.ifaceToNodeID[addr] = nodeID
} }
} }
} }

View File

@ -179,7 +179,7 @@ func TestLinksNodes(t *testing.T) {
"f4:f2:6d:d7:a3:0b": { "f4:f2:6d:d7:a3:0b": {
Neighbours: map[string]data.BatmanLink{ Neighbours: map[string]data.BatmanLink{
"f4:f2:6d:d7:a3:0a": { "f4:f2:6d:d7:a3:0a": {
Tq: 200, Lastseen: 0.42, Tq: 204, Lastseen: 0.42,
}, },
}, },
}, },
@ -198,11 +198,11 @@ func TestLinksNodes(t *testing.T) {
assert.Len(links, 1) assert.Len(links, 1)
link := links[0] link := links[0]
assert.Equal(link.SourceID, "f4f26dd7a30b") assert.Equal(link.SourceID, "f4f26dd7a30b")
assert.Equal(link.SourceMAC, "f4:f2:6d:d7:a3:0b") assert.Equal(link.SourceAddress, "f4:f2:6d:d7:a3:0b")
assert.Equal(link.TargetID, "f4f26dd7a30a") assert.Equal(link.TargetID, "f4f26dd7a30a")
assert.Equal(link.TargetMAC, "f4:f2:6d:d7:a3:0a") assert.Equal(link.TargetAddress, "f4:f2:6d:d7:a3:0a")
assert.Equal(link.TQ, 200) assert.Equal(link.TQ, float32(0.8))
nodeid := nodes.GetNodeIDbyMAC("f4:f2:6d:d7:a3:0a") nodeid := nodes.GetNodeIDbyAddress("f4:f2:6d:d7:a3:0a")
assert.Equal("f4f26dd7a30a", nodeid) assert.Equal("f4f26dd7a30a", nodeid)
} }