feat(link-info): move from meshviewer-ffrgb to runtime

This commit is contained in:
genofire 2024-07-20 00:31:16 +02:00
parent cc71150d53
commit 2015fdd668
7 changed files with 187 additions and 121 deletions

View File

@ -14,6 +14,7 @@ func TestToInflux(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
node := &runtime.Node{ node := &runtime.Node{
Online: true,
Statistics: &data.Statistics{ Statistics: &data.Statistics{
NodeID: "deadbeef", NodeID: "deadbeef",
LoadAverage: 0.5, LoadAverage: 0.5,
@ -105,6 +106,7 @@ func TestToInflux(t *testing.T) {
} }
neighbour := &runtime.Node{ neighbour := &runtime.Node{
Online: true,
Nodeinfo: &data.Nodeinfo{ Nodeinfo: &data.Nodeinfo{
NodeID: "foobar", NodeID: "foobar",
Network: data.Network{ Network: data.Network{
@ -132,6 +134,7 @@ func TestToInflux(t *testing.T) {
// do not add a empty statistics of a node // do not add a empty statistics of a node
droppednode := &runtime.Node{ droppednode := &runtime.Node{
Online: true,
Nodeinfo: &data.Nodeinfo{ Nodeinfo: &data.Nodeinfo{
NodeID: "notfound", NodeID: "notfound",
Network: data.Network{ Network: data.Network{

View File

@ -4,18 +4,10 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/bdlm/log"
"github.com/FreifunkBremen/yanic/lib/jsontime" "github.com/FreifunkBremen/yanic/lib/jsontime"
"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{
@ -25,7 +17,6 @@ func transform(nodes *runtime.Nodes) *Meshviewer {
} }
links := make(map[string]*Link) links := make(map[string]*Link)
typeList := make(map[string]string)
nodes.RLock() nodes.RLock()
defer nodes.RUnlock() defer nodes.RUnlock()
@ -38,19 +29,6 @@ func transform(nodes *runtime.Nodes) *Meshviewer {
continue 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) { 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
@ -62,36 +40,11 @@ func transform(nodes *runtime.Nodes) *Meshviewer {
} }
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 = linkOrigin.TQ link.TargetTQ = linkOrigin.TQ
linkType, linkTypeFound = typeList[linkOrigin.TargetAddress]
if !linkTypeFound {
linkType, linkTypeFound = typeList[linkOrigin.SourceAddress]
}
} else { } else {
link.SourceTQ = linkOrigin.TQ 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 continue
} }
link := &Link{ link := &Link{
@ -101,11 +54,7 @@ func transform(nodes *runtime.Nodes) *Meshviewer {
TargetAddress: linkOrigin.TargetAddress, TargetAddress: linkOrigin.TargetAddress,
SourceTQ: linkOrigin.TQ, SourceTQ: linkOrigin.TQ,
TargetTQ: 0, TargetTQ: 0,
} Type: linkOrigin.Type.String(),
linkType, linkTypeFound := typeList[linkOrigin.SourceAddress]
if !linkTypeFound {
linkType, linkTypeFound = typeList[linkOrigin.TargetAddress]
} }
if switchSourceTarget { if switchSourceTarget {
@ -115,22 +64,13 @@ func transform(nodes *runtime.Nodes) *Meshviewer {
link.TargetTQ = linkOrigin.TQ link.TargetTQ = linkOrigin.TQ
link.Target = linkOrigin.SourceID link.Target = linkOrigin.SourceID
link.TargetAddress = linkOrigin.SourceAddress 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 links[key] = link
meshviewer.Links = append(meshviewer.Links, link)
} }
} }
for _, link := range links {
meshviewer.Links = append(meshviewer.Links, link)
}
return meshviewer return meshviewer
} }

View File

@ -128,8 +128,8 @@ func TestTransform(t *testing.T) {
Other []string `json:"other,omitempty"` Other []string `json:"other,omitempty"`
Tunnel []string `json:"tunnel,omitempty"` Tunnel []string `json:"tunnel,omitempty"`
}{ }{
Wireless: []string{"node:b:mac:wifi"}, Wireless: []string{"node:d:mac:wifi"},
Other: []string{"node:b:mac:lan"}, Other: []string{"node:d:mac:lan"},
}, },
}, },
}, },
@ -155,28 +155,41 @@ func TestTransform(t *testing.T) {
meshviewer := transform(nodes) meshviewer := transform(nodes)
assert.NotNil(meshviewer) assert.NotNil(meshviewer)
assert.Len(meshviewer.Nodes, 4) 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 links := meshviewer.Links
assert.Len(links, 3) assert.Len(links, 3)
counter := 0
for _, link := range links { for _, link := range links {
switch link.SourceAddress { switch link.SourceAddress {
case "node:a:mac:lan": case "node:a:mac:lan":
assert.Equal("other", link.Type) assert.Equal("node:b:mac:lan", link.TargetAddress, "a:lan -> b:lan")
assert.Equal("node:b:mac:lan", link.TargetAddress) assert.Equal("other", link.Type, "a:lan -> b:lan")
assert.Equal(float32(0.2), link.SourceTQ) assert.Equal(float32(0.2), link.SourceTQ, "a:lan -> b:lan")
assert.Equal(float32(0), link.TargetTQ) assert.Equal(float32(0), link.TargetTQ, "a:lan -> b:lan")
counter++
case "node:a:mac:wifi": case "node:a:mac:wifi":
assert.Equal("wifi", link.Type) assert.Equal("node:b:mac:wifi", link.TargetAddress, "a:wifi <-> b:wifi")
assert.Equal("node:b:mac:wifi", link.TargetAddress) assert.Equal("wifi", link.Type, "a:wifi <-> b:wifi")
assert.Equal(float32(0.6), link.SourceTQ) assert.Equal(float32(0.6), link.SourceTQ, "a:wifi <-> b:wifi")
assert.Equal(float32(0.8), link.TargetTQ) assert.Equal(float32(0.8), link.TargetTQ, "a:wifi <-> b:wifi")
counter++
case "node:b:mac:lan": case "node:b:mac:lan":
assert.Equal("other", link.Type) assert.Equal("other", link.Type, "b:lan <-> c:lan")
assert.Equal("node:c:mac:lan", link.TargetAddress) assert.Equal("node:c:mac:lan", link.TargetAddress, "b:lan <-> c:lan")
assert.Equal(float32(0.8), link.SourceTQ) assert.Equal(float32(0.8), link.SourceTQ, "b:lan <-> c:lan")
assert.Equal(float32(0.4), link.TargetTQ) assert.Equal(float32(0.4), link.TargetTQ, "b:lan <-> c:lan")
counter++
default: default:
assert.False(true, "invalid link.SourceAddress found") assert.False(true, "invalid link.SourceAddress found")
} }
} }
assert.Equal(3, counter, "not found every link")
} }

56
runtime/link.go Normal file
View File

@ -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
}

View File

@ -19,17 +19,6 @@ type Node struct {
CustomFields map[string]interface{} `json:"custom_fields"` 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 // IsGateway returns whether the node is a gateway
func (node *Node) IsGateway() bool { func (node *Node) IsGateway() bool {
if info := node.Nodeinfo; info != nil { if info := node.Nodeinfo; info != nil {

View File

@ -14,18 +14,22 @@ import (
// Nodes struct: cache DB of Node's structs // Nodes struct: cache DB of Node's structs
type Nodes struct { type Nodes struct {
List map[string]*Node `json:"nodes"` // the current nodemap, indexed by node ID List map[string]*Node `json:"nodes"` // the current nodemap, indexed by node ID
ifaceToNodeID map[string]string // mapping from MAC address to NodeID ifaceToNodeID map[string]string // mapping from MAC address to NodeID
config *NodesConfig ifaceToLinkType map[string]LinkType // mapping from MAC address to LinkType
ifaceToLinkProtocol map[string]LinkProtocol // mapping from MAC address to LinkProtocol
config *NodesConfig
sync.RWMutex sync.RWMutex
} }
// NewNodes create Nodes structs // NewNodes create Nodes structs
func NewNodes(config *NodesConfig) *Nodes { func NewNodes(config *NodesConfig) *Nodes {
nodes := &Nodes{ nodes := &Nodes{
List: make(map[string]*Node), List: make(map[string]*Node),
ifaceToNodeID: make(map[string]string), ifaceToNodeID: make(map[string]string),
config: config, ifaceToLinkType: make(map[string]LinkType),
ifaceToLinkProtocol: make(map[string]LinkProtocol),
config: config,
} }
if config.StatePath != "" { if config.StatePath != "" {
@ -48,7 +52,7 @@ func (nodes *Nodes) AddNode(node *Node) {
nodes.Lock() nodes.Lock()
defer nodes.Unlock() defer nodes.Unlock()
nodes.List[nodeinfo.NodeID] = node nodes.List[nodeinfo.NodeID] = node
nodes.readIfaces(nodeinfo, false) nodes.readIfaces(nodeinfo, node.Neighbours, false)
} }
// Update a Node // Update a Node
@ -65,7 +69,7 @@ func (nodes *Nodes) Update(nodeID string, res *data.ResponseData) *Node {
nodes.List[nodeID] = node nodes.List[nodeID] = node
} }
if res.Nodeinfo != nil { if res.Nodeinfo != nil {
nodes.readIfaces(res.Nodeinfo, true) nodes.readIfaces(res.Nodeinfo, res.Neighbours, true)
} }
nodes.Unlock() nodes.Unlock()
@ -110,7 +114,7 @@ func (nodes *Nodes) GetNodeIDbyAddress(addr string) string {
func (nodes *Nodes) NodeLinks(node *Node) (result []Link) { func (nodes *Nodes) NodeLinks(node *Node) (result []Link) {
// Store link data // Store link data
neighbours := node.Neighbours neighbours := node.Neighbours
if neighbours == nil || neighbours.NodeID == "" { if neighbours == nil || neighbours.NodeID == "" || !node.Online {
return return
} }
@ -133,6 +137,11 @@ func (nodes *Nodes) NodeLinks(node *Node) (result []Link) {
if node.Nodeinfo != nil { if node.Nodeinfo != nil {
link.SourceHostname = node.Nodeinfo.Hostname 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) result = append(result, link)
} }
@ -141,27 +150,39 @@ func (nodes *Nodes) NodeLinks(node *Node) (result []Link) {
for _, iface := range neighbours.Babel { for _, iface := range neighbours.Babel {
for neighbourIP, link := range iface.Neighbours { for neighbourIP, link := range iface.Neighbours {
if neighbourID := nodes.ifaceToNodeID[neighbourIP]; neighbourID != "" { if neighbourID := nodes.ifaceToNodeID[neighbourIP]; neighbourID != "" {
result = append(result, Link{ link := Link{
SourceID: neighbours.NodeID, SourceID: neighbours.NodeID,
SourceAddress: iface.LinkLocalAddress, SourceAddress: iface.LinkLocalAddress,
TargetID: neighbourID, TargetID: neighbourID,
TargetAddress: neighbourIP, TargetAddress: neighbourIP,
TQ: 1.0 - (float32(link.Cost) / 65535.0), 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 sourceMAC, neighmacs := range neighbours.LLDP {
for _, neighmac := range neighmacs { for _, neighbourMAC := range neighmacs {
if neighbourID := nodes.ifaceToNodeID[neighmac]; neighbourID != "" { if neighbourID := nodes.ifaceToNodeID[neighbourMAC]; neighbourID != "" {
result = append(result, Link{ link := Link{
SourceID: neighbours.NodeID, SourceID: neighbours.NodeID,
SourceAddress: portmac, SourceAddress: sourceMAC,
TargetID: neighbourID, TargetID: neighbourID,
TargetAddress: neighmac, TargetAddress: neighbourMAC,
// TODO maybe change LLDP for link quality / 100M or 1GE // TODO maybe change LLDP for link quality / 100M or 1GE
TQ: 1.0, 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 // 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 nodeID := nodeinfo.NodeID
network := nodeinfo.Network network := nodeinfo.Network
@ -220,6 +251,15 @@ func (nodes *Nodes) readIfaces(nodeinfo *data.Nodeinfo, warning bool) {
addresses := []string{network.Mac} addresses := []string{network.Mac}
for _, iface := range network.Mesh { 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()...) addresses = append(addresses, iface.Addresses()...)
} }
@ -227,13 +267,32 @@ func (nodes *Nodes) readIfaces(nodeinfo *data.Nodeinfo, warning bool) {
if addr == "" { if addr == "" {
continue continue
} }
if oldNodeID := nodes.ifaceToNodeID[addr]; oldNodeID != nodeID { updateIface("nodeID", addr, nodes.ifaceToNodeID, nodeID, warning)
if oldNodeID != "" && warning { }
log.Warnf("override nodeID from %s to %s on MAC address %s", oldNodeID, nodeID, addr)
} if neighbours == nil || neighbours.NodeID == "" {
nodes.ifaceToNodeID[addr] = 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() { func (nodes *Nodes) load() {
@ -246,7 +305,7 @@ func (nodes *Nodes) load() {
nodes.Lock() nodes.Lock()
for _, node := range nodes.List { for _, node := range nodes.List {
if node.Nodeinfo != nil { if node.Nodeinfo != nil {
nodes.readIfaces(node.Nodeinfo, false) nodes.readIfaces(node.Nodeinfo, node.Neighbours, false)
} }
} }
nodes.Unlock() nodes.Unlock()

View File

@ -18,9 +18,11 @@ func TestExpire(t *testing.T) {
// to get default (100%) path of testing // to get default (100%) path of testing
// config.PruneAfter.Duration = time.Hour * 24 * 6 // config.PruneAfter.Duration = time.Hour * 24 * 6
nodes := &Nodes{ nodes := &Nodes{
config: config, config: config,
List: make(map[string]*Node), List: make(map[string]*Node),
ifaceToNodeID: make(map[string]string), ifaceToNodeID: make(map[string]string),
ifaceToLinkType: make(map[string]LinkType),
ifaceToLinkProtocol: make(map[string]LinkProtocol),
} }
nodes.Update("expire", &data.ResponseData{}) // should expire nodes.Update("expire", &data.ResponseData{}) // should expire
@ -89,8 +91,10 @@ func TestLoadAndSave(t *testing.T) {
func TestUpdateNodes(t *testing.T) { func TestUpdateNodes(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
nodes := &Nodes{ nodes := &Nodes{
List: make(map[string]*Node), List: make(map[string]*Node),
ifaceToNodeID: make(map[string]string), ifaceToNodeID: make(map[string]string),
ifaceToLinkType: make(map[string]LinkType),
ifaceToLinkProtocol: make(map[string]LinkProtocol),
} }
assert.Len(nodes.List, 0) assert.Len(nodes.List, 0)
@ -156,8 +160,10 @@ func TestLinksNodes(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
nodes := &Nodes{ nodes := &Nodes{
List: make(map[string]*Node), List: make(map[string]*Node),
ifaceToNodeID: make(map[string]string), ifaceToNodeID: make(map[string]string),
ifaceToLinkType: make(map[string]LinkType),
ifaceToLinkProtocol: make(map[string]LinkProtocol),
} }
assert.Len(nodes.List, 0) assert.Len(nodes.List, 0)