add snmpv3 support
This commit is contained in:
5
go.mod
5
go.mod
@@ -4,4 +4,7 @@ go 1.24.4
|
|||||||
|
|
||||||
require github.com/google/gopacket v1.1.19
|
require github.com/google/gopacket v1.1.19
|
||||||
|
|
||||||
require golang.org/x/sys v0.0.0-20190412213103-97732733099d // indirect
|
require (
|
||||||
|
github.com/gosnmp/gosnmp v1.42.1 // indirect
|
||||||
|
golang.org/x/sys v0.13.0 // indirect
|
||||||
|
)
|
||||||
|
|||||||
5
go.sum
5
go.sum
@@ -1,5 +1,7 @@
|
|||||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||||
|
github.com/gosnmp/gosnmp v1.42.1 h1:MEJxhpC5v1coL3tFRix08PYmky9nyb1TLRRgJAmXm8A=
|
||||||
|
github.com/gosnmp/gosnmp v1.42.1/go.mod h1:CxVS6bXqmWZlafUj9pZUnQX5e4fAltqPcijxWpCitDo=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
@@ -7,10 +9,13 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
|
|||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||||
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|||||||
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
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ func (t *Tendrils) Run() {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
go t.pollARP(ctx)
|
go t.pollARP(ctx)
|
||||||
|
go t.pollSNMP(ctx)
|
||||||
|
|
||||||
ticker := time.NewTicker(1 * time.Second)
|
ticker := time.NewTicker(1 * time.Second)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|||||||
Reference in New Issue
Block a user