From 4f368e625d89ba32280694908860003aca04f86b Mon Sep 17 00:00:00 2001
From: Pim van Pelt <pim@ipng.nl>
Date: Mon, 23 Jun 2025 19:11:28 +0200
Subject: [PATCH] Use interface details to populate the ifmib, on startup and
 after each event

---
 src/ifmib/ifmib.go   | 87 ++++++++++++++++++++++++++++++++++----------
 src/main.go          |  3 ++
 src/vpp/vpp_iface.go | 75 +++++++++++++++++++++++++++++++++++++-
 src/vpp/vpp_stats.go | 21 ++++++++++-
 4 files changed, 165 insertions(+), 21 deletions(-)

diff --git a/src/ifmib/ifmib.go b/src/ifmib/ifmib.go
index 01d5fd4..107f003 100644
--- a/src/ifmib/ifmib.go
+++ b/src/ifmib/ifmib.go
@@ -77,19 +77,21 @@ type VPPInterface struct {
 }
 
 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
+	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
+	interfaceDetails map[uint32]*vpp.InterfaceDetails  // indexed by interface index
 }
 
 func NewInterfaceMIB() *InterfaceMIB {
 	return &InterfaceMIB{
-		handler:      &agentx.ListHandler{},
-		stats:        make(map[uint32]*api.InterfaceCounters),
-		descriptions: make(map[string]string),
+		handler:          &agentx.ListHandler{},
+		stats:            make(map[uint32]*api.InterfaceCounters),
+		descriptions:     make(map[string]string),
+		interfaceDetails: make(map[uint32]*vpp.InterfaceDetails),
 	}
 }
 
@@ -142,6 +144,22 @@ func (m *InterfaceMIB) LoadVPPConfig(configPath string) error {
 	return nil
 }
 
