[TASK] make yanic babel compatible (#104)

This commit is contained in:
Martin Geno 2017-12-05 23:17:49 +01:00 committed by Martin/Geno
parent 35d6933d31
commit c20e216ed6
No known key found for this signature in database
GPG Key ID: 9D7D3C6BFF600C6A
22 changed files with 341 additions and 165 deletions

View File

@ -15,7 +15,7 @@ func TestReadConfig(t *testing.T) {
assert.NotNil(config)
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.Hour*24*7, config.Nodes.PruneAfter.Duration)
assert.Equal(time.Hour*24*7, config.Database.DeleteAfter.Duration)

View File

@ -4,6 +4,7 @@ import (
"encoding/json"
"log"
"net"
"strings"
"time"
"github.com/FreifunkBremen/yanic/respond"
@ -11,36 +12,54 @@ import (
"github.com/spf13/cobra"
)
var wait int
var (
wait int
port int
ipAddress string
)
// queryCmd represents the query 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",
Example: `yanic query wlan0 "fe80::eade:27ff:dead:beef"`,
Example: `yanic query "eth0,wlan0" "fe80::eade:27ff:dead:beef"`,
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
iface := args[0]
ifaces := strings.Split(args[0], ",")
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{})
sitesDomains := make(map[string][]string)
collector := respond.NewCollector(nil, nodes, sitesDomains, []string{iface}, 0)
collector := respond.NewCollector(nil, nodes, sitesDomains, ifacesConfigs)
defer collector.Close()
collector.SendPacket(dstAddress)
time.Sleep(time.Second * time.Duration(wait))
for id, data := range nodes.List {
bytes, err := json.Marshal(data)
jq, err := json.Marshal(data)
if err != nil {
log.Printf("%s: %+v", id, err)
log.Printf("%s: %+v", id, data)
} else {
log.Printf("%s: %+v", id, string(bytes))
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))
}
}
}
},
@ -49,4 +68,6 @@ var queryCmd = &cobra.Command{
func init() {
RootCmd.AddCommand(queryCmd)
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)
}
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)
defer collector.Close()
}

View File

@ -9,11 +9,6 @@ enable = true
synchronize = "1m"
# how often request per multicast
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)
#[respondd.sites.example]
@ -23,6 +18,20 @@ interfaces = ["br-ffhb"]
[respondd.sites.ffhb]
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.
# This is useful for testing purposes or for a little standalone installation.
[webserver]

View File

