add snmpv3 topology discovery with q-bridge support
This commit is contained in:
@@ -6,3 +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
|
||||
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) {
|
||||
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()
|
||||
defer n.mu.Unlock()
|
||||
|
||||
@@ -65,6 +69,13 @@ func (n *Nodes) Update(ips []net.IP, macs []net.HardwareAddr, parentPort, childP
|
||||
return
|
||||
}
|
||||
|
||||
parentID := 0
|
||||
if parentIP != nil {
|
||||
if id, exists := n.ipIndex[parentIP.String()]; exists {
|
||||
parentID = id
|
||||
}
|
||||
}
|
||||
|
||||
existingIDs := map[int]bool{}
|
||||
|
||||
for _, ip := range ips {
|
||||
@@ -86,7 +97,7 @@ func (n *Nodes) Update(ips []net.IP, macs []net.HardwareAddr, parentPort, childP
|
||||
n.nodes[targetID] = &Node{
|
||||
IPs: map[string]net.IP{},
|
||||
MACs: map[string]net.HardwareAddr{},
|
||||
ParentID: 0,
|
||||
ParentID: parentID,
|
||||
LocalPort: childPort,
|
||||
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())
|
||||
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]
|
||||
|
||||
97
snmp.go
97
snmp.go
@@ -3,13 +3,27 @@ package tendrils
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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 {
|
||||
username string
|
||||
authKey string
|
||||
@@ -63,7 +77,7 @@ func (t *Tendrils) connectSNMP(ip net.IP) (*gosnmp.GoSNMP, error) {
|
||||
}
|
||||
|
||||
func (t *Tendrils) pollSNMP(ctx context.Context) {
|
||||
ticker := time.NewTicker(5 * time.Minute)
|
||||
ticker := time.NewTicker(10 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
t.querySwitches()
|
||||
@@ -112,40 +126,59 @@ func (t *Tendrils) queryBridgeMIB(snmp *gosnmp.GoSNMP, deviceIP net.IP) {
|
||||
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)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
portMap := make(map[string]int)
|
||||
type macPortEntry struct {
|
||||
mac net.HardwareAddr
|
||||
bridgePort int
|
||||
}
|
||||
var macPorts []macPortEntry
|
||||
|
||||
for _, result := range portResults {
|
||||
if result.Type == gosnmp.Integer {
|
||||
oidSuffix := result.Name[len(portOID)+1:]
|
||||
portMap[oidSuffix] = result.Value.(int)
|
||||
oidSuffix := strings.TrimPrefix(result.Name[len(portOID):], ".")
|
||||
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)
|
||||
ifNames := t.getInterfaceNames(snmp)
|
||||
|
||||
for _, result := range macResults {
|
||||
if result.Type == gosnmp.OctetString {
|
||||
macBytes := result.Value.([]byte)
|
||||
if len(macBytes) != 6 {
|
||||
continue
|
||||
}
|
||||
for _, entry := range macPorts {
|
||||
mac := entry.mac
|
||||
bridgePort := entry.bridgePort
|
||||
|
||||
mac := net.HardwareAddr(macBytes)
|
||||
if isBroadcastOrZero(mac) {
|
||||
continue
|
||||
}
|
||||
|
||||
oidSuffix := result.Name[len(macOID)+1:]
|
||||
bridgePort, exists := portMap[oidSuffix]
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
|
||||
ifIndex, exists := bridgePortToIfIndex[bridgePort]
|
||||
if !exists {
|
||||
ifIndex = bridgePort
|
||||
@@ -156,11 +189,27 @@ func (t *Tendrils) queryBridgeMIB(snmp *gosnmp.GoSNMP, deviceIP net.IP) {
|
||||
ifName = "??"
|
||||
}
|
||||
|
||||
t.nodes.Update(nil, []net.HardwareAddr{mac}, ifName, "", "snmp")
|
||||
addToParent := false
|
||||
for _, rule := range addToParentRules {
|
||||
if rule.MatchString(ifName) {
|
||||
addToParent = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[snmp] queried bridge mib on %s", deviceIP)
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 = "??"
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -249,7 +296,7 @@ func (t *Tendrils) getBridgePortMapping(snmp *gosnmp.GoSNMP) map[int]int {
|
||||
mapping := make(map[int]int)
|
||||
for _, result := range results {
|
||||
if result.Type == gosnmp.Integer {
|
||||
oidParts := result.Name[len(oid)+1:]
|
||||
oidParts := strings.TrimPrefix(strings.TrimPrefix(result.Name, "."+oid), ".")
|
||||
var bridgePort int
|
||||
_, err := fmt.Sscanf(oidParts, "%d", &bridgePort)
|
||||
if err != nil {
|
||||
@@ -274,7 +321,7 @@ func (t *Tendrils) getInterfaceNames(snmp *gosnmp.GoSNMP) map[int]string {
|
||||
names := make(map[int]string)
|
||||
for _, result := range results {
|
||||
if result.Type == gosnmp.OctetString {
|
||||
oidParts := result.Name[len(oid)+1:]
|
||||
oidParts := strings.TrimPrefix(strings.TrimPrefix(result.Name, "."+oid), ".")
|
||||
var ifIndex int
|
||||
_, err := fmt.Sscanf(oidParts, "%d", &ifIndex)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user