From 75bbf4b0a4953b49bef45c0ddcc04a6271e83117 Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Sat, 29 Nov 2025 22:13:25 -0800 Subject: [PATCH] fix local address population and snmp reverse port discovery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit populate root node with local macs/ips at startup, excluding loopback addresses and permanent arp entries. detect when snmp finds parent node mac in child forwarding table and set child localport. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- arp.go | 5 +++++ nodes.go | 16 +++++++++------- snmp.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ tendrils.go | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+), 7 deletions(-) diff --git a/arp.go b/arp.go index f1a5d0c..9736c09 100644 --- a/arp.go +++ b/arp.go @@ -63,6 +63,11 @@ func (t *Tendrils) parseARPDarwin() []arpEntry { scanner := bufio.NewScanner(strings.NewReader(string(output))) for scanner.Scan() { line := scanner.Text() + + if strings.Contains(line, "permanent") { + continue + } + fields := strings.Fields(line) if len(fields) < 6 { continue diff --git a/nodes.go b/nodes.go index 7aaeed7..3c10a95 100644 --- a/nodes.go +++ b/nodes.go @@ -122,14 +122,16 @@ func (n *Nodes) UpdateWithParent(parentIP net.IP, ips []net.IP, macs []net.Hardw node := n.nodes[targetID] var added []string - if node.LocalPort == "" && childPort != "" { - node.LocalPort = childPort - added = append(added, "localPort="+childPort) - } + if targetID != 0 { + if node.LocalPort == "" && childPort != "" { + node.LocalPort = childPort + added = append(added, "localPort="+childPort) + } - if node.ParentPort == "" && parentPort != "" { - node.ParentPort = parentPort - added = append(added, "parentPort="+parentPort) + if node.ParentPort == "" && parentPort != "" { + node.ParentPort = parentPort + added = append(added, "parentPort="+parentPort) + } } for _, ip := range ips { diff --git a/snmp.go b/snmp.go index d098cc1..6d5321a 100644 --- a/snmp.go +++ b/snmp.go @@ -207,6 +207,30 @@ func (t *Tendrils) queryBridgeMIB(snmp *gosnmp.GoSNMP, deviceIP net.IP) { if addToParent { t.nodes.Update([]net.IP{deviceIP}, []net.HardwareAddr{mac}, "", "", "snmp") } else { + t.nodes.mu.RLock() + deviceNodeID := -1 + if id, exists := t.nodes.ipIndex[deviceIP.String()]; exists { + deviceNodeID = id + } + macNodeID := -1 + if id, exists := t.nodes.macIndex[mac.String()]; exists { + macNodeID = id + } + + if deviceNodeID != -1 && macNodeID != -1 { + deviceNode := t.nodes.nodes[deviceNodeID] + if deviceNode.ParentID == macNodeID { + t.nodes.mu.RUnlock() + t.nodes.mu.Lock() + if deviceNode.LocalPort == "" { + deviceNode.LocalPort = ifName + } + t.nodes.mu.Unlock() + continue + } + } + t.nodes.mu.RUnlock() + t.nodes.UpdateWithParent(deviceIP, nil, []net.HardwareAddr{mac}, ifName, "", "snmp") } } @@ -280,6 +304,30 @@ func (t *Tendrils) queryARPTable(snmp *gosnmp.GoSNMP, deviceIP net.IP) { ifName = "??" } + t.nodes.mu.RLock() + deviceNodeID := -1 + if id, exists := t.nodes.ipIndex[deviceIP.String()]; exists { + deviceNodeID = id + } + macNodeID := -1 + if id, exists := t.nodes.macIndex[mac.String()]; exists { + macNodeID = id + } + + if deviceNodeID != -1 && macNodeID != -1 { + deviceNode := t.nodes.nodes[deviceNodeID] + if deviceNode.ParentID == macNodeID { + t.nodes.mu.RUnlock() + t.nodes.mu.Lock() + if deviceNode.LocalPort == "" { + deviceNode.LocalPort = ifName + } + t.nodes.mu.Unlock() + continue + } + } + t.nodes.mu.RUnlock() + t.nodes.UpdateWithParent(deviceIP, ips, []net.HardwareAddr{mac}, ifName, "", "snmp") } } diff --git a/tendrils.go b/tendrils.go index 4b40529..2c8fdfe 100644 --- a/tendrils.go +++ b/tendrils.go @@ -23,6 +23,8 @@ func (t *Tendrils) Run() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() + t.populateLocalAddresses() + go t.pollARP(ctx) go t.pollSNMP(ctx) @@ -36,6 +38,41 @@ func (t *Tendrils) Run() { } } +func (t *Tendrils) populateLocalAddresses() { + interfaces, err := net.Interfaces() + if err != nil { + return + } + + t.nodes.mu.Lock() + defer t.nodes.mu.Unlock() + + root := t.nodes.nodes[0] + + for _, iface := range interfaces { + if len(iface.HardwareAddr) > 0 { + macKey := iface.HardwareAddr.String() + root.MACs[macKey] = iface.HardwareAddr + t.nodes.macIndex[macKey] = 0 + } + + addrs, err := iface.Addrs() + if err != nil { + continue + } + + for _, addr := range addrs { + if ipnet, ok := addr.(*net.IPNet); ok { + if ipnet.IP.To4() != nil && !ipnet.IP.IsLoopback() { + ipKey := ipnet.IP.String() + root.IPs[ipKey] = ipnet.IP + t.nodes.ipIndex[ipKey] = 0 + } + } + } + } +} + func (t *Tendrils) listInterfaces() []net.Interface { interfaces, err := net.Interfaces() if err != nil {