diff --git a/cmd/config_test.go b/cmd/config_test.go index eaa95f1..ce6cd61 100644 --- a/cmd/config_test.go +++ b/cmd/config_test.go @@ -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) diff --git a/cmd/query.go b/cmd/query.go index 9ab3785..7478d63 100644 --- a/cmd/query.go +++ b/cmd/query.go @@ -1,8 +1,10 @@ package cmd import ( + "encoding/json" "log" "net" + "strings" "time" "github.com/FreifunkBremen/yanic/respond" @@ -10,32 +12,55 @@ 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 ", + Use: "query ", 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 { - log.Printf("%s: %+v", id, data) + jq, err := json.Marshal(data) + if err != nil { + 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() { 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)") } diff --git a/cmd/serve.go b/cmd/serve.go index 36edaf8..d81eae8 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -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() } diff --git a/config_example.toml b/config_example.toml index bc76630..e80203b 100644 --- a/config_example.toml +++ b/config_example.toml @@ -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] diff --git a/data/neighbours.go b/data/neighbours.go index 08fd0d2..b45aea4 100644 --- a/data/neighbours.go +++ b/data/neighbours.go @@ -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"` diff --git a/data/nodeinfo.go b/data/nodeinfo.go index 3f9fb29..1b8e091 100644 --- a/data/nodeinfo.go +++ b/data/nodeinfo.go @@ -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"` diff --git a/data/nodeinfo_test.go b/data/nodeinfo_test.go index 40f7889..b32974b 100644 --- a/data/nodeinfo_test.go +++ b/data/nodeinfo_test.go @@ -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) } diff --git a/database/influxdb/link.go b/database/influxdb/link.go index 3d50468..9e54303 100644 --- a/database/influxdb/link.go +++ b/database/influxdb/link.go @@ -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) } diff --git a/database/influxdb/node_test.go b/database/influxdb/node_test.go index 47cb5fc..58ba694 100644 --- a/database/influxdb/node_test.go +++ b/database/influxdb/node_test.go @@ -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"]) diff --git a/docs/docs_configuration.md b/docs/docs_configuration.md index fce86d1..1fcfecf 100644 --- a/docs/docs_configuration.md +++ b/docs/docs_configuration.md @@ -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 %} diff --git a/docs/docs_quick_conf.md b/docs/docs_quick_conf.md index 4fbb45b..eb83b1c 100644 --- a/docs/docs_quick_conf.md +++ b/docs/docs_quick_conf.md @@ -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: diff --git a/docs/home_about.md b/docs/home_about.md index 6ed7971..86bcea1 100644 --- a/docs/home_about.md +++ b/docs/home_about.md @@ -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`. diff --git a/output/meshviewer-ffrgb/meshviewer.go b/output/meshviewer-ffrgb/meshviewer.go index 15d2620..cf9d01c 100644 --- a/output/meshviewer-ffrgb/meshviewer.go +++ b/output/meshviewer-ffrgb/meshviewer.go @@ -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) diff --git a/output/meshviewer-ffrgb/meshviewer_test.go b/output/meshviewer-ffrgb/meshviewer_test.go index 80f8083..7fcde4a 100644 --- a/output/meshviewer-ffrgb/meshviewer_test.go +++ b/output/meshviewer-ffrgb/meshviewer_test.go @@ -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") } } } diff --git a/output/meshviewer-ffrgb/struct.go b/output/meshviewer-ffrgb/struct.go index e39e775..f8975bc 100644 --- a/output/meshviewer-ffrgb/struct.go +++ b/output/meshviewer-ffrgb/struct.go @@ -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 } diff --git a/respond/collector.go b/respond/collector.go index b258158..1727bab 100644 --- a/respond/collector.go +++ b/respond/collector.go @@ -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) } diff --git a/respond/collector_test.go b/respond/collector_test.go index 82dca23..c16fdfb 100644 --- a/respond/collector_test.go +++ b/respond/collector_test.go @@ -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() diff --git a/respond/config.go b/respond/config.go index 1a52313..1d381ef 100644 --- a/respond/config.go +++ b/respond/config.go @@ -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"` +} diff --git a/respond/respond.go b/respond/respond.go index 068a6d6..9564b8e 100644 --- a/respond/respond.go +++ b/respond/respond.go @@ -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 diff --git a/runtime/node.go b/runtime/node.go index f49807b..3e2059c 100644 --- a/runtime/node.go +++ b/runtime/node.go @@ -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 diff --git a/runtime/nodes.go b/runtime/nodes.go index 99be5db..17fd26d 100644 --- a/runtime/nodes.go +++ b/runtime/nodes.go @@ -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 } } } diff --git a/runtime/nodes_test.go b/runtime/nodes_test.go index 05401da..7283f73 100644 --- a/runtime/nodes_test.go +++ b/runtime/nodes_test.go @@ -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) }