// Copyright 2025, IPng Networks GmbH, Pim van Pelt 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/vppstats" ) // IF-MIB OID bases: // ifEntry (classic): 1.3.6.1.2.1.2.2.1 // ifXTable (extended): 1.3.6.1.2.1.31.1.1.1 // ifEntry (1.3.6.1.2.1.2.2.1) - Classic Interface Table: // ifIndex .1 - Integer32 // ifDescr .2 - DisplayString // ifType .3 - IANAifType // ifMtu .4 - Integer32 // ifSpeed .5 - Gauge32 // ifPhysAddress .6 - PhysAddress // ifAdminStatus .7 - INTEGER // ifOperStatus .8 - INTEGER // ifLastChange .9 - TimeTicks // ifInOctets .10 - Counter32 // ifInUcastPkts .11 - Counter32 // ifInNUcastPkts .12 - Counter32 // ifInDiscards .13 - Counter32 // ifInErrors .14 - Counter32 // ifInUnknownProtos .15 - Counter32 // ifOutOctets .16 - Counter32 // ifOutUcastPkts .17 - Counter32 // ifOutNUcastPkts .18 - Counter32 // ifOutDiscards .19 - Counter32 // ifOutErrors .20 - Counter32 // ifOutQLen .21 - Gauge32 // ifSpecific .22 - OBJECT IDENTIFIER // ifXTable (1.3.6.1.2.1.31.1.1.1) - Extended Interface Table: // ifName .1 - DisplayString // ifInMulticastPkts .2 - Counter32 // ifInBroadcastPkts .3 - Counter32 // ifOutMulticastPkts .4 - Counter32 // ifOutBroadcastPkts .5 - Counter32 // ifHCInOctets .6 - Counter64 // ifHCInUcastPkts .7 - Counter64 // ifHCInMulticastPkts .8 - Counter64 // ifHCInBroadcastPkts .9 - Counter64 // ifHCOutOctets .10 - Counter64 // ifHCOutUcastPkts .11 - Counter64 // ifHCOutMulticastPkts .12 - Counter64 // ifHCOutBroadcastPkts .13 - Counter64 // ifAlias .18 - DisplayString 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 YAML 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 handler *agentx.ListHandler ifEntrySession *agentx.Session ifXTableSession *agentx.Session stats map[uint32]*api.InterfaceCounters // indexed by interface index descriptions map[string]string // interface name -> description mapping } func NewInterfaceMIB() *InterfaceMIB { return &InterfaceMIB{ handler: &agentx.ListHandler{}, stats: make(map[uint32]*api.InterfaceCounters), descriptions: make(map[string]string), } } func (m *InterfaceMIB) GetHandler() *agentx.ListHandler { return m.handler } func (m *InterfaceMIB) LoadVPPConfig(configPath string) error { m.mutex.Lock() defer m.mutex.Unlock() // Read YAML file data, err := os.ReadFile(configPath) if err != nil { return fmt.Errorf("failed to read VPP config file: %v", err) } // Parse YAML 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 logger.Debugf("Loaded description for interface %s: %s", ifName, ifConfig.Description) } // Process sub-interfaces for subID, subConfig := range ifConfig.SubInterfaces { if subConfig.Description != "" { subIfName := fmt.Sprintf("%s.%s", ifName, subID) m.descriptions[subIfName] = subConfig.Description logger.Debugf("Loaded description for sub-interface %s: %s", subIfName, subConfig.Description) } } } // Extract loopback descriptions for ifName, ifConfig := range config.Loopbacks { if ifConfig.Description != "" { m.descriptions[ifName] = ifConfig.Description logger.Debugf("Loaded description for loopback %s: %s", ifName, ifConfig.Description) } } logger.Printf("Loaded %d interface descriptions from VPP config", len(m.descriptions)) return nil } func (m *InterfaceMIB) UpdateStats(interfaceStats *api.InterfaceStats) { m.mutex.Lock() defer m.mutex.Unlock() logger.Debugf("Updating IF-MIB with %d interfaces", len(interfaceStats.Interfaces)) // Clear existing entries m.handler = &agentx.ListHandler{} m.stats = make(map[uint32]*api.InterfaceCounters) // Add new entries for _, iface := range interfaceStats.Interfaces { logger.Debugf("Processing interface %d (%s)", iface.InterfaceIndex, iface.InterfaceName) m.stats[iface.InterfaceIndex] = &iface m.addInterfaceToMIB(&iface) } // Update both sessions with the new handler if m.ifEntrySession != nil { m.ifEntrySession.Handler = m.handler } if m.ifXTableSession != nil { m.ifXTableSession.Handler = m.handler logger.Printf("Updated session handlers with new IF-MIB data") } logger.Debugf("IF-MIB now contains %d interfaces", len(m.stats)) } func (m *InterfaceMIB) addInterfaceToMIB(iface *api.InterfaceCounters) { idx := int(iface.InterfaceIndex) + *vppstats.IfIndexOffset // Add ifEntry (classic interface table) entries m.addIfEntry(iface, idx) // Add ifXTable (extended interface table) entries m.addIfXTable(iface, idx) logger.Debugf("Added interface %d (%s) to IF-MIB with SNMP index %d", iface.InterfaceIndex, iface.InterfaceName, idx) } func (m *InterfaceMIB) addIfEntry(iface *api.InterfaceCounters, idx int) { var item *agentx.ListItem // ifIndex (.1) item = m.handler.Add(fmt.Sprintf("%s.1.%d", ifEntryOID, idx)) item.Type = pdu.VariableTypeInteger item.Value = int32(idx) // ifDescr (.2) item = m.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 = m.handler.Add(fmt.Sprintf("%s.3.%d", ifEntryOID, idx)) item.Type = pdu.VariableTypeInteger item.Value = int32(6) // ifMtu (.4) - Default MTU 1500 item = m.handler.Add(fmt.Sprintf("%s.4.%d", ifEntryOID, idx)) item.Type = pdu.VariableTypeInteger item.Value = int32(1500) // ifSpeed (.5) - Default to 1Gbps (1000000000 bits/sec) item = m.handler.Add(fmt.Sprintf("%s.5.%d", ifEntryOID, idx)) item.Type = pdu.VariableTypeGauge32 item.Value = uint32(1000000000) // ifPhysAddress (.6) - Empty for now item = m.handler.Add(fmt.Sprintf("%s.6.%d", ifEntryOID, idx)) item.Type = pdu.VariableTypeOctetString item.Value = "" // ifAdminStatus (.7) - up(1) item = m.handler.Add(fmt.Sprintf("%s.7.%d", ifEntryOID, idx)) item.Type = pdu.VariableTypeInteger item.Value = int32(1) // ifOperStatus (.8) - up(1) item = m.handler.Add(fmt.Sprintf("%s.8.%d", ifEntryOID, idx)) item.Type = pdu.VariableTypeInteger item.Value = int32(1) // ifLastChange (.9) - 0 (unknown) item = m.handler.Add(fmt.Sprintf("%s.9.%d", ifEntryOID, idx)) item.Type = pdu.VariableTypeTimeTicks item.Value = 0 * time.Second // ifInOctets (.10) item = m.handler.Add(fmt.Sprintf("%s.10.%d", ifEntryOID, idx)) item.Type = pdu.VariableTypeCounter32 item.Value = uint32(iface.Rx.Bytes) // ifInUcastPkts (.11) item = m.handler.Add(fmt.Sprintf("%s.11.%d", ifEntryOID, idx)) item.Type = pdu.VariableTypeCounter32 item.Value = uint32(iface.RxUnicast.Packets) // ifInNUcastPkts (.12) - multicast + broadcast item = m.handler.Add(fmt.Sprintf("%s.12.%d", ifEntryOID, idx)) item.Type = pdu.VariableTypeCounter32 item.Value = uint32(iface.RxMulticast.Packets + iface.RxBroadcast.Packets) // ifInDiscards (.13) - using drops item = m.handler.Add(fmt.Sprintf("%s.13.%d", ifEntryOID, idx)) item.Type = pdu.VariableTypeCounter32 item.Value = uint32(iface.Drops) // ifInErrors (.14) item = m.handler.Add(fmt.Sprintf("%s.14.%d", ifEntryOID, idx)) item.Type = pdu.VariableTypeCounter32 item.Value = uint32(iface.RxErrors) // ifInUnknownProtos (.15) - 0 (not available) item = m.handler.Add(fmt.Sprintf("%s.15.%d", ifEntryOID, idx)) item.Type = pdu.VariableTypeCounter32 item.Value = uint32(0) // ifOutOctets (.16) item = m.handler.Add(fmt.Sprintf("%s.16.%d", ifEntryOID, idx)) item.Type = pdu.VariableTypeCounter32 item.Value = uint32(iface.Tx.Bytes) // ifOutUcastPkts (.17) item = m.handler.Add(fmt.Sprintf("%s.17.%d", ifEntryOID, idx)) item.Type = pdu.VariableTypeCounter32 item.Value = uint32(iface.TxUnicast.Packets) // ifOutNUcastPkts (.18) - multicast + broadcast item = m.handler.Add(fmt.Sprintf("%s.18.%d", ifEntryOID, idx)) item.Type = pdu.VariableTypeCounter32 item.Value = uint32(iface.TxMulticast.Packets + iface.TxBroadcast.Packets) // ifOutDiscards (.19) - 0 (not available) item = m.handler.Add(fmt.Sprintf("%s.19.%d", ifEntryOID, idx)) item.Type = pdu.VariableTypeCounter32 item.Value = uint32(0) // ifOutErrors (.20) item = m.handler.Add(fmt.Sprintf("%s.20.%d", ifEntryOID, idx)) item.Type = pdu.VariableTypeCounter32 item.Value = uint32(iface.TxErrors) // ifOutQLen (.21) - 0 (not available) item = m.handler.Add(fmt.Sprintf("%s.21.%d", ifEntryOID, idx)) item.Type = pdu.VariableTypeGauge32 item.Value = uint32(0) // ifSpecific (.22) - Skip this field as it's optional and causing issues } func (m *InterfaceMIB) addIfXTable(iface *api.InterfaceCounters, idx int) { var item *agentx.ListItem // ifName (.1) item = m.handler.Add(fmt.Sprintf("%s.1.%d", ifXTableOID, idx)) item.Type = pdu.VariableTypeOctetString item.Value = iface.InterfaceName // ifInMulticastPkts (.2) item = m.handler.Add(fmt.Sprintf("%s.2.%d", ifXTableOID, idx)) item.Type = pdu.VariableTypeCounter32 item.Value = uint32(iface.RxMulticast.Packets) // ifInBroadcastPkts (.3) item = m.handler.Add(fmt.Sprintf("%s.3.%d", ifXTableOID, idx)) item.Type = pdu.VariableTypeCounter32 item.Value = uint32(iface.RxBroadcast.Packets) // ifOutMulticastPkts (.4) item = m.handler.Add(fmt.Sprintf("%s.4.%d", ifXTableOID, idx)) item.Type = pdu.VariableTypeCounter32 item.Value = uint32(iface.TxMulticast.Packets) // ifOutBroadcastPkts (.5) item = m.handler.Add(fmt.Sprintf("%s.5.%d", ifXTableOID, idx)) item.Type = pdu.VariableTypeCounter32 item.Value = uint32(iface.TxBroadcast.Packets) // ifHCInOctets (.6) item = m.handler.Add(fmt.Sprintf("%s.6.%d", ifXTableOID, idx)) item.Type = pdu.VariableTypeCounter64 item.Value = iface.Rx.Bytes // ifHCInUcastPkts (.7) item = m.handler.Add(fmt.Sprintf("%s.7.%d", ifXTableOID, idx)) item.Type = pdu.VariableTypeCounter64 item.Value = iface.RxUnicast.Packets // ifHCInMulticastPkts (.8) item = m.handler.Add(fmt.Sprintf("%s.8.%d", ifXTableOID, idx)) item.Type = pdu.VariableTypeCounter64 item.Value = iface.RxMulticast.Packets // ifHCInBroadcastPkts (.9) item = m.handler.Add(fmt.Sprintf("%s.9.%d", ifXTableOID, idx)) item.Type = pdu.VariableTypeCounter64 item.Value = iface.RxBroadcast.Packets // ifHCOutOctets (.10) item = m.handler.Add(fmt.Sprintf("%s.10.%d", ifXTableOID, idx)) item.Type = pdu.VariableTypeCounter64 item.Value = iface.Tx.Bytes // ifHCOutUcastPkts (.11) item = m.handler.Add(fmt.Sprintf("%s.11.%d", ifXTableOID, idx)) item.Type = pdu.VariableTypeCounter64 item.Value = iface.TxUnicast.Packets // ifHCOutMulticastPkts (.12) item = m.handler.Add(fmt.Sprintf("%s.12.%d", ifXTableOID, idx)) item.Type = pdu.VariableTypeCounter64 item.Value = iface.TxMulticast.Packets // ifHCOutBroadcastPkts (.13) item = m.handler.Add(fmt.Sprintf("%s.13.%d", ifXTableOID, idx)) item.Type = pdu.VariableTypeCounter64 item.Value = iface.TxBroadcast.Packets // ifAlias (.18) - Interface description/alias item = m.handler.Add(fmt.Sprintf("%s.18.%d", ifXTableOID, idx)) item.Type = pdu.VariableTypeOctetString // Use description from VPP config if available, otherwise use interface name if desc, exists := m.descriptions[iface.InterfaceName]; exists { item.Value = desc } else { item.Value = iface.InterfaceName } } func (m *InterfaceMIB) RegisterWithClient(client *agentx.Client) error { m.mutex.Lock() defer m.mutex.Unlock() // Create separate sessions for each MIB ifEntrySession, err := client.Session() if err != nil { return fmt.Errorf("failed to create ifEntry session: %v", err) } ifXTableSession, err := client.Session() if err != nil { return fmt.Errorf("failed to create ifXTable session: %v", err) } m.ifEntrySession = ifEntrySession m.ifXTableSession = ifXTableSession // Set handlers for both sessions ifEntrySession.Handler = m.handler ifXTableSession.Handler = m.handler // Register the classic ifEntry err = ifEntrySession.Register(127, value.MustParseOID(ifEntryOID)) if err != nil { return fmt.Errorf("failed to register ifEntry: %v", err) } // Register the extended ifXTable err = ifXTableSession.Register(127, value.MustParseOID(ifXTableOID)) if err != nil { return fmt.Errorf("failed to register ifXTable: %v", err) } logger.Debugf("Registered IF-MIB ifEntry at OID %s", ifEntryOID) logger.Debugf("Registered IF-MIB ifXTable at OID %s", ifXTableOID) return nil }