+func (m *InterfaceMIB) UpdateInterfaceDetails(details []vpp.InterfaceDetails) {
+	m.mutex.Lock()
+	defer m.mutex.Unlock()
+
+	logger.Debugf("Updating interface details for %d interfaces", len(details))
+
+	// Update interface details map
+	for _, detail := range details {
+		m.interfaceDetails[uint32(detail.SwIfIndex)] = &detail
+		logger.Debugf("Updated details for interface %d (%s): MAC=%x, Speed=%d",
+			detail.SwIfIndex, detail.InterfaceName, detail.MacAddress, detail.Speed)
+	}
+
+	logger.Debugf("Interface details updated for %d interfaces", len(details))
+}
+
 func (m *InterfaceMIB) UpdateStats(interfaceStats *api.InterfaceStats) {
 	m.mutex.Lock()
 	defer m.mutex.Unlock()
@@ -186,6 +204,9 @@ func (m *InterfaceMIB) addInterfaceToMIB(iface *api.InterfaceCounters) {
 func (m *InterfaceMIB) addIfEntry(iface *api.InterfaceCounters, idx int) {
 	var item *agentx.ListItem
 
+	// Get interface details if available
+	details := m.interfaceDetails[iface.InterfaceIndex]
+
 	// ifIndex (.1)
 	item = m.handler.Add(fmt.Sprintf("%s.1.%d", ifEntryOID, idx))
 	item.Type = pdu.VariableTypeInteger
@@ -201,30 +222,58 @@ func (m *InterfaceMIB) addIfEntry(iface *api.InterfaceCounters, idx int) {
 	item.Type = pdu.VariableTypeInteger
 	item.Value = int32(6)
 
-	// ifMtu (.4) - Default MTU 1500
+	// ifMtu (.4) - Use real MTU if available, otherwise default to 1500
+	mtu := int32(1500)
+	if details != nil {
+		mtu = int32(details.MTU)
+	}
 	item = m.handler.Add(fmt.Sprintf("%s.4.%d", ifEntryOID, idx))
 	item.Type = pdu.VariableTypeInteger
-	item.Value = int32(1500)
+	item.Value = mtu
 
-	// ifSpeed (.5) - Default to 1Gbps (1000000000 bits/sec)
+	// ifSpeed (.5) - Use real speed if available, otherwise default to 1Gbps
+	speed := uint32(1000000000)
+	if details != nil && details.Speed > 0 {
+		speed = uint32(details.Speed)
+	}
 	item = m.handler.Add(fmt.Sprintf("%s.5.%d", ifEntryOID, idx))
 	item.Type = pdu.VariableTypeGauge32
-	item.Value = uint32(1000000000)
+	item.Value = speed
 
-	// ifPhysAddress (.6) - Empty for now
+	// ifPhysAddress (.6) - Use real MAC address if available
+	macAddr := ""
+	if details != nil && len(details.MacAddress) > 0 {
+		macAddr = string(details.MacAddress)
+	}
 	item = m.handler.Add(fmt.Sprintf("%s.6.%d", ifEntryOID, idx))
 	item.Type = pdu.VariableTypeOctetString
-	item.Value = ""
+	item.Value = macAddr
 
-	// ifAdminStatus (.7) - up(1)
+	// ifAdminStatus (.7) - Use real admin status if available
+	adminStatus := int32(1) // default up
+	if details != nil {
+		if details.AdminStatus {
+			adminStatus = 1 // up
+		} else {
+			adminStatus = 2 // down
+		}
+	}
 	item = m.handler.Add(fmt.Sprintf("%s.7.%d", ifEntryOID, idx))
 	item.Type = pdu.VariableTypeInteger
-	item.Value = int32(1)
+	item.Value = adminStatus
 
-	// ifOperStatus (.8) - up(1)
+	// ifOperStatus (.8) - Use real operational status if available
+	operStatus := int32(1) // default up
+	if details != nil {
+		if details.OperStatus {
+			operStatus = 1 // up
+		} else {
+			operStatus = 2 // down
+		}
+	}
 	item = m.handler.Add(fmt.Sprintf("%s.8.%d", ifEntryOID, idx))
 	item.Type = pdu.VariableTypeInteger
-	item.Value = int32(1)
+	item.Value = operStatus
 
 	// ifLastChange (.9) - 0 (unknown)
 	item = m.handler.Add(fmt.Sprintf("%s.9.%d", ifEntryOID, idx))
diff --git a/src/main.go b/src/main.go
index b431f45..75e977d 100644
--- a/src/main.go
+++ b/src/main.go
@@ -40,6 +40,9 @@ func main() {
 		log.Fatalf("Failed to start AgentX: %v", err)
 	}
 
+	// Set up interface event callback to update interface details
+	vpp.SetInterfaceEventCallback(interfaceMIB.UpdateInterfaceDetails)
+
 	// Start VPP stats routine with callback to update MIB
 	vpp.StartStatsRoutine(interfaceMIB.UpdateStats)
 
diff --git a/src/vpp/vpp_iface.go b/src/vpp/vpp_iface.go
index c110cf5..1fc2f6e 100644
--- a/src/vpp/vpp_iface.go
+++ b/src/vpp/vpp_iface.go
@@ -7,11 +7,73 @@ import (
 
 	"go.fd.io/govpp/api"
 	interfaces "go.fd.io/govpp/binapi/interface"
+	"go.fd.io/govpp/binapi/interface_types"
 
 	"govpp-snmp-agentx/logger"
 )
 
-func WatchInterfaceEvents(ch api.Channel) error {
+// InterfaceDetails holds detailed information about a VPP interface
+type InterfaceDetails struct {
+	SwIfIndex     interface_types.InterfaceIndex
+	InterfaceName string
+	MacAddress    []byte
+	Speed         uint64
+	AdminStatus   bool
+	OperStatus    bool
+	MTU           uint32
+}
+
+// InterfaceEventCallback is called when interface events occur
+type InterfaceEventCallback func(details []InterfaceDetails)
+
+// GetAllInterfaceDetails retrieves detailed information for all interfaces
+func GetAllInterfaceDetails(ch api.Channel) ([]InterfaceDetails, error) {
+	logger.Debugf("Retrieving all interface details from VPP")
+
+	// Get all interfaces
+	reqCtx := ch.SendMultiRequest(&interfaces.SwInterfaceDump{
+		SwIfIndex: ^interface_types.InterfaceIndex(0), // All interfaces
+	})
+
+	var details []InterfaceDetails
+
+	for {
+		iface := &interfaces.SwInterfaceDetails{}
+		stop, err := reqCtx.ReceiveReply(iface)
+		if stop {
+			break
+		}
+		if err != nil {
+			logger.Debugf("Error retrieving interface details: %v", err)
+			return nil, err
+		}
+
+		// Convert VPP interface flags to admin/oper status
+		adminUp := (iface.Flags & interface_types.IF_STATUS_API_FLAG_ADMIN_UP) != 0
+		operUp := (iface.Flags & interface_types.IF_STATUS_API_FLAG_LINK_UP) != 0
+
+		detail := InterfaceDetails{
+			SwIfIndex:     iface.SwIfIndex,
+			InterfaceName: string(iface.InterfaceName),
+			MacAddress:    iface.L2Address[:],
+			Speed:         uint64(iface.LinkSpeed) * 1000, // Convert Kbps to bps
+			AdminStatus:   adminUp,
+			OperStatus:    operUp,
+			MTU:           uint32(iface.LinkMtu),
+		}
+
+		details = append(details, detail)
+
+		logger.Debugf("Interface %d (%s): MAC=%x, Speed=%d, Admin=%t, Oper=%t, MTU=%d",
+			detail.SwIfIndex, detail.InterfaceName, detail.MacAddress,
+			detail.Speed, detail.AdminStatus, detail.OperStatus, detail.MTU)
+	}
+
+	logger.Debugf("Retrieved details for %d interfaces", len(details))
+	return details, nil
+}
+
+func WatchInterfaceEvents(ch api.Channel, callback InterfaceEventCallback) error {
 	logger.Debugf("WatchInterfaceEvents() called - starting interface event monitoring")
 
 	notifChan := make(chan api.Message, 100)
@@ -64,6 +126,17 @@ func WatchInterfaceEvents(ch api.Channel) error {
 			e := notif.(*interfaces.SwInterfaceEvent)
 			logger.Debugf("interface event: SwIfIndex=%d, Flags=%d, Deleted=%t",
 				e.SwIfIndex, e.Flags, e.Deleted)
+
+			// When an interface event occurs, retrieve all interface details and call callback
+			if callback != nil {
+				details, err := GetAllInterfaceDetails(ch)
+				if err != nil {
+					logger.Debugf("Failed to retrieve interface details after event: %v", err)
+				} else {
+					logger.Debugf("Calling interface event callback with %d interfaces", len(details))
+					callback(details)
+				}
+			}
 		}
 		logger.Debugf("Interface event listener goroutine ended")
 	}()
diff --git a/src/vpp/vpp_stats.go b/src/vpp/vpp_stats.go
index d6c3062..fed5df7 100644
--- a/src/vpp/vpp_stats.go
+++ b/src/vpp/vpp_stats.go
@@ -17,6 +17,9 @@ import (
 
 type StatsCallback func(*api.InterfaceStats)
 
+// Global callback for interface events
+var interfaceEventCallback InterfaceEventCallback
+
 var (
 	// Flags for VPP stats configuration
 	ApiAddr       = flag.String("vppstats.api.addr", "/var/run/vpp/api.sock", "VPP API socket path")
@@ -25,6 +28,11 @@ var (
 	Period        = flag.Int("vppstats.period", 10, "Interval in seconds for querying VPP interface stats")
 )
 
+// SetInterfaceEventCallback sets the callback for interface events
+func SetInterfaceEventCallback(callback InterfaceEventCallback) {
+	interfaceEventCallback = callback
+}
+
 // StartStatsRoutine starts a goroutine that queries VPP interface stats at the configured interval
 func StartStatsRoutine(callback StatsCallback) {
 	period := time.Duration(*Period) * time.Second
@@ -139,11 +147,22 @@ func statsRoutine(period time.Duration, callback StatsCallback) {
 				logger.Debugf("Failed to create API channel for interface events: %v", err)
 			} else {
 				logger.Debugf("API channel created successfully, calling WatchInterfaceEvents...")
-				if err := WatchInterfaceEvents(ch); err != nil {
+				if err := WatchInterfaceEvents(ch, interfaceEventCallback); err != nil {
 					logger.Debugf("Failed to start interface event watching: %v", err)
 					ch.Close()
 				} else {
 					logger.Printf("Interface event watching started successfully")
+
+					// Do initial retrieval of interface details
+					if interfaceEventCallback != nil {
+						details, err := GetAllInterfaceDetails(ch)
+						if err != nil {
+							logger.Debugf("Failed to get initial interface details: %v", err)
+						} else {
+							logger.Debugf("Retrieved initial interface details for %d interfaces", len(details))
+							interfaceEventCallback(details)
+						}
+					}
 				}
 			}
 		}