add snmpv3 support
This commit is contained in:
289
snmp.go
Normal file
289
snmp.go
Normal file
@@ -0,0 +1,289 @@
|
||||
package tendrils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/gosnmp/gosnmp"
|
||||
)
|
||||
|
||||
type snmpConfig struct {
|
||||
username string
|
||||
authKey string
|
||||
privKey string
|
||||
authProto gosnmp.SnmpV3AuthProtocol
|
||||
privProto gosnmp.SnmpV3PrivProtocol
|
||||
secLevel gosnmp.SnmpV3MsgFlags
|
||||
timeout time.Duration
|
||||
retries int
|
||||
}
|
||||
|
||||
func defaultSNMPConfig() *snmpConfig {
|
||||
return &snmpConfig{
|
||||
username: "tendrils",
|
||||
authKey: "tendrils",
|
||||
privKey: "tendrils",
|
||||
authProto: gosnmp.SHA512,
|
||||
privProto: gosnmp.AES,
|
||||
secLevel: gosnmp.AuthPriv,
|
||||
timeout: 5 * time.Second,
|
||||
retries: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tendrils) connectSNMP(ip net.IP) (*gosnmp.GoSNMP, error) {
|
||||
cfg := defaultSNMPConfig()
|
||||
|
||||
snmp := &gosnmp.GoSNMP{
|
||||
Target: ip.String(),
|
||||
Port: 161,
|
||||
Version: gosnmp.Version3,
|
||||
Timeout: cfg.timeout,
|
||||
Retries: cfg.retries,
|
||||
SecurityModel: gosnmp.UserSecurityModel,
|
||||
MsgFlags: cfg.secLevel,
|
||||
SecurityParameters: &gosnmp.UsmSecurityParameters{
|
||||
UserName: cfg.username,
|
||||
AuthenticationProtocol: cfg.authProto,
|
||||
AuthenticationPassphrase: cfg.authKey,
|
||||
PrivacyProtocol: cfg.privProto,
|
||||
PrivacyPassphrase: cfg.privKey,
|
||||
},
|
||||
}
|
||||
|
||||
err := snmp.Connect()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return snmp, nil
|
||||
}
|
||||
|
||||
func (t *Tendrils) pollSNMP(ctx context.Context) {
|
||||
ticker := time.NewTicker(5 * time.Minute)
|
||||
defer ticker.Stop()
|
||||
|
||||
t.querySwitches()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
t.querySwitches()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tendrils) querySwitches() {
|
||||
nodes := t.nodes.All()
|
||||
|
||||
for _, node := range nodes {
|
||||
for _, ip := range node.IPs {
|
||||
if ip.To4() == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
go t.querySNMPDevice(ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tendrils) querySNMPDevice(ip net.IP) {
|
||||
snmp, err := t.connectSNMP(ip)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer snmp.Conn.Close()
|
||||
|
||||
t.queryBridgeMIB(snmp, ip)
|
||||
t.queryARPTable(snmp, ip)
|
||||
}
|
||||
|
||||
func (t *Tendrils) queryBridgeMIB(snmp *gosnmp.GoSNMP, deviceIP net.IP) {
|
||||
macOID := "1.3.6.1.2.1.17.4.3.1.1"
|
||||
portOID := "1.3.6.1.2.1.17.4.3.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)
|
||||
for _, result := range portResults {
|
||||
if result.Type == gosnmp.Integer {
|
||||
oidSuffix := result.Name[len(portOID)+1:]
|
||||
portMap[oidSuffix] = result.Value.(int)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
ifName := ifNames[ifIndex]
|
||||
if ifName == "" {
|
||||
ifName = "??"
|
||||
}
|
||||
|
||||
t.nodes.Update(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) {
|
||||
macOID := "1.3.6.1.2.1.4.22.1.2"
|
||||
ipOID := "1.3.6.1.2.1.4.22.1.3"
|
||||
ifIndexOID := "1.3.6.1.2.1.4.22.1.1"
|
||||
|
||||
macResults, err := snmp.BulkWalkAll(macOID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ipResults, err := snmp.BulkWalkAll(ipOID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ifIndexResults, err := snmp.BulkWalkAll(ifIndexOID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ipMap := make(map[string]net.IP)
|
||||
for _, result := range ipResults {
|
||||
if result.Type == gosnmp.IPAddress {
|
||||
oidSuffix := result.Name[len(ipOID)+1:]
|
||||
ipBytes := result.Value.([]byte)
|
||||
ipMap[oidSuffix] = net.IP(ipBytes)
|
||||
}
|
||||
}
|
||||
|
||||
ifIndexMap := make(map[string]int)
|
||||
for _, result := range ifIndexResults {
|
||||
if result.Type == gosnmp.Integer {
|
||||
oidSuffix := result.Name[len(ifIndexOID)+1:]
|
||||
ifIndexMap[oidSuffix] = result.Value.(int)
|
||||
}
|
||||
}
|
||||
|
||||
ifNames := t.getInterfaceNames(snmp)
|
||||
|
||||
for _, result := range macResults {
|
||||
if result.Type == gosnmp.OctetString {
|
||||
macBytes := result.Value.([]byte)
|
||||
if len(macBytes) != 6 {
|
||||
continue
|
||||
}
|
||||
|
||||
mac := net.HardwareAddr(macBytes)
|
||||
if isBroadcastOrZero(mac) {
|
||||
continue
|
||||
}
|
||||
|
||||
oidSuffix := result.Name[len(macOID)+1:]
|
||||
ip, hasIP := ipMap[oidSuffix]
|
||||
ifIndex, hasIfIndex := ifIndexMap[oidSuffix]
|
||||
|
||||
var ips []net.IP
|
||||
if hasIP {
|
||||
ips = []net.IP{ip}
|
||||
}
|
||||
|
||||
ifName := ""
|
||||
if hasIfIndex {
|
||||
ifName = ifNames[ifIndex]
|
||||
}
|
||||
if ifName == "" {
|
||||
ifName = "??"
|
||||
}
|
||||
|
||||
t.nodes.Update(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 {
|
||||
oid := "1.3.6.1.2.1.17.1.4.1.2"
|
||||
|
||||
results, err := snmp.BulkWalkAll(oid)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
mapping := make(map[int]int)
|
||||
for _, result := range results {
|
||||
if result.Type == gosnmp.Integer {
|
||||
oidParts := result.Name[len(oid)+1:]
|
||||
var bridgePort int
|
||||
_, err := fmt.Sscanf(oidParts, "%d", &bridgePort)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ifIndex := result.Value.(int)
|
||||
mapping[bridgePort] = ifIndex
|
||||
}
|
||||
}
|
||||
|
||||
return mapping
|
||||
}
|
||||
|
||||
func (t *Tendrils) getInterfaceNames(snmp *gosnmp.GoSNMP) map[int]string {
|
||||
oid := "1.3.6.1.2.1.2.2.1.2"
|
||||
|
||||
results, err := snmp.BulkWalkAll(oid)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
names := make(map[int]string)
|
||||
for _, result := range results {
|
||||
if result.Type == gosnmp.OctetString {
|
||||
oidParts := result.Name[len(oid)+1:]
|
||||
var ifIndex int
|
||||
_, err := fmt.Sscanf(oidParts, "%d", &ifIndex)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
name := string(result.Value.([]byte))
|
||||
names[ifIndex] = name
|
||||
}
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
Reference in New Issue
Block a user