diff --git a/lldp.go b/lldp.go index e9acce1..989b52f 100644 --- a/lldp.go +++ b/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") } } } diff --git a/neighbors.go b/neighbors.go deleted file mode 100644 index 904f976..0000000 --- a/neighbors.go +++ /dev/null @@ -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 -} diff --git a/nodes.go b/nodes.go new file mode 100644 index 0000000..7e8a48a --- /dev/null +++ b/nodes.go @@ -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 +} diff --git a/tendrils.go b/tendrils.go index f88fb9b..2f1cc8e 100644 --- a/tendrils.go +++ b/tendrils.go @@ -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(), } }