add target node parameter to Update and SIGUSR1 dump handler

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Ian Gulliver
2026-01-18 14:44:15 -08:00
parent 8cb605b817
commit 9bcda6b96c
5 changed files with 80 additions and 92 deletions

2
arp.go
View File

@@ -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(entry.mac, []net.IP{entry.ip}, "", "", "arp") t.nodes.Update(nil, entry.mac, []net.IP{entry.ip}, "", "", "arp")
} }
} }

View File

@@ -68,7 +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(mac, nil, childPort, systemName, "lldp") t.nodes.Update(nil, mac, nil, childPort, systemName, "lldp")
} }
} }
} }

View File

@@ -69,38 +69,43 @@ type Nodes struct {
} }
func NewNodes(t *Tendrils) *Nodes { func NewNodes(t *Tendrils) *Nodes {
n := &Nodes{ return &Nodes{
nodes: map[int]*Node{}, nodes: map[int]*Node{},
ipIndex: map[string]int{}, ipIndex: map[string]int{},
macIndex: map[string]int{}, macIndex: map[string]int{},
nextID: 1, nextID: 1,
t: t, 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) { func (n *Nodes) Update(target *Node, 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 mac == nil { if mac == nil && target == nil {
return return
} }
macKey := mac.String()
targetID := -1 targetID := -1
isNew := false isNew := false
if id, exists := n.macIndex[macKey]; exists { if target != nil {
if _, nodeExists := n.nodes[id]; nodeExists { for id, node := range n.nodes {
targetID = id if node == target {
} else { targetID = id
delete(n.macIndex, macKey) break
}
}
}
if targetID == -1 && mac != nil {
macKey := mac.String()
if id, exists := n.macIndex[macKey]; exists {
if _, nodeExists := n.nodes[id]; nodeExists {
targetID = id
} else {
delete(n.macIndex, macKey)
}
} }
} }
@@ -115,10 +120,14 @@ func (n *Nodes) Update(mac net.HardwareAddr, ips []net.IP, ifaceName, nodeName,
node := n.nodes[targetID] node := n.nodes[targetID]
added := n.updateNodeInterface(node, targetID, mac, ips, ifaceName) var added []string
if mac != nil {
added = n.updateNodeInterface(node, targetID, mac, ips, ifaceName)
}
if nodeName != "" && node.Name == "" { if nodeName != "" && node.Name == "" {
node.Name = nodeName node.Name = nodeName
added = append(added, "name="+nodeName)
} }
if len(added) > 0 { if len(added) > 0 {
@@ -302,3 +311,13 @@ func (n *Nodes) All() []*Node {
} }
return result return result
} }
func (n *Nodes) LogAll() {
n.mu.RLock()
defer n.mu.RUnlock()
log.Printf("[sigusr1] ================ %d nodes ================", len(n.nodes))
for _, node := range n.nodes {
n.logNode(node)
}
}

74
snmp.go
View File

@@ -106,13 +106,13 @@ func (t *Tendrils) querySwitches() {
continue continue
} }
go t.querySNMPDevice(ip) go t.querySNMPDevice(node, ip)
} }
} }
} }
} }
func (t *Tendrils) querySNMPDevice(ip net.IP) { func (t *Tendrils) querySNMPDevice(node *Node, ip net.IP) {
snmp, err := t.connectSNMP(ip) snmp, err := t.connectSNMP(ip)
if err != nil { if err != nil {
if t.DebugSNMP { if t.DebugSNMP {
@@ -122,12 +122,12 @@ func (t *Tendrils) querySNMPDevice(ip net.IP) {
} }
defer snmp.Conn.Close() defer snmp.Conn.Close()
t.querySysName(snmp, ip) t.querySysName(snmp, node)
t.queryInterfaceMACs(snmp, ip) t.queryInterfaceMACs(snmp, node)
t.queryBridgeMIB(snmp, ip) t.queryBridgeMIB(snmp, ip)
} }
func (t *Tendrils) querySysName(snmp *gosnmp.GoSNMP, deviceIP net.IP) { func (t *Tendrils) querySysName(snmp *gosnmp.GoSNMP, node *Node) {
oid := "1.3.6.1.2.1.1.5.0" oid := "1.3.6.1.2.1.1.5.0"
result, err := snmp.Get([]string{oid}) result, err := snmp.Get([]string{oid})
@@ -135,29 +135,24 @@ func (t *Tendrils) querySysName(snmp *gosnmp.GoSNMP, deviceIP net.IP) {
return return
} }
if len(result.Variables) > 0 { if len(result.Variables) == 0 {
variable := result.Variables[0] return
if variable.Type == gosnmp.OctetString {
sysName := string(variable.Value.([]byte))
if sysName != "" {
t.nodes.mu.RLock()
if id, exists := t.nodes.ipIndex[deviceIP.String()]; exists {
t.nodes.mu.RUnlock()
t.nodes.mu.Lock()
node := t.nodes.nodes[id]
if node.Name == "" {
node.Name = sysName
}
t.nodes.mu.Unlock()
return
}
t.nodes.mu.RUnlock()
}
}
} }
variable := result.Variables[0]
if variable.Type != gosnmp.OctetString {
return
}
sysName := string(variable.Value.([]byte))
if sysName == "" {
return
}
t.nodes.Update(node, nil, nil, "", sysName, "snmp-sysname")
} }
func (t *Tendrils) queryInterfaceMACs(snmp *gosnmp.GoSNMP, deviceIP net.IP) { func (t *Tendrils) queryInterfaceMACs(snmp *gosnmp.GoSNMP, node *Node) {
oid := "1.3.6.1.2.1.2.2.1.6" oid := "1.3.6.1.2.1.2.2.1.6"
results, err := snmp.BulkWalkAll(oid) results, err := snmp.BulkWalkAll(oid)
@@ -167,12 +162,6 @@ func (t *Tendrils) queryInterfaceMACs(snmp *gosnmp.GoSNMP, deviceIP net.IP) {
ifNames := t.getInterfaceNames(snmp) ifNames := t.getInterfaceNames(snmp)
type ifaceEntry struct {
mac net.HardwareAddr
name string
}
var ifaces []ifaceEntry
for _, result := range results { for _, result := range results {
if result.Type != gosnmp.OctetString { if result.Type != gosnmp.OctetString {
continue continue
@@ -196,27 +185,10 @@ func (t *Tendrils) queryInterfaceMACs(snmp *gosnmp.GoSNMP, deviceIP net.IP) {
name := rewritePortName(ifNames[ifIndex]) name := rewritePortName(ifNames[ifIndex])
if t.DebugSNMP { if t.DebugSNMP {
log.Printf("[snmp] %s: interface %d mac=%s name=%s", deviceIP, ifIndex, mac, name) log.Printf("[snmp] %s: interface %d mac=%s name=%s", snmp.Target, ifIndex, mac, name)
} }
ifaces = append(ifaces, ifaceEntry{mac: mac, name: name}) t.nodes.Update(node, mac, nil, name, "", "snmp-ifmac")
}
var macs []net.HardwareAddr
for _, iface := range ifaces {
t.nodes.Update(iface.mac, nil, iface.name, "", "snmp-ifmac")
macs = append(macs, iface.mac)
}
existingNode := t.nodes.GetByIP(deviceIP)
if existingNode != nil {
for _, iface := range existingNode.Interfaces {
macs = append(macs, iface.MAC)
}
}
if len(macs) > 1 {
t.nodes.Merge(macs, "snmp-ifmac")
} }
} }
@@ -278,7 +250,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(mac, nil, "", "", "snmp") t.nodes.Update(nil, mac, nil, "", "", "snmp")
} }
} }

View File

@@ -5,6 +5,8 @@ import (
"log" "log"
"net" "net"
"os" "os"
"os/signal"
"syscall"
"time" "time"
) )
@@ -35,6 +37,14 @@ func (t *Tendrils) Run() {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGUSR1)
go func() {
for range sigCh {
t.nodes.LogAll()
}
}()
t.populateLocalAddresses() t.populateLocalAddresses()
if !t.DisableARP { if !t.DisableARP {
@@ -60,43 +70,30 @@ func (t *Tendrils) populateLocalAddresses() {
return return
} }
t.nodes.mu.Lock() hostname, _ := os.Hostname()
defer t.nodes.mu.Unlock()
root := t.nodes.nodes[0]
hostname, err := os.Hostname()
if err == nil {
root.Name = hostname
}
var target *Node
for _, netIface := range interfaces { for _, netIface := range interfaces {
if len(netIface.HardwareAddr) == 0 { if len(netIface.HardwareAddr) == 0 {
continue continue
} }
macKey := netIface.HardwareAddr.String() var ips []net.IP
iface := &Interface{
Name: netIface.Name,
MAC: netIface.HardwareAddr,
IPs: map[string]net.IP{},
}
addrs, err := netIface.Addrs() addrs, err := netIface.Addrs()
if err == nil { if err == nil {
for _, addr := range addrs { for _, addr := range addrs {
if ipnet, ok := addr.(*net.IPNet); ok { if ipnet, ok := addr.(*net.IPNet); ok {
if ipnet.IP.To4() != nil && !ipnet.IP.IsLoopback() { if ipnet.IP.To4() != nil && !ipnet.IP.IsLoopback() {
ipKey := ipnet.IP.String() ips = append(ips, ipnet.IP)
iface.IPs[ipKey] = ipnet.IP
t.nodes.ipIndex[ipKey] = 0
} }
} }
} }
} }
root.Interfaces[macKey] = iface t.nodes.Update(target, netIface.HardwareAddr, ips, netIface.Name, hostname, "local")
t.nodes.macIndex[macKey] = 0 if target == nil {
target = t.nodes.GetByMAC(netIface.HardwareAddr)
}
} }
} }
@@ -147,7 +144,7 @@ func (t *Tendrils) updateInterfaces(interfaces []net.Interface) {
for name, cancel := range t.activeInterfaces { for name, cancel := range t.activeInterfaces {
if !current[name] { if !current[name] {
log.Printf("interface removed: %s", name) log.Printf("[iface] remove: %s", name)
cancel() cancel()
delete(t.activeInterfaces, name) delete(t.activeInterfaces, name)
} }
@@ -155,7 +152,7 @@ func (t *Tendrils) updateInterfaces(interfaces []net.Interface) {
for _, iface := range interfaces { for _, iface := range interfaces {
if _, exists := t.activeInterfaces[iface.Name]; !exists { if _, exists := t.activeInterfaces[iface.Name]; !exists {
log.Printf("interface added: %s", iface.Name) log.Printf("[iface] add: %s", iface.Name)
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
t.activeInterfaces[iface.Name] = cancel t.activeInterfaces[iface.Name] = cancel
t.startInterface(ctx, iface) t.startInterface(ctx, iface)