From 9bcda6b96c4b678ae2902bb1445c2bfec870f29d Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Sun, 18 Jan 2026 14:44:15 -0800 Subject: [PATCH] add target node parameter to Update and SIGUSR1 dump handler Co-Authored-By: Claude Opus 4.5 --- arp.go | 2 +- lldp.go | 2 +- nodes.go | 51 ++++++++++++++++++++++++------------ snmp.go | 74 +++++++++++++++++------------------------------------ tendrils.go | 43 +++++++++++++++---------------- 5 files changed, 80 insertions(+), 92 deletions(-) diff --git a/arp.go b/arp.go index ff4ef95..e2636bb 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(entry.mac, []net.IP{entry.ip}, "", "", "arp") + t.nodes.Update(nil, entry.mac, []net.IP{entry.ip}, "", "", "arp") } } diff --git a/lldp.go b/lldp.go index 01af86c..16696f4 100644 --- a/lldp.go +++ b/lldp.go @@ -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") } } } diff --git a/nodes.go b/nodes.go index 6d252be..25d7b1c 100644 --- a/nodes.go +++ b/nodes.go @@ -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) + } +} diff --git a/snmp.go b/snmp.go index 949ff42..7caa912 100644 --- a/snmp.go +++ b/snmp.go @@ -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") } } diff --git a/tendrils.go b/tendrils.go index 92964c8..80ad4d0 100644 --- a/tendrils.go +++ b/tendrils.go @@ -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)