package tendrils import ( "fmt" "log" "net" "sort" "sync" ) type Interface struct { Name string MAC net.HardwareAddr IPs map[string]net.IP } func (i *Interface) String() string { var ips []string for _, ip := range i.IPs { ips = append(ips, ip.String()) } sort.Strings(ips) var parts []string parts = append(parts, i.MAC.String()) if i.Name != "" { parts = append(parts, fmt.Sprintf("(%s)", i.Name)) } if len(ips) > 0 { parts = append(parts, fmt.Sprintf("%v", ips)) } result := parts[0] for _, p := range parts[1:] { result += " " + p } return result } type Node struct { Name string Interfaces map[string]*Interface } func (n *Node) String() string { name := n.Name if name == "" { name = "??" } var ifaces []string for _, iface := range n.Interfaces { ifaces = append(ifaces, iface.String()) } sort.Strings(ifaces) return fmt.Sprintf("%s {%v}", name, ifaces) } type Nodes struct { mu sync.RWMutex nodes map[int]*Node ipIndex map[string]int macIndex map[string]int nextID int t *Tendrils } func NewNodes(t *Tendrils) *Nodes { n := &Nodes{ nodes: map[int]*Node{}, ipIndex: map[string]int{}, macIndex: map[string]int{}, nextID: 1, t: t, } n.nodes[0] = &Node{ Interfaces: map[string]*Interface{}, } return n } func (n *Nodes) Update(mac net.HardwareAddr, ips []net.IP, ifaceName, nodeName, source string) { n.mu.Lock() defer n.mu.Unlock() if mac == nil { return } macKey := mac.String() var targetID int isNew := false if id, exists := n.macIndex[macKey]; exists { targetID = id } else { targetID = n.nextID n.nextID++ n.nodes[targetID] = &Node{ Interfaces: map[string]*Interface{}, } isNew = true } node := n.nodes[targetID] var added []string iface, exists := node.Interfaces[macKey] if !exists { iface = &Interface{ MAC: mac, IPs: map[string]net.IP{}, } node.Interfaces[macKey] = iface n.macIndex[macKey] = targetID added = append(added, "mac="+macKey) } for _, ip := range ips { ipKey := ip.String() if _, exists := iface.IPs[ipKey]; !exists { added = append(added, "ip="+ipKey) } iface.IPs[ipKey] = ip n.ipIndex[ipKey] = targetID } if ifaceName != "" && iface.Name == "" { iface.Name = ifaceName } if nodeName != "" && node.Name == "" { node.Name = nodeName } if len(added) > 0 { if n.t.LogEvents { if isNew { log.Printf("[add] %s %v (via %s)", node, added, source) } else { log.Printf("[update] %s +%v (via %s)", node, added, source) } } if n.t.LogNodes { n.logNode(node) } } } func (n *Nodes) Merge(macs []net.HardwareAddr, source string) { n.mu.Lock() defer n.mu.Unlock() if len(macs) < 2 { return } existingIDs := map[int]bool{} for _, mac := range macs { if id, exists := n.macIndex[mac.String()]; exists { existingIDs[id] = true } } if len(existingIDs) < 2 { return } var ids []int for id := range existingIDs { ids = append(ids, id) } sort.Ints(ids) targetID := ids[0] for i := 1; i < len(ids); i++ { if n.t.LogEvents { log.Printf("[merge] %s into %s (via %s)", n.nodes[ids[i]], n.nodes[targetID], source) } n.mergeNodes(targetID, ids[i]) } if n.t.LogNodes { n.logNode(n.nodes[targetID]) } } func (n *Nodes) mergeNodes(keepID, mergeID int) { keep := n.nodes[keepID] merge := n.nodes[mergeID] if merge.Name != "" && keep.Name == "" { keep.Name = merge.Name } for macKey, iface := range merge.Interfaces { if existing, exists := keep.Interfaces[macKey]; exists { for ipKey, ip := range iface.IPs { existing.IPs[ipKey] = ip n.ipIndex[ipKey] = keepID } if existing.Name == "" && iface.Name != "" { existing.Name = iface.Name } } else { keep.Interfaces[macKey] = iface n.macIndex[macKey] = keepID for ipKey := range iface.IPs { n.ipIndex[ipKey] = keepID } } } delete(n.nodes, mergeID) } func (n *Nodes) GetByIP(ip net.IP) *Node { n.mu.RLock() defer n.mu.RUnlock() if id, exists := n.ipIndex[ip.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) logNode(node *Node) { name := node.Name if name == "" { name = "??" } log.Printf("[node] %s", name) var macKeys []string for macKey := range node.Interfaces { macKeys = append(macKeys, macKey) } sort.Strings(macKeys) for _, macKey := range macKeys { iface := node.Interfaces[macKey] log.Printf("[node] %s", iface) } } 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 }