restructure nodes to have interfaces with name, mac, and ips
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2
arp.go
2
arp.go
@@ -48,7 +48,7 @@ func (t *Tendrils) readARPTable() {
|
|||||||
log.Printf("[arp] %s: ip=%s mac=%s", entry.iface, entry.ip, entry.mac)
|
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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
6
lldp.go
6
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)
|
log.Printf("[lldp] %s: mac=%s port=%s name=%s", ifaceName, mac, childPort, systemName)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.nodes.Update(nil, []net.HardwareAddr{mac}, "lldp")
|
t.nodes.Update(mac, nil, childPort, systemName, "lldp")
|
||||||
|
|
||||||
if systemName != "" {
|
|
||||||
t.nodes.SetName(mac, systemName)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
185
nodes.go
185
nodes.go
@@ -8,31 +8,48 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Node struct {
|
type Interface struct {
|
||||||
Name string
|
Name string
|
||||||
|
MAC net.HardwareAddr
|
||||||
IPs map[string]net.IP
|
IPs map[string]net.IP
|
||||||
MACs map[string]net.HardwareAddr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Node) String() string {
|
func (i *Interface) String() string {
|
||||||
var macs []string
|
name := i.Name
|
||||||
for _, mac := range n.MACs {
|
if name == "" {
|
||||||
macs = append(macs, mac.String())
|
name = "??"
|
||||||
}
|
}
|
||||||
sort.Strings(macs)
|
|
||||||
|
|
||||||
var ips []string
|
var ips []string
|
||||||
for _, ip := range n.IPs {
|
for _, ip := range i.IPs {
|
||||||
ips = append(ips, ip.String())
|
ips = append(ips, ip.String())
|
||||||
}
|
}
|
||||||
sort.Strings(ips)
|
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
|
name := n.Name
|
||||||
if name == "" {
|
if name == "" {
|
||||||
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 {
|
type Nodes struct {
|
||||||
@@ -54,84 +71,64 @@ func NewNodes(t *Tendrils) *Nodes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
n.nodes[0] = &Node{
|
n.nodes[0] = &Node{
|
||||||
IPs: map[string]net.IP{},
|
Interfaces: map[string]*Interface{},
|
||||||
MACs: map[string]net.HardwareAddr{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return n
|
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()
|
n.mu.Lock()
|
||||||
defer n.mu.Unlock()
|
defer n.mu.Unlock()
|
||||||
|
|
||||||
if len(ips) == 0 && len(macs) == 0 {
|
if mac == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
existingIDs := map[int]bool{}
|
macKey := mac.String()
|
||||||
|
|
||||||
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
|
var targetID int
|
||||||
isNew := false
|
isNew := false
|
||||||
if len(existingIDs) == 0 {
|
|
||||||
|
if id, exists := n.macIndex[macKey]; exists {
|
||||||
|
targetID = id
|
||||||
|
} else {
|
||||||
targetID = n.nextID
|
targetID = n.nextID
|
||||||
n.nextID++
|
n.nextID++
|
||||||
n.nodes[targetID] = &Node{
|
n.nodes[targetID] = &Node{
|
||||||
IPs: map[string]net.IP{},
|
Interfaces: map[string]*Interface{},
|
||||||
MACs: map[string]net.HardwareAddr{},
|
|
||||||
}
|
}
|
||||||
isNew = true
|
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]
|
node := n.nodes[targetID]
|
||||||
var added []string
|
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 {
|
for _, ip := range ips {
|
||||||
ipKey := ip.String()
|
ipKey := ip.String()
|
||||||
if _, exists := node.IPs[ipKey]; !exists {
|
if _, exists := iface.IPs[ipKey]; !exists {
|
||||||
added = append(added, "ip="+ipKey)
|
added = append(added, "ip="+ipKey)
|
||||||
}
|
}
|
||||||
node.IPs[ipKey] = ip
|
iface.IPs[ipKey] = ip
|
||||||
n.ipIndex[ipKey] = targetID
|
n.ipIndex[ipKey] = targetID
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, mac := range macs {
|
if ifaceName != "" && iface.Name == "" {
|
||||||
macKey := mac.String()
|
iface.Name = ifaceName
|
||||||
if _, exists := node.MACs[macKey]; !exists {
|
}
|
||||||
added = append(added, "mac="+macKey)
|
|
||||||
}
|
if nodeName != "" && node.Name == "" {
|
||||||
node.MACs[macKey] = mac
|
node.Name = nodeName
|
||||||
n.macIndex[macKey] = targetID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(added) > 0 && n.t.LogEvents {
|
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) {
|
func (n *Nodes) mergeNodes(keepID, mergeID int) {
|
||||||
keep := n.nodes[keepID]
|
keep := n.nodes[keepID]
|
||||||
merge := n.nodes[mergeID]
|
merge := n.nodes[mergeID]
|
||||||
|
|
||||||
for ipKey, ip := range merge.IPs {
|
if merge.Name != "" && keep.Name == "" {
|
||||||
keep.IPs[ipKey] = ip
|
keep.Name = merge.Name
|
||||||
n.ipIndex[ipKey] = keepID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for macKey, mac := range merge.MACs {
|
for macKey, iface := range merge.Interfaces {
|
||||||
keep.MACs[macKey] = mac
|
if existing, exists := keep.Interfaces[macKey]; exists {
|
||||||
n.macIndex[macKey] = keepID
|
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)
|
delete(n.nodes, mergeID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Nodes) GetByIP(ipv4 net.IP) *Node {
|
func (n *Nodes) GetByIP(ip net.IP) *Node {
|
||||||
n.mu.RLock()
|
n.mu.RLock()
|
||||||
defer n.mu.RUnlock()
|
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 n.nodes[id]
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -180,18 +223,6 @@ func (n *Nodes) GetByMAC(mac net.HardwareAddr) *Node {
|
|||||||
return nil
|
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 {
|
func (n *Nodes) All() []*Node {
|
||||||
n.mu.RLock()
|
n.mu.RLock()
|
||||||
defer n.mu.RUnlock()
|
defer n.mu.RUnlock()
|
||||||
|
|||||||
21
snmp.go
21
snmp.go
@@ -83,12 +83,14 @@ func (t *Tendrils) querySwitches() {
|
|||||||
nodes := t.nodes.All()
|
nodes := t.nodes.All()
|
||||||
|
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
for _, ip := range node.IPs {
|
for _, iface := range node.Interfaces {
|
||||||
if ip.To4() == nil {
|
for _, ip := range iface.IPs {
|
||||||
continue
|
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 {
|
for _, mac := range macs {
|
||||||
t.nodes.Update([]net.IP{deviceIP}, macs, "snmp-ifmac")
|
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)
|
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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
36
tendrils.go
36
tendrils.go
@@ -69,27 +69,33 @@ func (t *Tendrils) populateLocalAddresses() {
|
|||||||
root.Name = hostname
|
root.Name = hostname
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, iface := range interfaces {
|
for _, netIface := range interfaces {
|
||||||
if len(iface.HardwareAddr) > 0 {
|
if len(netIface.HardwareAddr) == 0 {
|
||||||
macKey := iface.HardwareAddr.String()
|
|
||||||
root.MACs[macKey] = iface.HardwareAddr
|
|
||||||
t.nodes.macIndex[macKey] = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
addrs, err := iface.Addrs()
|
|
||||||
if err != nil {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, addr := range addrs {
|
macKey := netIface.HardwareAddr.String()
|
||||||
if ipnet, ok := addr.(*net.IPNet); ok {
|
iface := &Interface{
|
||||||
if ipnet.IP.To4() != nil && !ipnet.IP.IsLoopback() {
|
Name: netIface.Name,
|
||||||
ipKey := ipnet.IP.String()
|
MAC: netIface.HardwareAddr,
|
||||||
root.IPs[ipKey] = ipnet.IP
|
IPs: map[string]net.IP{},
|
||||||
t.nodes.ipIndex[ipKey] = 0
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user