@ -3,6 +3,7 @@ package data
// Neighbours struct
type Neighbours struct {
Batadv map[string]BatadvNeighbours `json:"batadv"`
Babel map[string]BabelNeighbours `json:"babel"`
LLDP map[string]LLDPNeighbours `json:"lldp"`
//WifiNeighbours map[string]WifiNeighbours `json:"wifi"`
NodeID string `json:"node_id"`
@ -27,11 +28,27 @@ type LLDPLink struct {
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
type BatadvNeighbours struct {
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
type WifiNeighbours struct {
Neighbours map[string]WifiLink `json:"neighbours"`

View File

@ -14,8 +14,8 @@ type NodeInfo struct {
Wireless *Wireless `json:"wireless,omitempty"`
}
// BatInterface struct
type BatInterface struct {
// NetworkInterface struct
type NetworkInterface struct {
Interfaces struct {
Wireless []string `json:"wireless,omitempty"`
Other []string `json:"other,omitempty"`
@ -24,16 +24,17 @@ type BatInterface struct {
}
// 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...)
}
// Network struct
type Network struct {
Mac string `json:"mac"`
Addresses []string `json:"addresses"`
Mesh map[string]*BatInterface `json:"mesh"`
MeshInterfaces []string `json:"mesh_interfaces"`
Mac string `json:"mac"`
Addresses []string `json:"addresses"`
Mesh map[string]*NetworkInterface `json:"mesh"`
// still used in gluon?
MeshInterfaces []string `json:"mesh_interfaces"`
}
// Owner struct
@ -64,6 +65,9 @@ type Software struct {
Version string `json:"version,omitempty"`
Compat int `json:"compat,omitempty"`
} `json:"batman-adv,omitempty"`
Babeld struct {
Version string `json:"version,omitempty"`
} `json:"babeld,omitempty"`
Fastd struct {
Enabled bool `json:"enabled,omitempty"`
Version string `json:"version,omitempty"`

View File

@ -8,7 +8,7 @@ import (
func TestNodeinfoBatAddresses(t *testing.T) {
assert := assert.New(t)
batIface := &BatInterface{
iface := &NetworkInterface{
Interfaces: struct {
Wireless []string `json:"wireless,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.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) {
tags := models.Tags{}
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.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

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

View File

@ -14,10 +14,15 @@ Group for configuration of respondd request.
enable = true
# synchronize = "1m"
collect_interval = "1m"
interfaces = ["br-ffhb"]
#port = 10001
#[respondd.sites.example]
#domains = ["city"]
[[respondd.interfaces]]
ifname = "br-ffhb"
#ip_address = "fe80::..."
#multicast_address = "ff02::2:1001"
#port = 10001
```
{% endmethod %}
@ -46,7 +51,7 @@ synchronize = "1m"
{% method %}
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`.
{% sample lang="toml" %}
```toml
@ -55,26 +60,16 @@ collect_interval = "1m"
{% endmethod %}
### interfaces
### sites
{% method %}
Interface that has an IP in your mesh network
List of sites to save stats for (empty for global only)
{% sample lang="toml" %}
```toml
interfaces = ["br-ffhb"]
sites = ["ffhb"]
```
{% 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]
{% method %}
Tables of sites to save stats for (not exists for global only).
@ -85,6 +80,7 @@ Here is the site _ffhb_.
domains = ["city"]
```
{% endmethod %}
#### domains
{% method %}
list of domains on this site to save stats for (empty for global only)
@ -95,6 +91,60 @@ domains = ["city"]
{% 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]
{% method %}

View File

@ -5,7 +5,7 @@ cp /opt/go/src/github.com/FreifunkBremen/yanic/config_example.toml /etc/yanic.co
```
# 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`.
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 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`.

View File

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

View File

@ -18,7 +18,7 @@ func TestTransform(t *testing.T) {
NodeID: "node_a",
Network: data.Network{
Mac: "node:a:mac",
Mesh: map[string]*data.BatInterface{
Mesh: map[string]*data.NetworkInterface{
"bat0": {
Interfaces: struct {
Wireless []string `json:"wireless,omitempty"`
@ -55,7 +55,7 @@ func TestTransform(t *testing.T) {
NodeID: "node_c",
Network: data.Network{
Mac: "node:c:mac",
Mesh: map[string]*data.BatInterface{
Mesh: map[string]*data.NetworkInterface{
"bat0": {
Interfaces: struct {
Wireless []string `json:"wireless,omitempty"`
@ -85,7 +85,7 @@ func TestTransform(t *testing.T) {
NodeID: "node_b",
Network: data.Network{
Mac: "node:b:mac",
Mesh: map[string]*data.BatInterface{
Mesh: map[string]*data.NetworkInterface{
"bat0": {
Interfaces: struct {
Wireless []string `json:"wireless,omitempty"`
@ -121,7 +121,7 @@ func TestTransform(t *testing.T) {
NodeID: "node_d",
Network: data.Network{
Mac: "node:d:mac",
Mesh: map[string]*data.BatInterface{
Mesh: map[string]*data.NetworkInterface{
"bat0": {
Interfaces: struct {
Wireless []string `json:"wireless,omitempty"`
@ -159,27 +159,27 @@ func TestTransform(t *testing.T) {
assert.Len(links, 3)
for _, link := range links {
switch link.SourceMAC {
switch link.SourceAddress {
case "node:a:mac:lan":
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.TargetTQ)
break
case "node:a:mac:wifi":
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.8), link.TargetTQ)
case "node:b:mac:lan":
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.4), link.TargetTQ)
break
default:
assert.False(true, "invalid link.SourceMAC found")
assert.False(true, "invalid link.SourceAddress found")
}
}
}

View File

@ -64,13 +64,13 @@ type Location struct {
// Link
type Link struct {
Type string `json:"type"`
Source string `json:"source"`
Target string `json:"target"`
SourceTQ float32 `json:"source_tq"`
TargetTQ float32 `json:"target_tq"`
SourceMAC string `json:"source_mac"`
TargetMAC string `json:"target_mac"`
Type string `json:"type"`
Source string `json:"source"`
Target string `json:"target"`
SourceTQ float32 `json:"source_tq"`
TargetTQ float32 `json:"target_tq"`
SourceAddress string `json:"source_addr"`
TargetAddress string `json:"target_addr"`
}
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.GatewayNexthop = nodes.GetNodeIDbyMAC(statistic.GatewayNexthop)
node.GatewayNexthop = nodes.GetNodeIDbyAddress(statistic.GatewayNexthop)
if node.GatewayNexthop == "" {
node.GatewayNexthop = statistic.GatewayNexthop
}
node.GatewayIPv4 = nodes.GetNodeIDbyMAC(statistic.GatewayIPv4)
node.GatewayIPv4 = nodes.GetNodeIDbyAddress(statistic.GatewayIPv4)
if node.GatewayIPv4 == "" {
node.GatewayIPv4 = statistic.GatewayIPv4
}
node.GatewayIPv6 = nodes.GetNodeIDbyMAC(statistic.GatewayIPv6)
node.GatewayIPv6 = nodes.GetNodeIDbyAddress(statistic.GatewayIPv6)
if node.GatewayIPv6 == "" {
node.GatewayIPv6 = statistic.GatewayIPv6
}

View File

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

View File

@ -17,7 +17,7 @@ const (
func TestCollector(t *testing.T) {
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)
time.Sleep(time.Millisecond * 10)
collector.Close()

View File

@ -5,9 +5,8 @@ import "github.com/FreifunkBremen/yanic/lib/duration"
type Config struct {
Enable bool `toml:"enable"`
Synchronize duration.Duration `toml:"synchronize"`
Interfaces []string `toml:"interfaces"`
Interfaces []InterfaceConfig `toml:"interfaces"`
Sites map[string]SiteConfig `toml:"sites"`
Port int `toml:"port"`
CollectInterval duration.Duration `toml:"collect_interval"`
}
@ -22,3 +21,10 @@ func (c *Config) SitesDomains() (result map[string][]string) {
type SiteConfig struct {
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"
)
// default multicast group used by announced
var multiCastGroup = net.ParseIP("ff02:0:0:0:0:0:2:1001")
const (
// default multicast group used by announced
multicastAddressDefault = "ff02:0:0:0:0:0:2:1001"
// default udp port used by announced
port = 1001

View File

@ -20,11 +20,11 @@ type Node struct {
// Link represents a link between two nodes
type Link struct {
SourceID string
SourceMAC string
TargetID string
TargetMAC string
TQ int
SourceID string
SourceAddress string
TargetID string
TargetAddress string
TQ float32
}
// 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
}
func (nodes *Nodes) GetNodeIDbyMAC(mac string) string {
return nodes.ifaceToNodeID[mac]
func (nodes *Nodes) GetNodeIDbyAddress(addr string) string {
return nodes.ifaceToNodeID[addr]
}
// NodeLinks returns a list of links to known neighbours
@ -116,11 +116,24 @@ func (nodes *Nodes) NodeLinks(node *Node) (result []Link) {
for neighbourMAC, link := range batadv.Neighbours {
if neighbourID := nodes.ifaceToNodeID[neighbourMAC]; neighbourID != "" {
result = append(result, Link{
SourceID: neighbours.NodeID,
SourceMAC: sourceMAC,
TargetID: neighbourID,
TargetMAC: neighbourMAC,
TQ: link.Tq,
SourceID: neighbours.NodeID,
SourceAddress: sourceMAC,
TargetID: neighbourID,
TargetAddress: neighbourMAC,
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}
for _, batinterface := range network.Mesh {
addresses = append(addresses, batinterface.Addresses()...)
for _, iface := range network.Mesh {
addresses = append(addresses, iface.Addresses()...)
}
for _, mac := range addresses {
if mac == "" {
for _, addr := range addresses {
if addr == "" {
continue
}
if oldNodeID, _ := nodes.ifaceToNodeID[mac]; oldNodeID != nodeID {
if oldNodeID, _ := nodes.ifaceToNodeID[addr]; oldNodeID != nodeID {
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": {
Neighbours: map[string]data.BatmanLink{
"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)
link := links[0]
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.TargetMAC, "f4:f2:6d:d7:a3:0a")
assert.Equal(link.TQ, 200)
assert.Equal(link.TargetAddress, "f4:f2:6d:d7:a3:0a")
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)
}