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)
}
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)
}
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 {
n := &Nodes{
return &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) {
func (n *Nodes) Update(target *Node, mac net.HardwareAddr, ips []net.IP, ifaceName, nodeName, source string) {
n.mu.Lock()
defer n.mu.Unlock()
if mac == nil {
if mac == nil && target == nil {
return
}
macKey := mac.String()
targetID := -1
isNew := false
if id, exists := n.macIndex[macKey]; exists {
if _, nodeExists := n.nodes[id]; nodeExists {
targetID = id
} else {
delete(n.macIndex, macKey)
if target != nil {
for id, node := range n.nodes {
if node == target {
targetID = id
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]
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 == "" {
node.Name = nodeName
added = append(added, "name="+nodeName)
}
if len(added) > 0 {
@@ -302,3 +311,13 @@ func (n *Nodes) All() []*Node {
}
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
}
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)
if err != nil {
if t.DebugSNMP {
@@ -122,12 +122,12 @@ func (t *Tendrils) querySNMPDevice(ip net.IP) {
}
defer snmp.Conn.Close()
t.querySysName(snmp, ip)
t.queryInterfaceMACs(snmp, ip)
t.querySysName(snmp, node)
t.queryInterfaceMACs(snmp, node)
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"
result, err := snmp.Get([]string{oid})
@@ -135,29 +135,24 @@ func (t *Tendrils) querySysName(snmp *gosnmp.GoSNMP, deviceIP net.IP) {
return
}
if len(result.Variables) > 0 {
variable := result.Variables[0]
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()
}
}
if len(result.Variables) == 0 {
return
}
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"
results, err := snmp.BulkWalkAll(oid)
@@ -167,12 +162,6 @@ func (t *Tendrils) queryInterfaceMACs(snmp *gosnmp.GoSNMP, deviceIP net.IP) {
ifNames := t.getInterfaceNames(snmp)
type ifaceEntry struct {
mac net.HardwareAddr
name string
}
var ifaces []ifaceEntry
for _, result := range results {
if result.Type != gosnmp.OctetString {
continue
@@ -196,27 +185,10 @@ func (t *Tendrils) queryInterfaceMACs(snmp *gosnmp.GoSNMP, deviceIP net.IP) {
name := rewritePortName(ifNames[ifIndex])
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})
}
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")
t.nodes.Update(node, mac, nil, name, "", "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)
}
t.nodes.Update(mac, nil, "", "", "snmp")
t.nodes.Update(nil, mac, nil, "", "", "snmp")
}
}

View File

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