// Copyright 2025, IPng Networks GmbH, Pim van Pelt package vpp import ( "os" "time" "go.fd.io/govpp/api" interfaces "go.fd.io/govpp/binapi/interface" "go.fd.io/govpp/binapi/interface_types" "govpp-snmp-agentx/logger" ) // 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) // InterfaceManager handles interface-related VPP operations type InterfaceManager struct { client *VPPClient eventCallback InterfaceEventCallback running bool watchingEvents bool } // NewInterfaceManager creates a new interface manager func NewInterfaceManager(client *VPPClient) *InterfaceManager { return &InterfaceManager{ client: client, } } // SetEventCallback sets the callback for interface events func (im *InterfaceManager) SetEventCallback(callback InterfaceEventCallback) { im.eventCallback = callback } // InitializeEventWatching starts event watching and retrieves initial interface details func (im *InterfaceManager) InitializeEventWatching() error { if !im.client.IsConnected() { return &VPPError{Message: "VPP client not connected"} } // Start watching interface events if err := im.StartEventWatcher(); err != nil { logger.Debugf("Failed to start interface event watching: %v", err) return err } logger.Debugf("Interface event watching started") // Get initial interface details if details, err := im.GetAllInterfaceDetails(); err != nil { logger.Debugf("Failed to get initial interface details: %v", err) return err } else { logger.Debugf("Retrieved initial interface details for %d interfaces", len(details)) if im.eventCallback != nil { im.eventCallback(details) } } im.watchingEvents = true return nil } // StartEventMonitoring starts continuous monitoring for VPP connection and restarts event watching as needed func (im *InterfaceManager) StartEventMonitoring() { if im.running { logger.Debugf("Interface event monitoring already running") return } im.running = true go im.eventMonitoringRoutine() } // StopEventMonitoring stops the event monitoring routine func (im *InterfaceManager) StopEventMonitoring() { im.running = false } // eventMonitoringRoutine continuously monitors VPP connection and manages event watching func (im *InterfaceManager) eventMonitoringRoutine() { logger.Debugf("Starting interface event monitoring routine") for { if !im.running { logger.Debugf("Interface event monitoring routine stopping") break } if im.client.IsConnected() { if !im.watchingEvents { if err := im.InitializeEventWatching(); err != nil { logger.Printf("Failed to initialize interface event watching: %v", err) } else { logger.Printf("Interface event watching started") } } } else { if im.watchingEvents { logger.Printf("VPP connection lost, interface event watching will restart on reconnection") im.watchingEvents = false } } time.Sleep(time.Second) } logger.Debugf("Interface event monitoring routine ended") } // GetAllInterfaceDetails retrieves detailed information for all interfaces func (im *InterfaceManager) GetAllInterfaceDetails() ([]InterfaceDetails, error) { if !im.client.IsConnected() { return nil, &VPPError{Message: "VPP client not connected"} } ch, err := im.client.NewAPIChannel() if err != nil { return nil, err } defer ch.Close() return getAllInterfaceDetails(ch) } // StartEventWatcher starts watching for interface events func (im *InterfaceManager) StartEventWatcher() error { if !im.client.IsConnected() { return &VPPError{Message: "VPP client not connected"} } ch, err := im.client.NewAPIChannel() if err != nil { return err } return watchInterfaceEvents(ch, im.handleInterfaceEvent) } // handleInterfaceEvent handles interface events and calls the callback func (im *InterfaceManager) handleInterfaceEvent() { if im.eventCallback != nil { details, err := im.GetAllInterfaceDetails() 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)) im.eventCallback(details) } } } // getAllInterfaceDetails retrieves detailed information for all interfaces (internal function) 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 } // watchInterfaceEvents watches for VPP interface events (internal function) func watchInterfaceEvents(ch api.Channel, callback func()) error { logger.Debugf("WatchInterfaceEvents() called - starting interface event monitoring") notifChan := make(chan api.Message, 100) // subscribe for specific event message logger.Debugf("Subscribing to interface events...") sub, err := ch.SubscribeNotification(notifChan, &interfaces.SwInterfaceEvent{}) if err != nil { logger.Debugf("error subscribing to interface events: %v", err) return err } logger.Debugf("Successfully subscribed to interface events") // enable interface events in VPP logger.Debugf("Enabling interface events in VPP...") err = ch.SendRequest(&interfaces.WantInterfaceEvents{ PID: uint32(os.Getpid()), EnableDisable: 1, }).ReceiveReply(&interfaces.WantInterfaceEventsReply{}) if err != nil { logger.Debugf("error enabling interface events: %v", err) return err } logger.Debugf("Interface events enabled in VPP, starting event listener goroutine") // receive notifications go func() { logger.Debugf("Interface event listener goroutine started") defer func() { logger.Debugf("Interface event listener goroutine shutting down") // disable interface events in VPP err = ch.SendRequest(&interfaces.WantInterfaceEvents{ PID: uint32(os.Getpid()), EnableDisable: 0, }).ReceiveReply(&interfaces.WantInterfaceEventsReply{}) if err != nil { logger.Debugf("error disabling interface events: %v", err) } // unsubscribe from receiving events err = sub.Unsubscribe() if err != nil { logger.Debugf("error unsubscribing from interface events: %v", err) } }() logger.Debugf("Interface event listener waiting for events...") for notif := range notifChan { e := notif.(*interfaces.SwInterfaceEvent) logger.Printf("interface event: SwIfIndex=%d, Flags=%d, Deleted=%t", e.SwIfIndex, e.Flags, e.Deleted) // When an interface event occurs, call the callback if callback != nil { callback() } } logger.Debugf("Interface event listener goroutine ended") }() return nil }