From 0d36c21db24fb31d177b03f084550b599795ff8a Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Sat, 29 Nov 2025 22:27:31 -0800 Subject: [PATCH] add node names from hostname, lldp, and snmp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit populate local node name from hostname. extract system name from lldp packets and snmp sysname oid. call logtree after node merges. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CLAUDE.md | 2 +- lldp.go | 13 +++++++++++++ nodes.go | 25 +++++++++++++++++++++++-- snmp.go | 31 +++++++++++++++++++++++++++++++ tendrils.go | 6 ++++++ 5 files changed, 74 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 8c8ff41..dfcbcf2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,4 +6,4 @@ - Always push after commiting - Use git add -A so you don't miss files when committing - Never use go build -- use go run instead -- Don't commit unless asked to \ No newline at end of file +- DO NOT commit unless asked to \ No newline at end of file diff --git a/lldp.go b/lldp.go index bc2dc2a..09e5045 100644 --- a/lldp.go +++ b/lldp.go @@ -55,7 +55,20 @@ func (t *Tendrils) handleLLDPPacket(ifaceName string, packet gopacket.Packet) { mac := net.HardwareAddr(lldp.ChassisID.ID) if !isBroadcastOrZero(mac) { childPort := string(lldp.PortID.ID) + + var systemName string + for _, opt := range lldp.Values { + if opt.Type == layers.LLDPTLVSysName { + systemName = string(opt.Value) + break + } + } + t.nodes.Update(nil, []net.HardwareAddr{mac}, ifaceName, childPort, "lldp") + + if systemName != "" { + t.nodes.SetName(mac, systemName) + } } } } diff --git a/nodes.go b/nodes.go index 3c10a95..0af9a8e 100644 --- a/nodes.go +++ b/nodes.go @@ -9,6 +9,7 @@ import ( ) type Node struct { + Name string IPs map[string]net.IP MACs map[string]net.HardwareAddr ParentID int @@ -29,7 +30,12 @@ func (n *Node) String() string { } sort.Strings(ips) - return fmt.Sprintf("{macs=%v ips=%v}", macs, ips) + name := n.Name + if name == "" { + name = "??" + } + + return fmt.Sprintf("%s {macs=%v ips=%v}", name, macs, ips) } type Nodes struct { @@ -117,6 +123,9 @@ func (n *Nodes) UpdateWithParent(parentIP net.IP, ips []net.IP, macs []net.Hardw n.mergeNodes(targetID, ids[i]) } log.Printf("merged nodes %v into %s (via %s)", merging, n.nodes[targetID], source) + n.mu.Unlock() + n.LogTree() + n.mu.Lock() } node := n.nodes[targetID] @@ -197,6 +206,18 @@ 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() @@ -219,7 +240,7 @@ func (n *Nodes) logNode(id int, prefix string, isLast bool) { node := n.nodes[id] if id == 0 { - log.Printf("[root] %s", node) + log.Printf("%s", node) n.logChildrenByInterface(id, "") } else { connector := "├──" diff --git a/snmp.go b/snmp.go index 39238ea..08849df 100644 --- a/snmp.go +++ b/snmp.go @@ -113,9 +113,40 @@ func (t *Tendrils) querySNMPDevice(ip net.IP) { } defer snmp.Conn.Close() + t.querySysName(snmp, ip) t.queryBridgeMIB(snmp, ip) } +func (t *Tendrils) querySysName(snmp *gosnmp.GoSNMP, deviceIP net.IP) { + oid := "1.3.6.1.2.1.1.5.0" + + result, err := snmp.Get([]string{oid}) + if err != nil { + 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() + } + } + } +} + func (t *Tendrils) queryBridgeMIB(snmp *gosnmp.GoSNMP, deviceIP net.IP) { portOID := "1.3.6.1.2.1.17.7.1.2.2.1.2" diff --git a/tendrils.go b/tendrils.go index 2c8fdfe..56534f4 100644 --- a/tendrils.go +++ b/tendrils.go @@ -4,6 +4,7 @@ import ( "context" "log" "net" + "os" "time" ) @@ -49,6 +50,11 @@ func (t *Tendrils) populateLocalAddresses() { root := t.nodes.nodes[0] + hostname, err := os.Hostname() + if err == nil { + root.Name = hostname + } + for _, iface := range interfaces { if len(iface.HardwareAddr) > 0 { macKey := iface.HardwareAddr.String()