From 2015fdd668a8572fd5d11c45da1cbaf646fbc147 Mon Sep 17 00:00:00 2001 From: genofire Date: Sat, 20 Jul 2024 00:31:16 +0200 Subject: [PATCH] feat(link-info): move from meshviewer-ffrgb to runtime --- database/influxdb/node_test.go | 3 + output/meshviewer-ffrgb/meshviewer.go | 68 +------------ output/meshviewer-ffrgb/meshviewer_test.go | 41 +++++--- runtime/link.go | 56 +++++++++++ runtime/node.go | 11 --- runtime/nodes.go | 109 ++++++++++++++++----- runtime/nodes_test.go | 20 ++-- 7 files changed, 187 insertions(+), 121 deletions(-) create mode 100644 runtime/link.go diff --git a/database/influxdb/node_test.go b/database/influxdb/node_test.go index 229fa78..1a3683a 100644 --- a/database/influxdb/node_test.go +++ b/database/influxdb/node_test.go @@ -14,6 +14,7 @@ func TestToInflux(t *testing.T) { assert := assert.New(t) node := &runtime.Node{ + Online: true, Statistics: &data.Statistics{ NodeID: "deadbeef", LoadAverage: 0.5, @@ -105,6 +106,7 @@ func TestToInflux(t *testing.T) { } neighbour := &runtime.Node{ + Online: true, Nodeinfo: &data.Nodeinfo{ NodeID: "foobar", Network: data.Network{ @@ -132,6 +134,7 @@ func TestToInflux(t *testing.T) { // do not add a empty statistics of a node droppednode := &runtime.Node{ + Online: true, Nodeinfo: &data.Nodeinfo{ NodeID: "notfound", Network: data.Network{ diff --git a/output/meshviewer-ffrgb/meshviewer.go b/output/meshviewer-ffrgb/meshviewer.go index 7ab0c81..a9fb151 100644 --- a/output/meshviewer-ffrgb/meshviewer.go +++ b/output/meshviewer-ffrgb/meshviewer.go @@ -4,18 +4,10 @@ import ( "fmt" "strings" - "github.com/bdlm/log" - "github.com/FreifunkBremen/yanic/lib/jsontime" "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{ @@ -25,7 +17,6 @@ func transform(nodes *runtime.Nodes) *Meshviewer { } links := make(map[string]*Link) - typeList := make(map[string]string) nodes.RLock() defer nodes.RUnlock() @@ -38,19 +29,6 @@ func transform(nodes *runtime.Nodes) *Meshviewer { continue } - if nodeinfo := nodeOrigin.Nodeinfo; nodeinfo != nil { - if meshes := nodeinfo.Network.Mesh; meshes != nil { - for _, mesh := range meshes { - for _, addr := range mesh.Interfaces.Wireless { - typeList[addr] = LINK_TYPE_WIRELESS - } - for _, addr := range mesh.Interfaces.Tunnel { - typeList[addr] = LINK_TYPE_TUNNEL - } - } - } - } - for _, linkOrigin := range nodes.NodeLinks(nodeOrigin) { var key string // keep source and target in the same order @@ -62,36 +40,11 @@ func transform(nodes *runtime.Nodes) *Meshviewer { } if link := links[key]; link != nil { - linkType, linkTypeFound := typeList[linkOrigin.SourceAddress] - if !linkTypeFound { - linkType, linkTypeFound = typeList[linkOrigin.TargetAddress] - } - if switchSourceTarget { link.TargetTQ = linkOrigin.TQ - - linkType, linkTypeFound = typeList[linkOrigin.TargetAddress] - if !linkTypeFound { - linkType, linkTypeFound = typeList[linkOrigin.SourceAddress] - } } else { link.SourceTQ = linkOrigin.TQ } - - if linkTypeFound && linkType != link.Type { - if link.Type == LINK_TYPE_FALLBACK { - link.Type = linkType - } else { - log.WithFields(map[string]interface{}{ - "link": fmt.Sprintf("%s-%s", linkOrigin.SourceAddress, linkOrigin.TargetAddress), - "prev": link.Type, - "new": linkType, - "source": typeList[linkOrigin.SourceAddress], - "target": typeList[linkOrigin.TargetAddress], - }).Warn("different linktypes") - } - } - continue } link := &Link{ @@ -101,11 +54,7 @@ func transform(nodes *runtime.Nodes) *Meshviewer { TargetAddress: linkOrigin.TargetAddress, SourceTQ: linkOrigin.TQ, TargetTQ: 0, - } - - linkType, linkTypeFound := typeList[linkOrigin.SourceAddress] - if !linkTypeFound { - linkType, linkTypeFound = typeList[linkOrigin.TargetAddress] + Type: linkOrigin.Type.String(), } if switchSourceTarget { @@ -115,22 +64,13 @@ func transform(nodes *runtime.Nodes) *Meshviewer { link.TargetTQ = linkOrigin.TQ link.Target = linkOrigin.SourceID link.TargetAddress = linkOrigin.SourceAddress - - linkType, linkTypeFound = typeList[linkOrigin.TargetAddress] - if !linkTypeFound { - linkType, linkTypeFound = typeList[linkOrigin.SourceAddress] - } } - if linkTypeFound { - link.Type = linkType - } else { - link.Type = LINK_TYPE_FALLBACK - } links[key] = link - meshviewer.Links = append(meshviewer.Links, link) } } - + for _, link := range links { + meshviewer.Links = append(meshviewer.Links, link) + } return meshviewer } diff --git a/output/meshviewer-ffrgb/meshviewer_test.go b/output/meshviewer-ffrgb/meshviewer_test.go index b03dbf2..3984496 100644 --- a/output/meshviewer-ffrgb/meshviewer_test.go +++ b/output/meshviewer-ffrgb/meshviewer_test.go @@ -128,8 +128,8 @@ func TestTransform(t *testing.T) { Other []string `json:"other,omitempty"` Tunnel []string `json:"tunnel,omitempty"` }{ - Wireless: []string{"node:b:mac:wifi"}, - Other: []string{"node:b:mac:lan"}, + Wireless: []string{"node:d:mac:wifi"}, + Other: []string{"node:d:mac:lan"}, }, }, }, @@ -155,28 +155,41 @@ func TestTransform(t *testing.T) { meshviewer := transform(nodes) assert.NotNil(meshviewer) assert.Len(meshviewer.Nodes, 4) + /* + links: + a:wifi <-> b:wifi 153 / 204 + a:lan -> b:lan 51 + c:lan <-> b:lan 102 / 204 + d:lan -> c:lan 204 (but offline) + d:wifi -> a:wifi 204 (but offline) + */ links := meshviewer.Links assert.Len(links, 3) + counter := 0 for _, link := range links { switch link.SourceAddress { case "node:a:mac:lan": - assert.Equal("other", link.Type) - assert.Equal("node:b:mac:lan", link.TargetAddress) - assert.Equal(float32(0.2), link.SourceTQ) - assert.Equal(float32(0), link.TargetTQ) + assert.Equal("node:b:mac:lan", link.TargetAddress, "a:lan -> b:lan") + assert.Equal("other", link.Type, "a:lan -> b:lan") + assert.Equal(float32(0.2), link.SourceTQ, "a:lan -> b:lan") + assert.Equal(float32(0), link.TargetTQ, "a:lan -> b:lan") + counter++ case "node:a:mac:wifi": - assert.Equal("wifi", link.Type) - assert.Equal("node:b:mac:wifi", link.TargetAddress) - assert.Equal(float32(0.6), link.SourceTQ) - assert.Equal(float32(0.8), link.TargetTQ) + assert.Equal("node:b:mac:wifi", link.TargetAddress, "a:wifi <-> b:wifi") + assert.Equal("wifi", link.Type, "a:wifi <-> b:wifi") + assert.Equal(float32(0.6), link.SourceTQ, "a:wifi <-> b:wifi") + assert.Equal(float32(0.8), link.TargetTQ, "a:wifi <-> b:wifi") + counter++ case "node:b:mac:lan": - assert.Equal("other", link.Type) - assert.Equal("node:c:mac:lan", link.TargetAddress) - assert.Equal(float32(0.8), link.SourceTQ) - assert.Equal(float32(0.4), link.TargetTQ) + assert.Equal("other", link.Type, "b:lan <-> c:lan") + assert.Equal("node:c:mac:lan", link.TargetAddress, "b:lan <-> c:lan") + assert.Equal(float32(0.8), link.SourceTQ, "b:lan <-> c:lan") + assert.Equal(float32(0.4), link.TargetTQ, "b:lan <-> c:lan") + counter++ default: assert.False(true, "invalid link.SourceAddress found") } } + assert.Equal(3, counter, "not found every link") } diff --git a/runtime/link.go b/runtime/link.go new file mode 100644 index 0000000..2cad206 --- /dev/null +++ b/runtime/link.go @@ -0,0 +1,56 @@ +package runtime + +type LinkType int + +const ( + UnknownLinkType LinkType = iota + WirelessLinkType + TunnelLinkType + OtherLinkType +) + +func (lt LinkType) String() string { + switch lt { + case WirelessLinkType: + return "wifi" + case TunnelLinkType: + return "vpn" + case OtherLinkType: + return "other" + } + return "unknown" +} + +type LinkProtocol int + +const ( + UnknownLinkProtocol LinkProtocol = iota + BatadvLinkProtocol + BabelLinkProtocol + LLDPLinkProtocol +) + +func (lp LinkProtocol) String() string { + switch lp { + case BatadvLinkProtocol: + return "batadv" + case BabelLinkProtocol: + return "babel" + case LLDPLinkProtocol: + return "lldp" + } + return "unkown" +} + +// Link represents a link between two nodes +type Link struct { + SourceID string + SourceHostname string + SourceAddress string + TargetID string + TargetAddress string + TargetHostname string + TQ float32 + Type LinkType + Protocol LinkProtocol +} diff --git a/runtime/node.go b/runtime/node.go index cf00377..a1af31a 100644 --- a/runtime/node.go +++ b/runtime/node.go @@ -19,17 +19,6 @@ type Node struct { CustomFields map[string]interface{} `json:"custom_fields"` } -// Link represents a link between two nodes -type Link struct { - SourceID string - SourceHostname string - SourceAddress string - TargetID string - TargetAddress string - TargetHostname string - TQ float32 -} - // IsGateway returns whether the node is a gateway func (node *Node) IsGateway() bool { if info := node.Nodeinfo; info != nil { diff --git a/runtime/nodes.go b/runtime/nodes.go index 7db9f73..138d3df 100644 --- a/runtime/nodes.go +++ b/runtime/nodes.go @@ -14,18 +14,22 @@ import ( // Nodes struct: cache DB of Node's structs type Nodes struct { - List map[string]*Node `json:"nodes"` // the current nodemap, indexed by node ID - ifaceToNodeID map[string]string // mapping from MAC address to NodeID - config *NodesConfig + List map[string]*Node `json:"nodes"` // the current nodemap, indexed by node ID + ifaceToNodeID map[string]string // mapping from MAC address to NodeID + ifaceToLinkType map[string]LinkType // mapping from MAC address to LinkType + ifaceToLinkProtocol map[string]LinkProtocol // mapping from MAC address to LinkProtocol + config *NodesConfig sync.RWMutex } // NewNodes create Nodes structs func NewNodes(config *NodesConfig) *Nodes { nodes := &Nodes{ - List: make(map[string]*Node), - ifaceToNodeID: make(map[string]string), - config: config, + List: make(map[string]*Node), + ifaceToNodeID: make(map[string]string), + ifaceToLinkType: make(map[string]LinkType), + ifaceToLinkProtocol: make(map[string]LinkProtocol), + config: config, } if config.StatePath != "" { @@ -48,7 +52,7 @@ func (nodes *Nodes) AddNode(node *Node) { nodes.Lock() defer nodes.Unlock() nodes.List[nodeinfo.NodeID] = node - nodes.readIfaces(nodeinfo, false) + nodes.readIfaces(nodeinfo, node.Neighbours, false) } // Update a Node @@ -65,7 +69,7 @@ func (nodes *Nodes) Update(nodeID string, res *data.ResponseData) *Node { nodes.List[nodeID] = node } if res.Nodeinfo != nil { - nodes.readIfaces(res.Nodeinfo, true) + nodes.readIfaces(res.Nodeinfo, res.Neighbours, true) } nodes.Unlock() @@ -110,7 +114,7 @@ func (nodes *Nodes) GetNodeIDbyAddress(addr string) string { func (nodes *Nodes) NodeLinks(node *Node) (result []Link) { // Store link data neighbours := node.Neighbours - if neighbours == nil || neighbours.NodeID == "" { + if neighbours == nil || neighbours.NodeID == "" || !node.Online { return } @@ -133,6 +137,11 @@ func (nodes *Nodes) NodeLinks(node *Node) (result []Link) { if node.Nodeinfo != nil { link.SourceHostname = node.Nodeinfo.Hostname } + if lt, ok := nodes.ifaceToLinkType[sourceMAC]; ok && lt != OtherLinkType { + link.Type = lt + } else if lt, ok := nodes.ifaceToLinkType[neighbourMAC]; ok { + link.Type = lt + } result = append(result, link) } @@ -141,27 +150,39 @@ func (nodes *Nodes) NodeLinks(node *Node) (result []Link) { for _, iface := range neighbours.Babel { for neighbourIP, link := range iface.Neighbours { if neighbourID := nodes.ifaceToNodeID[neighbourIP]; neighbourID != "" { - result = append(result, Link{ + link := Link{ SourceID: neighbours.NodeID, SourceAddress: iface.LinkLocalAddress, TargetID: neighbourID, TargetAddress: neighbourIP, TQ: 1.0 - (float32(link.Cost) / 65535.0), - }) + } + if lt, ok := nodes.ifaceToLinkType[iface.LinkLocalAddress]; ok && lt != OtherLinkType { + link.Type = lt + } else if lt, ok := nodes.ifaceToLinkType[neighbourIP]; ok { + link.Type = lt + } + result = append(result, link) } } } - for portmac, neighmacs := range neighbours.LLDP { - for _, neighmac := range neighmacs { - if neighbourID := nodes.ifaceToNodeID[neighmac]; neighbourID != "" { - result = append(result, Link{ + for sourceMAC, neighmacs := range neighbours.LLDP { + for _, neighbourMAC := range neighmacs { + if neighbourID := nodes.ifaceToNodeID[neighbourMAC]; neighbourID != "" { + link := Link{ SourceID: neighbours.NodeID, - SourceAddress: portmac, + SourceAddress: sourceMAC, TargetID: neighbourID, - TargetAddress: neighmac, + TargetAddress: neighbourMAC, // TODO maybe change LLDP for link quality / 100M or 1GE TQ: 1.0, - }) + } + if lt, ok := nodes.ifaceToLinkType[sourceMAC]; ok && lt != OtherLinkType { + link.Type = lt + } else if lt, ok := nodes.ifaceToLinkType[neighbourMAC]; ok { + link.Type = lt + } + result = append(result, link) } } } @@ -207,8 +228,18 @@ func (nodes *Nodes) expire() { } } +func updateIface[K string | LinkProtocol | LinkType](class string, addr string, dataMap map[string]K, value K, warning bool) { + if oldValue := dataMap[addr]; oldValue != value { + var empty K + if oldValue != empty && warning { + log.Warnf("override %s from %s to %s on %s", class, oldValue, value, addr) + } + dataMap[addr] = value + } +} + // adds the nodes interface addresses to the internal map -func (nodes *Nodes) readIfaces(nodeinfo *data.Nodeinfo, warning bool) { +func (nodes *Nodes) readIfaces(nodeinfo *data.Nodeinfo, neighbours *data.Neighbours, warning bool) { nodeID := nodeinfo.NodeID network := nodeinfo.Network @@ -220,6 +251,15 @@ func (nodes *Nodes) readIfaces(nodeinfo *data.Nodeinfo, warning bool) { addresses := []string{network.Mac} for _, iface := range network.Mesh { + for _, addr := range iface.Interfaces.Wireless { + updateIface("interface-type", addr, nodes.ifaceToLinkType, WirelessLinkType, warning) + } + for _, addr := range iface.Interfaces.Tunnel { + updateIface("interface-type", addr, nodes.ifaceToLinkType, TunnelLinkType, warning) + } + for _, addr := range iface.Interfaces.Other { + updateIface("interface-type", addr, nodes.ifaceToLinkType, OtherLinkType, warning) + } addresses = append(addresses, iface.Addresses()...) } @@ -227,13 +267,32 @@ func (nodes *Nodes) readIfaces(nodeinfo *data.Nodeinfo, warning bool) { if addr == "" { continue } - if oldNodeID := nodes.ifaceToNodeID[addr]; oldNodeID != nodeID { - if oldNodeID != "" && warning { - log.Warnf("override nodeID from %s to %s on MAC address %s", oldNodeID, nodeID, addr) - } - nodes.ifaceToNodeID[addr] = nodeID + updateIface("nodeID", addr, nodes.ifaceToNodeID, nodeID, warning) + } + + if neighbours == nil || neighbours.NodeID == "" { + return + } + + for sourceMAC, batadv := range neighbours.Batadv { + updateIface("mesh-protocol", sourceMAC, nodes.ifaceToLinkProtocol, BatadvLinkProtocol, warning) + for neighbourMAC := range batadv.Neighbours { + updateIface("mesh-protocol", neighbourMAC, nodes.ifaceToLinkProtocol, BatadvLinkProtocol, warning) } } + for _, iface := range neighbours.Babel { + updateIface("mesh-protocol", iface.LinkLocalAddress, nodes.ifaceToLinkProtocol, BabelLinkProtocol, warning) + for neighbourIP := range iface.Neighbours { + updateIface("mesh-protocol", neighbourIP, nodes.ifaceToLinkProtocol, BabelLinkProtocol, warning) + } + } + for portmac, neighmacs := range neighbours.LLDP { + updateIface("mesh-protocol", portmac, nodes.ifaceToLinkProtocol, LLDPLinkProtocol, warning) + for _, neighmac := range neighmacs { + updateIface("mesh-protocol", neighmac, nodes.ifaceToLinkProtocol, LLDPLinkProtocol, warning) + } + } + } func (nodes *Nodes) load() { @@ -246,7 +305,7 @@ func (nodes *Nodes) load() { nodes.Lock() for _, node := range nodes.List { if node.Nodeinfo != nil { - nodes.readIfaces(node.Nodeinfo, false) + nodes.readIfaces(node.Nodeinfo, node.Neighbours, false) } } nodes.Unlock() diff --git a/runtime/nodes_test.go b/runtime/nodes_test.go index fb2ad32..5f3f6ba 100644 --- a/runtime/nodes_test.go +++ b/runtime/nodes_test.go @@ -18,9 +18,11 @@ func TestExpire(t *testing.T) { // to get default (100%) path of testing // config.PruneAfter.Duration = time.Hour * 24 * 6 nodes := &Nodes{ - config: config, - List: make(map[string]*Node), - ifaceToNodeID: make(map[string]string), + config: config, + List: make(map[string]*Node), + ifaceToNodeID: make(map[string]string), + ifaceToLinkType: make(map[string]LinkType), + ifaceToLinkProtocol: make(map[string]LinkProtocol), } nodes.Update("expire", &data.ResponseData{}) // should expire @@ -89,8 +91,10 @@ func TestLoadAndSave(t *testing.T) { func TestUpdateNodes(t *testing.T) { assert := assert.New(t) nodes := &Nodes{ - List: make(map[string]*Node), - ifaceToNodeID: make(map[string]string), + List: make(map[string]*Node), + ifaceToNodeID: make(map[string]string), + ifaceToLinkType: make(map[string]LinkType), + ifaceToLinkProtocol: make(map[string]LinkProtocol), } assert.Len(nodes.List, 0) @@ -156,8 +160,10 @@ func TestLinksNodes(t *testing.T) { assert := assert.New(t) nodes := &Nodes{ - List: make(map[string]*Node), - ifaceToNodeID: make(map[string]string), + List: make(map[string]*Node), + ifaceToNodeID: make(map[string]string), + ifaceToLinkType: make(map[string]LinkType), + ifaceToLinkProtocol: make(map[string]LinkProtocol), } assert.Len(nodes.List, 0)