add snmpv3 topology discovery with q-bridge support
This commit is contained in:
@@ -5,4 +5,5 @@
|
|||||||
- Don't mention claude in commit messages. Keep them to a single, short, descriptive sentence
|
- Don't mention claude in commit messages. Keep them to a single, short, descriptive sentence
|
||||||
- Always push after commiting
|
- Always push after commiting
|
||||||
- Use git add -A so you don't miss files when committing
|
- Use git add -A so you don't miss files when committing
|
||||||
- Never use go build -- use go run instead
|
- Never use go build -- use go run instead
|
||||||
|
- Don't commit unless asked to
|
||||||
15
nodes.go
15
nodes.go
@@ -58,6 +58,10 @@ func NewNodes() *Nodes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *Nodes) Update(ips []net.IP, macs []net.HardwareAddr, parentPort, childPort, source string) {
|
func (n *Nodes) Update(ips []net.IP, macs []net.HardwareAddr, parentPort, childPort, source string) {
|
||||||
|
n.UpdateWithParent(nil, ips, macs, parentPort, childPort, source)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Nodes) UpdateWithParent(parentIP net.IP, ips []net.IP, macs []net.HardwareAddr, parentPort, childPort, source string) {
|
||||||
n.mu.Lock()
|
n.mu.Lock()
|
||||||
defer n.mu.Unlock()
|
defer n.mu.Unlock()
|
||||||
|
|
||||||
@@ -65,6 +69,13 @@ func (n *Nodes) Update(ips []net.IP, macs []net.HardwareAddr, parentPort, childP
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parentID := 0
|
||||||
|
if parentIP != nil {
|
||||||
|
if id, exists := n.ipIndex[parentIP.String()]; exists {
|
||||||
|
parentID = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
existingIDs := map[int]bool{}
|
existingIDs := map[int]bool{}
|
||||||
|
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
@@ -86,7 +97,7 @@ func (n *Nodes) Update(ips []net.IP, macs []net.HardwareAddr, parentPort, childP
|
|||||||
n.nodes[targetID] = &Node{
|
n.nodes[targetID] = &Node{
|
||||||
IPs: map[string]net.IP{},
|
IPs: map[string]net.IP{},
|
||||||
MACs: map[string]net.HardwareAddr{},
|
MACs: map[string]net.HardwareAddr{},
|
||||||
ParentID: 0,
|
ParentID: parentID,
|
||||||
LocalPort: childPort,
|
LocalPort: childPort,
|
||||||
ParentPort: parentPort,
|
ParentPort: parentPort,
|
||||||
}
|
}
|
||||||
@@ -105,7 +116,7 @@ func (n *Nodes) Update(ips []net.IP, macs []net.HardwareAddr, parentPort, childP
|
|||||||
merging = append(merging, n.nodes[ids[i]].String())
|
merging = append(merging, n.nodes[ids[i]].String())
|
||||||
n.mergeNodes(targetID, ids[i])
|
n.mergeNodes(targetID, ids[i])
|
||||||
}
|
}
|
||||||
log.Printf("[%s] merged nodes %v into %s", source, merging, n.nodes[targetID])
|
log.Printf("merged nodes %v into %s (via %s)", merging, n.nodes[targetID], source)
|
||||||
}
|
}
|
||||||
|
|
||||||
node := n.nodes[targetID]
|
node := n.nodes[targetID]
|
||||||
|
|||||||
133
snmp.go
133
snmp.go
@@ -3,22 +3,36 @@ package tendrils
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net"
|
"net"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gosnmp/gosnmp"
|
"github.com/gosnmp/gosnmp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
addToParentRules = []*regexp.Regexp{
|
||||||
|
regexp.MustCompile(`CPU Interface`),
|
||||||
|
}
|
||||||
|
|
||||||
|
portNameRewrites = []struct {
|
||||||
|
regex *regexp.Regexp
|
||||||
|
replacement string
|
||||||
|
}{
|
||||||
|
{regexp.MustCompile(`Slot: (\d+) Port: (\d+) .+`), "$1/$2"},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
type snmpConfig struct {
|
type snmpConfig struct {
|
||||||
username string
|
username string
|
||||||
authKey string
|
authKey string
|
||||||
privKey string
|
privKey string
|
||||||
authProto gosnmp.SnmpV3AuthProtocol
|
authProto gosnmp.SnmpV3AuthProtocol
|
||||||
privProto gosnmp.SnmpV3PrivProtocol
|
privProto gosnmp.SnmpV3PrivProtocol
|
||||||
secLevel gosnmp.SnmpV3MsgFlags
|
secLevel gosnmp.SnmpV3MsgFlags
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
retries int
|
retries int
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultSNMPConfig() *snmpConfig {
|
func defaultSNMPConfig() *snmpConfig {
|
||||||
@@ -63,7 +77,7 @@ func (t *Tendrils) connectSNMP(ip net.IP) (*gosnmp.GoSNMP, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tendrils) pollSNMP(ctx context.Context) {
|
func (t *Tendrils) pollSNMP(ctx context.Context) {
|
||||||
ticker := time.NewTicker(5 * time.Minute)
|
ticker := time.NewTicker(10 * time.Second)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
t.querySwitches()
|
t.querySwitches()
|
||||||
@@ -112,55 +126,90 @@ func (t *Tendrils) queryBridgeMIB(snmp *gosnmp.GoSNMP, deviceIP net.IP) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(macResults) == 0 {
|
||||||
|
macOID = "1.3.6.1.2.1.17.7.1.2.2.1.1"
|
||||||
|
portOID = "1.3.6.1.2.1.17.7.1.2.2.1.2"
|
||||||
|
macResults, err = snmp.BulkWalkAll(macOID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
portResults, err := snmp.BulkWalkAll(portOID)
|
portResults, err := snmp.BulkWalkAll(portOID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
portMap := make(map[string]int)
|
type macPortEntry struct {
|
||||||
|
mac net.HardwareAddr
|
||||||
|
bridgePort int
|
||||||
|
}
|
||||||
|
var macPorts []macPortEntry
|
||||||
|
|
||||||
for _, result := range portResults {
|
for _, result := range portResults {
|
||||||
if result.Type == gosnmp.Integer {
|
if result.Type == gosnmp.Integer {
|
||||||
oidSuffix := result.Name[len(portOID)+1:]
|
oidSuffix := strings.TrimPrefix(result.Name[len(portOID):], ".")
|
||||||
portMap[oidSuffix] = result.Value.(int)
|
parts := strings.Split(oidSuffix, ".")
|
||||||
|
|
||||||
|
if len(parts) >= 8 {
|
||||||
|
var macBytes []byte
|
||||||
|
for j := 2; j <= 7; j++ {
|
||||||
|
var b int
|
||||||
|
fmt.Sscanf(parts[j], "%d", &b)
|
||||||
|
macBytes = append(macBytes, byte(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(macBytes) == 6 {
|
||||||
|
mac := net.HardwareAddr(macBytes)
|
||||||
|
bridgePort := result.Value.(int)
|
||||||
|
macPorts = append(macPorts, macPortEntry{mac: mac, bridgePort: bridgePort})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bridgePortToIfIndex := t.getBridgePortMapping(snmp)
|
bridgePortToIfIndex := t.getBridgePortMapping(snmp)
|
||||||
ifNames := t.getInterfaceNames(snmp)
|
ifNames := t.getInterfaceNames(snmp)
|
||||||
|
|
||||||
for _, result := range macResults {
|
for _, entry := range macPorts {
|
||||||
if result.Type == gosnmp.OctetString {
|
mac := entry.mac
|
||||||
macBytes := result.Value.([]byte)
|
bridgePort := entry.bridgePort
|
||||||
if len(macBytes) != 6 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
mac := net.HardwareAddr(macBytes)
|
if isBroadcastOrZero(mac) {
|
||||||
if isBroadcastOrZero(mac) {
|
continue
|
||||||
continue
|
}
|
||||||
}
|
|
||||||
|
|
||||||
oidSuffix := result.Name[len(macOID)+1:]
|
ifIndex, exists := bridgePortToIfIndex[bridgePort]
|
||||||
bridgePort, exists := portMap[oidSuffix]
|
if !exists {
|
||||||
if !exists {
|
ifIndex = bridgePort
|
||||||
continue
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ifIndex, exists := bridgePortToIfIndex[bridgePort]
|
ifName := ifNames[ifIndex]
|
||||||
if !exists {
|
if ifName == "" {
|
||||||
ifIndex = bridgePort
|
ifName = "??"
|
||||||
}
|
}
|
||||||
|
|
||||||
ifName := ifNames[ifIndex]
|
addToParent := false
|
||||||
if ifName == "" {
|
for _, rule := range addToParentRules {
|
||||||
ifName = "??"
|
if rule.MatchString(ifName) {
|
||||||
|
addToParent = true
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
t.nodes.Update(nil, []net.HardwareAddr{mac}, ifName, "", "snmp")
|
for _, rewrite := range portNameRewrites {
|
||||||
|
if rewrite.regex.MatchString(ifName) {
|
||||||
|
ifName = rewrite.regex.ReplaceAllString(ifName, rewrite.replacement)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if addToParent {
|
||||||
|
t.nodes.Update([]net.IP{deviceIP}, []net.HardwareAddr{mac}, "", "", "snmp")
|
||||||
|
} else {
|
||||||
|
t.nodes.UpdateWithParent(deviceIP, nil, []net.HardwareAddr{mac}, ifName, "", "snmp")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[snmp] queried bridge mib on %s", deviceIP)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tendrils) queryARPTable(snmp *gosnmp.GoSNMP, deviceIP net.IP) {
|
func (t *Tendrils) queryARPTable(snmp *gosnmp.GoSNMP, deviceIP net.IP) {
|
||||||
@@ -231,11 +280,9 @@ func (t *Tendrils) queryARPTable(snmp *gosnmp.GoSNMP, deviceIP net.IP) {
|
|||||||
ifName = "??"
|
ifName = "??"
|
||||||
}
|
}
|
||||||
|
|
||||||
t.nodes.Update(ips, []net.HardwareAddr{mac}, ifName, "", "snmp")
|
t.nodes.UpdateWithParent(deviceIP, ips, []net.HardwareAddr{mac}, ifName, "", "snmp")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[snmp] queried arp table on %s", deviceIP)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tendrils) getBridgePortMapping(snmp *gosnmp.GoSNMP) map[int]int {
|
func (t *Tendrils) getBridgePortMapping(snmp *gosnmp.GoSNMP) map[int]int {
|
||||||
@@ -249,7 +296,7 @@ func (t *Tendrils) getBridgePortMapping(snmp *gosnmp.GoSNMP) map[int]int {
|
|||||||
mapping := make(map[int]int)
|
mapping := make(map[int]int)
|
||||||
for _, result := range results {
|
for _, result := range results {
|
||||||
if result.Type == gosnmp.Integer {
|
if result.Type == gosnmp.Integer {
|
||||||
oidParts := result.Name[len(oid)+1:]
|
oidParts := strings.TrimPrefix(strings.TrimPrefix(result.Name, "."+oid), ".")
|
||||||
var bridgePort int
|
var bridgePort int
|
||||||
_, err := fmt.Sscanf(oidParts, "%d", &bridgePort)
|
_, err := fmt.Sscanf(oidParts, "%d", &bridgePort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -274,7 +321,7 @@ func (t *Tendrils) getInterfaceNames(snmp *gosnmp.GoSNMP) map[int]string {
|
|||||||
names := make(map[int]string)
|
names := make(map[int]string)
|
||||||
for _, result := range results {
|
for _, result := range results {
|
||||||
if result.Type == gosnmp.OctetString {
|
if result.Type == gosnmp.OctetString {
|
||||||
oidParts := result.Name[len(oid)+1:]
|
oidParts := strings.TrimPrefix(strings.TrimPrefix(result.Name, "."+oid), ".")
|
||||||
var ifIndex int
|
var ifIndex int
|
||||||
_, err := fmt.Sscanf(oidParts, "%d", &ifIndex)
|
_, err := fmt.Sscanf(oidParts, "%d", &ifIndex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user