Refactor to tree-based topology with ASCII visualization
This commit is contained in:
6
lldp.go
6
lldp.go
@@ -51,13 +51,11 @@ func (t *Tendrils) handleLLDPPacket(ifaceName string, packet gopacket.Packet) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[%s] lldp packet received: ChassisID=%x PortID=%s TTL=%d",
|
||||
ifaceName, lldp.ChassisID.ID, lldp.PortID.ID, lldp.TTL)
|
||||
|
||||
if len(lldp.ChassisID.ID) == 6 {
|
||||
mac := net.HardwareAddr(lldp.ChassisID.ID)
|
||||
if !isBroadcastOrZero(mac) {
|
||||
t.neighbors.Update(nil, []net.HardwareAddr{mac}, "lldp:"+ifaceName)
|
||||
childPort := string(lldp.PortID.ID)
|
||||
t.nodes.Update(nil, []net.HardwareAddr{mac}, ifaceName, childPort, "lldp")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
169
neighbors.go
169
neighbors.go
@@ -1,169 +0,0 @@
|
||||
package tendrils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Neighbor struct {
|
||||
IPs map[string]net.IP
|
||||
MACs map[string]net.HardwareAddr
|
||||
}
|
||||
|
||||
func (n *Neighbor) String() string {
|
||||
var macs []string
|
||||
for _, mac := range n.MACs {
|
||||
macs = append(macs, mac.String())
|
||||
}
|
||||
sort.Strings(macs)
|
||||
|
||||
var ips []string
|
||||
for _, ip := range n.IPs {
|
||||
ips = append(ips, ip.String())
|
||||
}
|
||||
sort.Strings(ips)
|
||||
|
||||
return fmt.Sprintf("{macs=%v ips=%v}", macs, ips)
|
||||
}
|
||||
|
||||
type Neighbors struct {
|
||||
mu sync.RWMutex
|
||||
neighbors map[int]*Neighbor
|
||||
ipIndex map[string]int
|
||||
macIndex map[string]int
|
||||
nextID int
|
||||
}
|
||||
|
||||
func NewNeighbors() *Neighbors {
|
||||
return &Neighbors{
|
||||
neighbors: map[int]*Neighbor{},
|
||||
ipIndex: map[string]int{},
|
||||
macIndex: map[string]int{},
|
||||
nextID: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Neighbors) Update(ips []net.IP, macs []net.HardwareAddr, source string) {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
|
||||
if len(ips) == 0 && len(macs) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
existingIDs := map[int]bool{}
|
||||
|
||||
for _, ip := range ips {
|
||||
if id, exists := n.ipIndex[ip.String()]; exists {
|
||||
existingIDs[id] = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, mac := range macs {
|
||||
if id, exists := n.macIndex[mac.String()]; exists {
|
||||
existingIDs[id] = true
|
||||
}
|
||||
}
|
||||
|
||||
var targetID int
|
||||
if len(existingIDs) == 0 {
|
||||
targetID = n.nextID
|
||||
n.nextID++
|
||||
n.neighbors[targetID] = &Neighbor{
|
||||
IPs: map[string]net.IP{},
|
||||
MACs: map[string]net.HardwareAddr{},
|
||||
}
|
||||
} else if len(existingIDs) == 1 {
|
||||
for id := range existingIDs {
|
||||
targetID = id
|
||||
}
|
||||
} else {
|
||||
var ids []int
|
||||
for id := range existingIDs {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
targetID = ids[0]
|
||||
var merging []string
|
||||
for i := 1; i < len(ids); i++ {
|
||||
merging = append(merging, n.neighbors[ids[i]].String())
|
||||
n.mergeNeighbors(targetID, ids[i])
|
||||
}
|
||||
log.Printf("[%s] merged neighbors %v into %s", source, merging, n.neighbors[targetID])
|
||||
}
|
||||
|
||||
neighbor := n.neighbors[targetID]
|
||||
var added []string
|
||||
|
||||
for _, ip := range ips {
|
||||
ipKey := ip.String()
|
||||
if _, exists := neighbor.IPs[ipKey]; !exists {
|
||||
added = append(added, "ip="+ipKey)
|
||||
}
|
||||
neighbor.IPs[ipKey] = ip
|
||||
n.ipIndex[ipKey] = targetID
|
||||
}
|
||||
|
||||
for _, mac := range macs {
|
||||
macKey := mac.String()
|
||||
if _, exists := neighbor.MACs[macKey]; !exists {
|
||||
added = append(added, "mac="+macKey)
|
||||
}
|
||||
neighbor.MACs[macKey] = mac
|
||||
n.macIndex[macKey] = targetID
|
||||
}
|
||||
|
||||
if len(added) > 0 {
|
||||
log.Printf("[%s] updated %s +%v", source, neighbor, added)
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Neighbors) mergeNeighbors(keepID, mergeID int) {
|
||||
keep := n.neighbors[keepID]
|
||||
merge := n.neighbors[mergeID]
|
||||
|
||||
for ipKey, ip := range merge.IPs {
|
||||
keep.IPs[ipKey] = ip
|
||||
n.ipIndex[ipKey] = keepID
|
||||
}
|
||||
|
||||
for macKey, mac := range merge.MACs {
|
||||
keep.MACs[macKey] = mac
|
||||
n.macIndex[macKey] = keepID
|
||||
}
|
||||
|
||||
delete(n.neighbors, mergeID)
|
||||
}
|
||||
|
||||
func (n *Neighbors) GetByIP(ipv4 net.IP) *Neighbor {
|
||||
n.mu.RLock()
|
||||
defer n.mu.RUnlock()
|
||||
|
||||
if id, exists := n.ipIndex[ipv4.String()]; exists {
|
||||
return n.neighbors[id]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Neighbors) GetByMAC(mac net.HardwareAddr) *Neighbor {
|
||||
n.mu.RLock()
|
||||
defer n.mu.RUnlock()
|
||||
|
||||
if id, exists := n.macIndex[mac.String()]; exists {
|
||||
return n.neighbors[id]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Neighbors) All() []*Neighbor {
|
||||
n.mu.RLock()
|
||||
defer n.mu.RUnlock()
|
||||
|
||||
result := make([]*Neighbor, 0, len(n.neighbors))
|
||||
for _, neighbor := range n.neighbors {
|
||||
result = append(result, neighbor)
|
||||
}
|
||||
return result
|
||||
}
|
||||
237
nodes.go
Normal file
237
nodes.go
Normal file
@@ -0,0 +1,237 @@
|
||||
package tendrils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Node struct {
|
||||
IPs map[string]net.IP
|
||||
MACs map[string]net.HardwareAddr
|
||||
ParentID int
|
||||
LocalPort string
|
||||
ParentPort string
|
||||
}
|
||||
|
||||
func (n *Node) String() string {
|
||||
var macs []string
|
||||
for _, mac := range n.MACs {
|
||||
macs = append(macs, mac.String())
|
||||
}
|
||||
sort.Strings(macs)
|
||||
|
||||
var ips []string
|
||||
for _, ip := range n.IPs {
|
||||
ips = append(ips, ip.String())
|
||||
}
|
||||
sort.Strings(ips)
|
||||
|
||||
return fmt.Sprintf("{macs=%v ips=%v}", macs, ips)
|
||||
}
|
||||
|
||||
type Nodes struct {
|
||||
mu sync.RWMutex
|
||||
nodes map[int]*Node
|
||||
ipIndex map[string]int
|
||||
macIndex map[string]int
|
||||
nextID int
|
||||
}
|
||||
|
||||
func NewNodes() *Nodes {
|
||||
n := &Nodes{
|
||||
nodes: map[int]*Node{},
|
||||
ipIndex: map[string]int{},
|
||||
macIndex: map[string]int{},
|
||||
nextID: 1,
|
||||
}
|
||||
|
||||
n.nodes[0] = &Node{
|
||||
IPs: map[string]net.IP{},
|
||||
MACs: map[string]net.HardwareAddr{},
|
||||
ParentID: 0,
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func (n *Nodes) Update(ips []net.IP, macs []net.HardwareAddr, parentPort, childPort, source string) {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
|
||||
if len(ips) == 0 && len(macs) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
existingIDs := map[int]bool{}
|
||||
|
||||
for _, ip := range ips {
|
||||
if id, exists := n.ipIndex[ip.String()]; exists {
|
||||
existingIDs[id] = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, mac := range macs {
|
||||
if id, exists := n.macIndex[mac.String()]; exists {
|
||||
existingIDs[id] = true
|
||||
}
|
||||
}
|
||||
|
||||
var targetID int
|
||||
if len(existingIDs) == 0 {
|
||||
targetID = n.nextID
|
||||
n.nextID++
|
||||
n.nodes[targetID] = &Node{
|
||||
IPs: map[string]net.IP{},
|
||||
MACs: map[string]net.HardwareAddr{},
|
||||
ParentID: 0,
|
||||
LocalPort: childPort,
|
||||
ParentPort: parentPort,
|
||||
}
|
||||
} else if len(existingIDs) == 1 {
|
||||
for id := range existingIDs {
|
||||
targetID = id
|
||||
}
|
||||
} else {
|
||||
var ids []int
|
||||
for id := range existingIDs {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
targetID = ids[0]
|
||||
var merging []string
|
||||
for i := 1; i < len(ids); i++ {
|
||||
merging = append(merging, n.nodes[ids[i]].String())
|
||||
n.mergeNodes(targetID, ids[i])
|
||||
}
|
||||
log.Printf("[%s] merged nodes %v into %s", source, merging, n.nodes[targetID])
|
||||
}
|
||||
|
||||
node := n.nodes[targetID]
|
||||
var added []string
|
||||
|
||||
for _, ip := range ips {
|
||||
ipKey := ip.String()
|
||||
if _, exists := node.IPs[ipKey]; !exists {
|
||||
added = append(added, "ip="+ipKey)
|
||||
}
|
||||
node.IPs[ipKey] = ip
|
||||
n.ipIndex[ipKey] = targetID
|
||||
}
|
||||
|
||||
for _, mac := range macs {
|
||||
macKey := mac.String()
|
||||
if _, exists := node.MACs[macKey]; !exists {
|
||||
added = append(added, "mac="+macKey)
|
||||
}
|
||||
node.MACs[macKey] = mac
|
||||
n.macIndex[macKey] = targetID
|
||||
}
|
||||
|
||||
if len(added) > 0 {
|
||||
log.Printf("updated %s +%v (via %s)", node, added, source)
|
||||
n.mu.Unlock()
|
||||
n.LogTree()
|
||||
n.mu.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Nodes) mergeNodes(keepID, mergeID int) {
|
||||
keep := n.nodes[keepID]
|
||||
merge := n.nodes[mergeID]
|
||||
|
||||
for ipKey, ip := range merge.IPs {
|
||||
keep.IPs[ipKey] = ip
|
||||
n.ipIndex[ipKey] = keepID
|
||||
}
|
||||
|
||||
for macKey, mac := range merge.MACs {
|
||||
keep.MACs[macKey] = mac
|
||||
n.macIndex[macKey] = keepID
|
||||
}
|
||||
|
||||
delete(n.nodes, mergeID)
|
||||
}
|
||||
|
||||
func (n *Nodes) GetByIP(ipv4 net.IP) *Node {
|
||||
n.mu.RLock()
|
||||
defer n.mu.RUnlock()
|
||||
|
||||
if id, exists := n.ipIndex[ipv4.String()]; exists {
|
||||
return n.nodes[id]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Nodes) GetByMAC(mac net.HardwareAddr) *Node {
|
||||
n.mu.RLock()
|
||||
defer n.mu.RUnlock()
|
||||
|
||||
if id, exists := n.macIndex[mac.String()]; exists {
|
||||
return n.nodes[id]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Nodes) All() []*Node {
|
||||
n.mu.RLock()
|
||||
defer n.mu.RUnlock()
|
||||
|
||||
result := make([]*Node, 0, len(n.nodes))
|
||||
for _, node := range n.nodes {
|
||||
result = append(result, node)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (n *Nodes) LogTree() {
|
||||
n.mu.RLock()
|
||||
defer n.mu.RUnlock()
|
||||
|
||||
n.logNode(0, "", true)
|
||||
}
|
||||
|
||||
func (n *Nodes) logNode(id int, prefix string, isLast bool) {
|
||||
node := n.nodes[id]
|
||||
|
||||
if id == 0 {
|
||||
log.Printf("[root] %s", node)
|
||||
} else {
|
||||
connector := "├──"
|
||||
if isLast {
|
||||
connector = "└──"
|
||||
}
|
||||
|
||||
if node.ParentPort != "" && node.LocalPort != "" {
|
||||
log.Printf("%s%s %s -> %s on %s", prefix, connector, node.ParentPort, node.LocalPort, node)
|
||||
} else {
|
||||
log.Printf("%s%s %s", prefix, connector, node)
|
||||
}
|
||||
}
|
||||
|
||||
children := n.getChildren(id)
|
||||
for i, childID := range children {
|
||||
childIsLast := i == len(children)-1
|
||||
childPrefix := prefix
|
||||
if id != 0 {
|
||||
if isLast {
|
||||
childPrefix += " "
|
||||
} else {
|
||||
childPrefix += "│ "
|
||||
}
|
||||
}
|
||||
n.logNode(childID, childPrefix, childIsLast)
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Nodes) getChildren(parentID int) []int {
|
||||
var children []int
|
||||
for id, node := range n.nodes {
|
||||
if node.ParentID == parentID && id != 0 {
|
||||
children = append(children, id)
|
||||
}
|
||||
}
|
||||
sort.Ints(children)
|
||||
return children
|
||||
}
|
||||
@@ -9,13 +9,13 @@ import (
|
||||
|
||||
type Tendrils struct {
|
||||
activeInterfaces map[string]context.CancelFunc
|
||||
neighbors *Neighbors
|
||||
nodes *Nodes
|
||||
}
|
||||
|
||||
func New() *Tendrils {
|
||||
return &Tendrils{
|
||||
activeInterfaces: map[string]context.CancelFunc{},
|
||||
neighbors: NewNeighbors(),
|
||||
nodes: NewNodes(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user