486 lines
15 KiB
Go
486 lines
15 KiB
Go
// Copyright 2025, IPng Networks GmbH, Pim van Pelt <pim@ipng.ch>
|
|
|
|
package ifmib
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/posteo/go-agentx"
|
|
"github.com/posteo/go-agentx/pdu"
|
|
"github.com/posteo/go-agentx/value"
|
|
"go.fd.io/govpp/api"
|
|
"gopkg.in/yaml.v3"
|
|
|
|
"govpp-snmp-agentx/logger"
|
|
"govpp-snmp-agentx/vpp"
|
|
)
|
|
|
|
// IF-MIB OID bases
|
|
const ifEntryOID = "1.3.6.1.2.1.2.2.1"
|
|
const ifXTableOID = "1.3.6.1.2.1.31.1.1.1"
|
|
|
|
// VPP Config structures
|
|
type VPPConfig struct {
|
|
Interfaces map[string]VPPInterface `yaml:"interfaces"`
|
|
Loopbacks map[string]VPPInterface `yaml:"loopbacks"`
|
|
}
|
|
|
|
type VPPInterface struct {
|
|
Description string `yaml:"description"`
|
|
SubInterfaces map[string]VPPInterface `yaml:"sub-interfaces"`
|
|
}
|
|
|
|
type InterfaceMIB struct {
|
|
mutex sync.RWMutex
|
|
client *agentx.Client
|
|
ifEntrySession *agentx.Session
|
|
ifXTableSession *agentx.Session
|
|
|
|
// Simple approach: track interface set and rebuild when it changes
|
|
currentInterfaces map[uint32]bool
|
|
descriptions map[string]string
|
|
interfaceDetails map[uint32]*vpp.InterfaceDetails
|
|
|
|
// Counter items - direct references for fast updates
|
|
counterItems map[string]*agentx.ListItem
|
|
}
|
|
|
|
func NewInterfaceMIB() *InterfaceMIB {
|
|
return &InterfaceMIB{
|
|
currentInterfaces: make(map[uint32]bool),
|
|
descriptions: make(map[string]string),
|
|
interfaceDetails: make(map[uint32]*vpp.InterfaceDetails),
|
|
counterItems: make(map[string]*agentx.ListItem),
|
|
}
|
|
}
|
|
|
|
func (m *InterfaceMIB) GetHandler() *agentx.ListHandler {
|
|
// Always create a new handler - this is the key simplification
|
|
return &agentx.ListHandler{}
|
|
}
|
|
|
|
func (m *InterfaceMIB) LoadVPPConfig(configPath string) error {
|
|
m.mutex.Lock()
|
|
defer m.mutex.Unlock()
|
|
|
|
data, err := os.ReadFile(configPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read VPP config file: %v", err)
|
|
}
|
|
|
|
var config VPPConfig
|
|
if err := yaml.Unmarshal(data, &config); err != nil {
|
|
return fmt.Errorf("failed to parse VPP config YAML: %v", err)
|
|
}
|
|
|
|
// Extract interface descriptions
|
|
for ifName, ifConfig := range config.Interfaces {
|
|
if ifConfig.Description != "" {
|
|
m.descriptions[ifName] = ifConfig.Description
|
|
}
|
|
for subID, subConfig := range ifConfig.SubInterfaces {
|
|
if subConfig.Description != "" {
|
|
subIfName := fmt.Sprintf("%s.%s", ifName, subID)
|
|
m.descriptions[subIfName] = subConfig.Description
|
|
}
|
|
}
|
|
}
|
|
|
|
for ifName, ifConfig := range config.Loopbacks {
|
|
if ifConfig.Description != "" {
|
|
m.descriptions[ifName] = ifConfig.Description
|
|
}
|
|
}
|
|
|
|
logger.Printf("Loaded %d interface descriptions from VPP config", len(m.descriptions))
|
|
return nil
|
|
}
|
|
|
|
func (m *InterfaceMIB) UpdateInterfaceDetails(details []vpp.InterfaceDetails) {
|
|
m.mutex.Lock()
|
|
defer m.mutex.Unlock()
|
|
|
|
for _, detail := range details {
|
|
m.interfaceDetails[uint32(detail.SwIfIndex)] = &detail
|
|
}
|
|
logger.Debugf("Updated interface details for %d interfaces", len(details))
|
|
}
|
|
|
|
func (m *InterfaceMIB) UpdateStats(interfaceStats *api.InterfaceStats) {
|
|
m.mutex.Lock()
|
|
defer m.mutex.Unlock()
|
|
|
|
// Check if interface set changed
|
|
newInterfaces := make(map[uint32]bool)
|
|
for _, iface := range interfaceStats.Interfaces {
|
|
newInterfaces[iface.InterfaceIndex] = true
|
|
}
|
|
|
|
// Simple comparison - rebuild if different
|
|
needsRebuild := len(newInterfaces) != len(m.currentInterfaces)
|
|
if !needsRebuild {
|
|
for idx := range newInterfaces {
|
|
if !m.currentInterfaces[idx] {
|
|
needsRebuild = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if needsRebuild {
|
|
logger.Debugf("Interface set changed, rebuilding MIB")
|
|
if err := m.rebuildMIB(interfaceStats.Interfaces); err != nil {
|
|
logger.Printf("Failed to rebuild MIB: %v", err)
|
|
return
|
|
}
|
|
m.currentInterfaces = newInterfaces
|
|
} else {
|
|
// Fast path: just update counters
|
|
logger.Debugf("Updating counters for %d interfaces", len(interfaceStats.Interfaces))
|
|
for _, iface := range interfaceStats.Interfaces {
|
|
m.updateCounterValues(&iface)
|
|
}
|
|
}
|
|
|
|
logger.Printf("Updated IF-MIB data for %d interfaces", len(interfaceStats.Interfaces))
|
|
}
|
|
|
|
func (m *InterfaceMIB) rebuildMIB(interfaces []api.InterfaceCounters) error {
|
|
// If no client is available (e.g., during testing), just track interfaces
|
|
if m.client == nil {
|
|
logger.Debugf("No AgentX client available, only tracking interface set")
|
|
return nil
|
|
}
|
|
|
|
// Close old sessions
|
|
if m.ifEntrySession != nil {
|
|
m.ifEntrySession.Close()
|
|
m.ifEntrySession = nil
|
|
}
|
|
if m.ifXTableSession != nil {
|
|
m.ifXTableSession.Close()
|
|
m.ifXTableSession = nil
|
|
}
|
|
|
|
// Create fresh handler
|
|
handler := &agentx.ListHandler{}
|
|
m.counterItems = make(map[string]*agentx.ListItem)
|
|
|
|
// Build all MIB entries
|
|
for _, iface := range interfaces {
|
|
m.addInterfaceToHandler(handler, &iface)
|
|
}
|
|
|
|
// Register new sessions
|
|
ifEntrySession, err := m.client.Session(value.MustParseOID(ifEntryOID), "ifEntry", handler)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create ifEntry session: %v", err)
|
|
}
|
|
|
|
err = ifEntrySession.Register(127, value.MustParseOID(ifEntryOID))
|
|
if err != nil {
|
|
ifEntrySession.Close()
|
|
return fmt.Errorf("failed to register ifEntry: %v", err)
|
|
}
|
|
|
|
ifXTableSession, err := m.client.Session(value.MustParseOID(ifXTableOID), "ifXTable", handler)
|
|
if err != nil {
|
|
ifEntrySession.Close()
|
|
return fmt.Errorf("failed to create ifXTable session: %v", err)
|
|
}
|
|
|
|
err = ifXTableSession.Register(127, value.MustParseOID(ifXTableOID))
|
|
if err != nil {
|
|
ifEntrySession.Close()
|
|
ifXTableSession.Close()
|
|
return fmt.Errorf("failed to register ifXTable: %v", err)
|
|
}
|
|
|
|
m.ifEntrySession = ifEntrySession
|
|
m.ifXTableSession = ifXTableSession
|
|
|
|
logger.Debugf("Successfully rebuilt MIB with %d interfaces", len(interfaces))
|
|
return nil
|
|
}
|
|
|
|
func (m *InterfaceMIB) addInterfaceToHandler(handler *agentx.ListHandler, iface *api.InterfaceCounters) {
|
|
idx := int(iface.InterfaceIndex) + *vpp.IfIndexOffset
|
|
details := m.interfaceDetails[iface.InterfaceIndex]
|
|
|
|
// Add static fields (these don't change frequently)
|
|
m.addStaticFields(handler, iface, idx, details)
|
|
|
|
// Add counter fields and store references for fast updates
|
|
m.addCounterFields(handler, iface, idx)
|
|
|
|
logger.Debugf("Added interface %d (%s) to IF-MIB with SNMP index %d", iface.InterfaceIndex, iface.InterfaceName, idx)
|
|
}
|
|
|
|
func (m *InterfaceMIB) addStaticFields(handler *agentx.ListHandler, iface *api.InterfaceCounters, idx int, details *vpp.InterfaceDetails) {
|
|
var item *agentx.ListItem
|
|
|
|
// ifIndex (.1)
|
|
item = handler.Add(fmt.Sprintf("%s.1.%d", ifEntryOID, idx))
|
|
item.Type = pdu.VariableTypeInteger
|
|
item.Value = int32(idx)
|
|
|
|
// ifDescr (.2)
|
|
item = handler.Add(fmt.Sprintf("%s.2.%d", ifEntryOID, idx))
|
|
item.Type = pdu.VariableTypeOctetString
|
|
item.Value = iface.InterfaceName
|
|
|
|
// ifType (.3) - Using ethernetCsmacd(6) as default
|
|
item = handler.Add(fmt.Sprintf("%s.3.%d", ifEntryOID, idx))
|
|
item.Type = pdu.VariableTypeInteger
|
|
item.Value = int32(6)
|
|
|
|
// ifMtu (.4)
|
|
mtu := int32(1500)
|
|
if details != nil {
|
|
mtu = int32(details.MTU)
|
|
}
|
|
item = handler.Add(fmt.Sprintf("%s.4.%d", ifEntryOID, idx))
|
|
item.Type = pdu.VariableTypeInteger
|
|
item.Value = mtu
|
|
|
|
// ifSpeed (.5) - Only for speeds <= 2.5Gbps
|
|
if details != nil && details.Speed > 0 && details.Speed <= 2500000000 {
|
|
item = handler.Add(fmt.Sprintf("%s.5.%d", ifEntryOID, idx))
|
|
item.Type = pdu.VariableTypeGauge32
|
|
item.Value = uint32(details.Speed)
|
|
} else if details == nil || details.Speed == 0 {
|
|
item = handler.Add(fmt.Sprintf("%s.5.%d", ifEntryOID, idx))
|
|
item.Type = pdu.VariableTypeGauge32
|
|
item.Value = uint32(1000000000)
|
|
}
|
|
|
|
// ifPhysAddress (.6)
|
|
macAddr := ""
|
|
if details != nil && len(details.MacAddress) > 0 {
|
|
macAddr = string(details.MacAddress)
|
|
}
|
|
item = handler.Add(fmt.Sprintf("%s.6.%d", ifEntryOID, idx))
|
|
item.Type = pdu.VariableTypeOctetString
|
|
item.Value = macAddr
|
|
|
|
// ifAdminStatus (.7)
|
|
adminStatus := int32(1)
|
|
if details != nil && !details.AdminStatus {
|
|
adminStatus = 2
|
|
}
|
|
item = handler.Add(fmt.Sprintf("%s.7.%d", ifEntryOID, idx))
|
|
item.Type = pdu.VariableTypeInteger
|
|
item.Value = adminStatus
|
|
|
|
// ifOperStatus (.8)
|
|
operStatus := int32(1)
|
|
if details != nil && !details.OperStatus {
|
|
operStatus = 2
|
|
}
|
|
item = handler.Add(fmt.Sprintf("%s.8.%d", ifEntryOID, idx))
|
|
item.Type = pdu.VariableTypeInteger
|
|
item.Value = operStatus
|
|
|
|
// ifLastChange (.9)
|
|
item = handler.Add(fmt.Sprintf("%s.9.%d", ifEntryOID, idx))
|
|
item.Type = pdu.VariableTypeTimeTicks
|
|
item.Value = 0 * time.Second
|
|
|
|
// ifInUnknownProtos (.15)
|
|
item = handler.Add(fmt.Sprintf("%s.15.%d", ifEntryOID, idx))
|
|
item.Type = pdu.VariableTypeCounter32
|
|
item.Value = uint32(0)
|
|
|
|
// ifOutDiscards (.19)
|
|
item = handler.Add(fmt.Sprintf("%s.19.%d", ifEntryOID, idx))
|
|
item.Type = pdu.VariableTypeCounter32
|
|
item.Value = uint32(0)
|
|
|
|
// ifOutQLen (.21)
|
|
item = handler.Add(fmt.Sprintf("%s.21.%d", ifEntryOID, idx))
|
|
item.Type = pdu.VariableTypeGauge32
|
|
item.Value = uint32(0)
|
|
|
|
// ifXTable static fields
|
|
// ifName (.1)
|
|
item = handler.Add(fmt.Sprintf("%s.1.%d", ifXTableOID, idx))
|
|
item.Type = pdu.VariableTypeOctetString
|
|
item.Value = iface.InterfaceName
|
|
|
|
// ifHighSpeed (.15)
|
|
speedMbps := uint32(1000)
|
|
if details != nil && details.Speed > 0 {
|
|
speedMbps = uint32(details.Speed / 1000000)
|
|
}
|
|
item = handler.Add(fmt.Sprintf("%s.15.%d", ifXTableOID, idx))
|
|
item.Type = pdu.VariableTypeGauge32
|
|
item.Value = speedMbps
|
|
|
|
// ifAlias (.18)
|
|
item = handler.Add(fmt.Sprintf("%s.18.%d", ifXTableOID, idx))
|
|
item.Type = pdu.VariableTypeOctetString
|
|
if desc, exists := m.descriptions[iface.InterfaceName]; exists {
|
|
item.Value = desc
|
|
} else {
|
|
item.Value = iface.InterfaceName
|
|
}
|
|
}
|
|
|
|
func (m *InterfaceMIB) addCounterFields(handler *agentx.ListHandler, iface *api.InterfaceCounters, idx int) {
|
|
ifIdx := iface.InterfaceIndex
|
|
|
|
// ifEntry counters
|
|
counters := []struct {
|
|
oid string
|
|
key string
|
|
typ pdu.VariableType
|
|
}{
|
|
{fmt.Sprintf("%s.10.%d", ifEntryOID, idx), "ifInOctets", pdu.VariableTypeCounter32},
|
|
{fmt.Sprintf("%s.11.%d", ifEntryOID, idx), "ifInUcastPkts", pdu.VariableTypeCounter32},
|
|
{fmt.Sprintf("%s.12.%d", ifEntryOID, idx), "ifInNUcastPkts", pdu.VariableTypeCounter32},
|
|
{fmt.Sprintf("%s.13.%d", ifEntryOID, idx), "ifInDiscards", pdu.VariableTypeCounter32},
|
|
{fmt.Sprintf("%s.14.%d", ifEntryOID, idx), "ifInErrors", pdu.VariableTypeCounter32},
|
|
{fmt.Sprintf("%s.16.%d", ifEntryOID, idx), "ifOutOctets", pdu.VariableTypeCounter32},
|
|
{fmt.Sprintf("%s.17.%d", ifEntryOID, idx), "ifOutUcastPkts", pdu.VariableTypeCounter32},
|
|
{fmt.Sprintf("%s.18.%d", ifEntryOID, idx), "ifOutNUcastPkts", pdu.VariableTypeCounter32},
|
|
{fmt.Sprintf("%s.20.%d", ifEntryOID, idx), "ifOutErrors", pdu.VariableTypeCounter32},
|
|
|
|
// ifXTable counters
|
|
{fmt.Sprintf("%s.2.%d", ifXTableOID, idx), "ifInMulticastPkts", pdu.VariableTypeCounter32},
|
|
{fmt.Sprintf("%s.3.%d", ifXTableOID, idx), "ifInBroadcastPkts", pdu.VariableTypeCounter32},
|
|
{fmt.Sprintf("%s.4.%d", ifXTableOID, idx), "ifOutMulticastPkts", pdu.VariableTypeCounter32},
|
|
{fmt.Sprintf("%s.5.%d", ifXTableOID, idx), "ifOutBroadcastPkts", pdu.VariableTypeCounter32},
|
|
{fmt.Sprintf("%s.6.%d", ifXTableOID, idx), "ifHCInOctets", pdu.VariableTypeCounter64},
|
|
{fmt.Sprintf("%s.7.%d", ifXTableOID, idx), "ifHCInUcastPkts", pdu.VariableTypeCounter64},
|
|
{fmt.Sprintf("%s.8.%d", ifXTableOID, idx), "ifHCInMulticastPkts", pdu.VariableTypeCounter64},
|
|
{fmt.Sprintf("%s.9.%d", ifXTableOID, idx), "ifHCInBroadcastPkts", pdu.VariableTypeCounter64},
|
|
{fmt.Sprintf("%s.10.%d", ifXTableOID, idx), "ifHCOutOctets", pdu.VariableTypeCounter64},
|
|
{fmt.Sprintf("%s.11.%d", ifXTableOID, idx), "ifHCOutUcastPkts", pdu.VariableTypeCounter64},
|
|
{fmt.Sprintf("%s.12.%d", ifXTableOID, idx), "ifHCOutMulticastPkts", pdu.VariableTypeCounter64},
|
|
{fmt.Sprintf("%s.13.%d", ifXTableOID, idx), "ifHCOutBroadcastPkts", pdu.VariableTypeCounter64},
|
|
}
|
|
|
|
for _, counter := range counters {
|
|
item := handler.Add(counter.oid)
|
|
item.Type = counter.typ
|
|
m.counterItems[fmt.Sprintf("%d_%s", ifIdx, counter.key)] = item
|
|
}
|
|
|
|
// Set initial values
|
|
m.updateCounterValues(iface)
|
|
}
|
|
|
|
func (m *InterfaceMIB) updateCounterValues(iface *api.InterfaceCounters) {
|
|
ifIdx := iface.InterfaceIndex
|
|
|
|
// ifEntry counters
|
|
if item := m.counterItems[fmt.Sprintf("%d_ifInOctets", ifIdx)]; item != nil {
|
|
item.Value = uint32(iface.Rx.Bytes)
|
|
}
|
|
if item := m.counterItems[fmt.Sprintf("%d_ifInUcastPkts", ifIdx)]; item != nil {
|
|
if iface.RxUnicast.Packets == 0 {
|
|
item.Value = uint32(iface.Rx.Packets)
|
|
} else {
|
|
item.Value = uint32(iface.RxUnicast.Packets)
|
|
}
|
|
}
|
|
if item := m.counterItems[fmt.Sprintf("%d_ifInNUcastPkts", ifIdx)]; item != nil {
|
|
item.Value = uint32(iface.RxMulticast.Packets + iface.RxBroadcast.Packets)
|
|
}
|
|
if item := m.counterItems[fmt.Sprintf("%d_ifInDiscards", ifIdx)]; item != nil {
|
|
item.Value = uint32(iface.Drops)
|
|
}
|
|
if item := m.counterItems[fmt.Sprintf("%d_ifInErrors", ifIdx)]; item != nil {
|
|
item.Value = uint32(iface.RxErrors)
|
|
}
|
|
if item := m.counterItems[fmt.Sprintf("%d_ifOutOctets", ifIdx)]; item != nil {
|
|
item.Value = uint32(iface.Tx.Bytes)
|
|
}
|
|
if item := m.counterItems[fmt.Sprintf("%d_ifOutUcastPkts", ifIdx)]; item != nil {
|
|
if iface.TxUnicast.Packets == 0 {
|
|
item.Value = uint32(iface.Tx.Packets)
|
|
} else {
|
|
item.Value = uint32(iface.TxUnicast.Packets)
|
|
}
|
|
}
|
|
if item := m.counterItems[fmt.Sprintf("%d_ifOutNUcastPkts", ifIdx)]; item != nil {
|
|
item.Value = uint32(iface.TxMulticast.Packets + iface.TxBroadcast.Packets)
|
|
}
|
|
if item := m.counterItems[fmt.Sprintf("%d_ifOutErrors", ifIdx)]; item != nil {
|
|
item.Value = uint32(iface.TxErrors)
|
|
}
|
|
|
|
// ifXTable counters
|
|
if item := m.counterItems[fmt.Sprintf("%d_ifInMulticastPkts", ifIdx)]; item != nil {
|
|
item.Value = uint32(iface.RxMulticast.Packets)
|
|
}
|
|
if item := m.counterItems[fmt.Sprintf("%d_ifInBroadcastPkts", ifIdx)]; item != nil {
|
|
item.Value = uint32(iface.RxBroadcast.Packets)
|
|
}
|
|
if item := m.counterItems[fmt.Sprintf("%d_ifOutMulticastPkts", ifIdx)]; item != nil {
|
|
item.Value = uint32(iface.TxMulticast.Packets)
|
|
}
|
|
if item := m.counterItems[fmt.Sprintf("%d_ifOutBroadcastPkts", ifIdx)]; item != nil {
|
|
item.Value = uint32(iface.TxBroadcast.Packets)
|
|
}
|
|
if item := m.counterItems[fmt.Sprintf("%d_ifHCInOctets", ifIdx)]; item != nil {
|
|
item.Value = iface.Rx.Bytes
|
|
}
|
|
if item := m.counterItems[fmt.Sprintf("%d_ifHCInUcastPkts", ifIdx)]; item != nil {
|
|
if iface.RxUnicast.Packets == 0 {
|
|
item.Value = iface.Rx.Packets
|
|
} else {
|
|
item.Value = iface.RxUnicast.Packets
|
|
}
|
|
}
|
|
if item := m.counterItems[fmt.Sprintf("%d_ifHCInMulticastPkts", ifIdx)]; item != nil {
|
|
item.Value = iface.RxMulticast.Packets
|
|
}
|
|
if item := m.counterItems[fmt.Sprintf("%d_ifHCInBroadcastPkts", ifIdx)]; item != nil {
|
|
item.Value = iface.RxBroadcast.Packets
|
|
}
|
|
if item := m.counterItems[fmt.Sprintf("%d_ifHCOutOctets", ifIdx)]; item != nil {
|
|
item.Value = iface.Tx.Bytes
|
|
}
|
|
if item := m.counterItems[fmt.Sprintf("%d_ifHCOutUcastPkts", ifIdx)]; item != nil {
|
|
if iface.TxUnicast.Packets == 0 {
|
|
item.Value = iface.Tx.Packets
|
|
} else {
|
|
item.Value = iface.TxUnicast.Packets
|
|
}
|
|
}
|
|
if item := m.counterItems[fmt.Sprintf("%d_ifHCOutMulticastPkts", ifIdx)]; item != nil {
|
|
item.Value = iface.TxMulticast.Packets
|
|
}
|
|
if item := m.counterItems[fmt.Sprintf("%d_ifHCOutBroadcastPkts", ifIdx)]; item != nil {
|
|
item.Value = iface.TxBroadcast.Packets
|
|
}
|
|
}
|
|
|
|
func (m *InterfaceMIB) RegisterWithClient(client *agentx.Client) error {
|
|
m.mutex.Lock()
|
|
defer m.mutex.Unlock()
|
|
|
|
m.client = client
|
|
// Don't register anything yet - wait for first UpdateStats call
|
|
logger.Debugf("Stored AgentX client reference")
|
|
return nil
|
|
}
|
|
|
|
func (m *InterfaceMIB) Close() {
|
|
m.mutex.Lock()
|
|
defer m.mutex.Unlock()
|
|
|
|
if m.ifEntrySession != nil {
|
|
m.ifEntrySession.Close()
|
|
m.ifEntrySession = nil
|
|
}
|
|
if m.ifXTableSession != nil {
|
|
m.ifXTableSession.Close()
|
|
m.ifXTableSession = nil
|
|
}
|
|
}
|