diff --git a/ifmib/ifmib.go b/ifmib/ifmib.go new file mode 100644 index 0000000..08b715b --- /dev/null +++ b/ifmib/ifmib.go @@ -0,0 +1,163 @@ +package ifmib + +import ( + "fmt" + "log" + "sync" + + "github.com/posteo/go-agentx" + "github.com/posteo/go-agentx/pdu" + "github.com/posteo/go-agentx/value" + "go.fd.io/govpp/api" +) + +// IF-MIB OID base: 1.3.6.1.2.1.31.1.1.1 +// ifXTable entries: +// 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 + +const ifXTableOID = "1.3.6.1.2.1.31.1.1.1" + +type InterfaceMIB struct { + mutex sync.RWMutex + handler *agentx.ListHandler + session *agentx.Session + stats map[uint32]*api.InterfaceCounters // indexed by interface index + indexOffset int +} + +func NewInterfaceMIB(indexOffset int) *InterfaceMIB { + return &InterfaceMIB{ + handler: &agentx.ListHandler{}, + stats: make(map[uint32]*api.InterfaceCounters), + indexOffset: indexOffset, + } +} + +func (m *InterfaceMIB) GetHandler() *agentx.ListHandler { + return m.handler +} + +func (m *InterfaceMIB) UpdateStats(interfaceStats *api.InterfaceStats) { + m.mutex.Lock() + defer m.mutex.Unlock() + + log.Printf("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 { + log.Printf("Processing interface %d (%s)", iface.InterfaceIndex, iface.InterfaceName) + m.stats[iface.InterfaceIndex] = &iface + m.addInterfaceToMIB(&iface) + } + + // Update the session with the new handler + if m.session != nil { + m.session.Handler = m.handler + log.Printf("Updated session handler with new IF-MIB data") + } + + log.Printf("IF-MIB now contains %d interfaces", len(m.stats)) +} + +func (m *InterfaceMIB) addInterfaceToMIB(iface *api.InterfaceCounters) { + idx := int(iface.InterfaceIndex) + m.indexOffset + + // 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 + + log.Printf("Added interface %d (%s) to IF-MIB with SNMP index %d", iface.InterfaceIndex, iface.InterfaceName, idx) +} + +func (m *InterfaceMIB) RegisterWithSession(session *agentx.Session) error { + m.mutex.Lock() + defer m.mutex.Unlock() + + m.session = session + session.Handler = m.handler + // Register at the ifXTable level + err := session.Register(127, value.MustParseOID(ifXTableOID)) + if err != nil { + return fmt.Errorf("failed to register IF-MIB: %v", err) + } + + log.Printf("Registered IF-MIB at OID %s", ifXTableOID) + return nil +} \ No newline at end of file diff --git a/main.go b/main.go index 47c18c5..ec57928 100644 --- a/main.go +++ b/main.go @@ -1,42 +1,57 @@ package main import ( - "flag" - "log" - "strings" - "time" + "flag" + "log" + "strings" + "time" - "github.com/posteo/go-agentx" - - "govpp-snmp-example/vppstats" + "github.com/posteo/go-agentx" + + "govpp-snmp-example/ifmib" + "govpp-snmp-example/vppstats" ) func main() { - addr := flag.String("agentx-addr", "localhost:705", "Address to connect to (hostname:port or Unix socket path)") - vppStatsAddr := flag.String("vpp-stats-addr", "/var/run/vpp/stats.sock", "VPP stats socket path") - period := flag.Float64("period", 10.0, "Interval in seconds for querying VPP interface stats") - flag.Parse() + addr := flag.String("agentx-addr", "localhost:705", "Address to connect to (hostname:port or Unix socket path)") + vppStatsAddr := flag.String("vpp-stats-addr", "/var/run/vpp/stats.sock", "VPP stats socket path") + period := flag.Float64("period", 10.0, "Interval in seconds for querying VPP interface stats") + ifIndexOffset := flag.Int("vpp-ifindex-offset", 1000, "Offset to add to VPP interface indices for SNMP") + flag.Parse() - var network, address string - if strings.HasPrefix(*addr, "/") { - network = "unix" - address = *addr - } else { - network = "tcp" - address = *addr - } + var network, address string + if strings.HasPrefix(*addr, "/") { + network = "unix" + address = *addr + } else { + network = "tcp" + address = *addr + } - client, err := agentx.Dial(network, address) - if err != nil { - log.Fatalf("Failed to dial %s %s: %v", network, address, err) - } - client.Timeout = 1 * time.Minute - client.ReconnectInterval = 1 * time.Second + client, err := agentx.Dial(network, address) + if err != nil { + log.Fatalf("Failed to dial %s %s: %v", network, address, err) + } + client.Timeout = 1 * time.Minute + client.ReconnectInterval = 1 * time.Second - // Start VPP stats routine - vppstats.StartStatsRoutine(*vppStatsAddr, time.Duration(*period*1000)*time.Millisecond) + session, err := client.Session() + if err != nil { + log.Fatalf("Failed to create session: %v", err) + } - for { - time.Sleep(100 * time.Millisecond) - } + // Create the interface MIB + interfaceMIB := ifmib.NewInterfaceMIB(*ifIndexOffset) + + // Register the interface MIB with the AgentX session + if err := interfaceMIB.RegisterWithSession(session); err != nil { + log.Fatalf("Failed to register interface MIB: %v", err) + } + + // Start VPP stats routine with callback to update MIB + vppstats.StartStatsRoutine(*vppStatsAddr, time.Duration(*period*1000)*time.Millisecond, interfaceMIB.UpdateStats) + + for { + time.Sleep(100 * time.Millisecond) + } } diff --git a/vppstats/stats.go b/vppstats/stats.go index a3b404d..f2c3241 100644 --- a/vppstats/stats.go +++ b/vppstats/stats.go @@ -9,12 +9,14 @@ import ( "go.fd.io/govpp/core" ) +type StatsCallback func(*api.InterfaceStats) + // StartStatsRoutine starts a goroutine that queries VPP interface stats at the specified interval -func StartStatsRoutine(statsSocketPath string, period time.Duration) { - go statsRoutine(statsSocketPath, period) +func StartStatsRoutine(statsSocketPath string, period time.Duration, callback StatsCallback) { + go statsRoutine(statsSocketPath, period, callback) } -func statsRoutine(statsSocketPath string, period time.Duration) { +func statsRoutine(statsSocketPath string, period time.Duration, callback StatsCallback) { log.Printf("Starting VPP stats routine with socket: %s, period: %v", statsSocketPath, period) // Create stats client @@ -29,7 +31,7 @@ func statsRoutine(statsSocketPath string, period time.Duration) { defer c.Disconnect() // Query stats immediately on startup - queryInterfaceStats(c) + queryInterfaceStats(c, callback) ticker := time.NewTicker(period) defer ticker.Stop() @@ -37,12 +39,12 @@ func statsRoutine(statsSocketPath string, period time.Duration) { for { select { case <-ticker.C: - queryInterfaceStats(c) + queryInterfaceStats(c, callback) } } } -func queryInterfaceStats(c *core.StatsConnection) { +func queryInterfaceStats(c *core.StatsConnection, callback StatsCallback) { log.Printf("Querying VPP interface stats at %s", time.Now().Format(time.RFC3339)) // Create the proper struct for interface stats @@ -54,14 +56,17 @@ func queryInterfaceStats(c *core.StatsConnection) { return } - // Now you have properly structured data + // Log basic info + log.Printf("Retrieved stats for %d interfaces", len(stats.Interfaces)) for _, iface := range stats.Interfaces { - log.Printf("Interface %d (%s):", iface.InterfaceIndex, iface.InterfaceName) - log.Printf(" RX: %d packets, %d bytes", iface.Rx.Packets, iface.Rx.Bytes) - log.Printf(" TX: %d packets, %d bytes", iface.Tx.Packets, iface.Tx.Bytes) - log.Printf(" RX Errors: %d, TX Errors: %d", iface.RxErrors, iface.TxErrors) - log.Printf(" Drops: %d, Punts: %d", iface.Drops, iface.Punts) + log.Printf("Interface %d (%s): RX %d pkts/%d bytes, TX %d pkts/%d bytes", + iface.InterfaceIndex, iface.InterfaceName, + iface.Rx.Packets, iface.Rx.Bytes, + iface.Tx.Packets, iface.Tx.Bytes) } - log.Printf("Retrieved stats for %d interfaces", len(stats.Interfaces)) + // Call the callback to update the MIB + if callback != nil { + callback(stats) + } } \ No newline at end of file