diff --git a/arp.go b/arp.go index 3bca25b..ff4ef95 100644 --- a/arp.go +++ b/arp.go @@ -48,7 +48,7 @@ func (t *Tendrils) readARPTable() { log.Printf("[arp] %s: ip=%s mac=%s", entry.iface, entry.ip, entry.mac) } - t.nodes.Update([]net.IP{entry.ip}, []net.HardwareAddr{entry.mac}, "arp") + t.nodes.Update(entry.mac, []net.IP{entry.ip}, "", "", "arp") } } diff --git a/lldp.go b/lldp.go index 5f3601f..01af86c 100644 --- a/lldp.go +++ b/lldp.go @@ -68,11 +68,7 @@ func (t *Tendrils) handleLLDPPacket(ifaceName string, packet gopacket.Packet) { log.Printf("[lldp] %s: mac=%s port=%s name=%s", ifaceName, mac, childPort, systemName) } - t.nodes.Update(nil, []net.HardwareAddr{mac}, "lldp") - - if systemName != "" { - t.nodes.SetName(mac, systemName) - } + t.nodes.Update(mac, nil, childPort, systemName, "lldp") } } } diff --git a/nodes.go b/nodes.go index 78be15f..2d8fc02 100644 --- a/nodes.go +++ b/nodes.go @@ -8,31 +8,48 @@ import ( "sync" ) -type Node struct { +type Interface struct { Name string + MAC net.HardwareAddr IPs map[string]net.IP - MACs map[string]net.HardwareAddr } -func (n *Node) String() string { - var macs []string - for _, mac := range n.MACs { - macs = append(macs, mac.String()) +func (i *Interface) String() string { + name := i.Name + if name == "" { + name = "??" } - sort.Strings(macs) var ips []string - for _, ip := range n.IPs { + for _, ip := range i.IPs { ips = append(ips, ip.String()) } sort.Strings(ips) + if len(ips) == 0 { + return fmt.Sprintf("%s/%s", name, i.MAC) + } + return fmt.Sprintf("%s/%s %v", name, i.MAC, ips) +} + +type Node struct { + Name string + Interfaces map[string]*Interface +} + +func (n *Node) String() string { name := n.Name if name == "" { name = "??" } - return fmt.Sprintf("%s {macs=%v ips=%v}", name, macs, ips) + 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 { @@ -54,84 +71,64 @@ func NewNodes(t *Tendrils) *Nodes { } n.nodes[0] = &Node{ - IPs: map[string]net.IP{}, - MACs: map[string]net.HardwareAddr{}, + Interfaces: map[string]*Interface{}, } return n } -func (n *Nodes) Update(ips []net.IP, macs []net.HardwareAddr, source string) { +func (n *Nodes) Update(mac net.HardwareAddr, ips []net.IP, ifaceName, nodeName, source string) { n.mu.Lock() defer n.mu.Unlock() - if len(ips) == 0 && len(macs) == 0 { + if mac == nil { 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 - } - } - + macKey := mac.String() var targetID int isNew := false - if len(existingIDs) == 0 { + + if id, exists := n.macIndex[macKey]; exists { + targetID = id + } else { targetID = n.nextID n.nextID++ n.nodes[targetID] = &Node{ - IPs: map[string]net.IP{}, - MACs: map[string]net.HardwareAddr{}, + Interfaces: map[string]*Interface{}, } isNew = true - } 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]) - } - if n.t.LogEvents { - log.Printf("[merge] %v into %s (via %s)", merging, n.nodes[targetID], source) - } } 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 := node.IPs[ipKey]; !exists { + if _, exists := iface.IPs[ipKey]; !exists { added = append(added, "ip="+ipKey) } - node.IPs[ipKey] = ip + iface.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 ifaceName != "" && iface.Name == "" { + iface.Name = ifaceName + } + + if nodeName != "" && node.Name == "" { + node.Name = nodeName } if len(added) > 0 && n.t.LogEvents { @@ -143,28 +140,74 @@ func (n *Nodes) Update(ips []net.IP, macs []net.HardwareAddr, source string) { } } +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]) + } +} + 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 + if merge.Name != "" && keep.Name == "" { + keep.Name = merge.Name } - for macKey, mac := range merge.MACs { - keep.MACs[macKey] = mac - n.macIndex[macKey] = keepID + 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(ipv4 net.IP) *Node { +func (n *Nodes) GetByIP(ip net.IP) *Node { n.mu.RLock() defer n.mu.RUnlock() - if id, exists := n.ipIndex[ipv4.String()]; exists { + if id, exists := n.ipIndex[ip.String()]; exists { return n.nodes[id] } return nil @@ -180,18 +223,6 @@ func (n *Nodes) GetByMAC(mac net.HardwareAddr) *Node { return nil } -func (n *Nodes) SetName(mac net.HardwareAddr, name string) { - n.mu.Lock() - defer n.mu.Unlock() - - if id, exists := n.macIndex[mac.String()]; exists { - node := n.nodes[id] - if node.Name == "" { - node.Name = name - } - } -} - func (n *Nodes) All() []*Node { n.mu.RLock() defer n.mu.RUnlock() diff --git a/snmp.go b/snmp.go index c766650..e1e0b73 100644 --- a/snmp.go +++ b/snmp.go @@ -83,12 +83,14 @@ func (t *Tendrils) querySwitches() { nodes := t.nodes.All() for _, node := range nodes { - for _, ip := range node.IPs { - if ip.To4() == nil { - continue - } + for _, iface := range node.Interfaces { + for _, ip := range iface.IPs { + if ip.To4() == nil { + continue + } - go t.querySNMPDevice(ip) + go t.querySNMPDevice(ip) + } } } } @@ -166,8 +168,11 @@ func (t *Tendrils) queryInterfaceMACs(snmp *gosnmp.GoSNMP, deviceIP net.IP) { } } - if len(macs) > 0 { - t.nodes.Update([]net.IP{deviceIP}, macs, "snmp-ifmac") + for _, mac := range macs { + t.nodes.Update(mac, nil, "", "", "snmp-ifmac") + } + if len(macs) > 1 { + t.nodes.Merge(macs, "snmp-ifmac") } } @@ -229,7 +234,7 @@ func (t *Tendrils) queryBridgeMIB(snmp *gosnmp.GoSNMP, deviceIP net.IP) { log.Printf("[snmp] %s: mac=%s port=%s", deviceIP, mac, ifName) } - t.nodes.Update(nil, []net.HardwareAddr{mac}, "snmp") + t.nodes.Update(mac, nil, "", "", "snmp") } } diff --git a/tendrils.go b/tendrils.go index 5a19c46..6c2d1f9 100644 --- a/tendrils.go +++ b/tendrils.go @@ -69,27 +69,33 @@ func (t *Tendrils) populateLocalAddresses() { root.Name = hostname } - for _, iface := range interfaces { - if len(iface.HardwareAddr) > 0 { - macKey := iface.HardwareAddr.String() - root.MACs[macKey] = iface.HardwareAddr - t.nodes.macIndex[macKey] = 0 - } - - addrs, err := iface.Addrs() - if err != nil { + for _, netIface := range interfaces { + if len(netIface.HardwareAddr) == 0 { continue } - for _, addr := range addrs { - if ipnet, ok := addr.(*net.IPNet); ok { - if ipnet.IP.To4() != nil && !ipnet.IP.IsLoopback() { - ipKey := ipnet.IP.String() - root.IPs[ipKey] = ipnet.IP - t.nodes.ipIndex[ipKey] = 0 + macKey := netIface.HardwareAddr.String() + iface := &Interface{ + Name: netIface.Name, + MAC: netIface.HardwareAddr, + IPs: map[string]net.IP{}, + } + + addrs, err := netIface.Addrs() + if err == nil { + for _, addr := range addrs { + if ipnet, ok := addr.(*net.IPNet); ok { + if ipnet.IP.To4() != nil && !ipnet.IP.IsLoopback() { + ipKey := ipnet.IP.String() + iface.IPs[ipKey] = ipnet.IP + t.nodes.ipIndex[ipKey] = 0 + } } } } + + root.Interfaces[macKey] = iface + t.nodes.macIndex[macKey] = 0 } }