First Commit of Voltha-Go-Controller from Radisys
Change-Id: I8e2e908e7ab09a4fe3d86849da18b6d69dcf4ab0
diff --git a/internal/pkg/application/application.go b/internal/pkg/application/application.go
new file mode 100644
index 0000000..8b1e763
--- /dev/null
+++ b/internal/pkg/application/application.go
@@ -0,0 +1,2057 @@
+/*
+* Copyright 2022-present Open Networking Foundation
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+ */
+
+package application
+
+import (
+ "context"
+ "encoding/hex"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/google/gopacket"
+ "github.com/google/gopacket/layers"
+
+ "voltha-go-controller/internal/pkg/controller"
+ cntlr "voltha-go-controller/internal/pkg/controller"
+ "voltha-go-controller/database"
+ "voltha-go-controller/internal/pkg/intf"
+ "voltha-go-controller/internal/pkg/of"
+ "voltha-go-controller/internal/pkg/tasks"
+ "voltha-go-controller/internal/pkg/util"
+ errorCodes "voltha-go-controller/internal/pkg/errorcodes"
+ "github.com/opencord/voltha-lib-go/v7/pkg/log"
+)
+
+var logger log.CLogger
+var ctx = context.TODO()
+
+func init() {
+ // Setup this package so that it's log level can be modified at run time
+ var err error
+ logger, err = log.RegisterPackage(log.JSON, log.ErrorLevel, log.Fields{})
+ if err != nil {
+ panic(err)
+ }
+}
+
+const (
+ // TODO - Need to identify a right place for this
+
+ // PriorityNone constant.
+ PriorityNone uint8 = 8
+ // AnyVlan constant.
+ AnyVlan uint16 = 0xFFFF
+)
+
+// List of Mac Learning Type
+const (
+ MacLearningNone MacLearningType = iota
+ Learn
+ ReLearn
+)
+
+// MacLearningType represents Mac Learning Type
+type MacLearningType int
+
+var (
+ tickCount uint16
+ vgcRebooted bool
+ isUpgradeComplete bool
+)
+
+var db database.DBIntf
+
+// PacketHandlers : packet handler for different protocols
+var PacketHandlers map[string]CallBack
+
+// CallBack : registered call back function for different protocol packets
+type CallBack func(device string, port string, pkt gopacket.Packet)
+
+const (
+ // ARP packet
+ ARP string = "ARP"
+ // DHCPv4 packet
+ DHCPv4 string = "DHCPv4"
+ // DHCPv6 packet
+ DHCPv6 string = "DHCPv6"
+ // IGMP packet
+ IGMP string = "IGMP"
+ // PPPOE packet
+ PPPOE string = "PPPOE"
+ // US packet side
+ US string = "US"
+ // DS packet side
+ DS string = "DS"
+ // NNI port name
+ NNI string = "nni"
+)
+
+// RegisterPacketHandler : API to register callback function for every protocol
+func RegisterPacketHandler(protocol string, callback CallBack) {
+ if PacketHandlers == nil {
+ PacketHandlers = make(map[string]CallBack)
+ }
+ PacketHandlers[protocol] = callback
+}
+
+// ---------------------------------------------------------------------
+// VOLT Ports
+// ---------------------------------------------------------------------
+// VOLT Ports are ports associated with VOLT devices. Each port is classified into
+// Access/NNI. Each port is identified by Name (Identity known to the NB) and
+// Id (Identity used on the SB). Both identities are presented when a port is
+// discovered in the SB.
+
+// VoltPortType type for Port Type
+type VoltPortType uint8
+
+const (
+ // VoltPortTypeAccess constant.
+ VoltPortTypeAccess VoltPortType = 0
+ // VoltPortTypeNni constant.
+ VoltPortTypeNni VoltPortType = 1
+)
+
+// PortState type for Port State.
+type PortState uint8
+
+const (
+ // PortStateDown constant.
+ PortStateDown PortState = 0
+ // PortStateUp constant.
+ PortStateUp PortState = 1
+)
+
+// VoltPort structure that is used to store the ports. The name is the
+// the main identity used by the application. The SB and NB both present name
+// as the identity. The SB is abstracted by VPAgent and the VPAgent transacts
+// using name as identity
+type VoltPort struct {
+ ID uint32
+ Name string
+ Device string
+ PonPort uint32
+ Type VoltPortType
+ State PortState
+ ActiveChannels uint32
+ ChannelPerSubAlarmRaised bool
+}
+
+// NewVoltPort : Constructor for the port.
+func NewVoltPort(device string, name string, id uint32) *VoltPort {
+ var vp VoltPort
+ vp.Device = device
+ vp.Name = name
+ vp.ID = id
+ if util.IsNniPort(id) {
+ vp.Type = VoltPortTypeNni
+ } else {
+ vp.PonPort = GetPonPortIDFromUNIPort(id)
+ }
+ vp.State = PortStateDown
+ vp.ChannelPerSubAlarmRaised = false
+ return &vp
+}
+
+// SetPortID : The ID is used when constructing flows as the flows require ID.
+func (vp *VoltPort) SetPortID(id uint32) {
+ vp.ID = id
+ if util.IsNniPort(id) {
+ vp.Type = VoltPortTypeNni
+ }
+}
+
+// ---------------------------------------------------------------------
+// VOLT Device
+// ---------------------------------------------------------------------
+//
+// VoltDevice is an OLT which contains ports of type access and NNI. Each OLT
+// can only have one NNI port in the current release. The NNI port always uses
+// identity 65536 and all the access ports use identities less than 65535. The
+// identification of NNI is done by comparing the port identity with 65535
+
+// VoltDevice fields :
+// Name: This is the name presented by the device/VOLTHA. This doesn't
+// have any relation to the physical device
+// SerialNum: This is the serial number of the device and can be used to
+// correlate the devices
+// NniPort: The identity of the NNI port
+// Ports: List of all ports added to the device
+type VoltDevice struct {
+ Name string
+ SerialNum string
+ State controller.DeviceState
+ SouthBoundID string
+ NniPort string
+ Ports sync.Map
+ VlanPortStatus sync.Map
+ VpvsBySvlan *util.ConcurrentMap // map[svlan]map[vnet_port]*VoltPortVnet
+ IgmpDsFlowAppliedForMvlan map[uint16]bool
+ ConfiguredVlanForDeviceFlows *util.ConcurrentMap //map[string]map[string]bool
+ icmpv6GroupAdded bool
+ ActiveChannelsPerPon sync.Map // [PonPortID]*PonPortCfg
+ ActiveChannelCountLock sync.Mutex // This lock is used to update ActiveIGMPChannels
+ PonPortList sync.Map // [PonPortID]map[string]string
+ FlowAddEventMap *util.ConcurrentMap //map[string]*FlowEvent
+ FlowDelEventMap *util.ConcurrentMap //map[string]*FlowEvent
+ MigratingServices *util.ConcurrentMap //<vnetID,<RequestID, MigrateServicesRequest>>
+ GlobalDhcpFlowAdded bool
+}
+
+// NewVoltDevice : Constructor for the device
+func NewVoltDevice(name string, slno, southBoundID string) *VoltDevice {
+ var d VoltDevice
+ d.Name = name
+ d.SouthBoundID = southBoundID
+ d.State = controller.DeviceStateDOWN
+ d.NniPort = ""
+ d.SouthBoundID = southBoundID
+ d.SerialNum = slno
+ d.icmpv6GroupAdded = false
+ d.IgmpDsFlowAppliedForMvlan = make(map[uint16]bool)
+ d.ConfiguredVlanForDeviceFlows = util.NewConcurrentMap()
+ d.MigratingServices = util.NewConcurrentMap()
+ d.VpvsBySvlan = util.NewConcurrentMap()
+ d.FlowAddEventMap = util.NewConcurrentMap()
+ d.FlowDelEventMap = util.NewConcurrentMap()
+ d.GlobalDhcpFlowAdded = false
+ return &d
+}
+
+//GetAssociatedVpvsForDevice - return the associated VPVs for given device & svlan
+func (va *VoltApplication) GetAssociatedVpvsForDevice(device string, svlan of.VlanType) *util.ConcurrentMap {
+ if d := va.GetDevice(device); d != nil {
+ return d.GetAssociatedVpvs(svlan)
+ }
+ return nil
+}
+
+//AssociateVpvsToDevice - updates the associated VPVs for given device & svlan
+func (va *VoltApplication) AssociateVpvsToDevice(device string, vpv *VoltPortVnet) {
+ if d := va.GetDevice(device); d != nil {
+
+ vpvMap := d.GetAssociatedVpvs(vpv.SVlan)
+ vpvMap.Set(vpv, true)
+ d.VpvsBySvlan.Set(vpv.SVlan, vpvMap)
+ logger.Infow(ctx, "VPVMap: SET", log.Fields{"Map": vpvMap.Length()})
+ return
+ }
+ logger.Errorw(ctx, "Set VPVMap failed: Device Not Found", log.Fields{"Svlan": vpv.SVlan, "Device": device})
+}
+
+//DisassociateVpvsFromDevice - disassociated VPVs from given device & svlan
+func (va *VoltApplication) DisassociateVpvsFromDevice(device string, vpv *VoltPortVnet) {
+ if d := va.GetDevice(device); d != nil {
+ vpvMap := d.GetAssociatedVpvs(vpv.SVlan)
+ vpvMap.Remove(vpv)
+ d.VpvsBySvlan.Set(vpv.SVlan, vpvMap)
+ logger.Infow(ctx, "VPVMap: Remove", log.Fields{"Map": vpvMap.Length()})
+ return
+ }
+ logger.Errorw(ctx, "Remove VPVMap failed: Device Not Found", log.Fields{"Svlan": vpv.SVlan, "Device": device})
+}
+
+//GetAssociatedVpvs - returns the associated VPVs for the given Svlan
+func (d *VoltDevice) GetAssociatedVpvs(svlan of.VlanType) *util.ConcurrentMap {
+
+ var vpvMap *util.ConcurrentMap
+ var mapIntf interface{}
+ var ok bool
+
+ if mapIntf, ok = d.VpvsBySvlan.Get(svlan); ok {
+ vpvMap = mapIntf.(*util.ConcurrentMap)
+ } else {
+ vpvMap = util.NewConcurrentMap()
+ }
+ logger.Infow(ctx, "VPVMap: GET", log.Fields{"Map": vpvMap.Length()})
+ return vpvMap
+}
+
+// AddPort add port to the device.
+func (d *VoltDevice) AddPort(port string, id uint32) *VoltPort {
+ addPonPortFromUniPort := func(vPort *VoltPort) {
+ if vPort.Type == VoltPortTypeAccess {
+ ponPortID := GetPonPortIDFromUNIPort(vPort.ID)
+
+ if ponPortUniList, ok := d.PonPortList.Load(ponPortID); !ok {
+ uniList := make(map[string]uint32)
+ uniList[port] = vPort.ID
+ d.PonPortList.Store(ponPortID, uniList)
+ } else {
+ ponPortUniList.(map[string]uint32)[port] = vPort.ID
+ d.PonPortList.Store(ponPortID, ponPortUniList)
+ }
+ }
+ }
+ va := GetApplication()
+ if pIntf, ok := d.Ports.Load(port); ok {
+ voltPort := pIntf.(*VoltPort)
+ addPonPortFromUniPort(voltPort)
+ va.AggActiveChannelsCountPerSub(d.Name, port, voltPort)
+ d.Ports.Store(port, voltPort)
+ return voltPort
+ }
+ p := NewVoltPort(d.Name, port, id)
+ va.AggActiveChannelsCountPerSub(d.Name, port, p)
+ d.Ports.Store(port, p)
+ if util.IsNniPort(id) {
+ d.NniPort = port
+ }
+ addPonPortFromUniPort(p)
+ return p
+}
+
+// GetPort to get port information from the device.
+func (d *VoltDevice) GetPort(port string) *VoltPort {
+ if pIntf, ok := d.Ports.Load(port); ok {
+ return pIntf.(*VoltPort)
+ }
+ return nil
+}
+
+// DelPort to delete port from the device
+func (d *VoltDevice) DelPort(port string) {
+ if _, ok := d.Ports.Load(port); ok {
+ d.Ports.Delete(port)
+ } else {
+ logger.Warnw(ctx, "Port doesn't exist", log.Fields{"Device": d.Name, "Port": port})
+ }
+}
+
+// pushFlowsForUnis to send port-up-indication for uni ports.
+func (d *VoltDevice) pushFlowsForUnis() {
+
+ logger.Info(ctx, "NNI Discovered, Sending Port UP Ind for UNIs")
+ d.Ports.Range(func(key, value interface{}) bool {
+ port := key.(string)
+ vp := value.(*VoltPort)
+
+ logger.Infow(ctx, "NNI Discovered. Sending Port UP Ind for UNI", log.Fields{"Port" : port})
+ //Ignore if UNI port is not UP
+ if vp.State != PortStateUp {
+ return true
+ }
+
+ //Obtain all VPVs associated with the port
+ vnets, ok := GetApplication().VnetsByPort.Load(port)
+ if !ok {
+ return true
+ }
+
+ for _, vpv := range vnets.([]*VoltPortVnet) {
+ vpv.VpvLock.Lock()
+ vpv.PortUpInd(d, port)
+ vpv.VpvLock.Unlock()
+
+ }
+ return true
+ })
+}
+
+// ----------------------------------------------------------
+// VOLT Application - hosts all other objects
+// ----------------------------------------------------------
+//
+// The VOLT application is a singleton implementation where
+// there is just one instance in the system and is the gateway
+// to all other components within the controller
+// The declaration of the singleton object
+var vapplication *VoltApplication
+
+// VoltApplication fields :
+// ServiceByName - Stores the services by the name as key
+// A record of NB configuration.
+// VnetsByPort - Stores the VNETs by the ports configured
+// from NB. A record of NB configuration.
+// VnetsByTag - Stores the VNETs by the VLANS configured
+// from NB. A record of NB configuration.
+// VnetsByName - Stores the VNETs by the name configured
+// from NB. A record of NB configuration.
+// DevicesDisc - Stores the devices discovered from SB.
+// Should be updated only by events from SB
+// PortsDisc - Stores the ports discovered from SB.
+// Should be updated only by events from SB
+type VoltApplication struct {
+ ServiceByName sync.Map // [serName]*VoltService
+ VnetsByPort sync.Map // [portName][]*VoltPortVnet
+ VnetsByTag sync.Map // [svlan-cvlan-uvlan]*VoltVnet
+ VnetsByName sync.Map // [vnetName]*VoltVnet
+ VnetsBySvlan *util.ConcurrentMap
+ DevicesDisc sync.Map
+ PortsDisc sync.Map
+ IgmpGroups sync.Map // [grpKey]*IgmpGroup
+ IgmpGroupIds []*IgmpGroup
+ MvlanProfilesByTag sync.Map
+ MvlanProfilesByName sync.Map
+ Icmpv6Receivers sync.Map
+ MeterMgr
+ IgmpTasks tasks.Tasks
+ IndicationsTasks tasks.Tasks
+ MulticastAlarmTasks tasks.Tasks
+ portLock sync.Mutex
+ DataMigrationInfo DataMigration
+ DeviceCounters sync.Map //[logicalDeviceId]*DeviceCounters
+ ServiceCounters sync.Map //[serviceName]*ServiceCounters
+ NbDevice sync.Map // [OLTSouthBoundID]*NbDevice
+ IgmpKPIsTasks tasks.Tasks
+ pppoeTasks tasks.Tasks
+ IgmpProfilesByName sync.Map
+ OltIgmpInfoBySerial sync.Map
+ McastConfigMap sync.Map //[OltSerialNo_MvlanProfileID]*McastConfig
+ // MacAddress-Port MAP to avoid swap of mac accross ports.
+ macPortLock sync.RWMutex
+ macPortMap map[string]string
+
+ IgmpPendingPool map[string]map[*IgmpGroup]bool //[grpkey, map[groupObj]bool] //mvlan_grpName/IP
+ PendingPoolLock sync.RWMutex
+
+ VnetsToDelete map[string]bool
+ ServicesToDelete map[string]bool
+ VoltPortVnetsToDelete map[*VoltPortVnet]bool
+ PortAlarmProfileCache map[string]map[string]int // [portAlarmID][ThresholdLevelString]ThresholdLevel
+ vendorID string
+}
+
+// PonPortCfg contains NB port config and activeIGMPChannels count
+type PonPortCfg struct {
+ PortID uint32
+ MaxActiveChannels uint32
+ ActiveIGMPChannels uint32
+ EnableMulticastKPI bool
+ PortAlarmProfileID string
+}
+
+// NbDevice OLT Device info
+type NbDevice struct {
+ SouthBoundID string
+ PonPorts sync.Map // [PortID]*PonPortCfg
+}
+
+// RestoreNbDeviceFromDb restores the NB Device in case of VGC pod restart.
+func (va *VoltApplication) RestoreNbDeviceFromDb(deviceID string) *NbDevice {
+
+ nbDevice := NewNbDevice()
+ nbDevice.SouthBoundID = deviceID
+
+ nbPorts, _ := db.GetAllNbPorts(deviceID)
+
+ for key, p := range nbPorts {
+ b, ok := p.Value.([]byte)
+ if !ok {
+ logger.Warn(ctx, "The value type is not []byte")
+ continue
+ }
+ var port PonPortCfg
+ err := json.Unmarshal(b, &port)
+ if err != nil {
+ logger.Warn(ctx, "Unmarshal of PonPortCfg failed")
+ continue
+ }
+ logger.Debugw(ctx, "Port recovered", log.Fields{"port": port})
+ ponPortID, _ := strconv.Atoi(key)
+ nbDevice.PonPorts.Store(uint32(ponPortID), &port)
+ }
+ va.NbDevice.Store(deviceID, nbDevice)
+ return nbDevice
+}
+
+// NewNbDevice Constructor for NbDevice
+func NewNbDevice() *NbDevice {
+ var nbDevice NbDevice
+ return &nbDevice
+}
+
+// WriteToDb writes nb device port config to kv store
+func (nbd *NbDevice) WriteToDb(portID uint32, ponPort *PonPortCfg) {
+ b, err := json.Marshal(ponPort)
+ if err != nil {
+ logger.Errorw(ctx, "PonPortConfig-marshal-failed", log.Fields{"err": err})
+ return
+ }
+ db.PutNbDevicePort(nbd.SouthBoundID, portID, string(b))
+}
+
+// AddPortToNbDevice Adds pon port to NB Device and DB
+func (nbd *NbDevice) AddPortToNbDevice(portID, allowedChannels uint32,
+ enableMulticastKPI bool, portAlarmProfileID string) *PonPortCfg {
+
+ ponPort := &PonPortCfg{
+ PortID: portID,
+ MaxActiveChannels: allowedChannels,
+ EnableMulticastKPI: enableMulticastKPI,
+ PortAlarmProfileID: portAlarmProfileID,
+ }
+ nbd.PonPorts.Store(portID, ponPort)
+ nbd.WriteToDb(portID, ponPort)
+ return ponPort
+}
+
+// UpdatePortToNbDevice Adds pon port to NB Device and DB
+func (nbd *NbDevice) UpdatePortToNbDevice(portID, allowedChannels uint32, enableMulticastKPI bool, portAlarmProfileID string) *PonPortCfg {
+
+ p, exists := nbd.PonPorts.Load(portID)
+ if !exists {
+ logger.Errorw(ctx, "PON port not exists in nb-device", log.Fields{"portID": portID})
+ return nil
+ }
+ port := p.(*PonPortCfg)
+ if allowedChannels != 0 {
+ port.MaxActiveChannels = allowedChannels
+ port.EnableMulticastKPI = enableMulticastKPI
+ port.PortAlarmProfileID = portAlarmProfileID
+ }
+
+ nbd.PonPorts.Store(portID, port)
+ nbd.WriteToDb(portID, port)
+ return port
+}
+
+// DeletePortFromNbDevice Deletes pon port from NB Device and DB
+func (nbd *NbDevice) DeletePortFromNbDevice(portID uint32) {
+
+ if _, ok := nbd.PonPorts.Load(portID); ok {
+ nbd.PonPorts.Delete(portID)
+ }
+ db.DelNbDevicePort(nbd.SouthBoundID, portID)
+}
+
+// GetApplication : Interface to access the singleton object
+func GetApplication() *VoltApplication {
+ if vapplication == nil {
+ vapplication = newVoltApplication()
+ }
+ return vapplication
+}
+
+// newVoltApplication : Constructor for the singleton object. Hence this is not
+// an exported function
+func newVoltApplication() *VoltApplication {
+ var va VoltApplication
+ va.IgmpTasks.Initialize(context.TODO())
+ va.MulticastAlarmTasks.Initialize(context.TODO())
+ va.IgmpKPIsTasks.Initialize(context.TODO())
+ va.pppoeTasks.Initialize(context.TODO())
+ va.storeIgmpProfileMap(DefaultIgmpProfID, newDefaultIgmpProfile())
+ va.MeterMgr.Init()
+ va.AddIgmpGroups(5000)
+ va.macPortMap = make(map[string]string)
+ va.IgmpPendingPool = make(map[string]map[*IgmpGroup]bool)
+ va.VnetsBySvlan = util.NewConcurrentMap()
+ va.VnetsToDelete = make(map[string]bool)
+ va.ServicesToDelete = make(map[string]bool)
+ va.VoltPortVnetsToDelete = make(map[*VoltPortVnet]bool)
+ go va.Start(TimerCfg{tick: 100 * time.Millisecond}, tickTimer)
+ go va.Start(TimerCfg{tick: time.Duration(GroupExpiryTime) * time.Minute}, pendingPoolTimer)
+ InitEventFuncMapper()
+ db = database.GetDatabase()
+ return &va
+}
+
+//GetFlowEventRegister - returs the register based on flow mod type
+func (d *VoltDevice) GetFlowEventRegister(flowModType of.Command) (*util.ConcurrentMap, error) {
+
+ switch flowModType {
+ case of.CommandDel:
+ return d.FlowDelEventMap, nil
+ case of.CommandAdd:
+ return d.FlowAddEventMap, nil
+ default:
+ logger.Error(ctx, "Unknown Flow Mod received")
+ }
+ return util.NewConcurrentMap(), errors.New("Unknown Flow Mod")
+}
+
+// RegisterFlowAddEvent to register a flow event.
+func (d *VoltDevice) RegisterFlowAddEvent(cookie string, event *FlowEvent) {
+ logger.Debugw(ctx, "Registered Flow Add Event", log.Fields{"Cookie": cookie, "Event": event})
+ d.FlowAddEventMap.MapLock.Lock()
+ defer d.FlowAddEventMap.MapLock.Unlock()
+ d.FlowAddEventMap.Set(cookie, event)
+}
+
+// RegisterFlowDelEvent to register a flow event.
+func (d *VoltDevice) RegisterFlowDelEvent(cookie string, event *FlowEvent) {
+ logger.Debugw(ctx, "Registered Flow Del Event", log.Fields{"Cookie": cookie, "Event": event})
+ d.FlowDelEventMap.MapLock.Lock()
+ defer d.FlowDelEventMap.MapLock.Unlock()
+ d.FlowDelEventMap.Set(cookie, event)
+}
+
+// UnRegisterFlowEvent to unregister a flow event.
+func (d *VoltDevice) UnRegisterFlowEvent(cookie string, flowModType of.Command) {
+ logger.Debugw(ctx, "UnRegistered Flow Add Event", log.Fields{"Cookie": cookie, "Type": flowModType})
+ flowEventMap, err := d.GetFlowEventRegister(flowModType)
+ if err != nil {
+ logger.Debugw(ctx, "Flow event map does not exists", log.Fields{"flowMod": flowModType, "Error": err})
+ return
+ }
+ flowEventMap.MapLock.Lock()
+ defer flowEventMap.MapLock.Unlock()
+ flowEventMap.Remove(cookie)
+}
+
+// AddIgmpGroups to add Igmp groups.
+func (va *VoltApplication) AddIgmpGroups(numOfGroups uint32) {
+ //TODO: Temp change to resolve group id issue in pOLT
+ //for i := 1; uint32(i) <= numOfGroups; i++ {
+ for i := 2; uint32(i) <= (numOfGroups + 1); i++ {
+ ig := IgmpGroup{}
+ ig.GroupID = uint32(i)
+ va.IgmpGroupIds = append(va.IgmpGroupIds, &ig)
+ }
+}
+
+// GetAvailIgmpGroupID to get id of available igmp group.
+func (va *VoltApplication) GetAvailIgmpGroupID() *IgmpGroup {
+ var ig *IgmpGroup
+ if len(va.IgmpGroupIds) > 0 {
+ ig, va.IgmpGroupIds = va.IgmpGroupIds[0], va.IgmpGroupIds[1:]
+ return ig
+ }
+ return nil
+}
+
+// GetIgmpGroupID to get id of igmp group.
+func (va *VoltApplication) GetIgmpGroupID(gid uint32) (*IgmpGroup, error) {
+ for id, ig := range va.IgmpGroupIds {
+ if ig.GroupID == gid {
+ va.IgmpGroupIds = append(va.IgmpGroupIds[0:id], va.IgmpGroupIds[id+1:]...)
+ return ig, nil
+ }
+ }
+ return nil, errors.New("Group Id Missing")
+}
+
+// PutIgmpGroupID to add id of igmp group.
+func (va *VoltApplication) PutIgmpGroupID(ig *IgmpGroup) {
+ va.IgmpGroupIds = append([]*IgmpGroup{ig}, va.IgmpGroupIds[0:]...)
+}
+
+//RestoreUpgradeStatus - gets upgrade/migration status from DB and updates local flags
+func (va *VoltApplication) RestoreUpgradeStatus() {
+ Migrate := new(DataMigration)
+ if err := GetMigrationInfo(Migrate); err == nil {
+ if Migrate.Status == MigrationInProgress {
+ isUpgradeComplete = false
+ return
+ }
+ }
+ isUpgradeComplete = true
+
+ logger.Infow(ctx, "Upgrade Status Restored", log.Fields{"Upgrade Completed": isUpgradeComplete})
+}
+
+// ReadAllFromDb : If we are restarted, learn from the database the current execution
+// stage
+func (va *VoltApplication) ReadAllFromDb() {
+ logger.Info(ctx, "Reading the meters from DB")
+ va.RestoreMetersFromDb()
+ logger.Info(ctx, "Reading the VNETs from DB")
+ va.RestoreVnetsFromDb()
+ logger.Info(ctx, "Reading the VPVs from DB")
+ va.RestoreVpvsFromDb()
+ logger.Info(ctx, "Reading the Services from DB")
+ va.RestoreSvcsFromDb()
+ logger.Info(ctx, "Reading the MVLANs from DB")
+ va.RestoreMvlansFromDb()
+ logger.Info(ctx, "Reading the IGMP profiles from DB")
+ va.RestoreIGMPProfilesFromDb()
+ logger.Info(ctx, "Reading the Mcast configs from DB")
+ va.RestoreMcastConfigsFromDb()
+ logger.Info(ctx, "Reading the IGMP groups for DB")
+ va.RestoreIgmpGroupsFromDb()
+ logger.Info(ctx, "Reading Upgrade status from DB")
+ va.RestoreUpgradeStatus()
+ logger.Info(ctx, "Reconciled from DB")
+}
+
+// InitStaticConfig to initialise static config.
+func (va *VoltApplication) InitStaticConfig() {
+ va.InitIgmpSrcMac()
+}
+
+// SetVendorID to set vendor id
+func (va *VoltApplication) SetVendorID(vendorID string) {
+ va.vendorID = vendorID
+}
+
+// GetVendorID to get vendor id
+func (va *VoltApplication) GetVendorID() string {
+ return va.vendorID
+}
+
+// SetRebootFlag to set reboot flag
+func (va *VoltApplication) SetRebootFlag(flag bool) {
+ vgcRebooted = flag
+}
+
+// GetUpgradeFlag to get reboot status
+func (va *VoltApplication) GetUpgradeFlag() bool {
+ return isUpgradeComplete
+}
+
+// SetUpgradeFlag to set reboot status
+func (va *VoltApplication) SetUpgradeFlag(flag bool) {
+ isUpgradeComplete = flag
+}
+
+// ------------------------------------------------------------
+// Device related functions
+
+// AddDevice : Add a device and typically the device stores the NNI port on the device
+// The NNI port is used when the packets are emitted towards the network.
+// The outport is selected as the NNI port of the device. Today, we support
+// a single NNI port per OLT. This is true whether the network uses any
+// protection mechanism (LAG, ERPS, etc.). The aggregate of the such protection
+// is represented by a single NNI port
+func (va *VoltApplication) AddDevice(device string, slno, southBoundID string) {
+ logger.Warnw(ctx, "Received Device Ind: Add", log.Fields{"Device": device, "SrNo": slno})
+ if _, ok := va.DevicesDisc.Load(device); ok {
+ logger.Warnw(ctx, "Device Exists", log.Fields{"Device": device})
+ }
+ d := NewVoltDevice(device, slno, southBoundID)
+
+ addPort := func(key, value interface{}) bool {
+ portID := key.(uint32)
+ port := value.(*PonPortCfg)
+ va.AggActiveChannelsCountForPonPort(device, portID, port)
+ d.ActiveChannelsPerPon.Store(portID, port)
+ return true
+ }
+ if nbDevice, exists := va.NbDevice.Load(southBoundID); exists {
+ // Pon Ports added before OLT activate.
+ nbDevice.(*NbDevice).PonPorts.Range(addPort)
+ } else {
+ // Check if NbPort exists in DB. VGC restart case.
+ nbd := va.RestoreNbDeviceFromDb(southBoundID)
+ nbd.PonPorts.Range(addPort)
+ }
+ va.DevicesDisc.Store(device, d)
+}
+
+// GetDevice to get a device.
+func (va *VoltApplication) GetDevice(device string) *VoltDevice {
+ if d, ok := va.DevicesDisc.Load(device); ok {
+ return d.(*VoltDevice)
+ }
+ return nil
+}
+
+// DelDevice to delete a device.
+func (va *VoltApplication) DelDevice(device string) {
+ logger.Warnw(ctx, "Received Device Ind: Delete", log.Fields{"Device": device})
+ if vdIntf, ok := va.DevicesDisc.Load(device); ok {
+ vd := vdIntf.(*VoltDevice)
+ va.DevicesDisc.Delete(device)
+ _ = db.DelAllRoutesForDevice(device)
+ va.HandleFlowClearFlag(device, vd.SerialNum, vd.SouthBoundID)
+ _ = db.DelAllGroup(device)
+ _ = db.DelAllMeter(device)
+ _ = db.DelAllPorts(device)
+ logger.Debugw(ctx, "Device deleted", log.Fields{"Device": device})
+ } else {
+ logger.Warnw(ctx, "Device Doesn't Exist", log.Fields{"Device": device})
+ }
+}
+
+// GetDeviceBySerialNo to get a device by serial number.
+// TODO - Transform this into a MAP instead
+func (va *VoltApplication) GetDeviceBySerialNo(slno string) *VoltDevice {
+ var device *VoltDevice
+ getserial := func(key interface{}, value interface{}) bool {
+ device = value.(*VoltDevice)
+ return device.SerialNum != slno
+ }
+ va.DevicesDisc.Range(getserial)
+ return device
+}
+
+// PortAddInd : This is a PORT add indication coming from the VPAgent, which is essentially
+// a request coming from VOLTHA. The device and identity of the port is provided
+// in this request. Add them to the application for further use
+func (va *VoltApplication) PortAddInd(device string, id uint32, portName string) {
+ logger.Infow(ctx, "Received Port Ind: Add", log.Fields{"Device": device, "Port": portName})
+ va.portLock.Lock()
+ if d := va.GetDevice(device); d != nil {
+ p := d.AddPort(portName, id)
+ va.PortsDisc.Store(portName, p)
+ va.portLock.Unlock()
+ nni, _ := va.GetNniPort(device)
+ if nni == portName {
+ d.pushFlowsForUnis()
+ }
+ } else {
+ va.portLock.Unlock()
+ logger.Warnw(ctx, "Device Not Found - Dropping Port Ind: Add", log.Fields{"Device": device, "Port": portName})
+ }
+}
+
+// PortDelInd : Only the NNI ports are recorded in the device for now. When port delete
+// arrives, only the NNI ports need adjustments.
+func (va *VoltApplication) PortDelInd(device string, port string) {
+ logger.Infow(ctx, "Received Port Ind: Delete", log.Fields{"Device": device, "Port": port})
+ if d := va.GetDevice(device); d != nil {
+ p := d.GetPort(port)
+ if p != nil && p.State == PortStateUp {
+ logger.Infow(ctx, "Port state is UP. Trigerring Port Down Ind before deleting", log.Fields{"Port": p})
+ va.PortDownInd(device, port)
+ }
+ va.portLock.Lock()
+ defer va.portLock.Unlock()
+ d.DelPort(port)
+ if _, ok := va.PortsDisc.Load(port); ok {
+ va.PortsDisc.Delete(port)
+ }
+ } else {
+ logger.Warnw(ctx, "Device Not Found - Dropping Port Ind: Delete", log.Fields{"Device": device, "Port": port})
+ }
+}
+
+//PortUpdateInd Updates port Id incase of ONU movement
+func (va *VoltApplication) PortUpdateInd(device string, portName string, id uint32) {
+ logger.Infow(ctx, "Received Port Ind: Update", log.Fields{"Device": device, "Port": portName})
+ va.portLock.Lock()
+ defer va.portLock.Unlock()
+ if d := va.GetDevice(device); d != nil {
+ vp := d.GetPort(portName)
+ vp.ID = id
+ } else {
+ logger.Warnw(ctx, "Device Not Found", log.Fields{"Device": device, "Port": portName})
+ }
+}
+
+// AddNbPonPort Add pon port to nbDevice
+func (va *VoltApplication) AddNbPonPort(oltSbID string, portID, maxAllowedChannels uint32,
+ enableMulticastKPI bool, portAlarmProfileID string) error {
+
+ var nbd *NbDevice
+ nbDevice, ok := va.NbDevice.Load(oltSbID)
+
+ if !ok {
+ nbd = NewNbDevice()
+ nbd.SouthBoundID = oltSbID
+ } else {
+ nbd = nbDevice.(*NbDevice)
+ }
+ port := nbd.AddPortToNbDevice(portID, maxAllowedChannels, enableMulticastKPI, portAlarmProfileID)
+
+ // Add this port to voltDevice
+ addPort := func(key, value interface{}) bool {
+ voltDevice := value.(*VoltDevice)
+ if oltSbID == voltDevice.SouthBoundID {
+ if _, exists := voltDevice.ActiveChannelsPerPon.Load(portID); !exists {
+ voltDevice.ActiveChannelsPerPon.Store(portID, port)
+ }
+ return false
+ }
+ return true
+ }
+ va.DevicesDisc.Range(addPort)
+ va.NbDevice.Store(oltSbID, nbd)
+
+ return nil
+}
+
+// UpdateNbPonPort update pon port to nbDevice
+func (va *VoltApplication) UpdateNbPonPort(oltSbID string, portID, maxAllowedChannels uint32, enableMulticastKPI bool, portAlarmProfileID string) error {
+
+ var nbd *NbDevice
+ nbDevice, ok := va.NbDevice.Load(oltSbID)
+
+ if !ok {
+ logger.Errorw(ctx, "Device-doesn't-exists", log.Fields{"deviceID": oltSbID})
+ return fmt.Errorf("Device-doesn't-exists-%v", oltSbID)
+ }
+ nbd = nbDevice.(*NbDevice)
+
+ port := nbd.UpdatePortToNbDevice(portID, maxAllowedChannels, enableMulticastKPI, portAlarmProfileID)
+ if port == nil {
+ return fmt.Errorf("Port-doesn't-exists-%v", portID)
+ }
+ va.NbDevice.Store(oltSbID, nbd)
+
+ // Add this port to voltDevice
+ updPort := func(key, value interface{}) bool {
+ voltDevice := value.(*VoltDevice)
+ if oltSbID == voltDevice.SouthBoundID {
+ voltDevice.ActiveChannelCountLock.Lock()
+ if p, exists := voltDevice.ActiveChannelsPerPon.Load(portID); exists {
+ oldPort := p.(*PonPortCfg)
+ if port.MaxActiveChannels != 0 {
+ oldPort.MaxActiveChannels = port.MaxActiveChannels
+ oldPort.EnableMulticastKPI = port.EnableMulticastKPI
+ voltDevice.ActiveChannelsPerPon.Store(portID, oldPort)
+ }
+ }
+ voltDevice.ActiveChannelCountLock.Unlock()
+ return false
+ }
+ return true
+ }
+ va.DevicesDisc.Range(updPort)
+
+ return nil
+}
+
+// DeleteNbPonPort Delete pon port to nbDevice
+func (va *VoltApplication) DeleteNbPonPort(oltSbID string, portID uint32) error {
+ nbDevice, ok := va.NbDevice.Load(oltSbID)
+ if ok {
+ nbDevice.(*NbDevice).DeletePortFromNbDevice(portID)
+ va.NbDevice.Store(oltSbID, nbDevice.(*NbDevice))
+ } else {
+ logger.Warnw(ctx, "Delete pon received for unknown device", log.Fields{"oltSbID": oltSbID})
+ return nil
+ }
+ // Delete this port from voltDevice
+ delPort := func(key, value interface{}) bool {
+ voltDevice := value.(*VoltDevice)
+ if oltSbID == voltDevice.SouthBoundID {
+ if _, exists := voltDevice.ActiveChannelsPerPon.Load(portID); exists {
+ voltDevice.ActiveChannelsPerPon.Delete(portID)
+ }
+ return false
+ }
+ return true
+ }
+ va.DevicesDisc.Range(delPort)
+ return nil
+}
+
+// GetNniPort : Get the NNI port for a device. Called from different other applications
+// as a port to match or destination for a packet out. The VOLT application
+// is written with the assumption that there is a single NNI port. The OLT
+// device is responsible for translating the combination of VLAN and the
+// NNI port ID to identify possibly a single physical port or a logical
+// port which is a result of protection methods applied.
+func (va *VoltApplication) GetNniPort(device string) (string, error) {
+ va.portLock.Lock()
+ defer va.portLock.Unlock()
+ d, ok := va.DevicesDisc.Load(device)
+ if !ok {
+ return "", errors.New("Device Doesn't Exist")
+ }
+ return d.(*VoltDevice).NniPort, nil
+}
+
+// NniDownInd process for Nni down indication.
+func (va *VoltApplication) NniDownInd(deviceID string, devSrNo string) {
+
+ logger.Debugw(ctx, "NNI Down Ind", log.Fields{"device": devSrNo})
+
+ handleIgmpDsFlows := func(key interface{}, value interface{}) bool {
+ mvProfile := value.(*MvlanProfile)
+ mvProfile.removeIgmpMcastFlows(devSrNo)
+ return true
+ }
+ va.MvlanProfilesByName.Range(handleIgmpDsFlows)
+
+ //Clear Static Group
+ va.ReceiverDownInd(deviceID, StaticPort)
+}
+
+// DeviceUpInd changes device state to up.
+func (va *VoltApplication) DeviceUpInd(device string) {
+ logger.Warnw(ctx, "Received Device Ind: UP", log.Fields{"Device": device})
+ if d := va.GetDevice(device); d != nil {
+ d.State = controller.DeviceStateUP
+ } else {
+ logger.Errorw(ctx, "Ignoring Device indication: UP. Device Missing", log.Fields{"Device": device})
+ }
+}
+
+// DeviceDownInd changes device state to down.
+func (va *VoltApplication) DeviceDownInd(device string) {
+ logger.Warnw(ctx, "Received Device Ind: DOWN", log.Fields{"Device": device})
+ if d := va.GetDevice(device); d != nil {
+ d.State = controller.DeviceStateDOWN
+ } else {
+ logger.Errorw(ctx, "Ignoring Device indication: DOWN. Device Missing", log.Fields{"Device": device})
+ }
+}
+
+// DeviceRebootInd process for handling flow clear flag for device reboot
+func (va *VoltApplication) DeviceRebootInd(device string, serialNum string, southBoundID string) {
+ logger.Warnw(ctx, "Received Device Ind: Reboot", log.Fields{"Device": device, "SerialNumber": serialNum})
+
+ if d := va.GetDevice(device); d != nil {
+ if d.State == controller.DeviceStateREBOOTED {
+ logger.Warnw(ctx, "Ignoring Device Ind: Reboot, Device already in Reboot state", log.Fields{"Device": device, "SerialNumber": serialNum, "State": d.State})
+ return
+ }
+ d.State = controller.DeviceStateREBOOTED
+ }
+ va.HandleFlowClearFlag(device, serialNum, southBoundID)
+
+}
+
+// DeviceDisableInd handles device deactivation process
+func (va *VoltApplication) DeviceDisableInd(device string) {
+ logger.Warnw(ctx, "Received Device Ind: Disable", log.Fields{"Device": device})
+
+ d := va.GetDevice(device)
+ if d == nil {
+ logger.Errorw(ctx, "Ignoring Device indication: DISABLED. Device Missing", log.Fields{"Device": device})
+ return
+ }
+
+ d.State = controller.DeviceStateDISABLED
+ va.HandleFlowClearFlag(device, d.SerialNum, d.SouthBoundID)
+}
+
+// ProcessIgmpDSFlowForMvlan for processing Igmp DS flow for device
+func (va *VoltApplication) ProcessIgmpDSFlowForMvlan(d *VoltDevice, mvp *MvlanProfile, addFlow bool) {
+
+ logger.Debugw(ctx, "Process IGMP DS Flows for MVlan", log.Fields{"device": d.Name, "Mvlan": mvp.Mvlan, "addFlow": addFlow})
+ portState := false
+ p := d.GetPort(d.NniPort)
+ if p != nil && p.State == PortStateUp {
+ portState = true
+ }
+
+ if addFlow {
+ if portState {
+ mvp.pushIgmpMcastFlows(d.SerialNum)
+ }
+ } else {
+ mvp.removeIgmpMcastFlows(d.SerialNum)
+ }
+}
+
+// ProcessIgmpDSFlowForDevice for processing Igmp DS flow for device
+func (va *VoltApplication) ProcessIgmpDSFlowForDevice(d *VoltDevice, addFlow bool) {
+ logger.Debugw(ctx, "Process IGMP DS Flows for device", log.Fields{"device": d.Name, "addFlow": addFlow})
+
+ handleIgmpDsFlows := func(key interface{}, value interface{}) bool {
+ mvProfile := value.(*MvlanProfile)
+ va.ProcessIgmpDSFlowForMvlan(d, mvProfile, addFlow)
+ return true
+ }
+ va.MvlanProfilesByName.Range(handleIgmpDsFlows)
+}
+
+// GetDeviceFromPort : This is suitable only for access ports as their naming convention
+// makes them unique across all the OLTs. This must be called with
+// port name that is an access port. Currently called from VNETs, attached
+// only to access ports, and the services which are also attached only
+// to access ports
+func (va *VoltApplication) GetDeviceFromPort(port string) (*VoltDevice, error) {
+ va.portLock.Lock()
+ defer va.portLock.Unlock()
+ var err error
+ err = nil
+ p, ok := va.PortsDisc.Load(port)
+ if !ok {
+ return nil, errorCodes.ErrPortNotFound
+ }
+ d := va.GetDevice(p.(*VoltPort).Device)
+ if d == nil {
+ err = errorCodes.ErrDeviceNotFound
+ }
+ return d, err
+}
+
+// GetPortID : This too applies only to access ports. The ports can be indexed
+// purely by their names without the device forming part of the key
+func (va *VoltApplication) GetPortID(port string) (uint32, error) {
+ va.portLock.Lock()
+ defer va.portLock.Unlock()
+ p, ok := va.PortsDisc.Load(port)
+ if !ok {
+ return 0, errorCodes.ErrPortNotFound
+ }
+ return p.(*VoltPort).ID, nil
+}
+
+// GetPortName : This too applies only to access ports. The ports can be indexed
+// purely by their names without the device forming part of the key
+func (va *VoltApplication) GetPortName(port uint32) (string, error) {
+ va.portLock.Lock()
+ defer va.portLock.Unlock()
+ var portName string
+ va.PortsDisc.Range(func(key interface{}, value interface{}) bool {
+ portInfo := value.(*VoltPort)
+ if portInfo.ID == port {
+ portName = portInfo.Name
+ return false
+ }
+ return true
+ })
+ return portName, nil
+}
+
+// GetPonFromUniPort to get Pon info from UniPort
+func (va *VoltApplication) GetPonFromUniPort(port string) (string, error) {
+ uniPortID, err := va.GetPortID(port)
+ if err == nil {
+ ponPortID := (uniPortID & 0x0FF00000) >> 20 //pon(8) + onu(8) + uni(12)
+ return strconv.FormatUint(uint64(ponPortID), 10), nil
+ }
+ return "", err
+}
+
+// GetPortState : This too applies only to access ports. The ports can be indexed
+// purely by their names without the device forming part of the key
+func (va *VoltApplication) GetPortState(port string) (PortState, error) {
+ va.portLock.Lock()
+ defer va.portLock.Unlock()
+ p, ok := va.PortsDisc.Load(port)
+ if !ok {
+ return 0, errors.New("Port not configured")
+ }
+ return p.(*VoltPort).State, nil
+}
+
+// GetIcmpv6Receivers to get Icmp v6 receivers
+func (va *VoltApplication) GetIcmpv6Receivers(device string) []uint32 {
+ var receiverList []uint32
+ receivers, _ := va.Icmpv6Receivers.Load(device)
+ if receivers != nil {
+ receiverList = receivers.([]uint32)
+ }
+ return receiverList
+}
+
+// AddIcmpv6Receivers to add Icmp v6 receivers
+func (va *VoltApplication) AddIcmpv6Receivers(device string, portID uint32) []uint32 {
+ var receiverList []uint32
+ receivers, _ := va.Icmpv6Receivers.Load(device)
+ if receivers != nil {
+ receiverList = receivers.([]uint32)
+ }
+ receiverList = append(receiverList, portID)
+ va.Icmpv6Receivers.Store(device, receiverList)
+ logger.Debugw(ctx, "Receivers after addition", log.Fields{"Receivers": receiverList})
+ return receiverList
+}
+
+// DelIcmpv6Receivers to delete Icmp v6 receievers
+func (va *VoltApplication) DelIcmpv6Receivers(device string, portID uint32) []uint32 {
+ var receiverList []uint32
+ receivers, _ := va.Icmpv6Receivers.Load(device)
+ if receivers != nil {
+ receiverList = receivers.([]uint32)
+ }
+ for i, port := range receiverList {
+ if port == portID {
+ receiverList = append(receiverList[0:i], receiverList[i+1:]...)
+ va.Icmpv6Receivers.Store(device, receiverList)
+ break
+ }
+ }
+ logger.Debugw(ctx, "Receivers After deletion", log.Fields{"Receivers": receiverList})
+ return receiverList
+}
+
+// ProcessDevFlowForDevice - Process DS ICMPv6 & ARP flow for provided device and vnet profile
+// device - Device Obj
+// vnet - vnet profile name
+// enabled - vlan enabled/disabled - based on the status, the flow shall be added/removed
+func (va *VoltApplication) ProcessDevFlowForDevice(device *VoltDevice, vnet *VoltVnet, enabled bool) {
+ _, applied := device.ConfiguredVlanForDeviceFlows.Get(VnetKey(vnet.SVlan, vnet.CVlan, 0))
+ if enabled {
+ va.PushDevFlowForVlan(vnet)
+ } else if !enabled && applied {
+ //va.DeleteDevFlowForVlan(vnet)
+ va.DeleteDevFlowForVlanFromDevice(vnet, device.SerialNum)
+ }
+}
+
+//NniVlanIndToIgmp - Trigger receiver up indication to all ports with igmp enabled
+//and has the provided mvlan
+func (va *VoltApplication) NniVlanIndToIgmp(device *VoltDevice, mvp *MvlanProfile) {
+
+ logger.Infow(ctx, "Sending Igmp Receiver UP indication for all Services", log.Fields{"Vlan": mvp.Mvlan})
+
+ //Trigger nni indication for receiver only for first time
+ if device.IgmpDsFlowAppliedForMvlan[uint16(mvp.Mvlan)] {
+ return
+ }
+ device.Ports.Range(func(key, value interface{}) bool {
+ port := key.(string)
+
+ if state, _ := va.GetPortState(port); state == PortStateUp {
+ vpvs, _ := va.VnetsByPort.Load(port)
+ if vpvs == nil {
+ return true
+ }
+ for _, vpv := range vpvs.([]*VoltPortVnet) {
+ //Send indication only for subscribers with the received mvlan profile
+ if vpv.IgmpEnabled && vpv.MvlanProfileName == mvp.Name {
+ vpv.services.Range(ReceiverUpInd)
+ }
+ }
+ }
+ return true
+ })
+}
+
+// PortUpInd :
+// -----------------------------------------------------------------------
+// Port status change handling
+// ----------------------------------------------------------------------
+// Port UP indication is passed to all services associated with the port
+// so that the services can configure flows applicable when the port goes
+// up from down state
+func (va *VoltApplication) PortUpInd(device string, port string) {
+ d := va.GetDevice(device)
+
+ if d == nil {
+ logger.Warnw(ctx, "Device Not Found - Dropping Port Ind: UP", log.Fields{"Device": device, "Port": port})
+ return
+ }
+
+ //Fixme: If Port Update Comes in large numbers, this will result in slow update per device
+ va.portLock.Lock()
+ // Do not defer the port mutex unlock here
+ // Some of the following func calls needs the port lock, so defering the lock here
+ // may lead to dead-lock
+ p := d.GetPort(port)
+
+ if p == nil {
+ logger.Infow(ctx, "Ignoring Port Ind: UP, Port doesnt exist", log.Fields{"Device": device, "PortName": port, "PortId": p})
+ va.portLock.Unlock()
+ return
+ }
+ p.State = PortStateUp
+ va.portLock.Unlock()
+
+ logger.Infow(ctx, "Received SouthBound Port Ind: UP", log.Fields{"Device": device, "PortName": port, "PortId": p.ID})
+ if p.Type == VoltPortTypeNni {
+
+ logger.Warnw(ctx, "Received NNI Port Ind: UP", log.Fields{"Device": device, "PortName": port, "PortId": p.ID})
+ //va.PushDevFlowForDevice(d)
+ //Build Igmp TrapFlowRule
+ //va.ProcessIgmpDSFlowForDevice(d, true)
+ }
+ vpvs, ok := va.VnetsByPort.Load(port)
+ if !ok || nil == vpvs || len(vpvs.([]*VoltPortVnet)) == 0 {
+ logger.Infow(ctx, "No VNETs on port", log.Fields{"Device": device, "Port": port})
+ //msgbus.ProcessPortInd(msgbus.PortUp, d.SerialNum, p.Name, false, getServiceList(port))
+ return
+ }
+
+ //If NNI port is not UP, do not push Flows
+ if d.NniPort == "" {
+ logger.Warnw(ctx, "NNI port not UP. Not sending Port UP Ind for VPVs", log.Fields{"NNI": d.NniPort})
+ return
+ }
+
+ vpvList := vpvs.([]*VoltPortVnet)
+ if vpvList[0].PonPort != 0xFF && vpvList[0].PonPort != p.PonPort {
+ logger.Errorw(ctx, "UNI port discovered on wrong PON Port. Dropping Port Indication", log.Fields{"Device": device, "Port": port, "DetectedPon": p.PonPort, "ExpectedPon": vpvList[0].PonPort})
+
+ //Remove the flow (if any) which are already installed - Valid for PON switching when VGC pod is DOWN
+ for _, vpv := range vpvs.([]*VoltPortVnet) {
+ vpv.VpvLock.Lock()
+ logger.Warnw(ctx, "Removing existing VPVs/Services flows for for Subscriber: UNI Detected on wrong PON", log.Fields{"Port": vpv.Port, "Vnet": vpv.VnetName})
+ vpv.PortDownInd(device, port)
+ if vpv.IgmpEnabled {
+ va.ReceiverDownInd(device, port)
+ }
+ vpv.VpvLock.Unlock()
+ }
+ return
+ }
+
+/*
+ if p.Type != VoltPortTypeNni {
+ // Process port up indication
+ indTask := cntlr.NewAddPortInd(p.Name, msgbus.PortUp, d.SerialNum, true, getServiceList(port))
+ cntlr.GetController().PostIndication(device, indTask)
+ }
+*/
+
+ for _, vpv := range vpvs.([]*VoltPortVnet) {
+ vpv.VpvLock.Lock()
+
+ //Do not trigger indication for the vpv which is already removed from vpv list as
+ // part of service delete (during the lock wait duration)
+ // In that case, the services associated wil be zero
+ if vpv.servicesCount.Load() != 0 {
+ vpv.PortUpInd(d, port)
+ }
+ vpv.VpvLock.Unlock()
+ }
+ // At the end of processing inform the other entities that
+ // are interested in the events
+}
+
+/*
+func getServiceList(port string) map[string]bool {
+ serviceList := make(map[string]bool)
+
+ getServiceNames := func(key interface{}, value interface{}) bool {
+ serviceList[key.(string)] = value.(*VoltService).DsHSIAFlowsApplied
+ return true
+ }
+
+ if vpvs, _ := GetApplication().VnetsByPort.Load(port); vpvs != nil {
+ vpvList := vpvs.([]*VoltPortVnet)
+ for _, vpv := range vpvList {
+ vpv.services.Range(getServiceNames)
+ }
+ }
+ return serviceList
+
+}*/
+
+//ReceiverUpInd - Send receiver up indication for service with Igmp enabled
+func ReceiverUpInd(key, value interface{}) bool {
+ svc := value.(*VoltService)
+ var vlan of.VlanType
+
+ if !svc.IPAssigned() {
+ logger.Infow(ctx, "IP Not assigned, skipping general query", log.Fields{"Service": svc})
+ return false
+ }
+
+ //Send port up indication to igmp only for service with igmp enabled
+ if svc.IgmpEnabled {
+ if svc.VlanControl == ONUCVlan || svc.VlanControl == ONUCVlanOLTSVlan {
+ vlan = svc.CVlan
+ } else {
+ vlan = svc.UniVlan
+ }
+ if device, _ := GetApplication().GetDeviceFromPort(svc.Port); device != nil {
+ GetApplication().ReceiverUpInd(device.Name, svc.Port, svc.MvlanProfileName, vlan, svc.Pbits)
+ }
+ return false
+ }
+ return true
+}
+
+// PortDownInd : Port down indication is passed on to the services so that the services
+// can make changes at this transition.
+func (va *VoltApplication) PortDownInd(device string, port string) {
+ logger.Infow(ctx, "Received SouthBound Port Ind: DOWN", log.Fields{"Device": device, "Port": port})
+ d := va.GetDevice(device)
+
+ if d == nil {
+ logger.Warnw(ctx, "Device Not Found - Dropping Port Ind: DOWN", log.Fields{"Device": device, "Port": port})
+ return
+ }
+ //Fixme: If Port Update Comes in large numbers, this will result in slow update per device
+ va.portLock.Lock()
+ // Do not defer the port mutex unlock here
+ // Some of the following func calls needs the port lock, so defering the lock here
+ // may lead to dead-lock
+ p := d.GetPort(port)
+ if p == nil {
+ logger.Infow(ctx, "Ignoring Port Ind: Down, Port doesnt exist", log.Fields{"Device": device, "PortName": port, "PortId": p})
+ va.portLock.Unlock()
+ return
+ }
+ p.State = PortStateDown
+ va.portLock.Unlock()
+
+ if d.State == controller.DeviceStateREBOOTED {
+ logger.Infow(ctx, "Ignoring Port Ind: Down, Device has been Rebooted", log.Fields{"Device": device, "PortName": port, "PortId": p})
+ return
+ }
+
+ if p.Type == VoltPortTypeNni {
+ logger.Warnw(ctx, "Received NNI Port Ind: DOWN", log.Fields{"Device": device, "Port": port})
+ va.DeleteDevFlowForDevice(d)
+ va.NniDownInd(device, d.SerialNum)
+ va.RemovePendingGroups(device, true)
+ }
+ vpvs, ok := va.VnetsByPort.Load(port)
+ if !ok || nil == vpvs || len(vpvs.([]*VoltPortVnet)) == 0 {
+ logger.Infow(ctx, "No VNETs on port", log.Fields{"Device": device, "Port": port})
+ //msgbus.ProcessPortInd(msgbus.PortDown, d.SerialNum, p.Name, false, getServiceList(port))
+ return
+ }
+/*
+ if p.Type != VoltPortTypeNni {
+ // Process port down indication
+ indTask := cntlr.NewAddPortInd(p.Name, msgbus.PortDown, d.SerialNum, true, getServiceList(port))
+ cntlr.GetController().PostIndication(device, indTask)
+ }
+*/
+ for _, vpv := range vpvs.([]*VoltPortVnet) {
+ vpv.VpvLock.Lock()
+ vpv.PortDownInd(device, port)
+ if vpv.IgmpEnabled {
+ va.ReceiverDownInd(device, port)
+ }
+ vpv.VpvLock.Unlock()
+ }
+}
+
+// PacketInInd :
+// -----------------------------------------------------------------------
+// PacketIn Processing
+// Packet In Indication processing. It arrives with the identities of
+// the device and port on which the packet is received. At first, the
+// packet is decoded and the right processor is called. Currently, we
+// plan to support only DHCP and IGMP. In future, we can add more
+// capabilities as needed
+func (va *VoltApplication) PacketInInd(device string, port string, pkt []byte) {
+ // Decode the incoming packet
+ packetSide := US
+ if strings.Contains(port, NNI) {
+ packetSide = DS
+ }
+
+ logger.Debugw(ctx, "Received a Packet-In Indication", log.Fields{"Device": device, "Port": port})
+
+ gopkt := gopacket.NewPacket(pkt, layers.LayerTypeEthernet, gopacket.Default)
+
+ var dot1qFound = false
+ for _, l := range gopkt.Layers() {
+ if l.LayerType() == layers.LayerTypeDot1Q {
+ dot1qFound = true
+ break
+ }
+ }
+
+ if !dot1qFound {
+ logger.Debugw(ctx, "Ignoring Received Packet-In Indication without Dot1Q Header",
+ log.Fields{"Device": device, "Port": port})
+ return
+ }
+
+ logger.Debugw(ctx, "Received Southbound Packet In", log.Fields{"Pkt": hex.EncodeToString(gopkt.Data())})
+
+ // Classify the packet into packet types that we support
+ // The supported types are DHCP and IGMP. The DHCP packet is
+ // identified by matching the L4 protocol to UDP. The IGMP packet
+ // is identified by matching L3 protocol to IGMP
+ arpl := gopkt.Layer(layers.LayerTypeARP)
+ if arpl != nil {
+ if callBack, ok := PacketHandlers[ARP]; ok {
+ callBack(device, port, gopkt)
+ } else {
+ logger.Debugw(ctx, "ARP handler is not registered, dropping the packet", log.Fields{"Pkt": hex.EncodeToString(gopkt.Data())})
+ }
+ return
+ }
+ ipv4l := gopkt.Layer(layers.LayerTypeIPv4)
+ if ipv4l != nil {
+ ip := ipv4l.(*layers.IPv4)
+
+ if ip.Protocol == layers.IPProtocolUDP {
+ logger.Debugw(ctx, "Received Southbound UDP ipv4 packet in", log.Fields{"StreamSide": packetSide})
+ dhcpl := gopkt.Layer(layers.LayerTypeDHCPv4)
+ if dhcpl != nil {
+ if callBack, ok := PacketHandlers[DHCPv4]; ok {
+ callBack(device, port, gopkt)
+ } else {
+ logger.Debugw(ctx, "DHCPv4 handler is not registered, dropping the packet", log.Fields{"Pkt": hex.EncodeToString(gopkt.Data())})
+ }
+ }
+ } else if ip.Protocol == layers.IPProtocolIGMP {
+ logger.Debugw(ctx, "Received Southbound IGMP packet in", log.Fields{"StreamSide": packetSide})
+ if callBack, ok := PacketHandlers[IGMP]; ok {
+ callBack(device, port, gopkt)
+ } else {
+ logger.Debugw(ctx, "IGMP handler is not registered, dropping the packet", log.Fields{"Pkt": hex.EncodeToString(gopkt.Data())})
+ }
+ }
+ return
+ }
+ ipv6l := gopkt.Layer(layers.LayerTypeIPv6)
+ if ipv6l != nil {
+ ip := ipv6l.(*layers.IPv6)
+ if ip.NextHeader == layers.IPProtocolUDP {
+ logger.Debug(ctx, "Received Southbound UDP ipv6 packet in")
+ dhcpl := gopkt.Layer(layers.LayerTypeDHCPv6)
+ if dhcpl != nil {
+ if callBack, ok := PacketHandlers[DHCPv6]; ok {
+ callBack(device, port, gopkt)
+ } else {
+ logger.Debugw(ctx, "DHCPv6 handler is not registered, dropping the packet", log.Fields{"Pkt": hex.EncodeToString(gopkt.Data())})
+ }
+ }
+ }
+ return
+ }
+
+ pppoel := gopkt.Layer(layers.LayerTypePPPoE)
+ if pppoel != nil {
+ logger.Debugw(ctx, "Received Southbound PPPoE packet in", log.Fields{"StreamSide": packetSide})
+ if callBack, ok := PacketHandlers[PPPOE]; ok {
+ callBack(device, port, gopkt)
+ } else {
+ logger.Debugw(ctx, "PPPoE handler is not registered, dropping the packet", log.Fields{"Pkt": hex.EncodeToString(gopkt.Data())})
+ }
+ }
+}
+
+// GetVlans : This utility gets the VLANs from the packet. The VLANs are
+// used to identify the right service that must process the incoming
+// packet
+func GetVlans(pkt gopacket.Packet) []of.VlanType {
+ var vlans []of.VlanType
+ for _, l := range pkt.Layers() {
+ if l.LayerType() == layers.LayerTypeDot1Q {
+ q, ok := l.(*layers.Dot1Q)
+ if ok {
+ vlans = append(vlans, of.VlanType(q.VLANIdentifier))
+ }
+ }
+ }
+ return vlans
+}
+
+// GetPriority to get priority
+func GetPriority(pkt gopacket.Packet) uint8 {
+ for _, l := range pkt.Layers() {
+ if l.LayerType() == layers.LayerTypeDot1Q {
+ q, ok := l.(*layers.Dot1Q)
+ if ok {
+ return q.Priority
+ }
+ }
+ }
+ return PriorityNone
+}
+
+// HandleFlowClearFlag to handle flow clear flag during reboot
+func (va *VoltApplication) HandleFlowClearFlag(deviceID string, serialNum, southBoundID string) {
+ logger.Warnw(ctx, "Clear All flags for Device", log.Fields{"Device": deviceID, "SerialNum": serialNum, "SBID": southBoundID})
+ dev, ok := va.DevicesDisc.Load(deviceID)
+ if ok && dev != nil {
+ logger.Infow(ctx, "Clear Flags for device", log.Fields{"voltDevice": dev.(*VoltDevice).Name})
+ dev.(*VoltDevice).icmpv6GroupAdded = false
+ logger.Infow(ctx, "Clearing DS Icmpv6 Map",
+ log.Fields{"voltDevice": dev.(*VoltDevice).Name})
+ dev.(*VoltDevice).ConfiguredVlanForDeviceFlows = util.NewConcurrentMap()
+ logger.Infow(ctx, "Clearing DS IGMP Map",
+ log.Fields{"voltDevice": dev.(*VoltDevice).Name})
+ for k := range dev.(*VoltDevice).IgmpDsFlowAppliedForMvlan {
+ delete(dev.(*VoltDevice).IgmpDsFlowAppliedForMvlan, k)
+ }
+ //Delete group 1 - ICMPv6/ARP group
+ if err := ProcessIcmpv6McGroup(deviceID, true); err != nil {
+ logger.Errorw(ctx, "ProcessIcmpv6McGroup failed", log.Fields{"Device": deviceID, "Error": err})
+ }
+ } else {
+ logger.Warnw(ctx, "VoltDevice not found for device ", log.Fields{"deviceID": deviceID})
+ }
+
+ getVpvs := func(key interface{}, value interface{}) bool {
+ vpvs := value.([]*VoltPortVnet)
+ for _, vpv := range vpvs {
+ if vpv.Device == deviceID {
+ logger.Infow(ctx, "Clear Flags for vpv",
+ log.Fields{"device": vpv.Device, "port": vpv.Port,
+ "svlan": vpv.SVlan, "cvlan": vpv.CVlan, "univlan": vpv.UniVlan})
+ vpv.ClearAllServiceFlags()
+ vpv.ClearAllVpvFlags()
+
+ if vpv.IgmpEnabled {
+ va.ReceiverDownInd(vpv.Device, vpv.Port)
+ //Also clear service igmp stats
+ vpv.ClearServiceCounters()
+ }
+ }
+ }
+ return true
+ }
+ va.VnetsByPort.Range(getVpvs)
+
+ //Clear Static Group
+ va.ReceiverDownInd(deviceID, StaticPort)
+
+ logger.Warnw(ctx, "All flags cleared for device", log.Fields{"Device": deviceID})
+
+ //Reset pending group pool
+ va.RemovePendingGroups(deviceID, true)
+
+ //Process all Migrate Service Request - force udpate all profiles since resources are already cleaned up
+ if dev != nil {
+ triggerForceUpdate := func(key, value interface{}) bool {
+ msrList := value.(*util.ConcurrentMap)
+ forceUpdateServices := func(key, value interface{}) bool {
+ msr := value.(*MigrateServicesRequest)
+ forceUpdateAllServices(msr)
+ return true
+ }
+ msrList.Range(forceUpdateServices)
+ return true
+ }
+ dev.(*VoltDevice).MigratingServices.Range(triggerForceUpdate)
+ } else {
+ va.FetchAndProcessAllMigrateServicesReq(deviceID, forceUpdateAllServices)
+ }
+}
+
+//GetPonPortIDFromUNIPort to get pon port id from uni port
+func GetPonPortIDFromUNIPort(uniPortID uint32) uint32 {
+ ponPortID := (uniPortID & 0x0FF00000) >> 20
+ return ponPortID
+}
+
+//ProcessFlowModResultIndication - Processes Flow mod operation indications from controller
+func (va *VoltApplication) ProcessFlowModResultIndication(flowStatus intf.FlowStatus) {
+
+ d := va.GetDevice(flowStatus.Device)
+ if d == nil {
+ logger.Errorw(ctx, "Dropping Flow Mod Indication. Device not found", log.Fields{"Cookie": flowStatus.Cookie, "Device": flowStatus.Device})
+ return
+ }
+
+ cookieExists := ExecuteFlowEvent(d, flowStatus.Cookie, flowStatus)
+
+ if flowStatus.Flow != nil {
+ flowAdd := (flowStatus.FlowModType == of.CommandAdd)
+ if !cookieExists && !isFlowStatusSuccess(flowStatus.Status, flowAdd) {
+ pushFlowFailureNotif(flowStatus)
+ }
+ }
+}
+
+func pushFlowFailureNotif(flowStatus intf.FlowStatus) {
+ subFlow := flowStatus.Flow
+ cookie := subFlow.Cookie
+ uniPort := cookie >> 16 & 0xFFFFFFFF
+ logger.Errorw(ctx, "Flow Failure Notification", log.Fields{"uniPort": uniPort, "Cookie": cookie})
+/*
+ device := flowStatus.Device
+ priority := subFlow.Priority
+ isIgmp := false
+ var devSerialNum string
+ var service *VoltService
+
+ if subFlow.Match.L4Protocol == of.IPProtocolIgmp {
+ isIgmp = true
+ } else if priority != of.HsiaFlowPriority {
+ logger.Info(ctx, "Not HSIA flow, ignoring the failure notification")
+ return
+ }
+
+ cookie := subFlow.Cookie
+ pbit := subFlow.Pbits
+ uniPort := cookie >> 16 & 0xFFFFFFFF
+ portName, _ := GetApplication().GetPortName(uint32(uniPort))
+ portState := msgbus.PortDown
+ logger.Errorw(ctx, "Construct Flow Failure Notification", log.Fields{"uniPort": uniPort, "Cookie": cookie, "Pbit": pbit, "isIgmp": isIgmp})
+
+ if isIgmp {
+ cvlan := subFlow.TableMetadata & 0xFFFF
+ service = GetApplication().GetMatchingMcastService(portName, device, of.VlanType(cvlan))
+ } else {
+ service = GetApplication().GetServiceNameFromCookie(cookie, portName, uint8(pbit), device, subFlow.TableMetadata)
+ }
+ var trigger infra.Reason
+ if nil != service {
+ logger.Errorw(ctx, "Sending Flow Failure Notification", log.Fields{"uniPort": uniPort, "Cookie": cookie, "Pbit": pbit, "Service": service.Name, "ErrorCode": flowStatus.Status})
+ if vd := GetApplication().GetDevice(device); vd != nil {
+ devSerialNum = vd.SerialNum
+ if portSt, _ := GetApplication().GetPortState(service.Port); portSt == PortStateUp {
+ portState = msgbus.PortUp
+ }
+ trigger = service.getSrvDeactTrigger(vd, portState)
+ }
+ msgbus.PostAccessConfigInd(msgbus.Failed, devSerialNum, msgbus.HSIA, service.Name, int(flowStatus.Status), subFlow.ErrorReason, trigger, portState)
+ }
+*/
+}
+
+//UpdateMvlanProfilesForDevice to update mvlan profile for device
+func (va *VoltApplication) UpdateMvlanProfilesForDevice(device string) {
+
+ checkAndAddMvlanUpdateTask := func(key, value interface{}) bool {
+ mvp := value.(*MvlanProfile)
+ if mvp.IsUpdateInProgressForDevice(device) {
+ mvp.UpdateProfile(device)
+ }
+ return true
+ }
+ va.MvlanProfilesByName.Range(checkAndAddMvlanUpdateTask)
+}
+
+// TaskInfo structure that is used to store the task Info.
+type TaskInfo struct {
+ ID string
+ Name string
+ Timestamp string
+}
+
+// GetTaskList to get task list information.
+func (va *VoltApplication) GetTaskList(device string) map[int]*TaskInfo {
+ taskList := cntlr.GetController().GetTaskList(device)
+ taskMap := make(map[int]*TaskInfo)
+ for i, task := range taskList {
+ taskID := strconv.Itoa(int(task.TaskID()))
+ name := task.Name()
+ timestamp := task.Timestamp()
+ taskInfo := &TaskInfo{ID: taskID, Name: name, Timestamp: timestamp}
+ taskMap[i] = taskInfo
+ }
+ return taskMap
+}
+
+// UpdateDeviceSerialNumberList to update the device serial number list after device serial number is updated for vnet and mvlan
+func (va *VoltApplication) UpdateDeviceSerialNumberList(oldOltSlNo string, newOltSlNo string) {
+
+ voltDevice := va.GetDeviceBySerialNo(oldOltSlNo)
+
+ if voltDevice != nil {
+ // Device is present with old serial number ID
+ logger.Errorw(ctx, "OLT Migration cannot be completed as there are dangling devices", log.Fields{"Serial Number": oldOltSlNo})
+
+ } else {
+ logger.Infow(ctx, "No device present with old serial number", log.Fields{"Serial Number": oldOltSlNo})
+
+ // Add Serial Number to Blocked Devices List.
+ cntlr.GetController().AddBlockedDevices(oldOltSlNo)
+ cntlr.GetController().AddBlockedDevices(newOltSlNo)
+
+ updateSlNoForVnet := func(key, value interface{}) bool {
+ vnet := value.(*VoltVnet)
+ for i, deviceSlNo := range vnet.VnetConfig.DevicesList {
+ if deviceSlNo == oldOltSlNo {
+ vnet.VnetConfig.DevicesList[i] = newOltSlNo
+ logger.Infow(ctx, "device serial number updated for vnet profile", log.Fields{"Updated Serial Number": deviceSlNo, "Previous Serial Number": oldOltSlNo})
+ break
+ }
+ }
+ return true
+ }
+
+ updateSlNoforMvlan := func(key interface{}, value interface{}) bool {
+ mvProfile := value.(*MvlanProfile)
+ for deviceSlNo := range mvProfile.DevicesList {
+ if deviceSlNo == oldOltSlNo {
+ mvProfile.DevicesList[newOltSlNo] = mvProfile.DevicesList[oldOltSlNo]
+ delete(mvProfile.DevicesList, oldOltSlNo)
+ logger.Infow(ctx, "device serial number updated for mvlan profile", log.Fields{"Updated Serial Number": deviceSlNo, "Previous Serial Number": oldOltSlNo})
+ break
+ }
+ }
+ return true
+ }
+
+ va.VnetsByName.Range(updateSlNoForVnet)
+ va.MvlanProfilesByName.Range(updateSlNoforMvlan)
+
+ // Clear the serial number from Blocked Devices List
+ cntlr.GetController().DelBlockedDevices(oldOltSlNo)
+ cntlr.GetController().DelBlockedDevices(newOltSlNo)
+
+ }
+}
+
+// GetVpvsForDsPkt to get vpv for downstream packets
+func (va *VoltApplication) GetVpvsForDsPkt(cvlan of.VlanType, svlan of.VlanType, clientMAC net.HardwareAddr,
+ pbit uint8) ([]*VoltPortVnet, error) {
+
+ var matchVPVs []*VoltPortVnet
+ findVpv := func(key, value interface{}) bool {
+ vpvs := value.([]*VoltPortVnet)
+ for _, vpv := range vpvs {
+ if vpv.isVlanMatching(cvlan, svlan) && vpv.MatchesPriority(pbit) != nil {
+ var subMac net.HardwareAddr
+ if NonZeroMacAddress(vpv.MacAddr) {
+ subMac = vpv.MacAddr
+ } else if vpv.LearntMacAddr != nil && NonZeroMacAddress(vpv.LearntMacAddr) {
+ subMac = vpv.LearntMacAddr
+ } else {
+ matchVPVs = append(matchVPVs, vpv)
+ continue
+ }
+ if util.MacAddrsMatch(subMac, clientMAC) {
+ matchVPVs = append([]*VoltPortVnet{}, vpv)
+ logger.Infow(ctx, "Matching VPV found", log.Fields{"Port": vpv.Port, "SVLAN": vpv.SVlan, "CVLAN": vpv.CVlan, "UNIVlan": vpv.UniVlan, "MAC": clientMAC})
+ return false
+ }
+ }
+ }
+ return true
+ }
+ va.VnetsByPort.Range(findVpv)
+
+ if len(matchVPVs) != 1 {
+ logger.Infow(ctx, "No matching VPV found or multiple vpvs found", log.Fields{"Match VPVs": matchVPVs, "MAC": clientMAC})
+ return nil, errors.New("No matching VPV found or multiple vpvs found")
+ }
+ return matchVPVs, nil
+}
+
+// GetMacInPortMap to get PORT value based on MAC key
+func (va *VoltApplication) GetMacInPortMap(macAddr net.HardwareAddr) string {
+ if NonZeroMacAddress(macAddr) {
+ va.macPortLock.Lock()
+ defer va.macPortLock.Unlock()
+ if port, ok := va.macPortMap[macAddr.String()]; ok {
+ logger.Debugw(ctx, "found-entry-macportmap", log.Fields{"MacAddr": macAddr.String(), "Port": port})
+ return port
+ }
+ }
+ logger.Infow(ctx, "entry-not-found-macportmap", log.Fields{"MacAddr": macAddr.String()})
+ return ""
+}
+
+// UpdateMacInPortMap to update MAC PORT (key value) information in MacPortMap
+func (va *VoltApplication) UpdateMacInPortMap(macAddr net.HardwareAddr, port string) {
+ if NonZeroMacAddress(macAddr) {
+ va.macPortLock.Lock()
+ va.macPortMap[macAddr.String()] = port
+ va.macPortLock.Unlock()
+ logger.Debugw(ctx, "updated-macportmap", log.Fields{"MacAddr": macAddr.String(), "Port": port})
+ }
+}
+
+// DeleteMacInPortMap to remove MAC key from MacPortMap
+func (va *VoltApplication) DeleteMacInPortMap(macAddr net.HardwareAddr) {
+ if NonZeroMacAddress(macAddr) {
+ port := va.GetMacInPortMap(macAddr)
+ va.macPortLock.Lock()
+ delete(va.macPortMap, macAddr.String())
+ va.macPortLock.Unlock()
+ logger.Debugw(ctx, "deleted-from-macportmap", log.Fields{"MacAddr": macAddr.String(), "Port": port})
+ }
+}
+
+//AddGroupToPendingPool - adds the IgmpGroup with active group table entry to global pending pool
+func (va *VoltApplication) AddGroupToPendingPool(ig *IgmpGroup) {
+ var grpMap map[*IgmpGroup]bool
+ var ok bool
+
+ va.PendingPoolLock.Lock()
+ defer va.PendingPoolLock.Unlock()
+
+ logger.Infow(ctx, "Adding IgmpGroup to Global Pending Pool", log.Fields{"GroupID": ig.GroupID, "GroupName": ig.GroupName, "GroupAddr": ig.GroupAddr, "PendingDevices": len(ig.Devices)})
+ // Do Not Reset any current profile info since group table entry tied to mvlan profile
+ // The PonVlan is part of set field in group installed
+ // Hence, Group created is always tied to the same mvlan profile until deleted
+
+ for device := range ig.Devices {
+ key := getPendingPoolKey(ig.Mvlan, device)
+
+ if grpMap, ok = va.IgmpPendingPool[key]; !ok {
+ grpMap = make(map[*IgmpGroup]bool)
+ }
+ grpMap[ig] = true
+
+ //Add grpObj reference to all associated devices
+ va.IgmpPendingPool[key] = grpMap
+ }
+}
+
+//RemoveGroupFromPendingPool - removes the group from global pending group pool
+func (va *VoltApplication) RemoveGroupFromPendingPool(device string, ig *IgmpGroup) bool {
+ GetApplication().PendingPoolLock.Lock()
+ defer GetApplication().PendingPoolLock.Unlock()
+
+ logger.Infow(ctx, "Removing IgmpGroup from Global Pending Pool", log.Fields{"Device": device, "GroupID": ig.GroupID, "GroupName": ig.GroupName, "GroupAddr": ig.GroupAddr, "PendingDevices": len(ig.Devices)})
+
+ key := getPendingPoolKey(ig.Mvlan, device)
+ if _, ok := va.IgmpPendingPool[key]; ok {
+ delete(va.IgmpPendingPool[key], ig)
+ return true
+ }
+ return false
+}
+
+//RemoveGroupsFromPendingPool - removes the group from global pending group pool
+func (va *VoltApplication) RemoveGroupsFromPendingPool(device string, mvlan of.VlanType) {
+ GetApplication().PendingPoolLock.Lock()
+ defer GetApplication().PendingPoolLock.Unlock()
+
+ logger.Infow(ctx, "Removing IgmpGroups from Global Pending Pool for given Deivce & Mvlan", log.Fields{"Device": device, "Mvlan": mvlan.String()})
+
+ key := getPendingPoolKey(mvlan, device)
+ va.RemoveGroupListFromPendingPool(key)
+}
+
+//RemoveGroupListFromPendingPool - removes the groups for provided key
+// 1. Deletes the group from device
+// 2. Delete the IgmpGroup obj and release the group ID to pool
+// Note: Make sure to obtain PendingPoolLock lock before calling this func
+func (va *VoltApplication) RemoveGroupListFromPendingPool(key string) {
+ if grpMap, ok := va.IgmpPendingPool[key]; ok {
+ delete(va.IgmpPendingPool, key)
+ for ig := range grpMap {
+ for device := range ig.Devices {
+ ig.DeleteIgmpGroupDevice(device)
+ }
+ }
+ }
+}
+
+//RemoveGroupDevicesFromPendingPool - removes the group from global pending group pool
+func (va *VoltApplication) RemoveGroupDevicesFromPendingPool(ig *IgmpGroup) {
+
+ logger.Infow(ctx, "Removing IgmpGroup for all devices from Global Pending Pool", log.Fields{"GroupID": ig.GroupID, "GroupName": ig.GroupName, "GroupAddr": ig.GroupAddr, "PendingDevices": len(ig.Devices)})
+ for device := range ig.PendingGroupForDevice {
+ va.RemoveGroupFromPendingPool(device, ig)
+ }
+}
+
+//GetGroupFromPendingPool - Returns IgmpGroup obj from global pending pool
+func (va *VoltApplication) GetGroupFromPendingPool(mvlan of.VlanType, device string) *IgmpGroup {
+
+ var ig *IgmpGroup
+
+ va.PendingPoolLock.Lock()
+ defer va.PendingPoolLock.Unlock()
+
+ key := getPendingPoolKey(mvlan, device)
+ logger.Infow(ctx, "Getting IgmpGroup from Global Pending Pool", log.Fields{"Device": device, "Mvlan": mvlan.String(), "Key": key})
+
+ //Gets all IgmpGrp Obj for the device
+ grpMap, ok := va.IgmpPendingPool[key]
+ if !ok || len(grpMap) == 0 {
+ logger.Infow(ctx, "Matching IgmpGroup not found in Global Pending Pool", log.Fields{"Device": device, "Mvlan": mvlan.String()})
+ return nil
+ }
+
+ //Gets a random obj from available grps
+ for ig = range grpMap {
+
+ //Remove grp obj reference from all devices associated in pending pool
+ for dev := range ig.Devices {
+ key := getPendingPoolKey(mvlan, dev)
+ delete(va.IgmpPendingPool[key], ig)
+ }
+
+ //Safety check to avoid re-allocating group already in use
+ if ig.NumDevicesActive() == 0 {
+ return ig
+ }
+
+ //Iteration will continue only if IG is not allocated
+ }
+ return nil
+}
+
+//RemovePendingGroups - removes all pending groups for provided reference from global pending pool
+// reference - mvlan/device ID
+// isRefDevice - true - Device as reference
+// false - Mvlan as reference
+func (va *VoltApplication) RemovePendingGroups(reference string, isRefDevice bool) {
+ va.PendingPoolLock.Lock()
+ defer va.PendingPoolLock.Unlock()
+
+ logger.Infow(ctx, "Removing IgmpGroups from Global Pending Pool", log.Fields{"Reference": reference, "isRefDevice": isRefDevice})
+
+ //Pending Pool key: "<mvlan>_<DeviceID>""
+ paramPosition := 0
+ if isRefDevice {
+ paramPosition = 1
+ }
+
+ // 1.Remove the Entry from pending pool
+ // 2.Deletes the group from device
+ // 3.Delete the IgmpGroup obj and release the group ID to pool
+ for key := range va.IgmpPendingPool {
+ keyParams := strings.Split(key, "_")
+ if keyParams[paramPosition] == reference {
+ va.RemoveGroupListFromPendingPool(key)
+ }
+ }
+}
+
+func getPendingPoolKey(mvlan of.VlanType, device string) string {
+ return mvlan.String() + "_" + device
+}
+
+func (va *VoltApplication) removeExpiredGroups() {
+ logger.Debug(ctx, "Check for expired Igmp Groups")
+ removeExpiredGroups := func(key interface{}, value interface{}) bool {
+ ig := value.(*IgmpGroup)
+ ig.removeExpiredGroupFromDevice()
+ return true
+ }
+ va.IgmpGroups.Range(removeExpiredGroups)
+}
+
+//TriggerPendingProfileDeleteReq - trigger pending profile delete request
+func (va *VoltApplication) TriggerPendingProfileDeleteReq(device string) {
+ va.TriggerPendingServiceDeleteReq(device)
+ va.TriggerPendingVpvDeleteReq(device)
+ va.TriggerPendingVnetDeleteReq(device)
+ logger.Warnw(ctx, "All Pending Profile Delete triggered for device", log.Fields{"Device": device})
+}
+
+//TriggerPendingServiceDeleteReq - trigger pending service delete request
+func (va *VoltApplication) TriggerPendingServiceDeleteReq(device string) {
+
+ logger.Warnw(ctx, "Pending Services to be deleted", log.Fields{"Count": len(va.ServicesToDelete)})
+ for serviceName := range va.ServicesToDelete {
+ logger.Debugw(ctx, "Trigger Service Delete", log.Fields{"Service": serviceName})
+ if vs := va.GetService(serviceName); vs != nil {
+ if vs.Device == device {
+ logger.Warnw(ctx, "Triggering Pending Service delete", log.Fields{"Service": vs.Name})
+ vs.DelHsiaFlows()
+ if vs.ForceDelete {
+ vs.DelFromDb()
+ /*
+ portState := msgbus.PortDown
+ if d, err := va.GetDeviceFromPort(vs.Port); d != nil {
+
+ if portSt, _ := GetApplication().GetPortState(vs.Port); portSt == PortStateUp {
+ portState = msgbus.PortUp
+ }
+ indTask := cntlr.NewAddServiceIndTask(vs.Name, d.SerialNum, msgbus.DelHSIA, msgbus.Success, "", portState, infra.DelHSIAFromNB)
+ cntlr.GetController().PostIndication(d.Name, indTask)
+ } else {
+ // Port Not found can occur during ONU movement. However, port delete had already handled flow deletion,
+ // hence indication can be sent immediately
+ var devSrNo string
+ logger.Errorw(ctx, "Device/Port not found. Send indication directly", log.Fields{"serviceName": vs.Name, "error": err})
+ if vd := va.GetDevice(vs.Device); vd != nil {
+ devSrNo = vd.SerialNum
+ }
+ msgbus.PostAccessConfigInd(msgbus.Success, devSrNo, msgbus.DelHSIA, vs.Name, 0, "", infra.DelHSIAFromNB, portState)
+ }*/
+ }
+ }
+ } else {
+ logger.Errorw(ctx, "Pending Service Not found", log.Fields{"Service": serviceName})
+ }
+ }
+}
+
+//TriggerPendingVpvDeleteReq - trigger pending VPV delete request
+func (va *VoltApplication) TriggerPendingVpvDeleteReq(device string) {
+
+ logger.Warnw(ctx, "Pending VPVs to be deleted", log.Fields{"Count": len(va.VoltPortVnetsToDelete)})
+ for vpv := range va.VoltPortVnetsToDelete {
+ if vpv.Device == device {
+ logger.Warnw(ctx, "Triggering Pending VPv flow delete", log.Fields{"Port": vpv.Port, "Device": vpv.Device, "Vnet": vpv.VnetName})
+ va.DelVnetFromPort(vpv.Port, vpv)
+ }
+ }
+}
+
+//TriggerPendingVnetDeleteReq - trigger pending vnet delete request
+func (va *VoltApplication) TriggerPendingVnetDeleteReq(device string) {
+
+ logger.Warnw(ctx, "Pending Vnets to be deleted", log.Fields{"Count": len(va.VnetsToDelete)})
+ for vnetName := range va.VnetsToDelete {
+ if vnetIntf, _ := va.VnetsByName.Load(vnetName); vnetIntf != nil {
+ vnet := vnetIntf.(*VoltVnet)
+ logger.Warnw(ctx, "Triggering Pending Vnet flows delete", log.Fields{"Vnet": vnet.Name})
+ if d := va.GetDeviceBySerialNo(vnet.PendingDeviceToDelete); d != nil && d.SerialNum == vnet.PendingDeviceToDelete {
+ va.DeleteDevFlowForVlanFromDevice(vnet, vnet.PendingDeviceToDelete)
+ va.deleteVnetConfig(vnet)
+ } else {
+ logger.Warn(ctx, "Vnet Delete Failed : Device Not Found", log.Fields{"Vnet": vnet.Name, "Device": vnet.PendingDeviceToDelete})
+ }
+ }
+ }
+}
diff --git a/internal/pkg/application/dhcprelay.go b/internal/pkg/application/dhcprelay.go
new file mode 100644
index 0000000..06aa3db
--- /dev/null
+++ b/internal/pkg/application/dhcprelay.go
@@ -0,0 +1,1343 @@
+/*
+* Copyright 2022-present Open Networking Foundation
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+ */
+
+package application
+
+import (
+ "encoding/hex"
+ "errors"
+ "net"
+ "sync"
+
+ "github.com/google/gopacket"
+ "github.com/google/gopacket/layers"
+
+ cntlr "voltha-go-controller/internal/pkg/controller"
+ "voltha-go-controller/internal/pkg/of"
+ "voltha-go-controller/internal/pkg/util"
+ "github.com/opencord/voltha-lib-go/v7/pkg/log"
+)
+
+// DhcpRelayState type
+type DhcpRelayState uint8
+
+const (
+ // DhcpRelayStateNone constant
+ DhcpRelayStateNone DhcpRelayState = iota
+ // DhcpRelayStateDiscover constant
+ DhcpRelayStateDiscover
+ // DhcpRelayStateOffer constant
+ DhcpRelayStateOffer
+ // DhcpRelayStateRequest constant
+ DhcpRelayStateRequest
+ // DhcpRelayStateAck constant
+ DhcpRelayStateAck
+ // DhcpRelayStateNAK constant
+ DhcpRelayStateNAK
+ // DhcpRelayStateRelease constant
+ DhcpRelayStateRelease
+)
+
+// RemoteIDType represents data type for various RemoteID types
+type RemoteIDType string
+
+// List of RemoteID types supported
+const (
+ MACAddress RemoteIDType = "MAC_ADDRESS"
+ CustomRemotedID RemoteIDType = "Custom"
+)
+
+// MaxLenDhcpv6DUID constant
+const MaxLenDhcpv6DUID = 130 // 2: DUID-Type, 128: MaxLen of DUID value
+
+// opt82 constant
+const opt82 = 82
+
+// Dhcpv6RelayState type
+type Dhcpv6RelayState uint8
+
+const (
+ // Dhcpv6RelayStateNone constant
+ Dhcpv6RelayStateNone Dhcpv6RelayState = iota
+ // Dhcpv6RelayStateSolicit constant
+ Dhcpv6RelayStateSolicit
+ // Dhcpv6RelayStateReply constant
+ Dhcpv6RelayStateReply
+ // Dhcpv6RelayStateRelease constant
+ Dhcpv6RelayStateRelease
+)
+
+var (
+ // ErrSessionDoNotExist error type
+ ErrSessionDoNotExist = errors.New("Session Doesn't Exist")
+)
+
+// IDhcpRelaySession to get dhcp session field value
+type IDhcpRelaySession interface {
+ GetCircuitID() []byte
+ GetRemoteID() []byte
+ GetNniVlans() (uint16, uint16)
+ GetDhcpState() DhcpRelayState
+ GetDhcpv6State() Dhcpv6RelayState
+ SetDhcpState(DhcpRelayState)
+ SetDhcpv6State(Dhcpv6RelayState)
+ SetMacAddr(net.HardwareAddr)
+ DhcpResultInd(*layers.DHCPv4)
+ Dhcpv6ResultInd(ipv6Addr net.IP, leaseTime uint32)
+}
+
+// DhcpRelayVnet : The DHCP relay sessions are stored in a map to be retrieved from when
+// a response is received from the network. The map uses the VLANs and the
+// the MAC address as key to finding the service
+// DHCP Relay Virtual Network hosts a set of DHCP relay sessions that belong
+// to the network. It supports two VLANs as its identify. If a single VLAN or
+// no VLAN is to be used, those two should be passed as 4096 (VlanNone)
+type DhcpRelayVnet struct {
+ OuterVlan uint16
+ InnerVlan uint16
+ sessions map[[6]byte]IDhcpRelaySession
+ sessionsv6 map[[MaxLenDhcpv6DUID]byte]IDhcpRelaySession
+ sessionLock sync.RWMutex
+}
+
+// DhcpNetworks hosts different DHCP networks that in turn hold the DHCP
+// sessions
+type DhcpNetworks struct {
+ Networks map[uint32]*DhcpRelayVnet
+}
+
+func init() {
+ RegisterPacketHandler(DHCPv4, ProcessUDP4Packet)
+ RegisterPacketHandler(DHCPv6, ProcessUDP6Packet)
+}
+
+// NewDhcpRelayVnet is constructor for a DHCP Relay Virtual network
+func NewDhcpRelayVnet(outerVlan uint16, innerVlan uint16) *DhcpRelayVnet {
+ var drv DhcpRelayVnet
+
+ drv.OuterVlan = outerVlan
+ drv.InnerVlan = innerVlan
+ drv.sessions = make(map[[6]byte]IDhcpRelaySession)
+ drv.sessionsv6 = make(map[[MaxLenDhcpv6DUID]byte]IDhcpRelaySession)
+ return &drv
+}
+
+// GetDhcpVnet to add dhcp vnet
+func (dn *DhcpNetworks) GetDhcpVnet(outerVlan uint16, innerVlan uint16) *DhcpRelayVnet {
+ comboVlan := uint32(outerVlan)<<16 + uint32(innerVlan)
+ drv, ok := dn.Networks[comboVlan]
+ if ok {
+ return drv
+ }
+ return nil
+}
+
+// AddDhcpVnet to add dhcp vnet
+func (dn *DhcpNetworks) AddDhcpVnet(outerVlan uint16, innerVlan uint16) *DhcpRelayVnet {
+ comboVlan := uint32(outerVlan)<<16 + uint32(innerVlan)
+ if drv, ok := dn.Networks[comboVlan]; ok {
+ return drv
+ }
+ drv := NewDhcpRelayVnet(outerVlan, innerVlan)
+ dn.Networks[comboVlan] = drv
+ return drv
+}
+
+// NewDhcpNetworks to get new dhcp network
+func NewDhcpNetworks() *DhcpNetworks {
+ var dn DhcpNetworks
+ dn.Networks = make(map[uint32]*DhcpRelayVnet)
+ return &dn
+}
+
+// AddDhcpSession to add dhcp session
+func (dn *DhcpNetworks) AddDhcpSession(pkt gopacket.Packet, session IDhcpRelaySession) error {
+ var key [6]byte
+ ethl := pkt.Layer(layers.LayerTypeEthernet)
+ eth, _ := ethl.(*layers.Ethernet)
+ addr := eth.SrcMAC
+ if len(addr) != 6 {
+ logger.Errorw(ctx, "Invalid MAC address", log.Fields{"Addr": addr})
+ return errors.New("Invalid MAC address")
+ }
+ copy(key[:], addr[0:6])
+
+ drv := dn.AddDhcpVnet(session.GetNniVlans())
+
+ drv.sessionLock.Lock()
+ drv.sessions[key] = session
+ drv.sessionLock.Unlock()
+ return nil
+}
+
+// DelDhcpSession to delete dhcp session
+func (dn *DhcpNetworks) DelDhcpSession(pkt gopacket.Packet, session IDhcpRelaySession) {
+ var key [6]byte
+ ethl := pkt.Layer(layers.LayerTypeEthernet)
+ eth, _ := ethl.(*layers.Ethernet)
+ addr := eth.SrcMAC
+ if len(addr) != 6 {
+ logger.Errorw(ctx, "Invalid MAC address", log.Fields{"Addr": addr})
+ return
+ }
+ copy(key[:], addr[0:6])
+ drv := dn.AddDhcpVnet(session.GetNniVlans())
+ drv.sessionLock.Lock()
+ delete(drv.sessions, key)
+ drv.sessionLock.Unlock()
+}
+
+// delDhcpSessions to delete dhcp sessions
+func delDhcpSessions(addr net.HardwareAddr, outervlan of.VlanType, innervlan of.VlanType, sessionKey [MaxLenDhcpv6DUID]byte) {
+ var key [6]byte
+ if addr == nil || !NonZeroMacAddress(addr) {
+ logger.Warnw(ctx, "Invalid MAC address", log.Fields{"Addr": addr})
+ return
+ }
+ copy(key[:], addr[0:6])
+ drv := dhcpNws.AddDhcpVnet(uint16(outervlan), uint16(innervlan))
+ drv.sessionLock.Lock()
+ delete(drv.sessions, key)
+ delete(drv.sessionsv6, sessionKey)
+ drv.sessionLock.Unlock()
+ logger.Infow(ctx, "DHCP Sessions deleted", log.Fields{"MAC": addr})
+}
+
+// AddDhcp6Session to add dhcpv6 session
+func (dn *DhcpNetworks) AddDhcp6Session(key [MaxLenDhcpv6DUID]byte, session IDhcpRelaySession) error {
+ outerVlan, innerVlan := session.GetNniVlans()
+ logger.Infow(ctx, "Adding Session", log.Fields{"outerVlan": outerVlan, "innerVlan": innerVlan, "Addr": key})
+ drv := dn.AddDhcpVnet(outerVlan, innerVlan)
+ drv.sessionLock.Lock()
+ drv.sessionsv6[key] = session
+ drv.sessionLock.Unlock()
+ return nil
+}
+
+// DelDhcp6Session to delete dhcpv6 session
+func (dn *DhcpNetworks) DelDhcp6Session(key [MaxLenDhcpv6DUID]byte, session IDhcpRelaySession) {
+ outerVlan, innerVlan := session.GetNniVlans()
+ logger.Infow(ctx, "Get Session", log.Fields{"OuterVLAN": outerVlan, "InnerVLAN": innerVlan, "Addr": key})
+ drv := dn.GetDhcpVnet(outerVlan, innerVlan)
+ drv.sessionLock.Lock()
+ delete(drv.sessionsv6, key)
+ drv.sessionLock.Unlock()
+}
+
+// GetDhcpSession to get dhcp session info
+func (dn *DhcpNetworks) GetDhcpSession(outerVlan uint16, innerVlan uint16, addr net.HardwareAddr) (IDhcpRelaySession, error) {
+ var key [6]byte
+ if len(addr) != 6 {
+ logger.Errorw(ctx, "Invalid MAC address", log.Fields{"Addr": addr})
+ return nil, errors.New("Invalid MAC address")
+ }
+ copy(key[:], addr[0:6])
+ drv := dn.AddDhcpVnet(outerVlan, innerVlan)
+ drv.sessionLock.RLock()
+ defer drv.sessionLock.RUnlock()
+ if session, ok := drv.sessions[key]; ok {
+ return session, nil
+ }
+ return nil, ErrSessionDoNotExist
+}
+
+// GetDhcp6Session to get Dhcp6Session
+func (dn *DhcpNetworks) GetDhcp6Session(outerVlan uint16, innerVlan uint16, key [MaxLenDhcpv6DUID]byte) (IDhcpRelaySession, error) {
+ logger.Infow(ctx, "Locating Session", log.Fields{"OuterVlan": outerVlan, "InnerVlan": innerVlan, "key": key})
+
+ drv := dn.AddDhcpVnet(outerVlan, innerVlan)
+ drv.sessionLock.RLock()
+ defer drv.sessionLock.RUnlock()
+ if session, ok := drv.sessionsv6[key]; ok {
+ return session, nil
+ }
+ return nil, ErrSessionDoNotExist
+}
+
+// GetVlansFromPacket to get vlans from the packet
+func GetVlansFromPacket(pkt gopacket.Packet) (innerVlan of.VlanType, outerVlan of.VlanType) {
+
+ vlans := GetVlans(pkt)
+ if len(vlans) == 1 {
+ outerVlan = vlans[0]
+ innerVlan = of.VlanNone
+ } else if len(vlans) == 0 {
+ innerVlan = of.VlanNone
+ outerVlan = of.VlanNone
+ } else {
+ innerVlan = vlans[1]
+ outerVlan = vlans[0]
+ }
+ return
+}
+
+// GetVnetForV4Nni to get vnet for v4 Nni
+func GetVnetForV4Nni(dhcp *layers.DHCPv4, cvlan of.VlanType, svlan of.VlanType, pbit uint8) ([]*VoltPortVnet, error) {
+ var err error
+ var session IDhcpRelaySession
+ var vpvList []*VoltPortVnet
+ logger.Infow(ctx, "Mac Obtained MAC: ", log.Fields{"Addr": dhcp.ClientHWAddr})
+ session, err = dhcpNws.GetDhcpSession(uint16(svlan), uint16(cvlan), dhcp.ClientHWAddr)
+
+ if session != nil {
+ vpv, ok := session.(*VoltPortVnet)
+ logger.Infow(ctx, "Session Exist: VPV found", log.Fields{"VPV": vpv})
+ if ok {
+ vpvList = append(vpvList, vpv)
+ return vpvList, nil
+ }
+ }
+
+ if err == ErrSessionDoNotExist {
+ //No DHCP Session found, find matching VPV to send the packet out
+ logger.Info(ctx, "Session Doesnt Exist: Finding matching VPV")
+ return GetApplication().GetVpvsForDsPkt(cvlan, svlan, dhcp.ClientHWAddr, pbit)
+ }
+ return nil, errors.New("The session retrieved of wrong type")
+}
+
+// GetVnetForV6Nni to get vnet for v6 Nni
+func GetVnetForV6Nni(dhcp *layers.DHCPv6, cvlan of.VlanType, svlan of.VlanType,
+ pbit uint8, clientMAC net.HardwareAddr) ([]*VoltPortVnet, net.HardwareAddr, error) {
+ var err error
+ var session IDhcpRelaySession
+ var vpvList []*VoltPortVnet
+
+ var sessionKey [MaxLenDhcpv6DUID]byte
+
+ clientDuid, decodedDuid := getDhcpv6ClientDUID(dhcp)
+ if clientDuid == nil || decodedDuid == nil {
+ copy(sessionKey[:], clientMAC)
+ } else {
+ copy(sessionKey[:], clientDuid[0:])
+ if decodedDuid.Type == layers.DHCPv6DUIDTypeLLT || decodedDuid.Type == layers.DHCPv6DUIDTypeLL {
+ clientMAC = decodedDuid.LinkLayerAddress
+ }
+ }
+ session, err = dhcpNws.GetDhcp6Session(uint16(svlan), uint16(cvlan), sessionKey)
+ if session != nil {
+ vpv, ok := session.(*VoltPortVnet)
+ logger.Infow(ctx, "Session Exist: VPV found", log.Fields{"VPV": vpv})
+ if ok {
+ vpvList = append(vpvList, vpv)
+ return vpvList, clientMAC, nil
+ }
+ }
+
+ if err == ErrSessionDoNotExist {
+ //No DHCP Session found, find matching VPV to send the packet out
+ logger.Info(ctx, "Session Doesnt Exist: Finding matching VPV")
+ vpvList, err := GetApplication().GetVpvsForDsPkt(cvlan, svlan, clientMAC, pbit)
+ return vpvList, clientMAC, err
+ }
+ return nil, clientMAC, errors.New("The session retrieved of wrong type")
+}
+
+/*
+// getDhcpv4ClientMacAddr to get mac address for dhcpv4 client
+func getDhcpv4ClientMacAddr(pkt gopacket.Packet) net.HardwareAddr {
+ dhcp := pkt.Layer(layers.LayerTypeDHCPv4).(*layers.DHCPv4)
+ logger.Infow(ctx, "Mac Obtained v4: ", log.Fields{"Addr": dhcp.ClientHWAddr})
+ return dhcp.ClientHWAddr
+}
+
+// getDhcpv6ClientMacAddr to get mac address for dhcpv6 client
+func getDhcpv6ClientMacAddr(dhcpv6 *layers.DHCPv6) net.HardwareAddr {
+ var cID layers.DHCPv6Option
+ for _, option := range dhcpv6.Options {
+ if option.Code == layers.DHCPv6OptClientID {
+ cID = option
+ }
+ }
+ duid := &layers.DHCPv6DUID{}
+
+ //If cID is not found, DecodeFromBytes() returns error on empty cID
+ if err := duid.DecodeFromBytes(cID.Data); err == nil {
+ logger.Infow(ctx, "Mac Obtained v6: ", log.Fields{"Addr": duid.LinkLayerAddress, "Option": cID.String()})
+ return duid.LinkLayerAddress
+ }
+ return nil
+}*/
+
+// getDhcpv6ClientDUID to get Dhcpv6 client DUID
+func getDhcpv6ClientDUID(dhcpv6 *layers.DHCPv6) ([]byte, *layers.DHCPv6DUID) {
+
+ for _, option := range dhcpv6.Options {
+ logger.Debugw(ctx, "DHCPv6 Options", log.Fields{"option": option.Code})
+ if option.Code == layers.DHCPv6OptClientID {
+ duid := &layers.DHCPv6DUID{}
+ err := duid.DecodeFromBytes(option.Data)
+ if err == nil {
+ logger.Infow(ctx, "ClientIdentifier", log.Fields{"DUID": duid, "Option": option.String()})
+ duidLen := len(option.Data)
+ if duidLen > 130 {
+ duidLen = 130
+ }
+ return option.Data[0:duidLen], duid
+ }
+ logger.Errorw(ctx, "Client DUID decode failed", log.Fields{"error": err})
+ break
+ }
+ }
+ logger.Error(ctx, "Client DUID is not present in the packet")
+ return nil, nil
+}
+
+// AddDhcpv4Option82 : DHCPv4 packet operations
+// Addition of DHCP Option 82 which codes circuit-id and remote-id
+// into the packet. This happens as the request is relayed to the
+// DHCP servers on the NNI
+func AddDhcpv4Option82(svc *VoltService, rID []byte, dhcpv4 *layers.DHCPv4) {
+ //NOTE : both cID and rID should not be empty if this function is called
+ cID := svc.GetCircuitID()
+ var data []byte
+ if len(cID) != 0 {
+ data = append(data, 0x01)
+ data = append(data, byte(len(cID)))
+ data = append(data, cID...)
+ }
+ if len(rID) != 0 {
+ data = append(data, 0x02)
+ data = append(data, byte(len(rID)))
+ data = append(data, rID...)
+ }
+
+ if svc.isDataRateAttrPresent() {
+ minDrUs := util.Uint32ToByte(svc.MinDataRateUs)
+ data = append(data, TYPEMINDATAUS)
+ data = append(data, byte(len(minDrUs)))
+ data = append(data, minDrUs...)
+
+ minDrDs := util.Uint32ToByte(svc.MinDataRateDs)
+ data = append(data, TYPEMINDATADS)
+ data = append(data, byte(len(minDrDs)))
+ data = append(data, minDrDs...)
+
+ maxDrUs := util.Uint32ToByte(svc.MaxDataRateUs)
+ data = append(data, TYPEMAXDATAUS)
+ data = append(data, byte(len(maxDrUs)))
+ data = append(data, maxDrUs...)
+
+ maxDrDs := util.Uint32ToByte(svc.MaxDataRateDs)
+ data = append(data, TYPEMAXDATADS)
+ data = append(data, byte(len(maxDrDs)))
+ data = append(data, maxDrDs...)
+ }
+
+ option := layers.NewDHCPOption(82, data)
+ dhcpv4.Options = append(dhcpv4.Options, option)
+}
+
+// DelOption82 : Deletion of option 82 from the packet received on the NNI interface.
+// Once the packet is received, the option 82 is stripped off and the
+// packet is forwarded towards access
+func DelOption82(dhcpv4 *layers.DHCPv4) {
+ for index, option := range dhcpv4.Options {
+ if option.Type == opt82 {
+ dhcpv4.Options = append(dhcpv4.Options[0:index], dhcpv4.Options[index+1:]...)
+ return
+ }
+ }
+}
+
+// DhcpMsgType returns the DHCP message type from the packet
+func DhcpMsgType(dhcp *layers.DHCPv4) layers.DHCPMsgType {
+ for _, option := range dhcp.Options {
+ if option.Type == layers.DHCPOptMessageType {
+ return layers.DHCPMsgType(option.Data[0])
+ }
+ }
+ return layers.DHCPMsgTypeUnspecified
+}
+
+// GetIpv4Addr returns the IP address in the DHCP reply
+func GetIpv4Addr(dhcp *layers.DHCPv4) (net.IP, int64) {
+ var leaseTime uint32
+ for _, opt := range dhcp.Options {
+ if opt.Type == layers.DHCPOptLeaseTime {
+ leaseTime = GetIPv4LeaseTime(opt)
+ }
+ }
+ return dhcp.YourClientIP, int64(leaseTime)
+}
+
+//GetIPv4LeaseTime get ip lease time
+func GetIPv4LeaseTime(opt layers.DHCPOption) uint32 {
+ return uint32(opt.Data[0])<<24 | uint32(opt.Data[1])<<16 | uint32(opt.Data[2])<<8 | uint32(opt.Data[3])
+}
+
+// GetIpv6Addr returns the IPv6 address in the DHCPv6 reply
+func GetIpv6Addr(dhcp6 *layers.DHCPv6) (net.IP, uint32) {
+ var ipv6Addr net.IP
+ var leaseTime uint32
+
+ //Check for IANA allocation, if not present, then look for IAPD allocation
+ if dhcp6.MsgType == layers.DHCPv6MsgTypeReply {
+ ipv6Addr, leaseTime = GetIANAAddress(dhcp6)
+ if ipv6Addr == nil {
+ ipv6Addr, leaseTime = GetIAPDAddress(dhcp6)
+ }
+ }
+ return ipv6Addr, leaseTime
+}
+
+// GetIANAAddress returns the IPv6 address in the DHCPv6 reply
+func GetIANAAddress(dhcp6 *layers.DHCPv6) (net.IP, uint32) {
+ var ipv6Addr net.IP
+ var leaseTime uint32
+ if dhcp6.MsgType == layers.DHCPv6MsgTypeReply {
+ for _, o := range dhcp6.Options {
+ if o.Code == layers.DHCPv6OptIANA {
+
+ iana := &layers.DHCPv6IANA{}
+ err := iana.DecodeFromBytes(o.Data)
+ if err == nil {
+ ipv6Addr = iana.IA.IPv6Addr
+ leaseTime = iana.IA.ValidLifeTime
+ logger.Debugw(ctx, "IPv6 Allocated", log.Fields{"IANA IPv6": ipv6Addr})
+ return ipv6Addr, leaseTime
+ }
+ logger.Warn(ctx, "Decode of IANA Failed", log.Fields{"Reason": err.Error()})
+ break
+ }
+ }
+ }
+ return nil, 0
+}
+
+// GetIAPDAddress returns the IPv6 address in the DHCPv6 reply
+func GetIAPDAddress(dhcp6 *layers.DHCPv6) (net.IP, uint32) {
+ var ipv6Addr net.IP
+ var leaseTime uint32
+ if dhcp6.MsgType == layers.DHCPv6MsgTypeReply {
+ for _, o := range dhcp6.Options {
+ if o.Code == layers.DHCPv6OptIAPD {
+
+ iapd := &layers.DHCPv6IAPD{}
+ if err := iapd.DecodeFromBytes(o.Data); err == nil {
+ ipv6Addr = iapd.PD.Prefix
+ leaseTime = iapd.PD.ValidLifeTime
+ logger.Debugw(ctx, "IPv6 Allocated", log.Fields{"IAPD IPv6": ipv6Addr})
+ break
+ } else {
+ logger.Warn(ctx, "Decode of IAPD Failed", log.Fields{"Reason": err.Error()})
+ break
+ }
+ }
+ }
+ }
+ return ipv6Addr, leaseTime
+}
+
+// ProcessDsDhcpv4Packet : DHCPv4 packet processor functions
+// This function processes DS DHCP packet received on the NNI port.
+// The services are attached to the access ports. Thus, the DHCP
+// session is derived from the list of DHCP sessions stored in the
+// common map. The key for retrieval includes the VLAN tags in the
+// the packet and the MAC address of the client.
+func (va *VoltApplication) ProcessDsDhcpv4Packet(device string, port string, pkt gopacket.Packet) {
+
+ // Retrieve the layers to build the outgoing packet. It is not
+ // possible to add/remove layers to the existing packet and thus
+ // the lyayers are extracted to build the outgoing packet
+ eth := pkt.Layer(layers.LayerTypeEthernet).(*layers.Ethernet)
+ ip := pkt.Layer(layers.LayerTypeIPv4).(*layers.IPv4)
+ udp := pkt.Layer(layers.LayerTypeUDP).(*layers.UDP)
+ dhcp4 := pkt.Layer(layers.LayerTypeDHCPv4).(*layers.DHCPv4)
+ msgType := DhcpMsgType(dhcp4)
+
+ // Need to locate the service from the packet alone as the services
+ // are not attached to NNI port. The service is stored on DHCP relay
+ // application
+ logger.Infow(ctx, "Processing Southbound DS DHCPv4 packet", log.Fields{"Port": port, "Type": msgType})
+
+ // Retrieve the priority and drop eligible flags from the
+ // packet received
+ var priority uint8
+ var dsPbit uint8
+ var dropEligible bool
+ dot1ql := pkt.Layer(layers.LayerTypeDot1Q)
+ if dot1ql != nil {
+ dot1q := dot1ql.(*layers.Dot1Q)
+ priority = dot1q.Priority
+ dropEligible = dot1q.DropEligible
+ }
+
+ pktInnerlan, pktOuterlan := GetVlansFromPacket(pkt)
+ vpvList, _ := GetVnetForV4Nni(dhcp4, pktInnerlan, pktOuterlan, priority)
+ if len(vpvList) == 0 {
+ logger.Warn(ctx, "VNET couldn't be found for NNI")
+ return
+ }
+
+ // The DHCP option 82, if it exists is removed from the packet
+ DelOption82(dhcp4)
+ ipAddr, leaseTime := GetIpv4Addr(dhcp4)
+
+ for _, vpv := range vpvList {
+ dsPbit = vpv.GetRemarkedPriority(priority)
+ // Raise DHCP ACK/NCK indication
+ if vpv.DhcpRelay {
+ // Inform dhcp response information to dhcp server handler
+ dhcpResponseReceived(uint16(vpv.CVlan), uint16(vpv.SVlan))
+ // Process the Ack/Nack to track to state of the IP layer of the connection
+ if msgType == layers.DHCPMsgTypeAck || msgType == layers.DHCPMsgTypeNak {
+ // Install DS HSIA flows after DHCP ACK.
+ if msgType == layers.DHCPMsgTypeAck {
+ // Voltha will push US and DS HSIA flow on receivng the DS HSIA
+ // flow installation request, VGC to update US HSIA flow with leanrt MAC.
+ // separate go rotuine is spawned to avoid drop of ACK packet
+ // as HSIA flows will be deleted if new MAC is learnt.
+ go vpv.SetMacAddr(dhcp4.ClientHWAddr)
+ }
+ vpv.DhcpResultInd(dhcp4)
+
+ }
+ raiseDHCPv4Indication(msgType, vpv, dhcp4.ClientHWAddr, ipAddr, dsPbit, device, leaseTime)
+ }
+
+ // Create the outgoing bufer and set the checksum in the packet
+ buff := gopacket.NewSerializeBuffer()
+ if err := udp.SetNetworkLayerForChecksum(ip); err != nil {
+ logger.Error(ctx, "Error in setting checksum")
+ return
+ }
+ opts := gopacket.SerializeOptions{
+ FixLengths: true,
+ ComputeChecksums: true,
+ }
+
+ cTagType := layers.EthernetTypeIPv4
+ eth.EthernetType = layers.EthernetTypeDot1Q
+
+ var pktLayers []gopacket.SerializableLayer
+ pktLayers = append(pktLayers, eth)
+
+ var qVlans []of.VlanType
+ var qVlanLayers []gopacket.SerializableLayer
+
+ if vpv.AllowTransparent {
+ vlanThreshold := 2
+ // In case of ONU_CVLAN or OLT_SVLAN, the DS pkts have single configured vlan
+ // In case of ONU_CVLAN_OLT_SVLAN or OLT_CVLAN_OLT_SVLAN, the DS pkts have 2 configured vlan
+ // Based on that, the no. of vlans should be ignored to get only transparent vlans
+ if vpv.VlanControl == ONUCVlan || vpv.VlanControl == OLTSVlan || vpv.VlanControl == None {
+ vlanThreshold = 1
+ }
+ nxtLayer := layers.EthernetTypeDot1Q
+ if vlans := GetVlans(pkt); len(vlans) > vlanThreshold {
+ qVlans = vlans[vlanThreshold:]
+ cTagType = layers.EthernetTypeDot1Q
+ }
+ for i, qVlan := range qVlans {
+ vlan := uint16(qVlan)
+ if i == (len(qVlans) - 1) {
+ nxtLayer = layers.EthernetTypeIPv4
+ }
+ qdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: vlan, DropEligible: dropEligible, Type: nxtLayer}
+ qVlanLayers = append(qVlanLayers, qdot1q)
+ }
+ }
+ switch vpv.VlanControl {
+ case ONUCVlanOLTSVlan:
+ cdot1q := &layers.Dot1Q{Priority: dsPbit, VLANIdentifier: uint16(vpv.CVlan), DropEligible: dropEligible, Type: cTagType}
+ pktLayers = append(pktLayers, cdot1q)
+ case ONUCVlan,
+ None:
+ sdot1q := &layers.Dot1Q{Priority: dsPbit, VLANIdentifier: uint16(vpv.SVlan), DropEligible: dropEligible, Type: cTagType}
+ pktLayers = append(pktLayers, sdot1q)
+ case OLTCVlanOLTSVlan,
+ OLTSVlan:
+ udot1q := &layers.Dot1Q{Priority: dsPbit, VLANIdentifier: uint16(vpv.UniVlan), DropEligible: dropEligible, Type: cTagType}
+ pktLayers = append(pktLayers, udot1q)
+ default:
+ logger.Errorw(ctx, "Invalid Vlan Control Option", log.Fields{"Value": vpv.VlanControl})
+ }
+
+ pktLayers = append(pktLayers, qVlanLayers...)
+ pktLayers = append(pktLayers, ip)
+ pktLayers = append(pktLayers, udp)
+ pktLayers = append(pktLayers, dhcp4)
+ logger.Debugw(ctx, "Layers Count", log.Fields{"Count": len(pktLayers)})
+ if err := gopacket.SerializeMultiLayers(buff, opts, pktLayers); err != nil {
+ logger.Errorw(ctx, "Packet Serialization Failed", log.Fields{"Reason": err.Error()})
+ return
+ }
+
+ if err := cntlr.GetController().PacketOutReq(device, vpv.Port, port, buff.Bytes(), false); err != nil {
+ logger.Errorw(ctx, "PacketOutReq Failed", log.Fields{"Error" : err})
+ }
+ }
+}
+
+// raiseDHCPv4Indication process DHCPv4 packet and raise indication
+func raiseDHCPv4Indication(msgType layers.DHCPMsgType, vpv *VoltPortVnet, smac net.HardwareAddr,
+ ip net.IP, pktPbit uint8, device string, leaseTime int64) {
+
+ logger.Debugw(ctx, "Processing Dhcpv4 packet", log.Fields{"ethsrcMac": smac.String(),
+ "MacLearningInVPV": vpv.MacLearning, "MacConfigured": vpv.MacAddr, "dhcpType": msgType,
+ "vlanPriority": pktPbit, "VPVLearntMac": vpv.LearntMacAddr})
+
+ matchServiceAndRaiseInd := func(key, value interface{}) bool {
+ // walk through all svcs under vpv and match pbit with packet.
+ svc := value.(*VoltService)
+
+ if svc.IsPbitExist(of.PbitType(pktPbit)) {
+ logger.Debugw(ctx, "Matching Pbit found in service config", log.Fields{"ServiceName": svc.Name, "Pbit": pktPbit})
+ return false
+ }
+ return true
+ }
+
+ switch msgType {
+ case layers.DHCPMsgTypeDiscover, layers.DHCPMsgTypeRequest:
+ if msgType == layers.DHCPMsgTypeDiscover {
+ vpv.SetDhcpState(DhcpRelayStateDiscover)
+ } else if msgType == layers.DHCPMsgTypeRequest {
+ vpv.SetDhcpState(DhcpRelayStateRequest)
+ }
+ // Reset learnt mac address in case of DHCPv4 release
+ case layers.DHCPMsgTypeRelease:
+ vpv.LearntMacAddr, _ = net.ParseMAC("00:00:00:00:00:00")
+ vpv.services.Range(matchServiceAndRaiseInd)
+ vpv.SetDhcpState(DhcpRelayStateRelease)
+
+ case layers.DHCPMsgTypeAck, layers.DHCPMsgTypeNak:
+ vpv.services.Range(matchServiceAndRaiseInd)
+ if msgType == layers.DHCPMsgTypeAck {
+ vpv.SetDhcpState(DhcpRelayStateAck)
+ } else if msgType == layers.DHCPMsgTypeNak {
+ vpv.SetDhcpState(DhcpRelayStateNAK)
+ }
+ case layers.DHCPMsgTypeOffer:
+ vpv.SetDhcpState(DhcpRelayStateOffer)
+ }
+}
+
+// raiseDHCPv6Indication process DHCPv6 packet and raise indication
+func raiseDHCPv6Indication(msgType layers.DHCPv6MsgType, vpv *VoltPortVnet,
+ smac net.HardwareAddr, ip net.IP, pktPbit uint8, device string, leaseTime uint32) {
+
+ logger.Debugw(ctx, "Processing DHCPv6 packet", log.Fields{"dhcpType": msgType,
+ "vlanPriority": pktPbit, "dhcpClientMac": smac.String(),
+ "MacLearningInVPV": vpv.MacLearning, "MacConfigured": vpv.MacAddr,
+ "VPVLearntMac": vpv.LearntMacAddr})
+
+ matchServiceAndRaiseInd := func(key, value interface{}) bool {
+ svc := value.(*VoltService)
+ if svc.IsPbitExist(of.PbitType(pktPbit)) {
+ logger.Debugw(ctx, "Matching Pbit found in service config", log.Fields{"ServiceName": svc.Name, "Pbit": pktPbit})
+ return false
+ }
+ return true
+ }
+
+ switch msgType {
+ case layers.DHCPv6MsgTypeSolicit:
+ vpv.SetDhcpv6State(Dhcpv6RelayStateSolicit)
+ // Reset learnt mac address in case of DHCPv6 release
+ case layers.DHCPv6MsgTypeRelease:
+ vpv.LearntMacAddr, _ = net.ParseMAC("00:00:00:00:00:00")
+ vpv.services.Range(matchServiceAndRaiseInd)
+ vpv.SetDhcpv6State(Dhcpv6RelayStateRelease)
+
+ case layers.DHCPv6MsgTypeReply:
+ vpv.services.Range(matchServiceAndRaiseInd)
+ vpv.SetDhcpv6State(Dhcpv6RelayStateReply)
+ }
+}
+
+// ProcessUsDhcpv4Packet : The US DHCPv4 packet is identified the DHCP OP in the packet. A request is considered upstream
+// and the service associated with the packet is located by the port and VLANs in the packet
+func (va *VoltApplication) ProcessUsDhcpv4Packet(device string, port string, pkt gopacket.Packet) {
+ // We received the packet on an access port and the service for the packet can be
+ // gotten from the port and the packet
+ vpv, svc := va.GetVnetFromPkt(device, port, pkt)
+ if vpv == nil {
+ logger.Warn(ctx, "VNET couldn't be found from packet")
+ return
+ }
+
+ outport, _ := va.GetNniPort(device)
+ if outport == "" || outport == "0" {
+ logger.Errorw(ctx, "NNI Port not found for device. Dropping Packet", log.Fields{"NNI": outport})
+ return
+ }
+
+ // Extract the layers in the packet to prepare the outgoing packet
+ // We use the layers to build the outgoing packet from scratch as
+ // the packet received can't be modified to add/remove layers
+ eth := pkt.Layer(layers.LayerTypeEthernet).(*layers.Ethernet)
+ ip := pkt.Layer(layers.LayerTypeIPv4).(*layers.IPv4)
+ udp := pkt.Layer(layers.LayerTypeUDP).(*layers.UDP)
+ dhcp4 := pkt.Layer(layers.LayerTypeDHCPv4).(*layers.DHCPv4)
+ msgType := DhcpMsgType(dhcp4)
+ logger.Infow(ctx, "Processing Southbound US DHCPv4 packet", log.Fields{"Device": device, "Port": port, "Type": msgType})
+
+ // Learn the 8021P values from the packet received
+ var priority uint8
+ var dropEligible bool
+ dot1ql := pkt.Layer(layers.LayerTypeDot1Q)
+ if dot1ql != nil {
+ dot1q := dot1ql.(*layers.Dot1Q)
+ priority = dot1q.Priority
+ dropEligible = dot1q.DropEligible
+ }
+ // If this is the first message in the DHCP sequence, the service
+ // is added to the DHCP relay application. The reply packets locate
+ // the associated service/session from the relay application.
+ if msgType == layers.DHCPMsgTypeDiscover || msgType == layers.DHCPMsgTypeRequest {
+ if err := dhcpNws.AddDhcpSession(pkt, vpv); err != nil {
+ logger.Errorw(ctx, "Adding dhcp session failed", log.Fields{"Error": err})
+ }
+ }
+
+ // Raise mac-learnt(DHCP Discover) indication when mac learning is enabled and learnt mac
+ // is not same as received mac address. If mac learning disabled, we have mac address in the
+ // service configuration. Hence mac learnt indication is not raised
+ // Reset learnt mac address in case of DHCP release and raise the indication
+ if vpv.DhcpRelay {
+ // If this is the first message in the DHCP sequence, the service
+ // is added to the DHCP relay application. The reply packets locate
+ // the associated service/session from the relay application.
+ // DS HSIA flows will be added after DHCP ACK .
+ if msgType == layers.DHCPMsgTypeDiscover || msgType == layers.DHCPMsgTypeRequest {
+ if !util.MacAddrsMatch(vpv.MacAddr, dhcp4.ClientHWAddr) {
+ // MAC is different and relearning is disabled.
+ if NonZeroMacAddress(vpv.MacAddr) && vpv.MacLearning == Learn {
+ // update learnt mac for debug purpose
+ vpv.LearntMacAddr = dhcp4.ClientHWAddr
+ vpv.WriteToDb()
+ logger.Warnw(ctx, "Dropping the packet Mac relearn is disabled",
+ log.Fields{"vpv.MacAddr": vpv.MacAddr, "LearntMac": dhcp4.ClientHWAddr})
+ return
+ }
+ expectedPort := va.GetMacInPortMap(dhcp4.ClientHWAddr)
+ if expectedPort != "" && expectedPort != vpv.Port {
+ logger.Errorw(ctx, "mac-learnt-from-different-port-ignoring-dhcp-message", log.Fields{"MsgType": msgType, "ExpectedPort": expectedPort, "ReceivedPort": vpv.Port, "LearntMacAdrr": vpv.MacAddr, "NewMacAdrr": dhcp4.ClientHWAddr.String()})
+ return
+ }
+ }
+ }
+ raiseDHCPv4Indication(msgType, vpv, dhcp4.ClientHWAddr, vpv.Ipv4Addr, priority, device, 0)
+
+ // Check IsOption82Disabled flag in configuration. if true(disabled), do not add option82 into dhcpv4 header.
+ // Remote id can be custom or mac address.
+ // If remote id is custom, then add service will carry the remote id
+ // If remote id is mac address, and if mac is configured, then add service will carry the remote id
+ // If remote id is mac address, in mac learning case, then mac has to be taken from dhcp packet
+ if !svc.IsOption82Disabled {
+ var remoteID []byte
+ if svc.RemoteIDType == string(MACAddress) {
+ remoteID = []byte((dhcp4.ClientHWAddr).String())
+ } else if svc.RemoteID != nil {
+ remoteID = svc.RemoteID
+ }
+ AddDhcpv4Option82(svc, remoteID, dhcp4)
+ }
+ }
+
+ buff := gopacket.NewSerializeBuffer()
+ if err := udp.SetNetworkLayerForChecksum(ip); err != nil {
+ logger.Error(ctx, "Error in setting checksum")
+ return
+ }
+ opts := gopacket.SerializeOptions{
+ FixLengths: true,
+ ComputeChecksums: true,
+ }
+
+ cTagType := layers.EthernetTypeIPv4
+ outerVlan, innerVlan := vpv.GetNniVlans()
+ logger.Debugw(ctx, "Vnet Vlans", log.Fields{"Svlan": outerVlan, "Cvlan": innerVlan})
+ eth.EthernetType = vpv.SVlanTpid
+
+ var pktLayers []gopacket.SerializableLayer
+ pktLayers = append(pktLayers, eth)
+
+ var qVlans []of.VlanType
+ var qVlanLayers []gopacket.SerializableLayer
+
+ if vpv.AllowTransparent {
+ nxtLayer := layers.EthernetTypeDot1Q
+ if vlans := GetVlans(pkt); len(vlans) > 1 {
+ qVlans = vlans[1:]
+ logger.Debugw(ctx, "Q Vlans", log.Fields{"Vlan List": qVlans})
+ cTagType = layers.EthernetTypeDot1Q
+ }
+ for i, qVlan := range qVlans {
+ vlan := uint16(qVlan)
+ if i == (len(qVlans) - 1) {
+ nxtLayer = layers.EthernetTypeIPv4
+ }
+ qdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: vlan, DropEligible: dropEligible, Type: nxtLayer}
+ qVlanLayers = append(qVlanLayers, qdot1q)
+ }
+ }
+ switch vpv.VlanControl {
+ case ONUCVlanOLTSVlan,
+ OLTCVlanOLTSVlan:
+ sdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: outerVlan, DropEligible: dropEligible, Type: layers.EthernetTypeDot1Q}
+ pktLayers = append(pktLayers, sdot1q)
+ cdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: innerVlan, DropEligible: dropEligible, Type: cTagType}
+ pktLayers = append(pktLayers, cdot1q)
+ case ONUCVlan,
+ OLTSVlan,
+ None:
+ cdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: outerVlan, DropEligible: dropEligible, Type: cTagType}
+ pktLayers = append(pktLayers, cdot1q)
+ default:
+ logger.Errorw(ctx, "Invalid Vlan Control Option", log.Fields{"Value": vpv.VlanControl})
+ }
+
+ pktLayers = append(pktLayers, qVlanLayers...)
+ pktLayers = append(pktLayers, ip)
+ pktLayers = append(pktLayers, udp)
+ pktLayers = append(pktLayers, dhcp4)
+ logger.Debugw(ctx, "Layers Count", log.Fields{"Count": len(pktLayers)})
+ if err := gopacket.SerializeMultiLayers(buff, opts, pktLayers); err != nil {
+ return
+ }
+
+ // Now the packet constructed is output towards the switch to be emitted on
+ // the NNI port
+ if err := cntlr.GetController().PacketOutReq(device, outport, port, buff.Bytes(), false); err != nil {
+ logger.Errorw(ctx, "PacketOutReq Failed", log.Fields{"Error" : err})
+ }
+ if vpv.DhcpRelay {
+ // Inform dhcp request information to dhcp server handler
+ dhcpRequestReceived(uint16(vpv.CVlan), uint16(vpv.SVlan), eth.SrcMAC.String())
+ }
+}
+
+// ProcessUDP4Packet : CallBack function registered with application to handle DHCP packetIn
+func ProcessUDP4Packet(device string, port string, pkt gopacket.Packet) {
+ GetApplication().ProcessUDP4Packet(device, port, pkt)
+}
+
+// ProcessUDP4Packet : The packet is a UDP packet and currently only DHCP relay application is supported
+// We determine the packet direction and process it based on the direction
+func (va *VoltApplication) ProcessUDP4Packet(device string, port string, pkt gopacket.Packet) {
+ // Currently DHCP is the only application supported by the application
+ // We check for DHCP before proceeding futher. In future, this could be
+ // based on registration and the callbacks
+ dhcpl := pkt.Layer(layers.LayerTypeDHCPv4)
+ if dhcpl == nil {
+ return
+ }
+ //logger.Debugw(ctx, "Received Packet In", log.Fields{"Pkt": hex.EncodeToString(pkt.Data())})
+ dhcp4 := pkt.Layer(layers.LayerTypeDHCPv4).(*layers.DHCPv4)
+ if dhcp4.Operation == layers.DHCPOpRequest {
+ // This is treated as an upstream packet in the VOLT application
+ // as VOLT serves access subscribers who use DHCP to acquire IP
+ // address and these packets go upstream to the network
+ va.ProcessUsDhcpv4Packet(device, port, pkt)
+ } else {
+ // This is a downstream packet
+ va.ProcessDsDhcpv4Packet(device, port, pkt)
+ }
+
+}
+
+// ProcessUDP6Packet : CallBack function registered with application to handle DHCPv6 packetIn
+func ProcessUDP6Packet(device string, port string, pkt gopacket.Packet) {
+ GetApplication().ProcessUDP6Packet(device, port, pkt)
+}
+
+// ProcessUDP6Packet : As a LDRA node, we expect to see only RelayReply from the DHCP server and we always
+// pack the received request and send it to the server as a RelayForward message
+// We expect to see Solicit, Request in the most normal cases. Before the lease expires
+// we should also see Renew. However, we should always pack the US message by adding
+// additional option that identifies to the server that the DHCP packet is forwarded
+// by an LDRA node.
+func (va *VoltApplication) ProcessUDP6Packet(device string, port string, pkt gopacket.Packet) []byte {
+ dhcpl := pkt.Layer(layers.LayerTypeDHCPv6)
+ if dhcpl == nil {
+ return nil
+ }
+ logger.Infow(ctx, "Processing DHCPv6 packet", log.Fields{"Port": port})
+ dhcpv6 := dhcpl.(*layers.DHCPv6)
+ switch dhcpv6.MsgType {
+ case layers.DHCPv6MsgTypeSolicit, layers.DHCPv6MsgTypeRequest, layers.DHCPv6MsgTypeRenew,
+ layers.DHCPv6MsgTypeRelease, layers.DHCPv6MsgTypeRebind, layers.DHCPv6MsgTypeInformationRequest,
+ layers.DHCPv6MsgTypeDecline:
+ va.ProcessUsDhcpv6Packet(device, port, pkt)
+ case layers.DHCPv6MsgTypeAdvertise, layers.DHCPv6MsgTypeConfirm, layers.DHCPv6MsgTypeReconfigure:
+ logger.Warnw(ctx, "SouthBound DHCPv6 DS Messages Expected For a Relay Agent", log.Fields{"Type": dhcpv6.MsgType})
+ case layers.DHCPv6MsgTypeRelayForward:
+ logger.Warn(ctx, "As the first DHCPv6 Relay Agent, Unexpected Relay Forward")
+ case layers.DHCPv6MsgTypeRelayReply:
+ // We received a response from the server
+ va.ProcessDsDhcpv6Packet(device, port, pkt)
+ }
+ return nil
+}
+
+// GetRelayReplyBytes to get relay reply bytes
+func GetRelayReplyBytes(dhcp6 *layers.DHCPv6) []byte {
+ for _, o := range dhcp6.Options {
+ logger.Infow(ctx, "Received Option", log.Fields{"Code": o.Code})
+ if o.Code == layers.DHCPv6OptRelayMessage {
+ return o.Data
+ }
+ }
+ return nil
+}
+
+// BuildRelayFwd to build forward relay
+func BuildRelayFwd(paddr net.IP, intfID []byte, remoteID []byte, payload []byte, isOption82Disabled bool, dhcpRelay bool) *layers.DHCPv6 {
+ dhcp6 := &layers.DHCPv6{MsgType: layers.DHCPv6MsgTypeRelayForward, LinkAddr: net.ParseIP("::"), PeerAddr: []byte(paddr)}
+ dhcp6.Options = append(dhcp6.Options, layers.NewDHCPv6Option(layers.DHCPv6OptRelayMessage, payload))
+ // Check IsOption82Disabled flag in configuration. if true(disabled), do not add remoteID and circuitID into dhcpv6 header.
+ if dhcpRelay {
+ if !isOption82Disabled {
+ remote := &layers.DHCPv6RemoteId{RemoteId: remoteID}
+ if len(remoteID) != 0 {
+ dhcp6.Options = append(dhcp6.Options, layers.NewDHCPv6Option(layers.DHCPv6OptRemoteID, remote.Encode()))
+ }
+ if len(intfID) != 0 {
+ intf := &layers.DHCPv6IntfId{Data: intfID}
+ dhcp6.Options = append(dhcp6.Options, layers.NewDHCPv6Option(layers.DHCPv6OptInterfaceID, intf.Encode()))
+ }
+ }
+ }
+ return dhcp6
+}
+
+// ProcessUsDhcpv6Packet to rpocess upstream DHCPv6 packet
+func (va *VoltApplication) ProcessUsDhcpv6Packet(device string, port string, pkt gopacket.Packet) {
+ // We received the packet on an access port and the service for the packet can be
+ // gotten from the port and the packet
+ logger.Infow(ctx, "Processing Southbound US DHCPv6 packet", log.Fields{"Port": port})
+ logger.Debugw(ctx, "Packet IN", log.Fields{"Pkt": hex.EncodeToString(pkt.Data())})
+ vpv, svc := va.GetVnetFromPkt(device, port, pkt)
+ if vpv == nil {
+ logger.Warn(ctx, "VNET couldn't be found from packet")
+ return
+ }
+
+ outport, _ := va.GetNniPort(device)
+ if outport == "" || outport == "0" {
+ logger.Errorw(ctx, "NNI Port not found for device. Dropping Packet", log.Fields{"NNI": outport})
+ return
+ }
+
+ // Extract the layers in the packet to prepare the outgoing packet
+ // We use the layers to build the outgoing packet from scratch as
+ // the packet received can't be modified to add/remove layers
+ eth := pkt.Layer(layers.LayerTypeEthernet).(*layers.Ethernet)
+ ip := pkt.Layer(layers.LayerTypeIPv6).(*layers.IPv6)
+ udp := pkt.Layer(layers.LayerTypeUDP).(*layers.UDP)
+ idhcp6 := pkt.Layer(layers.LayerTypeDHCPv6).(*layers.DHCPv6)
+
+ // Remote id can be custom or mac address.
+ // If remote id is custom, then add service will carry the remote id
+ // If remote id is mac address, and if mac is configured, then add service will carry the remote id
+ // If remote id is mac address, in mac learning case, then mac has to be taken from dhcp packet
+ var remoteID []byte
+ if svc.RemoteIDType == string(MACAddress) {
+ remoteID = []byte((eth.SrcMAC).String())
+ } else if svc.RemoteID != nil {
+ remoteID = svc.RemoteID
+ }
+ dhcp6 := BuildRelayFwd(ip.SrcIP, svc.GetCircuitID(), remoteID, udp.Payload, svc.IsOption82Disabled, vpv.DhcpRelay)
+
+ var sourceMac = eth.SrcMAC
+ var sessionKey [MaxLenDhcpv6DUID]byte
+
+ clientDuid, decodedDuid := getDhcpv6ClientDUID(idhcp6)
+ if clientDuid == nil || decodedDuid == nil {
+ copy(sessionKey[:], eth.SrcMAC)
+ } else {
+ copy(sessionKey[:], clientDuid[0:])
+ if decodedDuid.Type == layers.DHCPv6DUIDTypeLLT || decodedDuid.Type == layers.DHCPv6DUIDTypeLL {
+ sourceMac = decodedDuid.LinkLayerAddress
+ }
+ }
+ // Learn the 8021P values from the packet received
+ var priority uint8
+ var dropEligible bool
+ dot1ql := pkt.Layer(layers.LayerTypeDot1Q)
+ if dot1ql != nil {
+ dot1q := dot1ql.(*layers.Dot1Q)
+ priority = dot1q.Priority
+ dropEligible = dot1q.DropEligible
+ }
+ if idhcp6.MsgType == layers.DHCPv6MsgTypeSolicit {
+ if err := dhcpNws.AddDhcp6Session(sessionKey, vpv); err != nil {
+ logger.Errorw(ctx, "Adding dhcpv6 session failed", log.Fields{"Error": err})
+ }
+ vpv.DHCPv6DUID = sessionKey
+ }
+
+ // Raise mac-learnt(DHCPv6MsgTypeSolicit) indication when mac learning is enabled and learnt mac
+ // is not same as received mac address. If mac learning disabled, we have mac address in the
+ // service configuration. Hence mac learnt indication is not raised
+ if vpv.DhcpRelay {
+ if idhcp6.MsgType == layers.DHCPv6MsgTypeSolicit {
+ if !util.MacAddrsMatch(vpv.MacAddr, sourceMac) {
+ // MAC is different and relearning is disabled.
+ if NonZeroMacAddress(vpv.MacAddr) && vpv.MacLearning == Learn {
+ // update learnt mac for debug purpose
+ vpv.LearntMacAddr = sourceMac
+ vpv.WriteToDb()
+ logger.Warnw(ctx, "Dropping the packet Mac relearn is disabled",
+ log.Fields{"vpv.MacAddr": vpv.MacAddr, "LearntMac": sourceMac})
+ return
+ }
+ expectedPort := va.GetMacInPortMap(sourceMac)
+ if expectedPort != "" && expectedPort != vpv.Port {
+ logger.Errorw(ctx, "mac-learnt-from-different-port-ignoring-dhcp-message", log.Fields{"MsgType": idhcp6.MsgType, "ExpectedPort": expectedPort, "ReceivedPort": vpv.Port, "LearntMacAdrr": vpv.MacAddr, "NewMacAdrr": sourceMac.String()})
+ return
+ }
+ }
+ }
+ raiseDHCPv6Indication(idhcp6.MsgType, vpv, sourceMac, vpv.Ipv6Addr, priority, device, 0)
+ }
+
+ // Create the buffer and the encode options for the outgoing packet
+ buff := gopacket.NewSerializeBuffer()
+ if err := udp.SetNetworkLayerForChecksum(ip); err != nil {
+ logger.Error(ctx, "Error in setting checksum")
+ return
+ }
+ opts := gopacket.SerializeOptions{
+ FixLengths: true,
+ ComputeChecksums: true,
+ }
+
+ cTagType := layers.EthernetTypeIPv6
+ outerVlan, innerVlan := vpv.GetNniVlans()
+ eth.EthernetType = vpv.SVlanTpid
+
+ var pktLayers []gopacket.SerializableLayer
+ pktLayers = append(pktLayers, eth)
+
+ var qVlans []of.VlanType
+ var qVlanLayers []gopacket.SerializableLayer
+
+ if vpv.AllowTransparent {
+ nxtLayer := layers.EthernetTypeDot1Q
+ if vlans := GetVlans(pkt); len(vlans) > 1 {
+ qVlans = vlans[1:]
+ cTagType = layers.EthernetTypeDot1Q
+ }
+ for i, qVlan := range qVlans {
+ vlan := uint16(qVlan)
+ if i == (len(qVlans) - 1) {
+ nxtLayer = layers.EthernetTypeIPv6
+ }
+ qdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: vlan, DropEligible: dropEligible, Type: nxtLayer}
+ qVlanLayers = append(qVlanLayers, qdot1q)
+ }
+
+ }
+ switch vpv.VlanControl {
+ case ONUCVlanOLTSVlan,
+ OLTCVlanOLTSVlan:
+ sdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: outerVlan, DropEligible: dropEligible, Type: layers.EthernetTypeDot1Q}
+ pktLayers = append(pktLayers, sdot1q)
+ cdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: innerVlan, DropEligible: dropEligible, Type: cTagType}
+ pktLayers = append(pktLayers, cdot1q)
+ case ONUCVlan,
+ OLTSVlan,
+ None:
+ cdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: outerVlan, DropEligible: dropEligible, Type: cTagType}
+ pktLayers = append(pktLayers, cdot1q)
+ default:
+ logger.Errorw(ctx, "Invalid Vlan Control Option", log.Fields{"Value": vpv.VlanControl})
+ }
+
+ pktLayers = append(pktLayers, qVlanLayers...)
+ pktLayers = append(pktLayers, ip)
+ pktLayers = append(pktLayers, udp)
+ pktLayers = append(pktLayers, dhcp6)
+ logger.Debugw(ctx, "Layers Count", log.Fields{"Count": len(pktLayers)})
+ if err := gopacket.SerializeMultiLayers(buff, opts, pktLayers); err != nil {
+ return
+ }
+ // Now the packet constructed is output towards the switch to be emitted on
+ // the NNI port
+ if err := cntlr.GetController().PacketOutReq(device, outport, port, buff.Bytes(), false); err != nil {
+ logger.Errorw(ctx, "PacketOutReq Failed", log.Fields{"Error" : err})
+ }
+ if vpv.DhcpRelay {
+ // Inform dhcp request information to dhcp server handler
+ dhcpRequestReceived(uint16(vpv.CVlan), uint16(vpv.SVlan), eth.SrcMAC.String())
+ }
+}
+
+// GetDhcpv6 to get dhcpv6 info
+func GetDhcpv6(payload []byte) (*layers.DHCPv6, error) {
+ pkt := gopacket.NewPacket(payload, layers.LayerTypeDHCPv6, gopacket.Default)
+ if dl := pkt.Layer(layers.LayerTypeDHCPv6); dl != nil {
+ if dhcp6, ok := dl.(*layers.DHCPv6); ok {
+ return dhcp6, nil
+ }
+ }
+ return nil, errors.New("Failed to decode DHCPv6")
+}
+
+// ProcessDsDhcpv6Packet to process downstream dhcpv6 packet
+func (va *VoltApplication) ProcessDsDhcpv6Packet(device string, port string, pkt gopacket.Packet) {
+ logger.Infow(ctx, "Processing Southbound DS DHCPv6 packet", log.Fields{"Port": port})
+ logger.Debugw(ctx, "Packet IN", log.Fields{"Pkt": hex.EncodeToString(pkt.Data())})
+
+ // Retrieve the layers to build the outgoing packet. It is not
+ // possible to add/remove layers to the existing packet and thus
+ // the lyayers are extracted to build the outgoing packet
+ // The DHCP layer is handled differently. The Relay-Reply option
+ // of DHCP is extracted and is made the UDP payload.
+ eth := pkt.Layer(layers.LayerTypeEthernet).(*layers.Ethernet)
+ ip := pkt.Layer(layers.LayerTypeIPv6).(*layers.IPv6)
+ udp := pkt.Layer(layers.LayerTypeUDP).(*layers.UDP)
+ idhcp6 := pkt.Layer(layers.LayerTypeDHCPv6).(*layers.DHCPv6)
+ //var dhcp6 *layers.DHCPv6
+ var payload []byte
+ if payload = GetRelayReplyBytes(idhcp6); payload == nil {
+ logger.Warn(ctx, "Didn't Receive RelayMessage IE")
+ return
+ }
+
+ dhcp6, err := GetDhcpv6(payload)
+ if err != nil {
+ logger.Warnw(ctx, "DHCPv6 Decode Failed", log.Fields{"Reason": err.Error()})
+ return
+ }
+
+ // Learn the 8021P values from the packet received
+ var priority uint8
+ var dsPbit uint8
+ var dropEligible bool
+ dot1ql := pkt.Layer(layers.LayerTypeDot1Q)
+ if dot1ql != nil {
+ dot1q := dot1ql.(*layers.Dot1Q)
+ priority = dot1q.Priority
+ dropEligible = dot1q.DropEligible
+ }
+
+ pktInnerlan, pktOuterlan := GetVlansFromPacket(pkt)
+ vpvList, clientMac, err := GetVnetForV6Nni(dhcp6, pktInnerlan, pktOuterlan, priority, eth.DstMAC)
+ if len(vpvList) == 0 {
+ logger.Warnw(ctx, "VNET couldn't be found for NNI", log.Fields{"Reason": err})
+ return
+ }
+
+ ipv6Addr, leaseTime := GetIpv6Addr(dhcp6)
+
+ for _, vpv := range vpvList {
+
+ dsPbit = vpv.GetRemarkedPriority(priority)
+ // Raise DHCPv6 Reply indication
+ if vpv.DhcpRelay {
+ // Inform dhcp response information to dhcp server handler
+ dhcpResponseReceived(uint16(vpv.CVlan), uint16(vpv.SVlan))
+
+ if dhcp6.MsgType == layers.DHCPv6MsgTypeReply && ipv6Addr != nil {
+ // separate go rotuine is spawned to avoid drop of ACK packet
+ // as HSIA flows will be deleted if new MAC is learnt.
+ if len(vpvList) == 1 {
+ go vpv.SetMacAddr(clientMac)
+ }
+ vpv.Dhcpv6ResultInd(ipv6Addr, leaseTime)
+ }
+ raiseDHCPv6Indication(dhcp6.MsgType, vpv, clientMac, ipv6Addr, dsPbit, device, leaseTime)
+ }
+
+ //Replace dst Port value to 546
+ udp.DstPort = 546
+ logger.Infow(ctx, "Packet Out UDP Port..", log.Fields{"UDP": udp, "Port": udp.DstPort})
+
+ // Create the buffer and the encode options for the outgoing packet
+ buff := gopacket.NewSerializeBuffer()
+ if err := udp.SetNetworkLayerForChecksum(ip); err != nil {
+ logger.Error(ctx, "Error in setting checksum")
+ return
+ }
+ opts := gopacket.SerializeOptions{
+ FixLengths: true,
+ ComputeChecksums: true,
+ }
+
+ cTagType := layers.EthernetTypeIPv6
+ eth.EthernetType = layers.EthernetTypeDot1Q
+
+ var pktLayers []gopacket.SerializableLayer
+ pktLayers = append(pktLayers, eth)
+
+ var qVlans []of.VlanType
+ var qVlanLayers []gopacket.SerializableLayer
+
+ if vpv.AllowTransparent {
+ vlanThreshold := 2
+ // In case of ONU_CVLAN or OLT_SVLAN, the DS pkts have single configured vlan
+ // In case of ONU_CVLAN_OLT_SVLAN or OLT_CVLAN_OLT_SVLAN, the DS pkts have 2 configured vlan
+ // Based on that, the no. of vlans should be ignored to get only transparent vlans
+ if vpv.VlanControl == ONUCVlan || vpv.VlanControl == OLTSVlan || vpv.VlanControl == None {
+ vlanThreshold = 1
+ }
+ nxtLayer := layers.EthernetTypeDot1Q
+ if vlans := GetVlans(pkt); len(vlans) > vlanThreshold {
+ qVlans = vlans[vlanThreshold:]
+ cTagType = layers.EthernetTypeDot1Q
+ }
+ for i, qVlan := range qVlans {
+ vlan := uint16(qVlan)
+ if i == (len(qVlans) - 1) {
+ nxtLayer = layers.EthernetTypeIPv6
+ }
+ qdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: vlan, DropEligible: dropEligible, Type: nxtLayer}
+ qVlanLayers = append(qVlanLayers, qdot1q)
+ }
+
+ }
+ switch vpv.VlanControl {
+ case ONUCVlanOLTSVlan:
+ cdot1q := &layers.Dot1Q{Priority: dsPbit, VLANIdentifier: uint16(vpv.CVlan), DropEligible: dropEligible, Type: cTagType}
+ pktLayers = append(pktLayers, cdot1q)
+ case ONUCVlan,
+ None:
+ sdot1q := &layers.Dot1Q{Priority: dsPbit, VLANIdentifier: uint16(vpv.SVlan), DropEligible: dropEligible, Type: cTagType}
+ pktLayers = append(pktLayers, sdot1q)
+ case OLTCVlanOLTSVlan,
+ OLTSVlan:
+ udot1q := &layers.Dot1Q{Priority: dsPbit, VLANIdentifier: uint16(vpv.UniVlan), DropEligible: dropEligible, Type: cTagType}
+ pktLayers = append(pktLayers, udot1q)
+ default:
+ logger.Errorw(ctx, "Invalid Vlan Control Option", log.Fields{"Value": vpv.VlanControl})
+ }
+
+ pktLayers = append(pktLayers, qVlanLayers...)
+ pktLayers = append(pktLayers, ip)
+ pktLayers = append(pktLayers, udp)
+ pktLayers = append(pktLayers, dhcp6)
+ logger.Debugw(ctx, "Layers Count", log.Fields{"Count": len(pktLayers)})
+ if err := gopacket.SerializeMultiLayers(buff, opts, pktLayers); err != nil {
+ logger.Errorw(ctx, "Packet Serialization Failed", log.Fields{"Reason": err.Error()})
+ return
+ }
+
+ if err := cntlr.GetController().PacketOutReq(device, vpv.Port, port, buff.Bytes(), false); err != nil {
+ logger.Errorw(ctx, "PacketOutReq Failed", log.Fields{"Reason": err.Error()})
+ }
+ }
+}
+
+// The DHCP relay application is maintained within the structures below
+var dhcpNws *DhcpNetworks
+
+func init() {
+ dhcpNws = NewDhcpNetworks()
+}
diff --git a/internal/pkg/application/dhcpserverhandler.go b/internal/pkg/application/dhcpserverhandler.go
new file mode 100644
index 0000000..c208910
--- /dev/null
+++ b/internal/pkg/application/dhcpserverhandler.go
@@ -0,0 +1,248 @@
+/*
+* Copyright 2022-present Open Networking Foundation
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+ */
+
+package application
+
+import (
+ "sync"
+ "time"
+
+ "github.com/opencord/voltha-lib-go/v7/pkg/log"
+)
+
+const (
+ dhcpTimeout uint8 = 60
+)
+
+// done channel required to gracefully stop dhcp server handler thread
+var done = make(chan bool)
+
+// dhcpServerInfo map having dhcp network as key and dhcp request response transaction as value
+var dhcpServerInfo map[dhcpServerTag]dhcpTransactionInfo
+
+// alarmsRaised is struct having array of dhcp network for which dhcp unreachable alarm raised
+var alarmsRaised alarmsRaisedInfo
+
+// mux is mutex variable used for lock unlock
+var mux sync.Mutex
+
+// StartDhcpServerHandler starts go routine periodically(every second) to verify DHCP server reachability.
+func StartDhcpServerHandler() {
+ // Intialize global dhcp map and ticker as one second
+ dhcpServerInfo = make(map[dhcpServerTag]dhcpTransactionInfo)
+ ticker := time.NewTicker(1 * time.Second)
+
+ // go routine runs checkDhcpTimeout every second and exit if done value is set.
+ go func() {
+ for {
+ select {
+ case <-done:
+ ticker.Stop()
+ return
+ case <-ticker.C:
+ mux.Lock()
+ checkDhcpTimeout()
+ mux.Unlock()
+
+ }
+ }
+ }()
+}
+
+// checkDhcpTimeout method called every second to verify dhcp timeout for each DHCP network
+func checkDhcpTimeout() {
+ // logger.Debugw(ctx, "[dhcptimeout] DHCP MAP Info", log.Fields{"Map": dhcpServerInfo})
+ for dsTag, dtInfo := range dhcpServerInfo {
+ dtInfo.decrementTimer()
+ if dtInfo.getTimer() == 0 {
+ logger.Debugw(ctx, "[dhcptimeout]Timer Expired", log.Fields{"ctag": dsTag.cTag, "stag": dsTag.sTag})
+ if dtInfo.getReceivedResponseCount() == 0 && !alarmsRaised.isexist(dsTag) {
+ alarmsRaised.add(dsTag)
+ logger.Infow(ctx, "Alarms Raised", log.Fields{"ctag": dsTag.cTag, "stag": dsTag.sTag})
+ }
+
+ // Reset helps in
+ // case 1: when 2 requests, 1 response received within timeout interval.
+ // case 2: 1 request and no response even after timeout. (Unreachable alarm raised)
+ // In both cases, reset method provides additional timeout to receive response before deleting
+ dtInfo.resetRequestResponseCount(dhcpTimeout)
+
+ // Delete dhcp entry in map and continue to process next entry if pending request set to 0
+ if dtInfo.getPendingRequestCount() == 0 {
+ delete(dhcpServerInfo, dsTag)
+ logger.Debugw(ctx, "[dhcptimeout]DhcpServerTag info removed", log.Fields{"ctag": dsTag.cTag, "stag": dsTag.sTag})
+ // logger.Debugw(ctx, "[dhcptimeout] DHCP MAP Info", log.Fields{"Map": dhcpServerInfo})
+ continue
+ }
+ }
+ // Update decremented timer value and continue loop
+ dhcpServerInfo[dsTag] = dtInfo
+ }
+}
+
+// dhcpRequestReceived called for every DHCP request received from client.
+func dhcpRequestReceived(cTag, sTag uint16, smac string) {
+ var dtInfo dhcpTransactionInfo
+ var valueExist bool
+ dsTag := newDhcpServerTag(cTag, sTag)
+
+ mux.Lock()
+ logger.Debugw(ctx, "dhcpRequestReceived", log.Fields{"ctag": cTag, "stag": sTag, "smac": smac})
+ if dtInfo, valueExist = dhcpServerInfo[dsTag]; !valueExist {
+ dtInfo = newDhcpTransactionInfo(dhcpTimeout, smac)
+ dtInfo.incrementPendingRequestCount()
+ }
+
+ // Source mac received in dhcp request is not same as dtInfo mac then
+ // Its new subscriber request, hence increment pending request count.
+ // If multiple dhcp request received with same mac are ignored.
+ if dtInfo.smac != smac {
+ dtInfo.incrementPendingRequestCount()
+ }
+
+ dhcpServerInfo[dsTag] = dtInfo
+ mux.Unlock()
+}
+
+// dhcpResponseReceived called for every DHCP response received from dhcp server.
+func dhcpResponseReceived(cTag, sTag uint16) {
+ var dtInfo dhcpTransactionInfo
+ var valueExist bool
+ dsTag := newDhcpServerTag(cTag, sTag)
+
+ mux.Lock()
+ logger.Debugw(ctx, "dhcpResponseReceived", log.Fields{"ctag": cTag, "stag": sTag})
+ if dtInfo, valueExist = dhcpServerInfo[dsTag]; !valueExist {
+ logger.Warnw(ctx, "Ignore unknown response", log.Fields{"DhcpResp": dsTag})
+ mux.Unlock()
+ return
+ }
+
+ // If already unreachable alarm raised, clear and remove from array
+ if alarmsRaised.isexist(dsTag) {
+ alarmsRaised.remove(dsTag)
+ logger.Infow(ctx, "Alarm Cleared", log.Fields{"ctag": dsTag.cTag, "stag": dsTag.sTag})
+ }
+
+ // Increments received count and decrement pending count
+ dtInfo.responseReceived()
+ logger.Debugw(ctx, "Updated dtInfo", log.Fields{"pendingReq": dtInfo.pendingRequestCount, "receivedReq": dtInfo.receivedResponseCount})
+
+ if dtInfo.getPendingRequestCount() == 0 {
+ delete(dhcpServerInfo, dsTag)
+ } else {
+ dhcpServerInfo[dsTag] = dtInfo
+ }
+ mux.Unlock()
+}
+
+// StopDhcpServerHandler stops dhcp server handler go routine
+func StopDhcpServerHandler() {
+ done <- true
+}
+
+// dhcpServerTag contains unique dhcp network information
+type dhcpServerTag struct {
+ sTag uint16
+ cTag uint16
+}
+
+func newDhcpServerTag(cTag, sTag uint16) dhcpServerTag {
+ var d dhcpServerTag
+ d.sTag = sTag
+ d.cTag = cTag
+ return d
+}
+
+// dhcpTransactionInfo contains DHCP request response transaction information.
+type dhcpTransactionInfo struct {
+ timer uint8
+ pendingRequestCount uint32
+ receivedResponseCount uint32
+ previousRequestCount uint32
+ smac string
+}
+
+func newDhcpTransactionInfo(timer uint8, smac string) dhcpTransactionInfo {
+ var dt dhcpTransactionInfo
+ dt.timer = timer
+ dt.smac = smac
+ return dt
+}
+
+func (dt *dhcpTransactionInfo) getTimer() uint8 {
+ return dt.timer
+}
+
+func (dt *dhcpTransactionInfo) decrementTimer() uint8 {
+ dt.timer--
+ return dt.timer
+}
+
+func (dt *dhcpTransactionInfo) getPendingRequestCount() uint32 {
+ return dt.pendingRequestCount
+}
+
+func (dt *dhcpTransactionInfo) incrementPendingRequestCount() {
+ dt.pendingRequestCount++
+}
+
+func (dt *dhcpTransactionInfo) getReceivedResponseCount() uint32 {
+ return dt.receivedResponseCount
+}
+
+func (dt *dhcpTransactionInfo) responseReceived() {
+ dt.receivedResponseCount++
+ dt.pendingRequestCount--
+}
+
+func (dt *dhcpTransactionInfo) resetRequestResponseCount(timer uint8) {
+ if dt.pendingRequestCount >= dt.previousRequestCount {
+ dt.pendingRequestCount = dt.pendingRequestCount - dt.previousRequestCount
+ }
+ dt.previousRequestCount = dt.pendingRequestCount
+ dt.receivedResponseCount = 0
+ dt.timer = timer
+}
+
+// alarmsRaisedInfo contains the all networks alarm raised information
+type alarmsRaisedInfo struct {
+ arrayInfo []dhcpServerTag
+}
+
+// add an entry into alarm raised array
+func (a *alarmsRaisedInfo) add(val dhcpServerTag) {
+ a.arrayInfo = append(a.arrayInfo, val)
+}
+
+// isexist check if entry exist in alarm raised array
+func (a *alarmsRaisedInfo) isexist(val dhcpServerTag) bool {
+ for _, srvTag := range a.arrayInfo {
+ if srvTag == val {
+ return true
+ }
+ }
+ return false
+}
+
+// remove deletes given entry from alarm raised array
+func (a *alarmsRaisedInfo) remove(val dhcpServerTag) {
+ for ind := range a.arrayInfo {
+ if a.arrayInfo[ind] == val {
+ a.arrayInfo = append(a.arrayInfo[:ind], a.arrayInfo[ind+1:]...)
+ break
+ }
+ }
+}
diff --git a/internal/pkg/application/flowevent.go b/internal/pkg/application/flowevent.go
new file mode 100644
index 0000000..4fe1a0a
--- /dev/null
+++ b/internal/pkg/application/flowevent.go
@@ -0,0 +1,192 @@
+/*
+* Copyright 2022-present Open Networking Foundation
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+ */
+
+package application
+
+import (
+ infraerrorcode "voltha-go-controller/internal/pkg/errorcodes/service"
+
+ "voltha-go-controller/internal/pkg/intf"
+ "github.com/opencord/voltha-lib-go/v7/pkg/log"
+)
+
+//Generic Framework to enabling all flow based event trigger and handling.
+//The eventMapper can be updated for dynamic func caller for future events
+
+//FlowEventType - Type of event enumeration
+type FlowEventType string
+
+//FlowEventHandler - Func prototype for flow event handling funcs
+type FlowEventHandler func(*FlowEvent, intf.FlowStatus)
+
+var eventMapper map[FlowEventType]FlowEventHandler
+
+const (
+ //EventTypeUsIgmpFlowAdded - Event type for IGMP US flow add
+ EventTypeUsIgmpFlowAdded FlowEventType = "USIgmpFlowAdded"
+ //EventTypeServiceFlowAdded - Event type for Service flow add
+ EventTypeServiceFlowAdded FlowEventType = "ServiceFlowAdded"
+ //EventTypeControlFlowAdded - Event type for Control flow add
+ EventTypeControlFlowAdded FlowEventType = "ControlFlowAdded"
+
+ //EventTypeDeviceFlowRemoved - Event type for Device flow del
+ EventTypeDeviceFlowRemoved FlowEventType = "DeviceFlowRemoved"
+ //EventTypeMcastFlowRemoved - Event type for Mcast flow del
+ EventTypeMcastFlowRemoved FlowEventType = "McastFlowRemoved"
+
+ //EventTypeServiceFlowRemoved - Event type for Service flow del
+ EventTypeServiceFlowRemoved FlowEventType = "ServiceFlowRemoved"
+ //EventTypeControlFlowRemoved - Event type for Control flow del
+ EventTypeControlFlowRemoved FlowEventType = "ControlFlowRemoved"
+)
+
+//FlowEvent - Event info for Flow event processing
+type FlowEvent struct {
+ eType FlowEventType
+ device string
+ cookie string
+ eventData interface{}
+}
+
+//InitEventFuncMapper - Initialization of flow event mapper
+func InitEventFuncMapper() {
+ eventMapper = map[FlowEventType]FlowEventHandler{
+ EventTypeUsIgmpFlowAdded: ProcessUsIgmpFlowAddEvent,
+ EventTypeControlFlowAdded: ProcessControlFlowAddEvent,
+ EventTypeServiceFlowAdded: ProcessServiceFlowAddEvent,
+ EventTypeControlFlowRemoved: ProcessControlFlowDelEvent,
+ EventTypeServiceFlowRemoved: ProcessServiceFlowDelEvent,
+ EventTypeDeviceFlowRemoved: ProcessDeviceFlowDelEvent,
+ EventTypeMcastFlowRemoved: ProcessMcastFlowDelEvent,
+ }
+}
+
+//ExecuteFlowEvent - Process flow based event triggers
+func ExecuteFlowEvent(vd *VoltDevice, cookie string, flowStatus intf.FlowStatus) bool {
+ var event interface{}
+
+ flowEventMap, err := vd.GetFlowEventRegister(flowStatus.FlowModType)
+ if err != nil {
+ logger.Debugw(ctx, "Flow event map does not exists", log.Fields{"flowMod": flowStatus.FlowModType, "Error": err})
+ return false
+ }
+ flowEventMap.MapLock.Lock()
+
+ if event, _ = flowEventMap.Get(cookie); event == nil {
+ logger.Debugw(ctx, "Event already processed or event not registered for the cookie", log.Fields{"Cookie": cookie})
+ flowEventMap.MapLock.Unlock()
+ return false
+ }
+ flowEventMap.Remove(cookie)
+ flowEventMap.MapLock.Unlock()
+ flowEvent := event.(*FlowEvent)
+ eventMapper[flowEvent.eType](flowEvent, flowStatus)
+ return true
+}
+
+//ProcessUsIgmpFlowAddEvent - Process Us Igmp Flow event trigger
+func ProcessUsIgmpFlowAddEvent(event *FlowEvent, flowStatus intf.FlowStatus) {
+
+ logger.Infow(ctx, "Processing Post Flow Add Event for US Igmp", log.Fields{"Cookie": event.cookie, "event": event})
+ vpv := event.eventData.(*VoltPortVnet)
+ if isFlowStatusSuccess(flowStatus.Status, true) {
+ vpv.services.Range(ReceiverUpInd)
+ } else {
+ vpv.IgmpFlowInstallFailure(event.cookie, flowStatus.Status, flowStatus.Reason)
+ }
+}
+
+//ProcessServiceFlowAddEvent - Process Service Flow event trigger
+func ProcessServiceFlowAddEvent(event *FlowEvent, flowStatus intf.FlowStatus) {
+
+ logger.Infow(ctx, "Processing Post Flow Add Event for Service", log.Fields{"Cookie": event.cookie, "event": event})
+ vs := event.eventData.(*VoltService)
+ if isFlowStatusSuccess(flowStatus.Status, true) {
+ vs.FlowInstallSuccess(event.cookie, flowStatus.AdditionalData)
+ } else {
+ vs.FlowInstallFailure(event.cookie, flowStatus.Status, flowStatus.Reason)
+ }
+}
+
+//ProcessControlFlowAddEvent - Process Control Flow event trigger
+func ProcessControlFlowAddEvent(event *FlowEvent, flowStatus intf.FlowStatus) {
+
+ logger.Infow(ctx, "Processing Post Flow Add Event for VPV", log.Fields{"Cookie": event.cookie, "event": event})
+ vpv := event.eventData.(*VoltPortVnet)
+ if !isFlowStatusSuccess(flowStatus.Status, true) {
+ vpv.FlowInstallFailure(event.cookie, flowStatus.Status, flowStatus.Reason)
+ }
+}
+
+//ProcessServiceFlowDelEvent - Process Service Flow event trigger
+func ProcessServiceFlowDelEvent(event *FlowEvent, flowStatus intf.FlowStatus) {
+
+ logger.Infow(ctx, "Processing Post Flow Remove Event for Service", log.Fields{"Cookie": event.cookie, "event": event})
+ vs := event.eventData.(*VoltService)
+ if isFlowStatusSuccess(flowStatus.Status, false) {
+ vs.FlowRemoveSuccess(event.cookie)
+ } else {
+ vs.FlowRemoveFailure(event.cookie, flowStatus.Status, flowStatus.Reason)
+ }
+}
+
+//ProcessControlFlowDelEvent - Process Control Flow event trigger
+func ProcessControlFlowDelEvent(event *FlowEvent, flowStatus intf.FlowStatus) {
+
+ logger.Infow(ctx, "Processing Post Flow Remove Event for VPV", log.Fields{"Cookie": event.cookie, "event": event})
+ vpv := event.eventData.(*VoltPortVnet)
+ if isFlowStatusSuccess(flowStatus.Status, false) {
+ vpv.FlowRemoveSuccess(event.cookie, event.device)
+ } else {
+ vpv.FlowRemoveFailure(event.cookie, event.device, flowStatus.Status, flowStatus.Reason)
+ }
+}
+
+//ProcessMcastFlowDelEvent - Process Control Flow event trigger
+func ProcessMcastFlowDelEvent(event *FlowEvent, flowStatus intf.FlowStatus) {
+
+ logger.Infow(ctx, "Processing Post Flow Remove Event for Mcast/Igmp", log.Fields{"Cookie": event.cookie, "event": event})
+ mvp := event.eventData.(*MvlanProfile)
+ if isFlowStatusSuccess(flowStatus.Status, false) {
+ mvp.FlowRemoveSuccess(event.cookie, event.device)
+ } else {
+ mvp.FlowRemoveFailure(event.cookie, event.device, flowStatus.Status, flowStatus.Reason)
+ }
+}
+
+//ProcessDeviceFlowDelEvent - Process Control Flow event trigger
+func ProcessDeviceFlowDelEvent(event *FlowEvent, flowStatus intf.FlowStatus) {
+
+ logger.Infow(ctx, "Processing Post Flow Remove Event for VNET", log.Fields{"Cookie": event.cookie, "event": event})
+ vnet := event.eventData.(*VoltVnet)
+ if isFlowStatusSuccess(flowStatus.Status, false) {
+ vnet.FlowRemoveSuccess(event.cookie, event.device)
+ } else {
+ vnet.FlowRemoveFailure(event.cookie, event.device, flowStatus.Status, flowStatus.Reason)
+ }
+}
+
+//TODO: Update the func or flowStatus struct once all flow status are based on NB error code
+func isFlowStatusSuccess(status uint32, flowAdd bool) bool {
+ result := false
+ errorCode := infraerrorcode.ErrorCode(status)
+
+ if errorCode == infraerrorcode.ErrOk {
+ result = true
+ } else if !flowAdd && errorCode == infraerrorcode.ErrNotExists {
+ result = true
+ }
+ return result
+}
diff --git a/internal/pkg/application/igmp.go b/internal/pkg/application/igmp.go
new file mode 100644
index 0000000..fa28c8e
--- /dev/null
+++ b/internal/pkg/application/igmp.go
@@ -0,0 +1,5275 @@
+/*
+* Copyright 2022-present Open Networking Foundation
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package application
+
+import (
+ "encoding/json"
+ "errors"
+ "net"
+ "reflect"
+ "voltha-go-controller/internal/pkg/types"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/google/gopacket"
+ "github.com/google/gopacket/layers"
+
+ cntlr "voltha-go-controller/internal/pkg/controller"
+ "voltha-go-controller/database"
+ "voltha-go-controller/internal/pkg/of"
+ "voltha-go-controller/internal/pkg/util"
+ "github.com/opencord/voltha-lib-go/v7/pkg/log"
+)
+
+const (
+ // IgmpVersion0 constant (Default init value)
+ IgmpVersion0 uint8 = 0
+ // IgmpVersion1 constant
+ IgmpVersion1 uint8 = 1
+ // IgmpVersion2 constant
+ IgmpVersion2 uint8 = 2
+ // IgmpVersion3 constant
+ IgmpVersion3 uint8 = 3
+ // MinKeepAliveInterval constant
+ MinKeepAliveInterval uint32 = 10
+ // MaxDiffKAIntervalResp constant
+ MaxDiffKAIntervalResp uint32 = 5
+ // StaticGroup constant
+ StaticGroup string = "static"
+ // DynamicGroup constant
+ DynamicGroup string = "dynamic"
+ // StaticPort constant
+ StaticPort string = "static_port"
+ // DefaultIgmpProfID constant
+ DefaultIgmpProfID = ""
+ //GroupExpiryTime - group expiry time in minutes
+ GroupExpiryTime uint32 = 15
+)
+
+const (
+ // JoinUnsuccessful constant
+ JoinUnsuccessful string = "JOIN-UNSUCCESSFUL"
+ // JoinUnsuccessfulExceededIGMPChanel constant
+ JoinUnsuccessfulExceededIGMPChanel string = "Exceeded subscriber or PON port IGMP channels threshold"
+ // JoinUnsuccessfulAddFlowGroupFailed constant
+ JoinUnsuccessfulAddFlowGroupFailed string = "Failed to add flow or group for a channel"
+ // JoinUnsuccessfulGroupNotConfigured constant
+ JoinUnsuccessfulGroupNotConfigured string = "Join received from a subscriber on non-configured group"
+ // JoinUnsuccessfulVlanDisabled constant
+ JoinUnsuccessfulVlanDisabled string = "Vlan is disabled"
+ // JoinUnsuccessfulDescription constant
+ JoinUnsuccessfulDescription string = "igmp join unsuccessful"
+ // QueryExpired constant
+ QueryExpired string = "QUERY-EXPIRED"
+ // QueryExpiredGroupSpecific constant
+ QueryExpiredGroupSpecific string = "Group specific multicast query expired"
+ // QueryExpiredDescription constant
+ QueryExpiredDescription string = "igmp query expired"
+)
+
+// IgmpProfile structure
+type IgmpProfile struct {
+ ProfileID string
+ UnsolicitedTimeOut uint32 //In seconds
+ MaxResp uint32
+ KeepAliveInterval uint32
+ KeepAliveCount uint32
+ LastQueryInterval uint32
+ LastQueryCount uint32
+ FastLeave bool
+ PeriodicQuery bool
+ IgmpCos uint8
+ WithRAUpLink bool
+ WithRADownLink bool
+ IgmpVerToServer string
+ IgmpSourceIP net.IP
+ Version string
+}
+
+// McastConfig structure
+type McastConfig struct {
+ OltSerialNum string
+ MvlanProfileID string
+ IgmpProfileID string
+ IgmpProxyIP net.IP
+ OperState OperInProgress
+ Version string
+ // This map will help in updating the igds whenever there is a igmp profile id update
+ IgmpGroupDevices sync.Map `json:"-"` // Key is group id
+}
+
+var (
+ // NullIPAddr is null ip address var
+ NullIPAddr = net.ParseIP("0.0.0.0")
+ // igmpSrcMac for the proxy
+ igmpSrcMac string
+)
+
+func init() {
+ RegisterPacketHandler(IGMP, ProcessIgmpPacket)
+}
+
+// ProcessIgmpPacket : CallBack function registered with application to handle IGMP packetIn
+func ProcessIgmpPacket(device string, port string, pkt gopacket.Packet) {
+ GetApplication().IgmpPacketInd(device, port, pkt)
+}
+
+func ipv4ToUint(ip net.IP) uint32 {
+ result := uint32(0)
+ addr := ip.To4()
+ if addr == nil {
+ logger.Warnw(ctx, "Invalid Group Addr", log.Fields{"IP": ip})
+ return 0
+ }
+ result = result + uint32(addr[0])<<24
+ result = result + uint32(addr[1])<<16
+ result = result + uint32(addr[2])<<8
+ result = result + uint32(addr[3])
+ return result
+}
+
+func getPodMacAddr() (string, error) {
+ ifas, err := net.Interfaces()
+ if err != nil {
+ return "", err
+ }
+ var ipv4Addr net.IP
+ for _, ifa := range ifas {
+ addrs, err := ifa.Addrs()
+ if err != nil {
+ return "", err
+ }
+ for _, addr := range addrs {
+ if ipv4Addr = addr.(*net.IPNet).IP.To4(); ipv4Addr != nil {
+ if ipv4Addr.IsGlobalUnicast() {
+ logger.Infow(ctx, "Igmp Static config", log.Fields{"MacAddr": ifa.HardwareAddr.String(), "ipAddr": ipv4Addr})
+ return ifa.HardwareAddr.String(), nil
+ }
+ }
+ }
+
+ }
+ return "", errors.New("MAC Address not found,Setting default")
+}
+
+// IgmpUsEthLayer : Layers defined for upstream communication
+// Ethernet layer for upstream communication
+func IgmpUsEthLayer(mcip net.IP) *layers.Ethernet {
+ eth := &layers.Ethernet{}
+ // TODO: Set the source MAC properly and remove hardcoding
+ eth.SrcMAC, _ = net.ParseMAC(igmpSrcMac)
+ eth.DstMAC, _ = net.ParseMAC("01:00:5e:00:00:00")
+ eth.DstMAC[3] = mcip[1] & 0x7f
+ eth.DstMAC[4] = mcip[2]
+ eth.DstMAC[5] = mcip[3]
+ eth.EthernetType = layers.EthernetTypeDot1Q
+ return eth
+}
+
+// IgmpUsDot1qLayer set US VLAN layer
+func IgmpUsDot1qLayer(vlan of.VlanType, priority uint8) *layers.Dot1Q {
+ dot1q := &layers.Dot1Q{}
+ dot1q.Priority = priority
+ dot1q.DropEligible = false
+ dot1q.VLANIdentifier = uint16(vlan)
+ dot1q.Type = layers.EthernetTypeIPv4
+ return dot1q
+}
+
+// Igmpv2UsIpv4Layer : Set the IP layer for IGMPv2
+// TODO - Identify correct way of obtaining source IP
+// This should be the configured IGMP proxy address which should be per OLT
+// We should probably be able to have a single function for both
+// upstream and downstream
+func Igmpv2UsIpv4Layer(src net.IP, mcip net.IP) *layers.IPv4 {
+ ip := &layers.IPv4{}
+ ip.Version = 4
+ ip.Protocol = layers.IPProtocolIGMP
+ ip.TTL = 1
+ ip.SrcIP = src
+ ip.DstIP = mcip
+ return ip
+}
+
+// Igmpv3UsIpv4Layer : Set the IP layer for IGMPv3
+// TODO - Identify correct way of obtaining source IP
+// This should be the configured IGMP proxy address which should be per OLT
+// We should probably be able to have a single function for both
+// upstream and downstream
+func Igmpv3UsIpv4Layer(src net.IP) *layers.IPv4 {
+ ip := &layers.IPv4{}
+ ip.Version = 4
+ ip.Protocol = layers.IPProtocolIGMP
+ ip.TTL = 1
+ ip.SrcIP = src
+ ip.DstIP = net.ParseIP("224.0.0.22")
+ return ip
+}
+
+// IgmpDsEthLayer : Layers defined for downstream communication
+// Ethernet layer for downstream communication
+func IgmpDsEthLayer(mcip net.IP) *layers.Ethernet {
+ eth := &layers.Ethernet{}
+ // TODO: Set the source and dest MAC properly and remove hardcoding
+ eth.SrcMAC, _ = net.ParseMAC(igmpSrcMac)
+ eth.DstMAC, _ = net.ParseMAC("01:00:5e:00:00:00")
+ eth.DstMAC[3] = mcip[1] & 0x7f
+ eth.DstMAC[4] = mcip[2]
+ eth.DstMAC[5] = mcip[3]
+ eth.EthernetType = layers.EthernetTypeDot1Q
+ return eth
+}
+
+// IgmpDsDot1qLayer set the DS VLAN layer
+func IgmpDsDot1qLayer(vlan of.VlanType, priority uint8) *layers.Dot1Q {
+ dot1q := &layers.Dot1Q{}
+ dot1q.Priority = priority
+ dot1q.DropEligible = false
+ dot1q.VLANIdentifier = uint16(vlan)
+ dot1q.Type = layers.EthernetTypeIPv4
+ return dot1q
+}
+
+// IgmpDsIpv4Layer set the IP layer
+func IgmpDsIpv4Layer(src net.IP, mcip net.IP) *layers.IPv4 {
+ ip := &layers.IPv4{}
+ ip.Version = 4
+ ip.Protocol = layers.IPProtocolIGMP
+ ip.TTL = 1
+ ip.SrcIP = src
+ if mcip.Equal(net.ParseIP("0.0.0.0")) {
+ mcip = net.ParseIP("224.0.0.1")
+ }
+ ip.DstIP = mcip
+ return ip
+}
+
+// IgmpQueryv2Layer : IGMP Query Layer
+func IgmpQueryv2Layer(mcip net.IP, resptime time.Duration) *layers.IGMPv1or2 {
+ igmp := &layers.IGMPv1or2{}
+ igmp.Type = layers.IGMPMembershipQuery
+ igmp.GroupAddress = mcip
+ igmp.MaxResponseTime = resptime
+ return igmp
+}
+
+// IgmpQueryv3Layer : IGMP v3 Query Layer
+func IgmpQueryv3Layer(mcip net.IP, resptime time.Duration) *layers.IGMP {
+ igmp := &layers.IGMP{}
+ igmp.Type = layers.IGMPMembershipQuery
+ igmp.GroupAddress = mcip
+ igmp.MaxResponseTime = resptime
+ return igmp
+}
+
+// IgmpReportv2Layer : IGMP Layer
+func IgmpReportv2Layer(mcip net.IP) *layers.IGMPv1or2 {
+ igmp := &layers.IGMPv1or2{}
+ igmp.Type = layers.IGMPMembershipReportV2
+ igmp.GroupAddress = mcip
+ return igmp
+}
+
+// IgmpLeavev2Layer : IGMP Leave Layer
+func IgmpLeavev2Layer(mcip net.IP) *layers.IGMPv1or2 {
+ igmp := &layers.IGMPv1or2{}
+ igmp.Type = layers.IGMPLeaveGroup
+ igmp.GroupAddress = mcip
+ return igmp
+}
+
+// IgmpReportv3Layer : IGMP v3 Report Layer
+func IgmpReportv3Layer(mcip net.IP, incl bool, srclist []net.IP) *layers.IGMP {
+ // IGMP base
+ igmp := &layers.IGMP{}
+ igmp.Type = layers.IGMPMembershipReportV3
+ igmp.NumberOfGroupRecords = 1
+
+ // IGMP Group
+ group := layers.IGMPv3GroupRecord{}
+ if incl {
+ group.Type = layers.IGMPIsIn
+ } else {
+ group.Type = layers.IGMPIsEx
+ }
+ group.MulticastAddress = mcip
+ group.NumberOfSources = uint16(len(srclist))
+ group.SourceAddresses = srclist
+ igmp.GroupRecords = append(igmp.GroupRecords, group)
+
+ return igmp
+}
+
+// Igmpv2QueryPacket : IGMP Query in Downstream
+func Igmpv2QueryPacket(mcip net.IP, vlan of.VlanType, selfip net.IP, pbit uint8, maxResp uint32) ([]byte, error) {
+ // Construct the layers that form the packet
+ eth := IgmpDsEthLayer(mcip)
+ dot1q := IgmpDsDot1qLayer(vlan, pbit)
+ ip := IgmpDsIpv4Layer(selfip, mcip)
+ igmp := IgmpQueryv2Layer(mcip, time.Duration(maxResp)*time.Second)
+
+ // Now prepare the buffer into which the layers are to be serialized
+ buff := gopacket.NewSerializeBuffer()
+ opts := gopacket.SerializeOptions{
+ FixLengths: true,
+ ComputeChecksums: true,
+ }
+ if err := gopacket.SerializeLayers(buff, opts, eth, dot1q, ip, igmp); err != nil {
+ logger.Error(ctx, "Error in serializing layers")
+ return nil, err
+ }
+ return buff.Bytes(), nil
+}
+
+// Igmpv3QueryPacket : IGMPv3 Query in Downstream
+func Igmpv3QueryPacket(mcip net.IP, vlan of.VlanType, selfip net.IP, pbit uint8, maxResp uint32) ([]byte, error) {
+ // Construct the layers that form the packet
+ eth := IgmpDsEthLayer(mcip)
+ dot1q := IgmpDsDot1qLayer(vlan, pbit)
+ ip := IgmpDsIpv4Layer(selfip, mcip)
+ igmp := IgmpQueryv3Layer(mcip, time.Duration(maxResp)*time.Second)
+
+ // Now prepare the buffer into which the layers are to be serialized
+ buff := gopacket.NewSerializeBuffer()
+ opts := gopacket.SerializeOptions{
+ FixLengths: true,
+ ComputeChecksums: true,
+ }
+ if err := gopacket.SerializeLayers(buff, opts, eth, dot1q, ip, igmp); err != nil {
+ logger.Error(ctx, "Error in serializing layers")
+ return nil, err
+ }
+ return buff.Bytes(), nil
+}
+
+// IgmpReportv2Packet : Packet - IGMP v2 report in upstream
+func IgmpReportv2Packet(mcip net.IP, vlan of.VlanType, priority uint8, selfip net.IP) ([]byte, error) {
+ // Construct the layers that form the packet
+ eth := IgmpUsEthLayer(mcip)
+ dot1q := IgmpUsDot1qLayer(vlan, priority)
+ ip := Igmpv2UsIpv4Layer(selfip, mcip)
+ igmp := IgmpReportv2Layer(mcip)
+
+ // Now prepare the buffer into which the layers are to be serialized
+ buff := gopacket.NewSerializeBuffer()
+ opts := gopacket.SerializeOptions{
+ FixLengths: true,
+ ComputeChecksums: true,
+ }
+ if err := gopacket.SerializeLayers(buff, opts, eth, dot1q, ip, igmp); err != nil {
+ logger.Error(ctx, "Error in serializing layers")
+ return nil, err
+ }
+ return buff.Bytes(), nil
+}
+
+// Igmpv3ReportPacket : Packet - IGMP v3 report in upstream
+func Igmpv3ReportPacket(mcip net.IP, vlan of.VlanType, priority uint8, selfip net.IP, incl bool, srclist []net.IP) ([]byte, error) {
+ // Construct the layers that form the packet
+ eth := IgmpUsEthLayer(net.ParseIP("224.0.0.22").To4())
+ dot1q := IgmpUsDot1qLayer(vlan, priority)
+ ip := Igmpv3UsIpv4Layer(selfip)
+ igmp := IgmpReportv3Layer(mcip, incl, srclist)
+
+ // Now prepare the buffer into which the layers are to be serialized
+ buff := gopacket.NewSerializeBuffer()
+ opts := gopacket.SerializeOptions{
+ FixLengths: true,
+ ComputeChecksums: true,
+ }
+ if err := gopacket.SerializeLayers(buff, opts, eth, dot1q, ip, igmp); err != nil {
+ logger.Error(ctx, "Error in serializing layers")
+ return nil, err
+ }
+ return buff.Bytes(), nil
+}
+
+// IgmpLeavePacket : Packet- IGMP Leave in upstream
+func IgmpLeavePacket(mcip net.IP, vlan of.VlanType, priority uint8, selfip net.IP) ([]byte, error) {
+ // Construct the layers that form the packet
+ eth := IgmpUsEthLayer(mcip)
+ dot1q := IgmpUsDot1qLayer(vlan, priority)
+ ip := Igmpv2UsIpv4Layer(selfip, mcip)
+ igmp := IgmpLeavev2Layer(mcip)
+
+ // Now prepare the buffer into which the layers are to be serialized
+ buff := gopacket.NewSerializeBuffer()
+ opts := gopacket.SerializeOptions{
+ FixLengths: true,
+ ComputeChecksums: true,
+ }
+ if err := gopacket.SerializeLayers(buff, opts, eth, dot1q, ip, igmp); err != nil {
+ logger.Error(ctx, "Error in serializing layers")
+ return nil, err
+ }
+ return buff.Bytes(), nil
+}
+
+// getVersion to get igmp version type
+func getVersion(ver string) uint8 {
+ if ver == "2" || ver == "v2" {
+ return IgmpVersion2
+ }
+ return IgmpVersion3
+}
+
+// IsIPPresent is Utility to check if an IP address is in a list
+func IsIPPresent(i net.IP, ips []net.IP) bool {
+ for _, ip := range ips {
+ if i.Equal(ip) {
+ return true
+ }
+ }
+ return false
+}
+
+// IgmpGroupPort : IGMP port implements a port which is associated with an IGMP
+// version and the list of sources it implements for a given IGMP
+// channel. We may improve this to have all IGMP channels so that
+// we can implement per subscriber IGMP channel registration limits
+// As a rule a single port cannot have both include and exclude
+// lists. If we receive a include list we should purge the other
+// list which is TODO
+type IgmpGroupPort struct {
+ Port string
+ CVlan uint16
+ Pbit uint8
+ Version uint8
+ Exclude bool
+ ExcludeList []net.IP
+ IncludeList []net.IP
+ QueryTimeoutCount uint32
+ PonPortID uint32
+}
+
+// NewIgmpGroupPort is constructor for a port
+func NewIgmpGroupPort(port string, cvlan uint16, pbit uint8, version uint8, incl bool, ponPortID uint32) *IgmpGroupPort {
+ var igp IgmpGroupPort
+ igp.Port = port
+ igp.CVlan = cvlan
+ igp.Pbit = pbit
+ igp.Version = version
+ igp.Exclude = !incl
+ igp.QueryTimeoutCount = 0
+ igp.PonPortID = ponPortID
+ return &igp
+}
+
+// InclSourceIsIn checks if a source is in include list
+func (igp *IgmpGroupPort) InclSourceIsIn(src net.IP) bool {
+ return IsIPPresent(src, igp.IncludeList)
+}
+
+// ExclSourceIsIn checks if a source is in exclude list
+func (igp *IgmpGroupPort) ExclSourceIsIn(src net.IP) bool {
+ return IsIPPresent(src, igp.ExcludeList)
+}
+
+// AddInclSource adds a source is in include list
+func (igp *IgmpGroupPort) AddInclSource(src net.IP) {
+ logger.Debugw(ctx, "Adding Include Source", log.Fields{"Port": igp.Port, "Src": src})
+ igp.IncludeList = append(igp.IncludeList, src)
+}
+
+// AddExclSource adds a source is in exclude list
+func (igp *IgmpGroupPort) AddExclSource(src net.IP) {
+ logger.Debugw(ctx, "Adding Exclude Source", log.Fields{"Port": igp.Port, "Src": src})
+ igp.ExcludeList = append(igp.ExcludeList, src)
+}
+
+// DelInclSource deletes a source is in include list
+func (igp *IgmpGroupPort) DelInclSource(src net.IP) {
+ logger.Debugw(ctx, "Deleting Include Source", log.Fields{"Port": igp.Port, "Src": src})
+ for i, addr := range igp.IncludeList {
+ if addr.Equal(src) {
+ igp.IncludeList = append(igp.IncludeList[:i], igp.IncludeList[i+1:]...)
+ return
+ }
+ }
+}
+
+// DelExclSource deletes a source is in exclude list
+func (igp *IgmpGroupPort) DelExclSource(src net.IP) {
+ logger.Debugw(ctx, "Deleting Exclude Source", log.Fields{"Port": igp.Port, "Src": src})
+ for i, addr := range igp.ExcludeList {
+ if addr.Equal(src) {
+ igp.ExcludeList = append(igp.ExcludeList[:i], igp.ExcludeList[i+1:]...)
+ return
+ }
+ }
+}
+
+// WriteToDb is utility to write IGMP Group Port Info to database
+func (igp *IgmpGroupPort) WriteToDb(mvlan of.VlanType, gip net.IP, device string) error {
+ b, err := json.Marshal(igp)
+ if err != nil {
+ return err
+ }
+ if err1 := db.PutIgmpRcvr(mvlan, gip, device, igp.Port, string(b)); err1 != nil {
+ return err1
+ }
+ return nil
+}
+
+// NewIgmpGroupPortFromBytes create the IGMP group port from a byte slice
+func NewIgmpGroupPortFromBytes(b []byte) (*IgmpGroupPort, error) {
+ var igp IgmpGroupPort
+ if err := json.Unmarshal(b, &igp); err != nil {
+ logger.Warnw(ctx, "Decode of port failed", log.Fields{"str": string(b)})
+ return nil, err
+ }
+ return &igp, nil
+}
+
+// IgmpGroupChannel structure
+type IgmpGroupChannel struct {
+ Device string
+ GroupID uint32
+ GroupName string
+ GroupAddr net.IP
+ Mvlan of.VlanType
+ Exclude int
+ ExcludeList []net.IP
+ IncludeList []net.IP
+ Version uint8
+ ServVersion *uint8 `json:"-"`
+ CurReceivers map[string]*IgmpGroupPort `json:"-"`
+ NewReceivers map[string]*IgmpGroupPort `json:"-"`
+ proxyCfg **IgmpProfile
+ IgmpProxyIP **net.IP `json:"-"`
+}
+
+// NewIgmpGroupChannel is constructor for a channel. The default IGMP version is set to 3
+// as the protocol defines the way to manage backward compatibility
+// The implementation handles simultaneous presense of lower versioned
+// receivers
+func NewIgmpGroupChannel(igd *IgmpGroupDevice, groupAddr net.IP, version uint8) *IgmpGroupChannel {
+ var igc IgmpGroupChannel
+ igc.Device = igd.Device
+ igc.GroupID = igd.GroupID
+ igc.GroupName = igd.GroupName
+ igc.GroupAddr = groupAddr
+ igc.Mvlan = igd.Mvlan
+ igc.Version = version
+ igc.CurReceivers = make(map[string]*IgmpGroupPort)
+ igc.NewReceivers = make(map[string]*IgmpGroupPort)
+ igc.proxyCfg = &igd.proxyCfg
+ igc.IgmpProxyIP = &igd.IgmpProxyIP
+ igc.ServVersion = igd.ServVersion
+ return &igc
+}
+
+// NewIgmpGroupChannelFromBytes create the IGMP group channel from a byte slice
+func NewIgmpGroupChannelFromBytes(b []byte) (*IgmpGroupChannel, error) {
+ var igc IgmpGroupChannel
+ if err := json.Unmarshal(b, &igc); err != nil {
+ return nil, err
+ }
+ igc.CurReceivers = make(map[string]*IgmpGroupPort)
+ igc.NewReceivers = make(map[string]*IgmpGroupPort)
+ return &igc, nil
+}
+
+// RestorePorts to restore ports
+func (igc *IgmpGroupChannel) RestorePorts() {
+
+ igc.migrateIgmpPorts()
+ ports, _ := db.GetIgmpRcvrs(igc.Mvlan, igc.GroupAddr, igc.Device)
+ for _, port := range ports {
+ b, ok := port.Value.([]byte)
+ if !ok {
+ logger.Warn(ctx, "The value type is not []byte")
+ continue
+ }
+ if igp, err := NewIgmpGroupPortFromBytes(b); err == nil {
+ igc.NewReceivers[igp.Port] = igp
+ logger.Infow(ctx, "Group Port Restored", log.Fields{"IGP": igp})
+ } else {
+ logger.Warn(ctx, "Failed to decode port from DB")
+ }
+ }
+ if err := igc.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp group channel Write to DB failed", log.Fields{"mvlan": igc.Mvlan, "GroupAddr": igc.GroupAddr})
+ }
+}
+
+// WriteToDb is utility to write IGMPGroupChannel Info to database
+func (igc *IgmpGroupChannel) WriteToDb() error {
+ b, err := json.Marshal(igc)
+ if err != nil {
+ return err
+ }
+ if err1 := db.PutIgmpChannel(igc.Mvlan, igc.GroupName, igc.Device, igc.GroupAddr, string(b)); err1 != nil {
+ return err1
+ }
+ logger.Info(ctx, "IGC Updated")
+ return nil
+}
+
+// UniPortList : UNI Port list per channle has stores the UNI port list for this
+// channel.
+type UniPortList struct {
+ UNIList *util.ConcurrentMap // [UNIPort] UNIPort
+}
+
+// NewUniPortsList is Constructor for UniPortList structure
+func NewUniPortsList() *UniPortList {
+ var uniPortsList UniPortList
+
+ uniPortsList.UNIList = util.NewConcurrentMap()
+ return &uniPortsList
+}
+
+// GetUniPortCount returns the number of UNI ports subscribed to
+// current channel.
+func (uniPortsList *UniPortList) GetUniPortCount() uint64 {
+ return uniPortsList.UNIList.Length()
+}
+
+// PonPortChannels : PON port channel map keeps the active channel list and its
+// count for this group.
+type PonPortChannels struct {
+ ChannelList *util.ConcurrentMap // [channelIP]*UniPortList
+}
+
+// NewPonPortChannels is constructor for PonPortChannel.
+func NewPonPortChannels() *PonPortChannels {
+ var ponPortChannel PonPortChannels
+
+ ponPortChannel.ChannelList = util.NewConcurrentMap()
+ return &ponPortChannel
+}
+
+// GetActiveChannelCount returns the number of active channel count
+// for this pon port in the current group.
+func (ponPortChannels *PonPortChannels) GetActiveChannelCount() uint32 {
+ return uint32(ponPortChannels.ChannelList.Length())
+}
+
+// AddChannelToMap Adds new channel to the pon port map
+func (ponPortChannels *PonPortChannels) AddChannelToMap(uniPort, channel string) bool {
+
+ isNewChannel := bool(false)
+ uniList, ok := ponPortChannels.ChannelList.Get(channel)
+ if !ok {
+ // Channel doesn't exists. Adding new channel.
+ uniList = NewUniPortsList()
+ isNewChannel = true
+ }
+ uniList.(*UniPortList).UNIList.Set(uniPort, uniPort)
+ ponPortChannels.ChannelList.Set(channel, uniList)
+ return isNewChannel
+}
+
+// RemoveChannelFromMap Removed channel from the pon port map
+func (ponPortChannels *PonPortChannels) RemoveChannelFromMap(uniPort, channel string) bool {
+
+ isDeleted := bool(false)
+ uniList, ok := ponPortChannels.ChannelList.Get(channel)
+ if ok {
+ uniList.(*UniPortList).UNIList.Remove(uniPort)
+ if uniList.(*UniPortList).UNIList.Length() == 0 {
+ // Last port from the channel is removed.
+ // Removing channel from PON port map.
+ ponPortChannels.ChannelList.Remove(channel)
+ isDeleted = true
+ } else {
+ ponPortChannels.ChannelList.Set(channel, uniList)
+ }
+ } else {
+ logger.Warnw(ctx, "Channel doesn't exists in the active channels list", log.Fields{"Channel": channel})
+ return isDeleted
+ }
+ return isDeleted
+}
+
+// IgmpGroupDevice : IGMP Group Device manages the IGMP group for all listerns on
+// a single OLT. It aggregates reports received on a single group
+// and performs the count. It is responsible for sending upstream
+// report when the first listener joins and is responsible for
+// sending responses to upstream queries
+type IgmpGroupDevice struct {
+ Device string
+ SerialNo string
+ GroupID uint32
+ GroupName string
+ GroupAddr net.IP
+ RecvVersion uint8
+ ServVersion *uint8
+ RecvVersionExpiry time.Time
+ ServVersionExpiry time.Time
+ Mvlan of.VlanType
+ PonVlan of.VlanType
+ IsPonVlanPresent bool
+ GroupInstalled bool
+ GroupChannels sync.Map `json:"-"` // [ipAddr]*IgmpGroupChannel
+ PortChannelMap sync.Map `json:"-"` // [portName][]net.IP
+ PonPortChannelMap *util.ConcurrentMap `json:"-"` // [ponPortId]*PonPortChannels
+ proxyCfg *IgmpProfile // IgmpSrcIp from IgmpProfile is not used, it is kept for backward compatibility
+ IgmpProxyIP *net.IP `json:"-"`
+ NextQueryTime time.Time
+ QueryExpiryTime time.Time
+}
+
+// NewIgmpGroupDevice is constructor for a device. The default IGMP version is set to 3
+// as the protocol defines the way to manage backward compatibility
+// The implementation handles simultaneous presense of lower versioned
+// receivers
+func NewIgmpGroupDevice(name string, ig *IgmpGroup, id uint32, version uint8) *IgmpGroupDevice {
+ var igd IgmpGroupDevice
+ igd.Device = name
+ igd.GroupID = id
+ igd.GroupName = ig.GroupName
+ igd.GroupAddr = ig.GroupAddr
+ igd.Mvlan = ig.Mvlan
+ igd.PonVlan = ig.PonVlan
+ igd.IsPonVlanPresent = ig.IsPonVlanPresent
+ igd.GroupInstalled = false
+ igd.RecvVersion = version
+ igd.RecvVersionExpiry = time.Now()
+ igd.ServVersionExpiry = time.Now()
+ igd.PonPortChannelMap = util.NewConcurrentMap()
+
+ va := GetApplication()
+ if vd := va.GetDevice(igd.Device); vd != nil {
+ igd.SerialNo = vd.SerialNum
+ } else {
+ logger.Errorw(ctx, "Volt Device not found. log.Fields", log.Fields{"igd.Device": igd.Device})
+ return nil
+ }
+ mvp := GetApplication().GetMvlanProfileByTag(igd.Mvlan)
+ igd.ServVersion = mvp.IgmpServVersion[igd.SerialNo]
+
+ var mcastCfg *McastConfig
+ igd.proxyCfg, igd.IgmpProxyIP, mcastCfg = getIgmpProxyCfgAndIP(ig.Mvlan, igd.SerialNo)
+
+ // mvlan profile id + olt serial number---igmp group id
+ //igmpgroup id
+ igd.NextQueryTime = time.Now().Add(time.Duration(igd.proxyCfg.KeepAliveInterval) * time.Second)
+ igd.QueryExpiryTime = time.Now().Add(time.Duration(igd.proxyCfg.KeepAliveInterval) * time.Second)
+
+ if mcastCfg != nil {
+ mcastCfg.IgmpGroupDevices.Store(id, &igd)
+ logger.Debugw(ctx, "Igd added to mcast config", log.Fields{"mvlan": mcastCfg.MvlanProfileID, "groupId": id})
+ }
+ return &igd
+}
+
+// IgmpGroupDeviceReInit is re-initializer for a device. The default IGMP version is set to 3
+// as the protocol defines the way to manage backward compatibility
+func (igd *IgmpGroupDevice) IgmpGroupDeviceReInit(ig *IgmpGroup) {
+
+ logger.Infow(ctx, "Reinitialize Igmp Group Device", log.Fields{"Device": igd.Device, "GroupID": ig.GroupID, "OldName": igd.GroupName, "Name": ig.GroupName, "OldAddr": igd.GroupAddr.String(), "GroupAddr": ig.GroupAddr.String()})
+
+ if (igd.GroupName != ig.GroupName) || !igd.GroupAddr.Equal(ig.GroupAddr) {
+ _ = db.DelIgmpDevice(igd.Mvlan, igd.GroupName, igd.GroupAddr, igd.Device)
+ igd.GroupName = ig.GroupName
+ igd.GroupAddr = ig.GroupAddr
+ }
+ igd.RecvVersionExpiry = time.Now()
+ igd.ServVersionExpiry = time.Now()
+ igd.PonPortChannelMap = util.NewConcurrentMap()
+
+ var mcastCfg *McastConfig
+ igd.proxyCfg, igd.IgmpProxyIP, mcastCfg = getIgmpProxyCfgAndIP(ig.Mvlan, igd.SerialNo)
+
+ igd.NextQueryTime = time.Now().Add(time.Duration(igd.proxyCfg.KeepAliveInterval) * time.Second)
+ igd.QueryExpiryTime = time.Now().Add(time.Duration(igd.proxyCfg.KeepAliveInterval) * time.Second)
+
+ if mcastCfg != nil {
+ mcastCfg.IgmpGroupDevices.Store(ig.GroupID, igd)
+ logger.Debugw(ctx, "Igd added to mcast config", log.Fields{"mvlan": mcastCfg.MvlanProfileID, "groupId": ig.GroupID})
+ }
+ if err := igd.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp group device Write to DB failed", log.Fields{"Device": igd.Device, "GroupName": igd.GroupName, "GroupAddr": igd.GroupAddr.String()})
+ }
+}
+
+func getIgmpProxyCfgAndIP(mvlan of.VlanType, serialNo string) (*IgmpProfile, *net.IP, *McastConfig) {
+ va := GetApplication()
+ mVLANProfileID := va.GetMvlanProfileByTag(mvlan).Name
+ var mcastCfg *McastConfig
+ if mcastCfg = va.GetMcastConfig(serialNo, mVLANProfileID); mcastCfg == nil || (mcastCfg != nil && mcastCfg.IgmpProfileID == "") {
+ logger.Debugw(ctx, "Default IGMP config to be used", log.Fields{"mVLANProfileID": mVLANProfileID, "OltSerialNo": serialNo})
+ igmpProf := va.getIgmpProfileMap(DefaultIgmpProfID)
+ return igmpProf, &igmpProf.IgmpSourceIP, mcastCfg
+ }
+ return va.getIgmpProfileMap(mcastCfg.IgmpProfileID), &mcastCfg.IgmpProxyIP, mcastCfg
+}
+
+// updateGroupName to update the group name
+func (igd *IgmpGroupDevice) updateGroupName(newGroupName string) {
+
+ oldName := igd.GroupName
+ igd.GroupName = newGroupName
+ updateGroupName := func(key, value interface{}) bool {
+ igc := value.(*IgmpGroupChannel)
+ igc.GroupName = newGroupName
+ if err := igc.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp group channel Write to DB failed", log.Fields{"mvlan": igc.Mvlan, "GroupAddr": igc.GroupAddr})
+ }
+ _ = db.DelIgmpChannel(igc.Mvlan, oldName, igc.Device, igc.GroupAddr)
+ return true
+ }
+ igd.GroupChannels.Range(updateGroupName)
+ if err := igd.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp group device Write to DB failed", log.Fields{"Device": igd.Device, "GroupName": igd.GroupName, "GroupAddr": igd.GroupAddr.String()})
+ }
+ _ = db.DelIgmpDevice(igd.Mvlan, oldName, igd.GroupAddr, igd.Device)
+}
+
+// NewIgmpGroupDeviceFromBytes is to create the IGMP group port from a byte slice
+func NewIgmpGroupDeviceFromBytes(b []byte) (*IgmpGroupDevice, error) {
+ var igd IgmpGroupDevice
+ if err := json.Unmarshal(b, &igd); err != nil {
+ return nil, err
+ }
+ return &igd, nil
+}
+
+// GetKey to get group name as key
+func (igd *IgmpGroupDevice) GetKey() string {
+
+ if !net.ParseIP("0.0.0.0").Equal(igd.GroupAddr) {
+ return igd.GroupName + "_" + igd.GroupAddr.String()
+ }
+ return igd.GroupName
+
+}
+
+// RestoreChannel to restore channel
+func (igd *IgmpGroupDevice) RestoreChannel(igmpGroupChannel []byte) {
+
+ if igc, err := NewIgmpGroupChannelFromBytes(igmpGroupChannel); err == nil {
+ igc.ServVersion = igd.ServVersion
+ igc.IgmpProxyIP = &igd.IgmpProxyIP
+ igc.proxyCfg = &igd.proxyCfg
+ igd.GroupChannels.Store(igc.GroupAddr.String(), igc)
+ igc.RestorePorts()
+
+ for port, igp := range igc.NewReceivers {
+ ipsList := []net.IP{}
+ ipsIntf, _ := igd.PortChannelMap.Load(port)
+ if ipsIntf != nil {
+ ipsList = ipsIntf.([]net.IP)
+ }
+
+ ipsList = append(ipsList, igc.GroupAddr)
+ igd.PortChannelMap.Store(port, ipsList)
+ logger.Infow(ctx, "Group Channels Restored", log.Fields{"IGC": igc})
+ igd.AddChannelToChannelsPerPon(port, igc.GroupAddr, igp.PonPortID)
+ }
+ } else {
+ logger.Warnw(ctx, "Failed to decode port from DB", log.Fields{"err": err})
+ }
+ logger.Info(ctx, "Group Device & Channels Restored")
+ igd.PortChannelMap.Range(printPortChannel)
+ igd.GroupChannels.Range(printChannel)
+
+}
+
+// RestoreChannels to restore channels
+func (igd *IgmpGroupDevice) RestoreChannels() {
+
+ igd.migrateIgmpChannels()
+ channels, _ := db.GetIgmpChannels(igd.Mvlan, igd.GroupName, igd.Device)
+ for _, channel := range channels {
+
+ b, ok := channel.Value.([]byte)
+ if !ok {
+ logger.Warn(ctx, "The value type is not []byte")
+ continue
+ }
+ igd.RestoreChannel(b)
+ }
+
+}
+
+// printChannel to print channel info
+func printChannel(key interface{}, value interface{}) bool {
+ logger.Infow(ctx, "ChannelMap", log.Fields{"Channel": key.(string), "Igc": value.(*IgmpGroupChannel)})
+ return true
+}
+
+// printPortChannel to print port channel
+func printPortChannel(key interface{}, value interface{}) bool {
+ logger.Infow(ctx, "PortChannelMap", log.Fields{"Port": key.(string), "List": value.([]net.IP)})
+ return true
+}
+
+// WriteToDb is utility to write IGMP Group Device Info to the database
+func (igd *IgmpGroupDevice) WriteToDb() error {
+ b, err := json.Marshal(igd)
+ if err != nil {
+ return err
+ }
+ if err1 := db.PutIgmpDevice(igd.Mvlan, igd.GroupName, igd.GroupAddr, igd.Device, string(b)); err1 != nil {
+ return err1
+ }
+ logger.Info(ctx, "IGD Updated")
+ return nil
+}
+
+// Tick processes timing tick used to run timers within the device
+func (igd *IgmpGroupDevice) Tick() uint8 {
+ /* Not using RecvVersionExpiry as it is not used anywhere
+ if time.Now().After(igd.RecvVersionExpiry) {
+ igd.RecvVersion = IgmpVersion3
+ return true
+ }
+ */
+ return 0
+}
+
+// GetSubscriberCountForChannelAndPonPort Gets the active subscriber count
+// for the given channel for one particular PON port
+func (igd *IgmpGroupDevice) GetSubscriberCountForChannelAndPonPort(ponPortID uint32, channelIP net.IP) uint64 {
+ if portMapIntf, ok := igd.PonPortChannelMap.Get(ponPortID); ok {
+ portChannelMap := portMapIntf.(*PonPortChannels)
+
+ if channel, present := portChannelMap.ChannelList.Get(channelIP.String()); present {
+ return channel.(*UniPortList).UNIList.Length()
+ }
+ } else {
+ logger.Warnw(ctx, "PON port not found in PortChannelMap", log.Fields{"PON": ponPortID, "channel": channelIP})
+ }
+ return 0
+}
+
+// AddChannelToChannelsPerPon Adds the new channel into the per Pon channel list
+func (igd *IgmpGroupDevice) AddChannelToChannelsPerPon(uniPort string, channelIP net.IP, ponPortID uint32) bool {
+ logger.Debugw(ctx, "Adding channel to ActiveChannelsPerPon list", log.Fields{"PonPort": ponPortID, "channelIP": channelIP})
+
+ isNewChannel := bool(false)
+ isNewReceiver := false
+ if port, ok := igd.PonPortChannelMap.Get(ponPortID); !ok {
+ // PON port not exists in igd. adding it.
+ isNewReceiver = true
+ ponPortChannels := NewPonPortChannels()
+ isNewChannel = ponPortChannels.AddChannelToMap(uniPort, channelIP.String())
+ igd.PonPortChannelMap.Set(ponPortID, ponPortChannels)
+ } else {
+ // PON port exists in igd. Appending the channel list
+ // in the PON port.
+ isNewChannel = port.(*PonPortChannels).AddChannelToMap(uniPort, channelIP.String())
+ igd.PonPortChannelMap.Set(ponPortID, port)
+ count := port.(*PonPortChannels).GetActiveChannelCount()
+
+ logger.Debugw(ctx, "activeChannelCount", log.Fields{"count": count})
+ }
+ GetApplication().UpdateActiveChannelCountForPonPort(igd.Device, uniPort, ponPortID, true, isNewChannel, igd)
+ return isNewReceiver
+}
+
+// RemoveChannelFromChannelsPerPon removes the channel from the per pon channel list.
+func (igd *IgmpGroupDevice) RemoveChannelFromChannelsPerPon(uniPort string, channelIP net.IP, ponPortID uint32) bool {
+ logger.Debugw(ctx, "Removing channel from ActiveChannelsPerPon list", log.Fields{"PonPort": ponPortID, "channelIP": channelIP})
+ var deleted bool
+ ponRemoved := false
+
+ if port, ok := igd.PonPortChannelMap.Get(ponPortID); ok {
+ channelPortMap := port.(*PonPortChannels)
+ deleted = channelPortMap.RemoveChannelFromMap(uniPort, channelIP.String())
+ if deleted && channelPortMap.ChannelList.Length() == 0 {
+ igd.PonPortChannelMap.Remove(ponPortID)
+ ponRemoved = true
+ }
+ GetApplication().UpdateActiveChannelCountForPonPort(igd.Device, uniPort, ponPortID, false, deleted, igd)
+ } else {
+ logger.Warnw(ctx, "PON port doesn't exists in the igd", log.Fields{"PonPortID": ponPortID})
+ }
+ return ponRemoved
+}
+
+// InclSourceIsIn checks if a source is in include list
+func (igc *IgmpGroupChannel) InclSourceIsIn(src net.IP) bool {
+ return IsIPPresent(src, igc.IncludeList)
+}
+
+// ExclSourceIsIn checks if a source is in exclude list
+func (igc *IgmpGroupChannel) ExclSourceIsIn(src net.IP) bool {
+ return IsIPPresent(src, igc.ExcludeList)
+}
+
+// AddInclSource adds a source is in include list
+func (igc *IgmpGroupChannel) AddInclSource(src net.IP) {
+ logger.Debugw(ctx, "Adding Include Source for Channel", log.Fields{"Channel": igc.GroupAddr.String(), "Device": igc.Device, "Src": src})
+ igc.IncludeList = append(igc.IncludeList, src)
+}
+
+// AddExclSource adds a source is in exclude list
+func (igc *IgmpGroupChannel) AddExclSource(src net.IP) {
+ logger.Debugw(ctx, "Adding Exclude Source for Channel", log.Fields{"Channel": igc.GroupAddr.String(), "Device": igc.Device, "Src": src})
+ igc.ExcludeList = append(igc.ExcludeList, src)
+}
+
+// UpdateExclSource update excl source list for the given channel
+func (igc *IgmpGroupChannel) UpdateExclSource(srcList []net.IP) bool {
+
+ logger.Debugw(ctx, "Updating Exclude Source for Channel", log.Fields{"Channel": igc.GroupAddr.String(), "Device": igc.Device, "Current List": igc.ExcludeList, "Incoming List": srcList})
+ if !igc.IsExclListChanged(srcList) {
+ return false
+ }
+
+ if igc.NumReceivers() == 1 {
+ igc.ExcludeList = srcList
+ } else {
+ igc.ExcludeList = igc.computeExclList(srcList)
+ }
+
+ logger.Debugw(ctx, "Updated Exclude Source for Channel", log.Fields{"Channel": igc.GroupAddr.String(), "Device": igc.Device, "Updated Excl List": igc.ExcludeList})
+ return true
+}
+
+// computeExclList computes intersection of pervious & current src list
+func (igc *IgmpGroupChannel) computeExclList(srcList []net.IP) []net.IP {
+
+ updatedSrcList := []net.IP{}
+ for _, src := range srcList {
+ for _, excl := range igc.ExcludeList {
+ if src.Equal(excl) {
+ updatedSrcList = append(updatedSrcList, src)
+ }
+ }
+ }
+ return updatedSrcList
+}
+
+// IsExclListChanged checks if excl list has been updated
+func (igc *IgmpGroupChannel) IsExclListChanged(srcList []net.IP) bool {
+
+ srcPresent := false
+ if len(igc.ExcludeList) != len(srcList) {
+ return true
+ }
+
+ for _, src := range srcList {
+ for _, excl := range igc.ExcludeList {
+ srcPresent = false
+ if src.Equal(excl) {
+ srcPresent = true
+ break
+ }
+ }
+ if !srcPresent {
+ return true
+ }
+ }
+ return false
+}
+
+// DelInclSource deletes a source is in include list
+func (igc *IgmpGroupChannel) DelInclSource(src net.IP) {
+ mvp := GetApplication().GetMvlanProfileByTag(igc.Mvlan)
+ /* If the SSM proxy is configured, then we can del the src ip from igc as whatever is in proxy that is final list */
+ if _, ok := mvp.Proxy[igc.GroupName]; !ok {
+ logger.Debugw(ctx, "Deleting Include Source for Channel", log.Fields{"Channel": igc.GroupAddr.String(), "Device": igc.Device, "Src": src})
+ for _, igp := range igc.CurReceivers {
+ if igp.InclSourceIsIn(src) {
+ logger.Infow(ctx, "Skipping deletion: Source Present for another Receiver", log.Fields{"Receiver": igp.Port})
+ return
+ }
+ }
+ for _, igp := range igc.NewReceivers {
+ if igp.InclSourceIsIn(src) {
+ logger.Infow(ctx, "Skipping deletion: Source Present for another Receiver", log.Fields{"Receiver": igp.Port})
+ return
+ }
+ }
+ } else {
+ logger.Debug(ctx, "Proxy configured, not Deleting Include Source for Channel")
+ }
+ for i, addr := range igc.IncludeList {
+ if addr.Equal(src) {
+ igc.IncludeList = append(igc.IncludeList[:i], igc.IncludeList[i+1:]...)
+ return
+ }
+ }
+}
+
+// DelExclSource deletes a source is in exclude list
+func (igc *IgmpGroupChannel) DelExclSource(src net.IP) {
+ logger.Debugw(ctx, "Deleting Exclude Source for Channel", log.Fields{"Channel": igc.GroupAddr.String(), "Device": igc.Device, "Src": src})
+
+ for _, igp := range igc.CurReceivers {
+ if igp.ExclSourceIsIn(src) {
+ logger.Infow(ctx, "Skipping deletion: Source Present for another Receiver", log.Fields{"Receiver": igp.Port})
+ return
+ }
+ }
+ for _, igp := range igc.NewReceivers {
+ if igp.ExclSourceIsIn(src) {
+ logger.Infow(ctx, "Skipping deletion: Source Present for another Receiver", log.Fields{"Receiver": igp.Port})
+ return
+ }
+ }
+ for i, addr := range igc.ExcludeList {
+ if addr.Equal(src) {
+ igc.ExcludeList = append(igc.ExcludeList[:i], igc.ExcludeList[i+1:]...)
+ return
+ }
+ }
+}
+
+// ProcessSources process the received list of either included sources or the excluded sources
+// The return value indicate sif the group is modified and needs to be informed
+// to the upstream multicast servers
+func (igc *IgmpGroupChannel) ProcessSources(port string, ip []net.IP, incl bool) (bool, bool) {
+ groupChanged := false
+ groupExclUpdated := false
+ receiverSrcListEmpty := false
+ // If the version type is 2, there isn't anything to process here
+ if igc.Version == IgmpVersion2 && *igc.ServVersion == IgmpVersion2 {
+ return false, false
+ }
+
+ igp := igc.GetReceiver(port)
+ if igp == nil {
+ logger.Warnw(ctx, "Receiver not found", log.Fields{"Port": port})
+ return false, false
+ }
+ mvp := GetApplication().GetMvlanProfileByTag(igc.Mvlan)
+ if incl {
+ for _, src := range ip {
+
+ if igp.ExclSourceIsIn(src) {
+ igp.DelExclSource(src)
+ if igc.ExclSourceIsIn(src) {
+ igc.DelExclSource(src)
+ groupChanged = true
+ }
+ }
+
+ // If the source is not in the list of include sources for the port
+ // add it. If so, check also if it is in list of include sources
+ // at the device level.
+ if !igp.InclSourceIsIn(src) {
+ igp.AddInclSource(src)
+ if !igc.InclSourceIsIn(src) {
+ igc.AddInclSource(src)
+ groupChanged = true
+ }
+ }
+ }
+ /* If any of the existing ip in the source list is removed we need to remove from the list in igp and igc */
+ if _, ok := mvp.Proxy[igc.GroupName]; ok {
+ /* If we get leave message from any subscriber, we do not have to delete the entries in the src list
+ Only if ther is any modification in the src list by proxy config update only then we need to update */
+ if len(ip) != 0 && len(ip) != len(igc.IncludeList) {
+ for i := len(igc.IncludeList) - 1; i >= 0; i-- {
+ src := igc.IncludeList[i]
+ if !IsIPPresent(src, ip) {
+ igp.DelInclSource(src)
+ igc.DelInclSource(src)
+ groupChanged = true
+ }
+ }
+ }
+ }
+ } else {
+ for _, src := range ip {
+
+ if igp.InclSourceIsIn(src) {
+ igp.DelInclSource(src)
+ if igc.InclSourceIsIn(src) {
+ igc.DelInclSource(src)
+ groupChanged = true
+ }
+ if len(igp.IncludeList) == 0 {
+ receiverSrcListEmpty = true
+ }
+ }
+
+ // If the source is not in the list of exclude sources for the port
+ // add it. If so, check also if it is in list of include sources
+ // at the device level.
+ if !igp.ExclSourceIsIn(src) {
+ igp.AddExclSource(src)
+ /* If there is any update in the src list of proxy we need to update the igc */
+ if _, ok := mvp.Proxy[igc.GroupName]; ok {
+ if !igc.ExclSourceIsIn(src) {
+ igc.AddExclSource(src)
+ groupChanged = true
+ }
+ }
+ }
+ }
+ /* If any of the existing ip in the source list is removed we need to remove from the list in igp and igc */
+ if _, ok := mvp.Proxy[igc.GroupName]; ok {
+ if len(ip) != len(igc.ExcludeList) {
+ for i := len(igc.ExcludeList) - 1; i >= 0; i-- {
+ src := igc.ExcludeList[i]
+ if !IsIPPresent(src, ip) {
+ igp.DelExclSource(src)
+ igc.DelExclSource(src)
+ groupChanged = true
+ }
+ }
+ }
+ }
+ groupExclUpdated = igc.UpdateExclSource(ip)
+ }
+ if err := igp.WriteToDb(igc.Mvlan, igc.GroupAddr, igc.Device); err != nil {
+ logger.Errorw(ctx, "Igmp group port Write to DB failed", log.Fields{"mvlan": igc.Mvlan, "GroupAddr": igc.GroupAddr})
+ }
+ return (groupChanged || groupExclUpdated), receiverSrcListEmpty
+}
+
+// GetReceiver to get receiver info
+func (igc *IgmpGroupChannel) GetReceiver(port string) *IgmpGroupPort {
+ igp := igc.NewReceivers[port]
+ if igp == nil {
+ igp = igc.CurReceivers[port]
+ }
+ return igp
+}
+
+// AddReceiver add the receiver to the device and perform other actions such as adding the group
+// to the physical device, add members, add flows to point the MC packets to the
+// group. Also, send a IGMP report upstream if there is a change in the group
+func (igd *IgmpGroupDevice) AddReceiver(port string, groupAddr net.IP,
+ group *layers.IGMPv3GroupRecord, version uint8, cvlan uint16, pbit uint8, ponPortID uint32) {
+
+ var igc *IgmpGroupChannel
+ logger.Debugw(ctx, "Processing receiver for device", log.Fields{"Channel": groupAddr, "Port": port, "Device": igd.Device})
+
+ igcIntf, ok := igd.GroupChannels.Load(groupAddr.String())
+ if !ok {
+ igc = NewIgmpGroupChannel(igd, groupAddr, version)
+ igd.GroupChannels.Store(groupAddr.String(), igc)
+ } else {
+ igc = igcIntf.(*IgmpGroupChannel)
+ }
+
+ if !igd.GroupInstalled {
+ igd.AddNewReceiver(port, groupAddr, group, cvlan, pbit, ponPortID)
+ return
+ }
+
+ isNewReceiver := igc.AddReceiver(port, group, cvlan, pbit)
+ if isNewReceiver {
+ ipsList := []net.IP{}
+ ipsIntf, _ := igd.PortChannelMap.Load(port)
+ if ipsIntf != nil {
+ ipsList = ipsIntf.([]net.IP)
+ }
+ ipsList = append(ipsList, groupAddr)
+ igd.PortChannelMap.Store(port, ipsList)
+ logger.Debugw(ctx, "Port Channel Updated", log.Fields{"Port": port, "AddedChannelList": ipsList, "Addr": groupAddr})
+
+ isNewPonReceiver := igd.AddChannelToChannelsPerPon(port, groupAddr, ponPortID)
+ //Modify group only if this is the first time the port is subscribing for the group
+ if isNewPonReceiver {
+ igd.ModMcGroup()
+ }
+ }
+ if err := igd.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp group device Write to DB failed", log.Fields{"Device": igd.Device, "GroupName": igd.GroupName, "GroupAddr": igd.GroupAddr.String()})
+ }
+}
+
+// AddNewReceiver to add new receiver
+func (igd *IgmpGroupDevice) AddNewReceiver(port string, groupAddr net.IP, group *layers.IGMPv3GroupRecord, cvlan uint16, pbit uint8, ponPortID uint32) {
+
+ logger.Debugw(ctx, "Adding New Device Receiver", log.Fields{"Channel": groupAddr, "Port": port, "Device": igd.Device})
+ igcIntf, _ := igd.GroupChannels.Load(groupAddr.String())
+ if igcIntf == nil {
+ logger.Warnw(ctx, "No Group Channel present for given channel", log.Fields{"Channel": groupAddr, "Port": port, "Device": igd.Device})
+ return
+ }
+
+ igc := igcIntf.(*IgmpGroupChannel)
+ ipsList := []net.IP{}
+ ipsIntf, _ := igd.PortChannelMap.Load(port)
+ if ipsIntf != nil {
+ ipsList = ipsIntf.([]net.IP)
+ }
+ ipsList = append(ipsList, groupAddr)
+ igd.PortChannelMap.Store(port, ipsList)
+ igd.AddChannelToChannelsPerPon(port, groupAddr, ponPortID)
+ logger.Debugw(ctx, "Port Channel Updated", log.Fields{"Port": port, "NewChannelList": ipsList, "Addr": groupAddr})
+
+ igd.AddMcGroup()
+ igc.AddReceiver(port, group, cvlan, pbit)
+ if err := igd.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp group device Write to DB failed", log.Fields{"Device": igd.Device, "GroupName": igd.GroupName, "GroupAddr": igd.GroupAddr.String()})
+ }
+}
+
+// AddReceiver add the receiver to the device and perform other actions such as adding the group
+// to the physical device, add members, add flows to point the MC packets to the
+// group. Also, send a IGMP report upstream if there is a change in the group
+func (igc *IgmpGroupChannel) AddReceiver(port string, group *layers.IGMPv3GroupRecord, cvlan uint16, pbit uint8) bool {
+
+ var igp *IgmpGroupPort
+ var groupModified = false
+ var isNewReceiver = false
+
+ var ip []net.IP
+ incl := false
+ mvp := GetApplication().GetMvlanProfileByTag(igc.Mvlan)
+ if _, ok := mvp.Proxy[igc.GroupName]; ok {
+ if mvp.Proxy[igc.GroupName].Mode == common.Include {
+ incl = true
+ }
+ ip = mvp.Proxy[igc.GroupName].SourceList
+ } else if group != nil {
+ incl = isIncl(group.Type)
+ ip = group.SourceAddresses
+ }
+ logger.Debugw(ctx, "Attempting to add receiver", log.Fields{"Version": igc.Version, "Port": port, "Incl": incl, "srcIp": ip})
+
+ //logger.Infow(ctx, "Receivers", log.Fields{"New": igc.NewReceivers, "Current": igc.CurReceivers})
+ logger.Debugw(ctx, "Receiver Group", log.Fields{"Igd GId": igc.GroupID})
+ logger.Debugw(ctx, "Receiver Channel", log.Fields{"Igd addr": igc.GroupAddr})
+ logger.Debugw(ctx, "Receiver Mvlan", log.Fields{"Igd mvlan": igc.Mvlan})
+ logger.Debugw(ctx, "Receiver Sources", log.Fields{"Igd addr": ip})
+
+ ponPortID := GetApplication().GetPonPortID(igc.Device, port)
+
+ // Process the IGMP receiver. If it is already in, we should only process the changes
+ // to source list.
+ var newRcvExists bool
+ igp, newRcvExists = igc.NewReceivers[port]
+ if !newRcvExists {
+ // Add the receiver to the list of receivers and make the necessary group modification
+ // if this is the first time the receiver is added
+ var curRcvExists bool
+ if igp, curRcvExists = igc.CurReceivers[port]; curRcvExists {
+ logger.Debugw(ctx, "Existing IGMP receiver", log.Fields{"Group": igc.GroupAddr.String(), "Port": port})
+ delete(igc.CurReceivers, port)
+ igp.QueryTimeoutCount = 0
+ igc.NewReceivers[port] = igp
+ } else {
+ // New receiver who wasn't part of earlier list
+ // Need to send out IGMP group modification for this port
+ igp = NewIgmpGroupPort(port, cvlan, pbit, igc.Version, incl, uint32(ponPortID))
+ igc.NewReceivers[port] = igp
+ isNewReceiver = true
+ logger.Debugw(ctx, "New IGMP receiver", log.Fields{"Group": igc.GroupAddr.String(), "Port": port})
+ if len(igc.NewReceivers) == 1 && len(igc.CurReceivers) == 0 {
+ groupModified = true
+ igc.AddMcFlow()
+ logger.Debugw(ctx, "Added New Flow", log.Fields{"Group": igc.GroupAddr.String(), "Port": port})
+ }
+ if !incl {
+ igc.Exclude++
+ }
+ }
+ }
+
+ // Process the include/exclude list which may end up modifying the group
+ if change, _ := igc.ProcessSources(port, ip, incl); change {
+ groupModified = true
+ }
+ igc.ProcessMode(port, incl)
+
+ // If the group is modified as this is the first receiver or due to include/exclude list modification
+ // send a report to the upstream multicast servers
+ if groupModified {
+ logger.Debug(ctx, "Group Modified and IGMP report sent to the upstream server")
+ igc.SendReport(false)
+ } else if newRcvExists {
+ return false
+ }
+
+ logger.Debugw(ctx, "Channel Receiver Added", log.Fields{"Group Channel": igc.GroupAddr, "Group Port": igp})
+
+ if err := igc.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp group channel Write to DB failed", log.Fields{"mvlan": igc.Mvlan, "GroupAddr": igc.GroupAddr})
+ }
+ if err := igp.WriteToDb(igc.Mvlan, igc.GroupAddr, igc.Device); err != nil {
+ logger.Errorw(ctx, "Igmp group port Write to DB failed", log.Fields{"mvlan": igc.Mvlan, "GroupAddr": igc.GroupAddr})
+ }
+ return isNewReceiver
+}
+
+// DelReceiver is called when Query expiry happened for a receiver. This removes the receiver from the
+// the group
+func (igc *IgmpGroupChannel) DelReceiver(port string, incl bool, srcList []net.IP) bool {
+ // The receiver may exist either in NewReceiver list or
+ // the CurReceivers list. Find and remove it from either
+ // of the lists.
+ logger.Debugw(ctx, "Deleting Receiver from Channel", log.Fields{"Port": port, "SrcList": srcList, "Incl": incl})
+ logger.Debugw(ctx, "New Receivers", log.Fields{"New": igc.NewReceivers})
+ logger.Debugw(ctx, "Current Receivers", log.Fields{"Current": igc.CurReceivers})
+
+ receiversUpdated := false
+ groupModified, receiverSrcListEmpty := igc.ProcessSources(port, srcList, incl)
+
+ if len(srcList) == 0 || len(igc.IncludeList) == 0 || receiverSrcListEmpty {
+ if igp, ok := igc.NewReceivers[port]; ok {
+ logger.Debug(ctx, "Deleting from NewReceivers")
+ delete(igc.NewReceivers, port)
+ receiversUpdated = true
+ if igp.Exclude {
+ igc.Exclude--
+ }
+ } else {
+ if igp, ok1 := igc.CurReceivers[port]; ok1 {
+ logger.Debug(ctx, "Deleting from CurReceivers")
+ delete(igc.CurReceivers, port)
+ receiversUpdated = true
+ if igp.Exclude {
+ igc.Exclude--
+ }
+ } else {
+ logger.Debug(ctx, "Receiver doesnot exist. Dropping Igmp leave")
+ return false
+ }
+ }
+ _ = db.DelIgmpRcvr(igc.Mvlan, igc.GroupAddr, igc.Device, port)
+ }
+
+ if igc.NumReceivers() == 0 {
+ igc.DelMcFlow()
+ mvp := GetApplication().GetMvlanProfileByTag(igc.Mvlan)
+ /* If proxy is configured and NumReceivers is 0, then we can reset the igc src list so that we send leave */
+ if _, ok := mvp.Proxy[igc.GroupName]; ok {
+ igc.IncludeList = []net.IP{}
+ }
+ igc.SendLeaveToServer()
+ logger.Debugw(ctx, "Deleted the receiver Flow", log.Fields{"Num Receivers": igc.NumReceivers()})
+ return true
+ }
+ if groupModified {
+ igc.SendReport(false)
+ logger.Infow(ctx, "Updated SourceList for Channel", log.Fields{"Current": igc.CurReceivers, "New": igc.NewReceivers})
+ }
+ if err := igc.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp group channel Write to DB failed", log.Fields{"mvlan": igc.Mvlan, "GroupAddr": igc.GroupAddr})
+ }
+ logger.Infow(ctx, "Updated Receiver info for Channel", log.Fields{"Current": igc.CurReceivers, "New": igc.NewReceivers})
+
+ return receiversUpdated
+}
+
+// NumReceivers to get number of receivers
+func (igd *IgmpGroupDevice) NumReceivers() int {
+ var numReceivers int
+ len := func(key interface{}, value interface{}) bool {
+ numReceivers++
+ return true
+ }
+ igd.PortChannelMap.Range(len)
+ return numReceivers
+}
+
+// DelReceiver is called when Query expiry happened for a receiver. This removes the receiver from the
+// the group
+func (igd *IgmpGroupDevice) DelReceiver(groupAddr net.IP, port string, group *layers.IGMPv3GroupRecord, ponPortID uint32) {
+
+ logger.Debugw(ctx, "Deleting Receiver for Device", log.Fields{"port": port, "GroupIP": groupAddr.String()})
+ var igc *IgmpGroupChannel
+ var igcIntf interface{}
+ var ok bool
+ var srcList []net.IP
+ incl := false
+ mvp := GetApplication().GetMvlanProfileByTag(igd.Mvlan)
+
+ if _, ok := mvp.Proxy[igd.GroupName]; ok {
+ incl = true
+ } else if group != nil {
+ srcList = group.SourceAddresses
+ incl = isIncl(group.Type)
+ }
+
+ if igcIntf, ok = igd.GroupChannels.Load(groupAddr.String()); !ok {
+ logger.Warnw(ctx, "Igmp Channel for group IP doesnt exist", log.Fields{"GroupAddr": groupAddr.String()})
+ return
+ }
+ igc = igcIntf.(*IgmpGroupChannel)
+ if ok := igc.DelReceiver(port, incl, srcList); !ok {
+ return
+ }
+
+ if igc.NumReceivers() == 0 {
+ igd.DelIgmpGroupChannel(igc)
+ }
+ igd.DelPortFromChannel(port, groupAddr)
+ isGroupModified := igd.RemoveChannelFromChannelsPerPon(port, groupAddr, ponPortID)
+
+ //Remove port from receiver if port has no subscription to any of the group channels
+ if isGroupModified {
+ igd.ModMcGroup()
+ }
+ if err := igd.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp group device Write to DB failed", log.Fields{"Device": igd.Device, "GroupName": igd.GroupName, "GroupAddr": igd.GroupAddr.String()})
+ }
+}
+
+// DelChannelReceiver is called when Query expiry happened for a receiver. This removes the receiver from the
+// the group
+func (igd *IgmpGroupDevice) DelChannelReceiver(groupAddr net.IP) map[string]*IgmpGroupPort {
+
+ portsRemoved := make(map[string]*IgmpGroupPort)
+ groupModified := false
+ // ifEmpty := true
+ igcIntf, _ := igd.GroupChannels.Load(groupAddr.String())
+
+ if igcIntf == nil {
+ return portsRemoved
+ }
+ igc := igcIntf.(*IgmpGroupChannel)
+
+ for port, igp := range igc.NewReceivers {
+ _ = db.DelIgmpRcvr(igc.Mvlan, igc.GroupAddr, igc.Device, port) //TODO: Y not here
+ igd.DelPortFromChannel(port, igc.GroupAddr)
+ ponPortID := GetApplication().GetPonPortID(igd.Device, port)
+ groupModified = igd.RemoveChannelFromChannelsPerPon(port, igc.GroupAddr, ponPortID)
+ delete(igc.NewReceivers, port)
+ portsRemoved[port] = igp
+ }
+ for port, igp := range igc.CurReceivers {
+ _ = db.DelIgmpRcvr(igc.Mvlan, igc.GroupAddr, igc.Device, port)
+ igd.DelPortFromChannel(port, igc.GroupAddr)
+ ponPortID := GetApplication().GetPonPortID(igd.Device, port)
+ groupModified = igd.RemoveChannelFromChannelsPerPon(port, igc.GroupAddr, ponPortID)
+ delete(igc.CurReceivers, port)
+ portsRemoved[port] = igp
+ }
+
+ igc.DelMcFlow()
+ igd.DelIgmpGroupChannel(igc)
+ igc.Exclude = 0
+ igc.SendLeaveToServer()
+
+ if groupModified {
+ igd.ModMcGroup()
+ }
+ if err := igd.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp group device Write to DB failed", log.Fields{"Device": igd.Device, "GroupName": igd.GroupName, "GroupAddr": igd.GroupAddr.String()})
+ }
+ logger.Debugw(ctx, "Deleted the receiver Flow", log.Fields{"Num Receivers": igc.NumReceivers()})
+ return portsRemoved
+}
+
+// DelIgmpGroupChannel to delete igmp group channel
+func (igd *IgmpGroupDevice) DelIgmpGroupChannel(igc *IgmpGroupChannel) {
+
+ if igc.NumReceivers() != 0 {
+ igc.DelAllReceivers()
+ }
+ _ = db.DelIgmpChannel(igc.Mvlan, igc.GroupName, igc.Device, igc.GroupAddr)
+ igd.GroupChannels.Delete(igc.GroupAddr.String())
+ logger.Infow(ctx, "Deleted the Channel from Device", log.Fields{"Channel": igc.GroupAddr.String()})
+ isLenZero := true
+ checkIfEmpty := func(key interface{}, value interface{}) bool {
+ isLenZero = false
+ return false
+ }
+ igd.GroupChannels.Range(checkIfEmpty)
+ if isLenZero {
+ logger.Infow(ctx, "No more active channels. Deleting MC Group", log.Fields{"Device": igd.Device, "Group": igd.GroupName})
+ igd.DelMcGroup(false)
+ }
+}
+
+// func (igd *IgmpGroupDevice) DelIgmpChannel(igc *IgmpGroupChannel) {
+// db.DelIgmpChannel(igc.GroupName, igc.Device, igc.GroupAddr)
+// delete(igd.GroupChannels, igc.GroupAddr.String())
+// logger.Debugw(ctx, "Deleted the Channel", log.Fields{"Num Receivers": igc.NumReceivers()})
+// }
+
+// DelPortFromChannel to delete port from channel
+func (igd *IgmpGroupDevice) DelPortFromChannel(port string, groupAddr net.IP) bool {
+ ipsList := []net.IP{}
+ ipsListIntf, _ := igd.PortChannelMap.Load(port)
+ if ipsListIntf != nil {
+ ipsList = ipsListIntf.([]net.IP)
+ }
+ for i, addr := range ipsList {
+ if addr.Equal(groupAddr) {
+ ipsList = append(ipsList[:i], ipsList[i+1:]...)
+ //Remove port from receiver if port has no subscription to any of the group channels
+ if len(ipsList) == 0 {
+ igd.PortChannelMap.Delete(port)
+ } else {
+ //Update the map with modified ips list
+ igd.PortChannelMap.Store(port, ipsList)
+ }
+ logger.Debugw(ctx, "Port Channel Updated", log.Fields{"Port": port, "DelChannelList": ipsList, "Addr": groupAddr.String()})
+ return true
+ }
+ }
+ return false
+}
+
+// DelIgmpGroup deletes all devices for the provided igmp group
+func (ig *IgmpGroup) DelIgmpGroup() {
+ logger.Infow(ctx, "Deleting All Device for Group", log.Fields{"Group": ig.GroupName})
+ for _, igd := range ig.Devices {
+ ig.DelIgmpGroupDevice(igd)
+ }
+ GetApplication().DelIgmpGroup(ig)
+}
+
+// DelAllChannels deletes all receiver for the provided igmp device
+func (igd *IgmpGroupDevice) DelAllChannels() {
+ logger.Infow(ctx, "Deleting All Channel for Device", log.Fields{"Device": igd.Device, "Group": igd.GroupName})
+ delGroupChannels := func(key interface{}, value interface{}) bool {
+ igc := value.(*IgmpGroupChannel)
+ igd.DelIgmpGroupChannel(igc)
+ return true
+ }
+ igd.GroupChannels.Range(delGroupChannels)
+}
+
+// DelAllReceivers deletes all receiver for the provided igmp device
+func (igc *IgmpGroupChannel) DelAllReceivers() {
+ logger.Infow(ctx, "Deleting All Receiver for Channel", log.Fields{"Device": igc.Device, "Channel": igc.GroupAddr.String()})
+ _ = db.DelAllIgmpRcvr(igc.Mvlan, igc.GroupAddr, igc.Device)
+ igc.Exclude = 0
+ igc.DelMcFlow()
+ igc.SendLeaveToServer()
+ logger.Infow(ctx, "MC Flow deleted and Leave sent", log.Fields{"Channel": igc.GroupAddr.String(), "Device": igc.Device})
+}
+
+// ProcessQuery process query received from the upstream IGMP server
+func (igd *IgmpGroupDevice) ProcessQuery(groupAddr net.IP, ver uint8) {
+ logger.Debugw(ctx, "Received Query From Server", log.Fields{"Version": ver})
+ if ver != *igd.ServVersion {
+ igd.ServVersionExpiry = time.Now().Add(time.Duration(2*igd.proxyCfg.KeepAliveInterval) * time.Second)
+ *igd.ServVersion = ver
+ mvp := GetApplication().GetMvlanProfileByTag(igd.Mvlan)
+ if err := mvp.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Mvlan profile write to DB failed", log.Fields{"ProfileName": mvp.Name})
+ }
+ }
+ if igc, ok := igd.GroupChannels.Load(groupAddr.String()); ok {
+ igc.(*IgmpGroupChannel).SendReport(true)
+ return
+ }
+ logger.Infow(ctx, "No Members for Channel. Dropping Igmp Query", log.Fields{"Group": igd.GroupName, "Channel": groupAddr.String()})
+}
+
+// Igmpv2ReportPacket build an IGMPv2 Report for the upstream servers
+func (igc *IgmpGroupChannel) Igmpv2ReportPacket() ([]byte, error) {
+ logger.Debugw(ctx, "Buidling IGMP version 2 Report", log.Fields{"Device": igc.Device})
+ return IgmpReportv2Packet(igc.GroupAddr, igc.Mvlan, (*igc.proxyCfg).IgmpCos, **igc.IgmpProxyIP)
+}
+
+// Igmpv3ReportPacket build an IGMPv3 Report for the upstream servers
+func (igc *IgmpGroupChannel) Igmpv3ReportPacket() ([]byte, error) {
+ logger.Debugw(ctx, "Buidling IGMP version 3 Report", log.Fields{"Device": igc.Device, "Exclude": igc.Exclude})
+ if igc.Exclude > 0 {
+ return Igmpv3ReportPacket(igc.GroupAddr, igc.Mvlan, (*igc.proxyCfg).IgmpCos, **igc.IgmpProxyIP, false, igc.ExcludeList)
+ }
+ return Igmpv3ReportPacket(igc.GroupAddr, igc.Mvlan, (*igc.proxyCfg).IgmpCos, **igc.IgmpProxyIP, true, igc.IncludeList)
+}
+
+// SendReport send a consolidated report to the server
+func (igc *IgmpGroupChannel) SendReport(isQuery bool) {
+ var report []byte
+ var err error
+ logger.Debugw(ctx, "Checking Version", log.Fields{"IGC Version": igc.Version, "Proxy Version": (*igc.proxyCfg).IgmpVerToServer,
+ "Result": (getVersion((*igc.proxyCfg).IgmpVerToServer) == IgmpVersion2)})
+
+ /**
+ +------------------------------------------------------------------------+
+ | IGMP version(towards BNG) Configured at VGC |
+ +-------------------------------+----------------------------------------+
+ | v2 | v3 |
+ +===================+==========+===============================+========================================+
+ | Received From RG | V2 Join | Process and Send as V2 to BNG | Process, Convert to v3 and Send to BNG |
+ | | | | Process, Send as v2, if the BNG is v2 |
+ +===================+----------+-------------------------------+----------------------------------------+
+ | V3 Join | Process and Send as V2 to BNG | Process, Send v3 to BNG |
+ | | | Process, Convert, Send as v2, if the |
+ | | | BNG is v2 |
+ +===================+==========+===============================+========================================+
+ | Received From BNG | V2 Query | V2 response to BNG | V2 response to BNG |
+ +===================+----------+-------------------------------+----------------------------------------+
+ | V3 Query | Discard | V3 response to BNG |
+ +==========+===============================+========================================+
+ */
+ // igc.Version: igmp version received from RG.
+ // igc.ServVersion: igmp version received from BNG or IgmpVerToServer present in proxy igmp conf.
+
+ if isQuery && *igc.ServVersion == IgmpVersion3 && getVersion((*igc.proxyCfg).IgmpVerToServer) == IgmpVersion2 {
+ // This is the last scenario where we must discard the query processing.
+ logger.Debug(ctx, "Dropping query packet since the server verion is v3 but igmp proxy version is v2")
+ return
+ }
+
+ if *igc.ServVersion == IgmpVersion2 || getVersion((*igc.proxyCfg).IgmpVerToServer) == IgmpVersion2 {
+ report, err = igc.Igmpv2ReportPacket()
+ } else {
+ report, err = igc.Igmpv3ReportPacket()
+ }
+ if err != nil {
+ logger.Warnw(ctx, "Error Preparing Report", log.Fields{"Device": igc.Device, "Ver": igc.Version, "Reason": err.Error()})
+ return
+ }
+ nni, err := GetApplication().GetNniPort(igc.Device)
+ if err == nil {
+ _ = cntlr.GetController().PacketOutReq(igc.Device, nni, nni, report, false)
+ } else {
+ logger.Warnw(ctx, "Didn't find NNI port", log.Fields{"Device": igc.Device})
+ }
+}
+
+// AddMcFlow adds flow to the device when the first receiver joins
+func (igc *IgmpGroupChannel) AddMcFlow() {
+ flow, err := igc.BuildMcFlow()
+ if err != nil {
+ logger.Warnw(ctx, "MC Flow Build Failed", log.Fields{"Reason": err.Error()})
+ return
+ }
+ port, _ := GetApplication().GetNniPort(igc.Device)
+ _ = cntlr.GetController().AddFlows(port, igc.Device, flow)
+}
+
+// DelMcFlow deletes flow from the device when the last receiver leaves
+func (igc *IgmpGroupChannel) DelMcFlow() {
+ flow, err := igc.BuildMcFlow()
+ if err != nil {
+ logger.Warnw(ctx, "MC Flow Build Failed", log.Fields{"Reason": err.Error()})
+ return
+ }
+ flow.ForceAction = true
+ device := GetApplication().GetDevice(igc.Device)
+
+ if mvpIntf, _ := GetApplication().MvlanProfilesByTag.Load(igc.Mvlan); mvpIntf != nil {
+ mvp := mvpIntf.(*MvlanProfile)
+ err := mvp.DelFlows(device, flow)
+ if err != nil {
+ logger.Warnw(ctx, "Delering IGMP Flow for device failed ", log.Fields{"Device": device, "err": err})
+ }
+ }
+}
+
+// BuildMcFlow builds the flow using which it is added/deleted
+func (igc *IgmpGroupChannel) BuildMcFlow() (*of.VoltFlow, error) {
+ flow := &of.VoltFlow{}
+ flow.SubFlows = make(map[uint64]*of.VoltSubFlow)
+ //va := GetApplication()
+ logger.Infow(ctx, "Building Mcast flow", log.Fields{"Mcast Group": igc.GroupAddr.String(), "Mvlan": igc.Mvlan.String()})
+ uintGroupAddr := ipv4ToUint(igc.GroupAddr)
+ subFlow := of.NewVoltSubFlow()
+ subFlow.SetMatchVlan(igc.Mvlan)
+ subFlow.SetIpv4Match()
+ subFlow.SetMatchDstIpv4(igc.GroupAddr)
+ mvp := GetApplication().GetMvlanProfileByTag(igc.Mvlan)
+ //nni, err := va.GetNniPort(igc.Device)
+ //if err != nil {
+ // return nil, err
+ //}
+ //inport, err := va.GetPortID(nni)
+ //if err != nil {
+ // return nil, err
+ //}
+ //subFlow.SetInPort(inport)
+ subFlow.SetOutGroup(igc.GroupID)
+ cookiePort := uintGroupAddr
+ subFlow.Cookie = uint64(cookiePort)<<32 | uint64(igc.Mvlan)
+ subFlow.Priority = of.McFlowPriority
+ metadata := uint64(mvp.PonVlan)
+ subFlow.SetTableMetadata(metadata)
+
+ flow.SubFlows[subFlow.Cookie] = subFlow
+ logger.Infow(ctx, "Built Mcast flow", log.Fields{"cookie": subFlow.Cookie, "subflow": subFlow})
+ return flow, nil
+}
+
+//DelFlows - Triggers flow deletion after registering for flow indication event
+func (mvp *MvlanProfile) DelFlows(device *VoltDevice, flow *of.VoltFlow) error {
+ mvp.mvpFlowLock.Lock()
+ defer mvp.mvpFlowLock.Unlock()
+
+ var flowMap map[string]bool
+ var ok bool
+
+ for cookie := range flow.SubFlows {
+ cookie := strconv.FormatUint(cookie, 10)
+ fe := &FlowEvent{
+ eType: EventTypeMcastFlowRemoved,
+ device: device.Name,
+ cookie: cookie,
+ eventData: mvp,
+ }
+ device.RegisterFlowDelEvent(cookie, fe)
+
+ if flowMap, ok = mvp.PendingDeleteFlow[device.Name]; !ok {
+ flowMap = make(map[string]bool)
+ }
+ flowMap[cookie] = true
+ mvp.PendingDeleteFlow[device.Name] = flowMap
+ }
+ if err := mvp.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Mvlan profile write to DB failed", log.Fields{"ProfileName": mvp.Name})
+ }
+ return cntlr.GetController().DelFlows(device.NniPort, device.Name, flow)
+}
+
+//FlowRemoveSuccess - Process flow success indication
+func (mvp *MvlanProfile) FlowRemoveSuccess(cookie string, device string) {
+ mvp.mvpFlowLock.Lock()
+ defer mvp.mvpFlowLock.Unlock()
+
+ logger.Infow(ctx, "Mvlan Flow Remove Success Notification", log.Fields{"MvlanProfile": mvp.Name, "Cookie": cookie, "Device": device})
+
+ if _, ok := mvp.PendingDeleteFlow[device]; ok {
+ delete(mvp.PendingDeleteFlow[device], cookie)
+ }
+
+ if err := mvp.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Mvlan profile write to DB failed", log.Fields{"ProfileName": mvp.Name})
+ }
+}
+
+//FlowRemoveFailure - Process flow failure indication
+func (mvp *MvlanProfile) FlowRemoveFailure(cookie string, device string, errorCode uint32, errReason string) {
+
+ mvp.mvpFlowLock.Lock()
+ defer mvp.mvpFlowLock.Unlock()
+
+ if flowMap, ok := mvp.PendingDeleteFlow[device]; ok {
+ if _, ok := flowMap[cookie]; ok {
+ logger.Errorw(ctx, "Mvlan Flow Remove Failure Notification", log.Fields{"MvlanProfile": mvp.Name, "Cookie": cookie, "ErrorCode": errorCode, "ErrorReason": errReason, "Device": device})
+ return
+ }
+ }
+ logger.Errorw(ctx, "Mvlan Flow Del Failure Notification for Unknown cookie", log.Fields{"MvlanProfile": mvp.Name, "Cookie": cookie, "ErrorCode": errorCode, "ErrorReason": errReason})
+
+}
+
+// AddMcGroup add the new group on the device when a receiver joins the group
+func (igd *IgmpGroupDevice) AddMcGroup() {
+ if !igd.GroupInstalled {
+ group := &of.Group{}
+ group.Command = of.GroupCommandAdd
+ group.GroupID = igd.GroupID
+ group.Device = igd.Device
+ group.SetVlan = igd.PonVlan
+ group.IsPonVlanPresent = igd.IsPonVlanPresent
+
+ addbuckets := func(key interface{}, value interface{}) bool {
+ port := key.(string)
+ var portID uint32
+ if d := GetApplication().GetDevice(group.Device); d != nil {
+ GetApplication().portLock.Lock()
+ p := d.GetPort(port)
+ GetApplication().portLock.Unlock()
+ portID = p.ID
+ }
+ //ponPortID := key.(uint32)
+ if portID != 0xFF {
+ group.Buckets = append(group.Buckets, portID)
+ }
+ return true
+ }
+ igd.PortChannelMap.Range(addbuckets)
+
+ port, _ := GetApplication().GetNniPort(igd.Device)
+ _ = cntlr.GetController().GroupUpdate(port, igd.Device, group)
+ igd.GroupInstalled = true
+ }
+}
+
+// ModMcGroup updates the group on the device when either a receiver leaves
+// or joins the group
+func (igd *IgmpGroupDevice) ModMcGroup() {
+ if igd.GroupInstalled {
+ group := &of.Group{}
+ group.Command = of.GroupCommandMod
+ group.GroupID = igd.GroupID
+ group.Device = igd.Device
+ group.SetVlan = igd.PonVlan
+ group.IsPonVlanPresent = igd.IsPonVlanPresent
+
+ addbuckets := func(key interface{}, value interface{}) bool {
+ port := key.(string)
+ var portID uint32
+ if d := GetApplication().GetDevice(group.Device); d != nil {
+ GetApplication().portLock.Lock()
+ p := d.GetPort(port)
+ GetApplication().portLock.Unlock()
+ portID = p.ID
+ }
+ //ponPortID := key.(uint32)
+ if portID != 0xFF {
+ group.Buckets = append(group.Buckets, portID)
+ }
+ return true
+ }
+ igd.PortChannelMap.Range(addbuckets)
+
+ port, _ := GetApplication().GetNniPort(igd.Device)
+ _ = cntlr.GetController().GroupUpdate(port, igd.Device, group)
+ } else {
+ logger.Warnw(ctx, "Update Group Failed. Group not yet created", log.Fields{"Igd": igd.Device})
+ }
+}
+
+// DelMcGroup : The group is deleted when the last receiver leaves the group
+func (igd *IgmpGroupDevice) DelMcGroup(forceDelete bool) {
+
+ logger.Infow(ctx, "Delete Mc Group Request", log.Fields{"Device": igd.Device, "GroupID": igd.GroupID, "ForceFlag": forceDelete, "GroupInstalled": igd.GroupInstalled})
+ /*
+ if !forceDelete && !checkIfForceGroupRemove(igd.Device) {
+ if success := AddToPendingPool(igd.Device, igd.getKey()); success {
+ return
+ }
+ }*/
+ if igd.GroupInstalled {
+ logger.Debugw(ctx, "Deleting Group", log.Fields{"Device": igd.Device, "Id": igd.GroupID})
+ group := &of.Group{}
+ group.Command = of.GroupCommandDel
+ group.GroupID = igd.GroupID
+ group.Device = igd.Device
+ group.ForceAction = true
+
+ port, _ := GetApplication().GetNniPort(igd.Device)
+ _ = cntlr.GetController().GroupUpdate(port, igd.Device, group)
+ igd.GroupInstalled = false
+ }
+}
+
+//AddToPendingPool - adds Igmp Device obj to pending pool
+func AddToPendingPool(device string, groupKey string) bool {
+
+ logger.Infow(ctx, "Add Device to IgmpGroup Pending Pool", log.Fields{"Device": device, "GroupKey": groupKey})
+ if grp, ok := GetApplication().IgmpGroups.Load(groupKey); ok {
+ ig := grp.(*IgmpGroup)
+ ig.PendingPoolLock.Lock()
+ logger.Infow(ctx, "Adding Device to IgmpGroup Pending Pool", log.Fields{"Device": device, "GroupID": ig.GroupID, "GroupName": ig.GroupName, "GroupAddr": ig.GroupAddr.String()})
+ ig.PendingGroupForDevice[device] = time.Now().Add(time.Duration(GroupExpiryTime) * time.Minute)
+ ig.PendingPoolLock.Unlock()
+ if err := ig.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp group Write to DB failed", log.Fields{"groupName": ig.GroupName})
+ }
+ return true
+ }
+ return false
+}
+
+/*
+func checkIfForceGroupRemove(device string) bool {
+ if d := GetApplication().GetDevice(device); d != nil {
+ if d.State == cntlr.DeviceStateREBOOTED || d.State == cntlr.DeviceStateDOWN {
+ return true
+ }
+ }
+ return false
+}*/
+
+// IgmpLeaveToServer sends IGMP leave to server. Called when the last receiver leaves the group
+func (igc *IgmpGroupChannel) IgmpLeaveToServer() {
+ if leave, err := IgmpLeavePacket(igc.GroupAddr, igc.Mvlan, (*igc.proxyCfg).IgmpCos, **igc.IgmpProxyIP); err == nil {
+ nni, err1 := GetApplication().GetNniPort(igc.Device)
+ if err1 == nil {
+ _ = cntlr.GetController().PacketOutReq(igc.Device, nni, nni, leave, false)
+ }
+ }
+}
+
+// SendLeaveToServer delete the group when the last receiver leaves the group
+func (igc *IgmpGroupChannel) SendLeaveToServer() {
+ /**
+ +-------------------------------------------------------------------------+
+ | IGMP version(towards BNG) Configured at VGC |
+ +-------------------------------+-----------------------------------------+
+ | v2 | v3 |
+ +===================+==========+===============================+=========================================+
+ | Received From RG | V2 Leave | Process and Send as V2 to BNG | Process, Convert to V3 and Send to BNG/ |
+ | | | | Process, Send as V2, if the BNG is V2 |
+ +===================+----------+-------------------------------+-----------------------------------------+
+ | V3 Leave | Process and Send as V2 to BNG | Process, Send V3 to BNG |
+ | | | Process, Convert, Send as V2, if the |
+ | | | BNG is v2 |
+ +==========+===============================+=========================================+
+ */
+ // igc.Version: igmp version received from RG.
+ // igc.ServVersion: igmp version received from BNG or IgmpVerToServer present in proxy igmp conf.
+
+ logger.Debugw(ctx, "Sending IGMP leave upstream", log.Fields{"Device": igc.Device})
+ if *igc.ServVersion == IgmpVersion2 || getVersion((*igc.proxyCfg).IgmpVerToServer) == IgmpVersion2 {
+ igc.IgmpLeaveToServer()
+ } else {
+ igc.SendReport(false)
+ }
+}
+
+// QueryExpiry processes query expiry. Upon expiry, take stock of the situation
+// add either retain/release the group based on number of receivers left
+func (igd *IgmpGroupDevice) QueryExpiry() {
+ logger.Debugw(ctx, "Query Expiry", log.Fields{"Device": igd.Device})
+
+
+ // Delete the IGMP flow added for this port if port state is down or query count exceeded
+ handleQueryExp := func(key interface{}, value interface{}) bool {
+ igc := value.(*IgmpGroupChannel)
+ for portKey, port := range igc.CurReceivers {
+
+ if portKey == StaticPort {
+ continue
+ }
+
+ logger.Warnw(ctx, "Expired Receiver Port", log.Fields{"PortKey": portKey, "IGP": port, "GroupAddr": igc.GroupAddr,
+ "Count": port.QueryTimeoutCount})
+ state, err := cntlr.GetController().GetPortState(igc.Device, portKey)
+ logger.Debugw(ctx, "Expired Member Port State", log.Fields{"state": state})
+ ponPortID := GetApplication().GetPonPortID(igd.Device, portKey)
+ if err == nil && state == cntlr.PortStateDown {
+ igd.DelReceiver(igc.GroupAddr, portKey, nil, ponPortID)
+ }
+
+ port.QueryTimeoutCount++
+ logger.Debugw(ctx, "Expired Port TimeoutCount", log.Fields{"count": port.QueryTimeoutCount})
+ if port.QueryTimeoutCount >= (*igc.proxyCfg).KeepAliveCount {
+ logger.Errorw(ctx, "Expiry Timeout count exceeded. Trigger delete receiver", log.Fields{"PortKey": portKey,
+ "GroupAddr": igc.GroupAddr, "Count": port.QueryTimeoutCount})
+ igd.DelReceiver(igc.GroupAddr, portKey, nil, ponPortID)
+ SendQueryExpiredEventGroupSpecific(portKey, igd, igc)
+ } else {
+ _ = port.WriteToDb(igc.Mvlan, igc.GroupAddr, igc.Device)
+ }
+ }
+ return true
+ }
+ igd.GroupChannels.Range(handleQueryExp)
+}
+
+// SendQueryExpiredEventGroupSpecific to send group specific query expired event.
+func SendQueryExpiredEventGroupSpecific(portKey string, igd *IgmpGroupDevice, igc *IgmpGroupChannel) {
+
+ logger.Info(ctx, "Processing-SendQueryExpiredEventGroupSpecific-Event")
+ va := GetApplication()
+ mvpName := va.GetMvlanProfileByTag(igd.Mvlan).Name
+
+ sendEvent := func(key interface{}, value interface{}) bool {
+ if value.(*VoltService).IgmpEnabled && value.(*VoltService).MvlanProfileName == mvpName {
+ logger.Debugw(ctx, "sending-query-expired-group-specific-event", log.Fields{"EventType": QueryExpiredGroupSpecific, "ServiceName": value.(*VoltService).Name})
+ }
+ return false
+ }
+
+ // Fetching service name to send with query expired event.
+ vpvs, _ := va.VnetsByPort.Load(portKey)
+ if vpvs == nil {
+ logger.Errorw(ctx, "volt-port-vnet-is-nil", log.Fields{"vpvs": vpvs})
+ return
+ }
+
+ for _, vpv := range vpvs.([]*VoltPortVnet) {
+ vpv.services.Range(sendEvent)
+ }
+}
+
+// GetMcastServiceForSubAlarm to get mcast service name for subscriber alarm.
+func GetMcastServiceForSubAlarm(uniPort *VoltPort, mvp *MvlanProfile) string {
+
+ var serviceName string
+ mvpName := mvp.Name
+
+ va := GetApplication()
+
+ sendAlm := func(key interface{}, value interface{}) bool {
+ if value.(*VoltService).IgmpEnabled && value.(*VoltService).MvlanProfileName == mvpName {
+ serviceName = value.(*VoltService).Name
+ }
+ return true
+ }
+
+ // Fetching service name to send with active channels exceeded per subscriber alarm.
+ vpvs, _ := va.VnetsByPort.Load(uniPort.Name)
+ if vpvs == nil {
+ logger.Errorw(ctx, "volt-port-vnet-is-nil", log.Fields{"vpvs": vpvs})
+ return serviceName
+ }
+
+ for _, vpv := range vpvs.([]*VoltPortVnet) {
+ vpv.services.Range(sendAlm)
+ }
+
+ return serviceName
+
+}
+
+// NumReceivers returns total number of receivers left on the group
+func (igc *IgmpGroupChannel) NumReceivers() uint32 {
+ return uint32(len(igc.CurReceivers) + len(igc.NewReceivers))
+}
+
+// SendQuery sends query to the receivers for counting purpose
+func (igc *IgmpGroupChannel) SendQuery() {
+ //var b []byte
+ //var err error
+ for portKey, port := range igc.NewReceivers {
+ igc.CurReceivers[portKey] = port
+ }
+
+ igc.NewReceivers = make(map[string]*IgmpGroupPort)
+
+ logger.Debugw(ctx, "Sending Query to receivers", log.Fields{"Receivers": igc.CurReceivers})
+ for port, groupPort := range igc.CurReceivers {
+ if port == StaticPort {
+ continue
+ }
+ if queryPkt, err := igc.buildQuery(igc.GroupAddr, of.VlanType(groupPort.CVlan), groupPort.Pbit); err == nil {
+ _ = cntlr.GetController().PacketOutReq(igc.Device, port, port, queryPkt, false)
+ logger.Debugw(ctx, "Query Sent", log.Fields{"Device": igc.Device, "Port": port, "Packet": queryPkt})
+ } else {
+ logger.Warnw(ctx, "Query Creation Failed", log.Fields{"Reason": err.Error()})
+ }
+ }
+
+}
+
+// buildQuery to build query packet
+func (igc *IgmpGroupChannel) buildQuery(groupAddr net.IP, cVlan of.VlanType, pbit uint8) ([]byte, error) {
+ if igc.Version == IgmpVersion2 {
+ return Igmpv2QueryPacket(igc.GroupAddr, cVlan, **igc.IgmpProxyIP, pbit, (*igc.proxyCfg).MaxResp)
+ }
+ return Igmpv3QueryPacket(igc.GroupAddr, cVlan, **igc.IgmpProxyIP, pbit, (*igc.proxyCfg).MaxResp)
+}
+
+// IgmpGroup implements a single MCIP that may have multiple receivers
+// connected via multiple devices (OLTs). The IGMP group is stored on the
+// VOLT application.
+type IgmpGroup struct {
+ GroupID uint32
+ Mvlan of.VlanType
+ PonVlan of.VlanType
+ GroupName string
+ GroupAddr net.IP
+ Devices map[string]*IgmpGroupDevice `json:"-"`
+ PendingGroupForDevice map[string]time.Time //map [deviceId, timestamp] (ExpiryTime = leave time + 15mins)
+ Version string
+ IsPonVlanPresent bool
+ IsChannelBasedGroup bool
+ PendingPoolLock sync.RWMutex
+ IsGroupStatic bool
+ IgmpGroupLock sync.RWMutex
+}
+
+// NewIgmpGroup is constructor for an IGMP group
+func NewIgmpGroup(name string, vlan of.VlanType) *IgmpGroup {
+ ig := IgmpGroup{}
+ ig.GroupName = name
+ ig.Mvlan = vlan
+ ig.Devices = make(map[string]*IgmpGroupDevice)
+ ig.PendingGroupForDevice = make(map[string]time.Time)
+ return &ig
+}
+
+// IgmpGroupInit to initialize igmp group members
+func (ig *IgmpGroup) IgmpGroupInit(name string, gip net.IP, mvp *MvlanProfile) {
+ ig.GroupName = name
+ ig.Mvlan = mvp.Mvlan
+ ig.PonVlan = mvp.PonVlan
+ ig.IsPonVlanPresent = mvp.IsPonVlanPresent
+ ig.Devices = make(map[string]*IgmpGroupDevice)
+ ig.PendingGroupForDevice = make(map[string]time.Time)
+ ig.IsChannelBasedGroup = mvp.IsChannelBasedGroup
+ ig.IsGroupStatic = mvp.Groups[name].IsStatic
+ if ig.IsChannelBasedGroup {
+ ig.GroupAddr = gip
+ } else {
+ ig.GroupAddr = net.ParseIP("0.0.0.0")
+ }
+}
+
+// IgmpGroupReInit to re-initialize igmp group members
+func (ig *IgmpGroup) IgmpGroupReInit(name string, gip net.IP) {
+
+ logger.Infow(ctx, "Reinitialize Igmp Group", log.Fields{"GroupID": ig.GroupID, "OldName": ig.GroupName, "Name": name, "OldAddr": ig.GroupAddr.String(), "GroupAddr": gip.String()})
+
+ ig.GroupName = name
+ if ig.IsChannelBasedGroup {
+ ig.GroupAddr = gip
+ } else {
+ ig.GroupAddr = net.ParseIP("0.0.0.0")
+ }
+
+ for _, igd := range ig.Devices {
+ igd.IgmpGroupDeviceReInit(ig)
+ }
+}
+
+// IsStaticGroup to check if group is static
+func (mvp *MvlanProfile) IsStaticGroup(groupName string) bool {
+ return mvp.Groups[groupName].IsStatic
+}
+
+// updateGroupName to update group name
+func (ig *IgmpGroup) updateGroupName(newGroupName string) {
+ if !ig.IsChannelBasedGroup {
+ logger.Errorw(ctx, "Group name update not supported for GroupChannel based group", log.Fields{"Ig": ig})
+ return
+ }
+ oldKey := ig.getKey()
+ ig.GroupName = newGroupName
+ for _, igd := range ig.Devices {
+ igd.updateGroupName(newGroupName)
+ }
+ if err := ig.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp group Write to DB failed", log.Fields{"groupName": ig.GroupName})
+ }
+ if !ig.IsChannelBasedGroup {
+ _ = db.DelIgmpGroup(oldKey)
+ }
+}
+
+//HandleGroupMigration - handles migration of group members between static & dynamic
+func (ig *IgmpGroup) HandleGroupMigration(deviceID string, groupAddr net.IP) {
+
+ var group *layers.IGMPv3GroupRecord
+ app := GetApplication()
+ if deviceID == "" {
+ logger.Infow(ctx, "Handle Group Migration Request for all devices", log.Fields{"DeviceID": deviceID, "GroupAddr": groupAddr, "IG": ig.GroupName, "Mvlan": ig.Mvlan})
+ for device := range ig.Devices {
+ ig.HandleGroupMigration(device, groupAddr)
+ }
+ } else {
+ logger.Infow(ctx, "Handle Group Migration Request", log.Fields{"DeviceID": deviceID, "GroupAddr": groupAddr, "IG": ig.GroupName})
+ var newIg *IgmpGroup
+ receivers := ig.DelIgmpChannel(deviceID, groupAddr)
+ if ig.NumDevicesActive() == 0 {
+ app.DelIgmpGroup(ig)
+ }
+ if newIg = app.GetIgmpGroup(ig.Mvlan, groupAddr); newIg == nil {
+ logger.Infow(ctx, "IG Group doesn't exist, creating new group", log.Fields{"DeviceID": deviceID, "GroupAddr": groupAddr, "IG": ig.GroupName, "Mvlan": ig.Mvlan})
+ if newIg = app.AddIgmpGroup(app.GetMvlanProfileByTag(ig.Mvlan).Name, groupAddr, deviceID); newIg == nil {
+ logger.Errorw(ctx, "Group Creation failed during group migration", log.Fields{"DeviceID": deviceID, "GroupAddr": groupAddr})
+ return
+ }
+ }
+ mvp := app.GetMvlanProfileByTag(ig.Mvlan)
+ isStaticGroup := mvp.IsStaticGroup(ig.GroupName)
+ logger.Infow(ctx, "Existing receivers for old group", log.Fields{"Receivers": receivers})
+ newIg.IgmpGroupLock.Lock()
+ for port, igp := range receivers {
+ if !isStaticGroup && port == StaticPort {
+ continue
+ }
+ group = nil
+ var reqType layers.IGMPv3GroupRecordType
+ srcAddresses := []net.IP{}
+ if igp.Version == IgmpVersion3 {
+ if igp.Exclude {
+ srcAddresses = append(srcAddresses, igp.ExcludeList...)
+ reqType = layers.IGMPIsEx
+ } else {
+ srcAddresses = append(srcAddresses, igp.IncludeList...)
+ reqType = layers.IGMPIsIn
+ }
+ group = &layers.IGMPv3GroupRecord{
+ SourceAddresses: srcAddresses,
+ Type: reqType,
+ }
+ }
+ logger.Infow(ctx, "Adding receiver to new group", log.Fields{"DeviceID": deviceID, "GroupAddr": groupAddr, "newIg": newIg.GroupName, "IGP": igp})
+ ponPort := GetApplication().GetPonPortID(deviceID, port)
+ newIg.AddReceiver(deviceID, port, groupAddr, group, igp.Version, igp.CVlan, igp.Pbit, ponPort)
+ }
+ newIg.IgmpGroupLock.Unlock()
+ }
+}
+
+// AddIgmpGroupDevice add a device to the group which happens when the first receiver of the device
+// is added to the IGMP group.
+func (ig *IgmpGroup) AddIgmpGroupDevice(device string, id uint32, version uint8) *IgmpGroupDevice {
+ logger.Infow(ctx, "Adding Device to IGMP group", log.Fields{"Device": device, "GroupName": ig.GroupName})
+ igd := NewIgmpGroupDevice(device, ig, id, version)
+ ig.Devices[device] = igd
+ if err := igd.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp group device Write to DB failed", log.Fields{"Device": igd.Device, "GroupName": igd.GroupName, "GroupAddr": igd.GroupAddr.String()})
+ }
+ return igd
+}
+
+// DelIgmpGroupDevice delete the device from the group which happens when we receive a leave or when
+// there is not response for IGMP query from the receiver
+func (ig *IgmpGroup) DelIgmpGroupDevice(igd *IgmpGroupDevice) {
+ logger.Infow(ctx, "Deleting Device from IGMP group", log.Fields{"Device": igd.Device, "Name": ig.GroupName})
+ va := GetApplication()
+ countersToBeUpdated := false
+ if igd.NumReceivers() != 0 {
+ countersToBeUpdated = true
+ }
+ igd.DelAllChannels()
+
+ //Clear all internal maps so that the groups can be reused
+ igd.PortChannelMap.Range(func(key, value interface{}) bool {
+
+ //Update the counters only if not already updated
+ //(i.e) 1. In case of channel remove during Mvlan Update
+ if countersToBeUpdated {
+ port := key.(string)
+ channelList := value.([]net.IP)
+ ponPortID := va.GetPonPortID(igd.Device, port)
+
+ for _, channel := range channelList {
+ igd.RemoveChannelFromChannelsPerPon(port, channel, ponPortID)
+ }
+ }
+
+ igd.PortChannelMap.Delete(key)
+ return true
+ })
+ igd.PonPortChannelMap = util.NewConcurrentMap()
+
+ if mcastCfg := va.GetMcastConfig(igd.SerialNo, va.GetMvlanProfileByTag(igd.Mvlan).Name); mcastCfg != nil {
+ mcastCfg.IgmpGroupDevices.Delete(igd.GroupID)
+ logger.Debugw(ctx, "Igd deleted from mcast config", log.Fields{"mvlan": mcastCfg.MvlanProfileID, "groupId": igd.GroupID})
+ }
+ if !igd.GroupInstalled {
+ _ = db.DelIgmpDevice(igd.Mvlan, ig.GroupName, ig.GroupAddr, igd.Device)
+ delete(ig.Devices, igd.Device)
+ }
+}
+
+// AddReceiver delete the device from the group which happens when we receive a leave or when
+// there is not response for IGMP query from the receiver
+func (ig *IgmpGroup) AddReceiver(device string, port string, groupIP net.IP,
+ group *layers.IGMPv3GroupRecord, ver uint8, cvlan uint16, pbit uint8, ponPort uint32) {
+
+ logger.Debugw(ctx, "Adding Receiver", log.Fields{"Port": port})
+ if igd, ok := ig.getIgmpGroupDevice(device); !ok {
+ igd = ig.AddIgmpGroupDevice(device, ig.GroupID, ver)
+ igd.AddReceiver(port, groupIP, group, ver, cvlan, pbit, ponPort)
+ } else {
+ logger.Infow(ctx, "IGMP Group Receiver", log.Fields{"IGD": igd.Device})
+ igd.AddReceiver(port, groupIP, group, ver, cvlan, pbit, ponPort)
+ }
+}
+
+func (ig *IgmpGroup) getIgmpGroupDevice(device string) (*IgmpGroupDevice, bool) {
+ ig.PendingPoolLock.Lock()
+ defer ig.PendingPoolLock.Unlock()
+
+ if _, ok := ig.PendingGroupForDevice[device]; ok {
+ logger.Infow(ctx, "Removing the IgmpGroupDevice from pending pool", log.Fields{"GroupID": ig.GroupID, "Device": device, "GroupName": ig.GroupName, "GroupAddr": ig.GroupAddr.String()})
+ delete(ig.PendingGroupForDevice, device)
+ if err := ig.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp group Write to DB failed", log.Fields{"groupName": ig.GroupName})
+ }
+ }
+ igd, ok := ig.Devices[device]
+ return igd, ok
+}
+
+// DelReceiveronDownInd deletes a receiver which is the combination of device (OLT)
+// and port on Port Down event
+func (ig *IgmpGroup) DelReceiveronDownInd(device string, port string, ponPortID uint32) {
+ logger.Debugw(ctx, "Deleting Receiver for Group", log.Fields{"Device": device, "port": port})
+
+ mvp := GetApplication().GetMvlanProfileByTag(ig.Mvlan)
+ mvp.mvpLock.RLock()
+ defer mvp.mvpLock.RUnlock()
+ igd, ok := ig.Devices[device]
+ if !ok {
+ logger.Infow(ctx, "IGMP Group device was not found for ", log.Fields{"Device": device})
+ return
+ }
+ ipsList := []net.IP{}
+ ipsListIntf, ok := igd.PortChannelMap.Load(port)
+ if ok {
+ ipsList = append(ipsList, ipsListIntf.([]net.IP)...)
+ }
+ logger.Infow(ctx, "Port Channel List", log.Fields{"Port": port, "IPsList": ipsList})
+ igd.PortChannelMap.Range(printPortChannel)
+
+
+ for _, groupAddr := range ipsList {
+ logger.Debugw(ctx, "Port Channels", log.Fields{"Port": port, "IPsList": ipsList, "GroupAddr": groupAddr, "Len": len(ipsList)})
+ igd.DelReceiver(groupAddr, port, nil, ponPortID)
+ }
+
+ if igd.NumReceivers() == 0 {
+ ig.DelIgmpGroupDevice(igd)
+ }
+}
+
+// DelReceiver deletes a receiver which is the combination of device (OLT)
+// and port
+func (ig *IgmpGroup) DelReceiver(device string, port string, groupAddr net.IP, group *layers.IGMPv3GroupRecord, ponPortID uint32) {
+ logger.Debugw(ctx, "Deleting Receiver for Group", log.Fields{"Device": device, "port": port, "GroupIP": groupAddr.String()})
+ if igd, ok := ig.Devices[device]; ok {
+ //igd.DelReceiverForGroupAddr(groupAddr, port)
+ igd.DelReceiver(groupAddr, port, group, ponPortID)
+ if igd.NumReceivers() == 0 {
+ ig.DelIgmpGroupDevice(igd)
+ }
+ }
+}
+
+// GetAllIgmpChannelForDevice - Returns all channels with active members associated to the Igmp Group for the given device
+func (ig *IgmpGroup) GetAllIgmpChannelForDevice(deviceID string) map[string]string {
+
+ if deviceID == "" {
+ return ig.GetAllIgmpChannel()
+ }
+
+ allChannels := make(map[string]string)
+ igd := ig.Devices[deviceID]
+ getAllChannels := func(key interface{}, value interface{}) bool {
+ channels := key.(string)
+ allChannels[channels] = channels //same value as only key is required
+ return true
+ }
+ igd.GroupChannels.Range(getAllChannels)
+
+ return allChannels
+}
+
+// GetAllIgmpChannel - Returns all channels with active members associated to the Igmp Group
+func (ig *IgmpGroup) GetAllIgmpChannel() map[string]string {
+ allChannels := make(map[string]string)
+ for _, igd := range ig.Devices {
+ getAllChannels := func(key interface{}, value interface{}) bool {
+ channels := key.(string)
+ allChannels[channels] = channels
+ return true
+ }
+ igd.GroupChannels.Range(getAllChannels)
+ }
+ return allChannels
+}
+
+// DelIgmpChannel deletes all receivers for the provided igmp group channel for the given device
+func (ig *IgmpGroup) DelIgmpChannel(deviceID string, groupAddr net.IP) map[string]*IgmpGroupPort {
+ logger.Infow(ctx, "Deleting Channel from devices", log.Fields{"Device": deviceID, "Group": ig.GroupName, "Channel": groupAddr.String()})
+ if deviceID == "" {
+ for device := range ig.Devices {
+ ig.DelIgmpChannel(device, groupAddr)
+ }
+ return nil
+ }
+ igd := ig.Devices[deviceID]
+ receivers := igd.DelChannelReceiver(groupAddr)
+ if igd.NumReceivers() == 0 {
+ ig.DelIgmpGroupDevice(igd)
+ }
+ return receivers
+}
+
+// IsNewReceiver checks if the received port is new receiver or existing one.
+// Returns true if new receiver.
+func (ig *IgmpGroup) IsNewReceiver(device, uniPortID string, groupAddr net.IP) bool {
+ if ig == nil {
+ // IGMP group does not exists. So considering it as new receiver.
+ return true
+ }
+ logger.Debugw(ctx, "IGMP Group", log.Fields{"channel": groupAddr, "groupName": ig.GroupName}) // TODO: Remove me
+ igd, exists := ig.Devices[device]
+ if !exists || !igd.GroupInstalled {
+ // IGMP group not exists OR Group is not created in the device.
+ // So this is a new receiver.
+ logger.Debugw(ctx, "igd not exists or group is not created in device", log.Fields{"exists": exists}) // TODO: Remove me
+ return true
+ }
+ if igc, ok := igd.GroupChannels.Load(groupAddr.String()); ok {
+ logger.Debugw(ctx, "IGMP Channel receivers", log.Fields{"igc-receivers": igc.(*IgmpGroupChannel).CurReceivers}) // TODO: Remove me
+ _, rcvrExistCur := igc.(*IgmpGroupChannel).CurReceivers[uniPortID]
+ _, rcvrExistNew := igc.(*IgmpGroupChannel).NewReceivers[uniPortID]
+ if rcvrExistCur || rcvrExistNew {
+ // Existing receiver
+ return false
+ }
+ }
+ return true
+}
+
+// Tick for Addition of groups to an MVLAN profile
+func (ig *IgmpGroup) Tick() {
+ now := time.Now()
+ for _, igd := range ig.Devices {
+ var igdChangeCnt uint8
+
+ if _, ok := GetApplication().DevicesDisc.Load(igd.Device); !ok {
+ logger.Info(ctx, "Skipping Query and Expiry check since Device is unavailable")
+ continue
+ }
+ if now.After(igd.NextQueryTime) {
+ // Set the next query time and the query expiry time to
+ // KeepAliveInterval and MaxResp seconds after current time
+ igd.NextQueryTime = now.Add(time.Duration(igd.proxyCfg.KeepAliveInterval) * time.Second)
+ igd.QueryExpiryTime = now.Add(time.Duration(igd.proxyCfg.MaxResp) * time.Second)
+ logger.Debugw(ctx, "Query Start", log.Fields{"NextQuery": igd.NextQueryTime, "Expiry": igd.QueryExpiryTime})
+ igdChangeCnt++
+ logger.Debugw(ctx, "Sending Query to device", log.Fields{"Device": igd.Device})
+ sendQueryForAllChannels := func(key interface{}, value interface{}) bool {
+ igc := value.(*IgmpGroupChannel)
+ //TODO - Do generic query to avoid multiple msgs
+ igc.SendQuery()
+ return true
+ }
+ igd.GroupChannels.Range(sendQueryForAllChannels)
+ }
+ if now.After(igd.QueryExpiryTime) {
+ igd.QueryExpiry()
+ // This will keep it quiet till the next query time and then
+ // it will be reset to a value after the query initiation time
+ igd.QueryExpiryTime = igd.NextQueryTime
+ logger.Debugw(ctx, "Expiry", log.Fields{"NextQuery": igd.NextQueryTime, "Expiry": igd.QueryExpiryTime})
+ igdChangeCnt++
+ if igd.NumReceivers() == 0 {
+ ig.DelIgmpGroupDevice(igd)
+ continue
+ }
+ }
+
+ igdChangeCnt += igd.Tick()
+
+ if igdChangeCnt > 0 {
+ if err := igd.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp group device Write to DB failed", log.Fields{"Device": igd.Device,
+ "GroupName": igd.GroupName, "GroupAddr": igd.GroupAddr.String()})
+ }
+ }
+ }
+}
+
+// QueryExpiry processes expiry of query sent to the receivers. Up on
+// expiry, process the consolidated response for each of the devices participating
+// in the MC stream. When a device has no receivers, the device is deleted
+// from the group.
+func (ig *IgmpGroup) QueryExpiry() {
+ for _, igd := range ig.Devices {
+ if _, ok := GetApplication().DevicesDisc.Load(igd.Device); ok {
+ igd.QueryExpiry()
+ if igd.NumReceivers() == 0 {
+ ig.DelIgmpGroupDevice(igd)
+ }
+
+ } else {
+ logger.Info(ctx, "Skipping Expiry since Device is unavailable")
+ }
+ }
+}
+
+// Hash : The IGMP group hash is used to distribute the processing of timers so that
+// the processing is spread across doesn't spike at one instant. This also
+// ensures that there is sufficient responsiveness to other requests happening
+// simultaneously.
+func (ig *IgmpGroup) Hash() uint16 {
+ mvp := GetApplication().GetMvlanProfileByTag(ig.Mvlan)
+
+ if mvp == nil {
+ return 0
+ }
+
+ mvp.mvpLock.RLock()
+ defer mvp.mvpLock.RUnlock()
+ group := mvp.Groups[ig.GroupName]
+
+ //Case where mvlan update in-progress
+ if group == nil || len(group.McIPs) == 0 {
+ return 0
+ }
+ groupIP := group.McIPs[0]
+ return uint16(groupIP[2])<<8 + uint16(groupIP[3])
+}
+
+// NumDevicesAll returns the number of devices (OLT) active on the IGMP group. When
+// the last device leaves the IGMP group is removed. If this is not done,
+// the number of IGMP groups only keep increasing and can impact CPU when
+// the system runs for a very long duration
+func (ig *IgmpGroup) NumDevicesAll() int {
+ return len(ig.Devices)
+}
+
+// NumDevicesActive returns the number of devices (OLT) active on the IGMP group. When
+// the last device leaves the IGMP group is removed. If this is not done,
+// the number of IGMP groups only keep increasing and can impact CPU when
+// the system runs for a very long duration
+func (ig *IgmpGroup) NumDevicesActive() int {
+ count := 0
+ for _, igd := range ig.Devices {
+ if igd.NumReceivers() == 0 && igd.GroupInstalled {
+ continue
+ }
+ count++
+ }
+ return count
+}
+
+// NumReceivers to return receiver list
+func (ig *IgmpGroup) NumReceivers() map[string]int {
+ receiverList := make(map[string]int)
+ for device, igd := range ig.Devices {
+ receiverList[device] = igd.NumReceivers()
+ }
+ return receiverList
+}
+
+// RestoreDevices : IGMP group write to DB
+func (ig *IgmpGroup) RestoreDevices() {
+
+ ig.migrateIgmpDevices()
+ devices, _ := db.GetIgmpDevices(ig.Mvlan, ig.GroupName, ig.GroupAddr)
+ for _, device := range devices {
+ b, ok := device.Value.([]byte)
+ if !ok {
+ logger.Warn(ctx, "The value type is not []byte")
+ continue
+ }
+ if igd, err := NewIgmpGroupDeviceFromBytes(b); err == nil {
+ igd.PonPortChannelMap = util.NewConcurrentMap()
+ // Update the proxy config pointers.
+ var mcastCfg *McastConfig
+ igd.proxyCfg, igd.IgmpProxyIP, mcastCfg = getIgmpProxyCfgAndIP(ig.Mvlan, igd.SerialNo)
+ if mcastCfg != nil {
+ mcastCfg.IgmpGroupDevices.Store(igd.GroupID, igd)
+ logger.Debugw(ctx, "Igd added to mcast config", log.Fields{"mvlan": mcastCfg.MvlanProfileID, "groupId": igd.GroupID})
+ }
+
+ mvp := GetApplication().GetMvlanProfileByTag(igd.Mvlan)
+ igd.ServVersion = mvp.IgmpServVersion[igd.SerialNo]
+
+ // During vgc upgrade from old version, igd.NextQueryTime and igd.QueryExpiryTime will not be present in db.
+ // hence they are initialized with current time offset.
+ emptyTime := time.Time{}
+ if emptyTime == igd.NextQueryTime {
+ logger.Debugw(ctx, "VGC igd upgrade", log.Fields{"igd grp name": igd.GroupName})
+ igd.NextQueryTime = time.Now().Add(time.Duration(igd.proxyCfg.KeepAliveInterval) * time.Second)
+ igd.QueryExpiryTime = time.Now().Add(time.Duration(igd.proxyCfg.KeepAliveInterval) * time.Second)
+ if err := igd.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp group device Write to DB failed", log.Fields{"Device": igd.Device,
+ "GroupName": igd.GroupName, "GroupAddr": igd.GroupAddr.String()})
+ }
+ }
+
+ ig.Devices[igd.Device] = igd
+ if ig.IsChannelBasedGroup {
+ channel, _ := db.GetIgmpChannel(igd.Mvlan, igd.GroupName, igd.Device, igd.GroupAddr)
+ igd.RestoreChannel([]byte(channel))
+ } else {
+ igd.RestoreChannels()
+ }
+ igd.PortChannelMap.Range(printPortChannel)
+ logger.Infow(ctx, "Group Device Restored", log.Fields{"IGD": igd})
+ } else {
+ logger.Warnw(ctx, "Unable to decode device from database", log.Fields{"str": string(b)})
+ }
+ }
+}
+
+// getKey to return group key
+func (ig *IgmpGroup) getKey() string {
+ profile, ok := GetApplication().MvlanProfilesByTag.Load(ig.Mvlan)
+ if ok {
+ mvp := profile.(*MvlanProfile)
+ return mvp.generateGroupKey(ig.GroupName, ig.GroupAddr.String())
+ }
+ return ""
+}
+
+/*
+// getKey to return group key
+func (igd *IgmpGroupDevice) getKey() string {
+ profile, ok := GetApplication().MvlanProfilesByTag.Load(igd.Mvlan)
+ if ok {
+ mvp := profile.(*MvlanProfile)
+ return mvp.generateGroupKey(igd.GroupName, igd.GroupAddr.String())
+ }
+ return ""
+}*/
+
+// generateGroupKey to generate group key
+func (mvp *MvlanProfile) generateGroupKey(name string, ipAddr string) string {
+ if mvp.IsChannelBasedGroup {
+ return mvp.Mvlan.String() + "_" + ipAddr
+ }
+ return mvp.Mvlan.String() + "_" + name
+}
+
+// WriteToDb is utility to write Igmp Group Info to database
+func (ig *IgmpGroup) WriteToDb() error {
+ ig.Version = database.PresentVersionMap[database.IgmpGroupPath]
+ b, err := json.Marshal(ig)
+ if err != nil {
+ return err
+ }
+ if err1 := db.PutIgmpGroup(ig.getKey(), string(b)); err1 != nil {
+ return err1
+ }
+ return nil
+}
+
+// RestoreIgmpGroupsFromDb to restore igmp groups from database
+func (va *VoltApplication) RestoreIgmpGroupsFromDb() {
+
+ groups, _ := db.GetIgmpGroups()
+ for _, group := range groups {
+ b, ok := group.Value.([]byte)
+ if !ok {
+ logger.Warn(ctx, "The value type is not []byte")
+ continue
+ }
+ var ig IgmpGroup
+ err := json.Unmarshal(b, &ig)
+ if err != nil {
+ logger.Warn(ctx, "Unmarshal of IGMP Group failed")
+ continue
+ }
+ ig.Devices = make(map[string]*IgmpGroupDevice)
+
+ //For Upgrade Case
+ if len(ig.PendingGroupForDevice) == 0 {
+ ig.PendingGroupForDevice = make(map[string]time.Time)
+ }
+ logger.Infow(ctx, "Restoring Groups", log.Fields{"igGroupID": ig.GroupID, "igGroupName": ig.GroupName, "igMvlan": ig.Mvlan})
+ grpKey := ig.getKey()
+ va.IgmpGroups.Store(grpKey, &ig)
+ // Just delete and lose the IGMP group with the same group Id
+ if _, err := va.GetIgmpGroupID(ig.GroupID); err != nil {
+ logger.Warnw(ctx, "GetIgmpGroupID Failed", log.Fields{"igGroupID": ig.GroupID, "Error": err})
+ }
+ ig.RestoreDevices()
+
+ if ig.NumDevicesActive() == 0 {
+ va.AddGroupToPendingPool(&ig)
+ }
+ logger.Infow(ctx, "Restored Groups", log.Fields{"igGroupID": ig.GroupID, "igGroupName": ig.GroupName, "igMvlan": ig.Mvlan})
+ }
+}
+
+// AddIgmpGroup : When the first IGMP packet is received, the MVLAN profile is identified
+// for the IGMP group and grp obj is obtained from the available pending pool of groups.
+// If not, new group obj will be created based on available group IDs
+func (va *VoltApplication) AddIgmpGroup(mvpName string, gip net.IP, device string) *IgmpGroup {
+
+ var ig *IgmpGroup
+ if mvp, grpName := va.GetMvlanProfileForMcIP(mvpName, gip); mvp != nil {
+ if ig = va.GetGroupFromPendingPool(mvp.Mvlan, device); ig != nil {
+ logger.Infow(ctx, "Igmp Group obtained from global pending pool", log.Fields{"MvlanProfile": mvpName, "GroupID": ig.GroupID, "Device": device, "GroupName": ig.GroupName, "GroupAddr": ig.GroupAddr.String()})
+ oldKey := mvp.generateGroupKey(ig.GroupName, ig.GroupAddr.String())
+ ig.IgmpGroupReInit(grpName, gip)
+ ig.IsGroupStatic = mvp.Groups[grpName].IsStatic
+ ig.UpdateIgmpGroup(oldKey, ig.getKey())
+ } else {
+ logger.Infow(ctx, "No Igmp Group available in global pending pool. Creating new Igmp Group", log.Fields{"MvlanProfile": mvpName, "Device": device, "GroupAddr": gip.String()})
+ if ig = va.GetAvailIgmpGroupID(); ig == nil {
+ logger.Error(ctx, "Igmp Group Creation Failed: Group Id Unavailable")
+ return nil
+ }
+ ig.IgmpGroupInit(grpName, gip, mvp)
+ grpKey := ig.getKey()
+ va.IgmpGroups.Store(grpKey, ig)
+ }
+ if err := ig.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp group Write to DB failed", log.Fields{"groupName": ig.GroupName})
+ }
+ return ig
+ }
+ logger.Error(ctx, "GetMvlan Pro failed", log.Fields{"Group": gip})
+ return nil
+}
+
+// GetIgmpGroup helps search for the IGMP group from the list of
+// active IGMP groups. For now, the assumption is that a group
+// cannot belong to more than on MVLAN. If we change that definition,
+// we have to take a relook at this implementation. The key will include
+// both MVLAN and the group IP.
+func (va *VoltApplication) GetIgmpGroup(mvlan of.VlanType, gip net.IP) *IgmpGroup {
+
+ profile, _ := va.MvlanProfilesByTag.Load(mvlan)
+ if profile == nil {
+ logger.Errorw(ctx, "Mvlan Profile not found for incoming packet. Dropping Request", log.Fields{"Mvlan": mvlan, "GroupAddr": gip.String()})
+ return nil
+ }
+ mvp := profile.(*MvlanProfile)
+ _, gName := va.GetMvlanProfileForMcIP(mvp.Name, gip)
+ grpKey := mvp.generateGroupKey(gName, gip.String())
+ logger.Debugw(ctx, "Get IGMP Group", log.Fields{"Group": grpKey})
+ igIntf, ok := va.IgmpGroups.Load(grpKey)
+ if ok {
+ logger.Debugw(ctx, "Get IGMP Group Success", log.Fields{"Group": grpKey})
+ ig := igIntf.(*IgmpGroup)
+
+ //Case: Group was part of pending and Join came with same channel or different channel from same group
+ // (from same or different device)
+ // In that case, the same group will be allocated since the group is still part of va.IgmpGroups
+ // So, the groups needs to be removed from global pending pool
+ va.RemoveGroupDevicesFromPendingPool(ig)
+ return ig
+ }
+ return nil
+}
+
+// GetStaticGroupName to get static igmp group
+func (mvp *MvlanProfile) GetStaticGroupName(gip net.IP) string {
+ for _, mvg := range mvp.Groups {
+ if mvg.IsStatic {
+ if doesIPMatch(gip, mvg.McIPs) {
+ return mvg.Name
+ }
+ }
+ }
+ return ""
+}
+
+// GetStaticIgmpGroup to get static igmp group
+func (mvp *MvlanProfile) GetStaticIgmpGroup(gip net.IP) *IgmpGroup {
+
+ staticGroupName := mvp.GetStaticGroupName(gip)
+ grpKey := mvp.generateGroupKey(staticGroupName, gip.String())
+ logger.Debugw(ctx, "Get Static IGMP Group", log.Fields{"Group": grpKey})
+ ig, ok := GetApplication().IgmpGroups.Load(grpKey)
+ if ok {
+ logger.Debugw(ctx, "Get Static IGMP Group Success", log.Fields{"Group": grpKey})
+ return ig.(*IgmpGroup)
+ }
+ return nil
+}
+
+// UpdateIgmpGroup : When the pending group is allocated to new
+func (ig *IgmpGroup) UpdateIgmpGroup(oldKey, newKey string) {
+
+ //If the group is allocated to same McastGroup, no need to update the
+ //IgmpGroups map
+ if oldKey == newKey {
+ return
+ }
+ logger.Infow(ctx, "Updating Igmp Group with new MVP Group Info", log.Fields{"OldKey": oldKey, "NewKey": newKey, "GroupID": ig.GroupID})
+
+ GetApplication().IgmpGroups.Delete(oldKey)
+ _ = db.DelIgmpGroup(oldKey)
+
+ GetApplication().IgmpGroups.Store(newKey, ig)
+ if err := ig.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp group Write to DB failed", log.Fields{"groupName": ig.GroupName})
+ }
+}
+
+// DelIgmpGroup : When the last subscriber leaves the IGMP group across all the devices
+// the IGMP group is removed.
+func (va *VoltApplication) DelIgmpGroup(ig *IgmpGroup) {
+
+ profile, found := GetApplication().MvlanProfilesByTag.Load(ig.Mvlan)
+ if found {
+ mvp := profile.(*MvlanProfile)
+
+ grpKey := mvp.generateGroupKey(ig.GroupName, ig.GroupAddr.String())
+
+ if igIntf, ok := va.IgmpGroups.Load(grpKey); ok {
+ ig := igIntf.(*IgmpGroup)
+ ig.IgmpGroupLock.Lock()
+ if ig.NumDevicesAll() == 0 {
+ logger.Debugw(ctx, "Deleting IGMP Group", log.Fields{"Group": grpKey})
+ va.PutIgmpGroupID(ig)
+ va.IgmpGroups.Delete(grpKey)
+ _ = db.DelIgmpGroup(grpKey)
+ } else {
+ logger.Infow(ctx, "Skipping IgmpGroup Device. Pending Igmp Group Devices present", log.Fields{"GroupID": ig.GroupID, "GroupName": ig.GroupName, "GroupAddr": ig.GroupAddr.String(), "PendingDevices": len(ig.Devices)})
+ va.AddGroupToPendingPool(ig)
+ if err := ig.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp group Write to DB failed", log.Fields{"groupName": ig.GroupName})
+ }
+ }
+ ig.IgmpGroupLock.Unlock()
+ }
+
+ }
+}
+
+// GetPonPortID Gets the PON port ID from uniPortID
+func (va *VoltApplication) GetPonPortID(device, uniPortID string) uint32 {
+
+ isNNI := strings.Contains(uniPortID, "nni")
+ if isNNI || uniPortID == StaticPort {
+ logger.Debugw(ctx, "Cannot get pon port from UNI port", log.Fields{"port": uniPortID})
+ return 0xFF
+ }
+ dIntf, ok := va.DevicesDisc.Load(device)
+ if !ok {
+ return 0xFF
+ }
+ d := dIntf.(*VoltDevice)
+
+ uniPort := d.GetPort(uniPortID)
+ if uniPort == nil {
+ return 0xFF
+ }
+ return GetPonPortIDFromUNIPort(uniPort.ID)
+}
+
+// AggActiveChannelsCountPerSub aggregates the active channel count for given uni port.
+// It will iterate over all the groups and store the sum of active channels in VoltPort.
+func (va *VoltApplication) AggActiveChannelsCountPerSub(device, uniPort string, port *VoltPort) {
+ var activeChannelCount uint32
+
+ collectActiveChannelCount := func(key interface{}, value interface{}) bool {
+ ig := value.(*IgmpGroup)
+ igd := ig.Devices[device]
+ if igd == nil {
+ return true
+ }
+ if portChannels, ok := igd.PortChannelMap.Load(uniPort); ok {
+ channelList := portChannels.([]net.IP)
+ activeChannelCount += uint32(len(channelList))
+ }
+ return true
+ }
+ va.IgmpGroups.Range(collectActiveChannelCount)
+
+ logger.Debugw(ctx, "AggrActiveChannelCount for Subscriber",
+ log.Fields{"UniPortID": uniPort, "count": activeChannelCount})
+
+ port.ActiveChannels = activeChannelCount
+}
+
+// AggActiveChannelsCountForPonPort Aggregates the active channel count for given pon port.
+// It will iterate over all the groups and store the sum of active channels in VoltDevice.
+func (va *VoltApplication) AggActiveChannelsCountForPonPort(device string, ponPortID uint32, port *PonPortCfg) {
+
+ var activeChannelCount uint32
+
+ collectActiveChannelCount := func(key interface{}, value interface{}) bool {
+ ig := value.(*IgmpGroup)
+ igd := ig.Devices[device]
+ if igd == nil {
+ return true
+ }
+ if ponPortChannels, ok := igd.PonPortChannelMap.Get(ponPortID); ok {
+ activeChannelCount += ponPortChannels.(*PonPortChannels).GetActiveChannelCount()
+ }
+ return true
+ }
+ va.IgmpGroups.Range(collectActiveChannelCount)
+
+ logger.Debugw(ctx, "AggrActiveChannelCount for Pon Port",
+ log.Fields{"PonPortID": ponPortID, "count": activeChannelCount})
+
+ port.ActiveIGMPChannels = activeChannelCount
+}
+
+// UpdateActiveChannelCountForPonPort increments the global counter for active
+// channel count per pon port.
+func (va *VoltApplication) UpdateActiveChannelCountForPonPort(device, uniPortID string, ponPortID uint32, isAdd, isChannel bool, igd *IgmpGroupDevice) {
+ incrDecr := func(value uint32) uint32 {
+ if isAdd {
+ return value + 1
+ }
+ return value - 1
+ }
+ if d, exists := va.DevicesDisc.Load(device); exists {
+ voltDevice := d.(*VoltDevice)
+
+ if isChannel {
+ voltDevice.ActiveChannelCountLock.Lock()
+ // If New channel is added/deleted, then only update the ActiveChannelsPerPon
+ if value, ok := voltDevice.ActiveChannelsPerPon.Load(ponPortID); ok {
+ port := value.(*PonPortCfg)
+ port.ActiveIGMPChannels = incrDecr(port.ActiveIGMPChannels)
+ voltDevice.ActiveChannelsPerPon.Store(ponPortID, port)
+ logger.Debugw(ctx, "+++ActiveChannelsPerPon", log.Fields{"count": port.ActiveIGMPChannels}) // TODO: remove me
+ }
+ voltDevice.ActiveChannelCountLock.Unlock()
+ }
+ if uPort, ok := voltDevice.Ports.Load(uniPortID); ok {
+ uniPort := uPort.(*VoltPort)
+ uniPort.ActiveChannels = incrDecr(uniPort.ActiveChannels)
+ voltDevice.Ports.Store(uniPortID, uniPort)
+ logger.Debugw(ctx, "+++ActiveChannelsPerSub", log.Fields{"count": uniPort.ActiveChannels}) // TODO: remove me
+ }
+ }
+}
+
+// IsMaxChannelsCountExceeded checks if the PON port active channel
+// capacity and subscriber level channel capacity is reached to max allowed
+// channel per pon threshold. If Exceeds, return true else return false.
+func (va *VoltApplication) IsMaxChannelsCountExceeded(device, uniPortID string,
+ ponPortID uint32, ig *IgmpGroup, channelIP net.IP, mvp *MvlanProfile) bool {
+
+ // New receiver check is required to identify the IgmpReportMsg received
+ // in response to the IGMP Query sent from VGC.
+ if newReceiver := ig.IsNewReceiver(device, uniPortID, channelIP); !newReceiver {
+ logger.Debugw(ctx, "Not a new receiver. It is a response to IGMP Query",
+ log.Fields{"port": uniPortID, "channel": channelIP})
+ return false
+ }
+
+ if vDev, exists := va.DevicesDisc.Load(device); exists {
+ voltDevice := vDev.(*VoltDevice)
+
+ // Checking subscriber active channel count with maxChannelsAllowedPerSub
+ if uniPort, present := voltDevice.Ports.Load(uniPortID); present {
+ if uniPort.(*VoltPort).ActiveChannels >= mvp.MaxActiveChannels {
+ logger.Errorw(ctx, "Max allowed channels per subscriber is exceeded",
+ log.Fields{"activeCount": uniPort.(*VoltPort).ActiveChannels, "channel": channelIP, "UNI": uniPort.(*VoltPort).Name})
+ if !(uniPort.(*VoltPort).ChannelPerSubAlarmRaised) {
+ serviceName := GetMcastServiceForSubAlarm(uniPort.(*VoltPort), mvp)
+ logger.Debugw(ctx, "Raising-SendActiveChannelPerSubscriberAlarm-Initiated", log.Fields{"ActiveChannels": uniPort.(*VoltPort).ActiveChannels, "ServiceName": serviceName})
+ uniPort.(*VoltPort).ChannelPerSubAlarmRaised = true
+ }
+ return true
+ }
+ } else {
+ logger.Errorw(ctx, "UNI port not found in VoltDevice", log.Fields{"uniPortID": uniPortID})
+ }
+ if value, ok := voltDevice.ActiveChannelsPerPon.Load(ponPortID); ok {
+ ponPort := value.(*PonPortCfg)
+
+ logger.Debugw(ctx, "----Active channels count for PON port",
+ log.Fields{"PonPortID": ponPortID, "activeChannels": ponPort.ActiveIGMPChannels,
+ "maxAllowedChannelsPerPon": ponPort.MaxActiveChannels})
+
+ if ponPort.ActiveIGMPChannels < ponPort.MaxActiveChannels {
+ // PON port active channel capacity is not yet reached to max allowed channels per pon.
+ // So allowing to add receiver.
+ return false
+ } else if ponPort.ActiveIGMPChannels >= ponPort.MaxActiveChannels && ig != nil {
+ // PON port active channel capacity is reached to max allowed channels per pon.
+ // Check if same channel is already configured on that PON port.
+ // If that channel is present, then allow AddReceiver else it will be rejected.
+ igd, isPresent := ig.Devices[device]
+ if isPresent {
+ if channelListForPonPort, _ := igd.PonPortChannelMap.Get(ponPortID); channelListForPonPort != nil {
+ if _, isExists := channelListForPonPort.(*PonPortChannels).ChannelList.Get(channelIP.String()); isExists {
+ return false
+ }
+ }
+ }
+ }
+ logger.Errorw(ctx, "Active channels count for PON port exceeded",
+ log.Fields{"PonPortID": ponPortID, "activeChannels": ponPort.ActiveIGMPChannels, "channel": channelIP, "UNI": uniPortID})
+ } else {
+ logger.Warnw(ctx, "PON port level active channel count does not exists",
+ log.Fields{"ponPortID": ponPortID})
+ return false
+ }
+ }
+ logger.Warnw(ctx, "Max allowed channels per pon threshold is reached", log.Fields{"PonPortID": ponPortID})
+ return true
+}
+
+// ProcessIgmpv2Pkt : This is IGMPv2 packet.
+func (va *VoltApplication) ProcessIgmpv2Pkt(device string, port string, pkt gopacket.Packet) {
+ // First get the layers of interest
+ dot1Q := pkt.Layer(layers.LayerTypeDot1Q).(*layers.Dot1Q)
+ pktVlan := of.VlanType(dot1Q.VLANIdentifier)
+ igmpv2 := pkt.Layer(layers.LayerTypeIGMP).(*layers.IGMPv1or2)
+
+ ponPortID := va.GetPonPortID(device, port)
+
+ var vpv *VoltPortVnet
+
+ logger.Debugw(ctx, "Received IGMPv2 Type", log.Fields{"Type": igmpv2.Type})
+
+ if igmpv2.Type == layers.IGMPMembershipReportV2 || igmpv2.Type == layers.IGMPMembershipReportV1 {
+
+ logger.Infow(ctx, "IGMP Join received: v2", log.Fields{"Addr": igmpv2.GroupAddress, "Port": port})
+
+ // This is a report coming from the PON. We must be able to first find the
+ // subscriber from the VLAN tag and port and verify if the IGMP proxy is
+ // enabled for the subscriber
+ vpv, _ = va.GetVnetFromPkt(device, port, pkt)
+
+ if vpv == nil {
+ logger.Errorw(ctx, "Couldn't find VNET associated with port", log.Fields{"Port": port})
+ return
+ } else if !vpv.IgmpEnabled {
+ logger.Errorw(ctx, "IGMP is not activated on the port", log.Fields{"Port": port})
+ return
+ }
+
+ mvp := va.GetMvlanProfileByName(vpv.MvlanProfileName)
+ if mvp == nil {
+ logger.Errorw(ctx, "Igmp Packet Received for Subscriber with Missing Mvlan Profile",
+ log.Fields{"Receiver": vpv.Port, "MvlanProfile": vpv.MvlanProfileName})
+ return
+ }
+ mvlan := mvp.Mvlan
+
+ mvp.mvpLock.RLock()
+ defer mvp.mvpLock.RUnlock()
+ // The subscriber is validated and now process the IGMP report
+ ig := va.GetIgmpGroup(mvlan, igmpv2.GroupAddress)
+
+ if yes := va.IsMaxChannelsCountExceeded(device, port, ponPortID, ig, igmpv2.GroupAddress, mvp); yes {
+ logger.Warnw(ctx, "Dropping IGMP Join v2: Active channel threshold exceeded",
+ log.Fields{"PonPortID": ponPortID, "Addr": igmpv2.GroupAddress, "MvlanProfile": vpv.MvlanProfileName})
+ return
+ }
+ if ig != nil {
+ logger.Infow(ctx, "IGMP Group", log.Fields{"Group": ig.GroupID, "devices": ig.Devices})
+ // If the IGMP group is already created. just add the receiver
+ ig.IgmpGroupLock.Lock()
+ // Check for port state to avoid race condition where PortDown event
+ // acquired lock before packet processing
+ vd := GetApplication().GetDevice(device)
+ vp := vd.GetPort(port)
+ if vp == nil || vp.State != PortStateUp {
+ logger.Warnw(ctx, "Join received from a Port that is DOWN or not present",
+ log.Fields{"Port": port})
+ ig.IgmpGroupLock.Unlock()
+ return
+ }
+ ig.AddReceiver(device, port, igmpv2.GroupAddress, nil, IgmpVersion2, dot1Q.VLANIdentifier, dot1Q.Priority, ponPortID)
+ ig.IgmpGroupLock.Unlock()
+ } else {
+ // Create the IGMP group and then add the receiver to the group
+ if ig := va.AddIgmpGroup(vpv.MvlanProfileName, igmpv2.GroupAddress, device); ig != nil {
+ logger.Infow(ctx, "New IGMP Group", log.Fields{"Group": ig.GroupID, "devices": ig.Devices})
+ ig.IgmpGroupLock.Lock()
+ // Check for port state to avoid race condition where PortDown event
+ // acquired lock before packet processing
+ vd := GetApplication().GetDevice(device)
+ vp := vd.GetPort(port)
+ if vp == nil || vp.State != PortStateUp {
+ logger.Warnw(ctx, "Join received from a Port that is DOWN or not present",
+ log.Fields{"Port": port})
+ ig.IgmpGroupLock.Unlock()
+ return
+ }
+ ig.AddReceiver(device, port, igmpv2.GroupAddress, nil, IgmpVersion2, dot1Q.VLANIdentifier, dot1Q.Priority, ponPortID)
+ ig.IgmpGroupLock.Unlock()
+ } else {
+ logger.Errorw(ctx, "IGMP Group Creation Failed", log.Fields{"Addr": igmpv2.GroupAddress})
+ return
+ }
+ }
+ } else if igmpv2.Type == layers.IGMPLeaveGroup {
+ // This is a IGMP leave coming from one of the receivers. We essentially remove the
+ // the receiver.
+ logger.Infow(ctx, "IGMP Leave received: v2", log.Fields{"Addr": igmpv2.GroupAddress, "Port": port})
+
+ vpv, _ = va.GetVnetFromPkt(device, port, pkt)
+ if vpv == nil {
+ logger.Errorw(ctx, "Couldn't find VNET associated with port", log.Fields{"Port": port})
+ return
+ } else if !vpv.IgmpEnabled {
+ logger.Errorw(ctx, "IGMP is not activated on the port", log.Fields{"Port": port})
+ return
+ }
+
+ mvp := va.GetMvlanProfileByName(vpv.MvlanProfileName)
+ mvp.mvpLock.RLock()
+ defer mvp.mvpLock.RUnlock()
+ mvlan := mvp.Mvlan
+ // The subscriber is validated and now process the IGMP report
+ if ig := va.GetIgmpGroup(mvlan, igmpv2.GroupAddress); ig != nil {
+ ig.IgmpGroupLock.Lock()
+ // Delete the receiver once the IgmpGroup is identified
+ ig.DelReceiver(device, port, igmpv2.GroupAddress, nil, ponPortID)
+ ig.IgmpGroupLock.Unlock()
+ if ig.NumDevicesActive() == 0 {
+ va.DelIgmpGroup(ig)
+ }
+ }
+ } else {
+ // This must be a query on the NNI port. However, we dont make that assumption.
+ // Need to look for the IGMP group based on the VLAN in the packet as
+ // the MVLAN
+
+ //Check if mvlan profile exist for the incoming pkt vlan
+ profile, _ := va.MvlanProfilesByTag.Load(pktVlan)
+ if profile == nil {
+ logger.Errorw(ctx, "Mvlan Profile not found for incoming packet. Dropping Request", log.Fields{"Mvlan": pktVlan})
+ return
+ }
+ mvp := profile.(*MvlanProfile)
+ mvp.mvpLock.RLock()
+ defer mvp.mvpLock.RUnlock()
+
+ if net.ParseIP("0.0.0.0").Equal(igmpv2.GroupAddress) {
+ va.processIgmpQueries(device, pktVlan, IgmpVersion2)
+ } else {
+ if ig := va.GetIgmpGroup(pktVlan, igmpv2.GroupAddress); ig != nil {
+ ig.IgmpGroupLock.Lock()
+ igd, ok := ig.Devices[device]
+ if ok {
+ igd.ProcessQuery(igmpv2.GroupAddress, IgmpVersion2)
+ } else {
+ logger.Warnw(ctx, "IGMP Device not found", log.Fields{"Device": device, "Group": igmpv2.GroupAddress})
+ }
+ ig.IgmpGroupLock.Unlock()
+ }
+ }
+ }
+}
+
+// ProcessIgmpv3Pkt : Process IGMPv3 packet
+func (va *VoltApplication) ProcessIgmpv3Pkt(device string, port string, pkt gopacket.Packet) {
+ // First get the layers of interest
+ dot1QLayer := pkt.Layer(layers.LayerTypeDot1Q)
+
+ if dot1QLayer == nil {
+ logger.Error(ctx, "Igmp Packet Received without Vlan - Dropping pkt")
+ return
+ }
+ dot1Q := dot1QLayer.(*layers.Dot1Q)
+ pktVlan := of.VlanType(dot1Q.VLANIdentifier)
+ igmpv3 := pkt.Layer(layers.LayerTypeIGMP).(*layers.IGMP)
+
+ ponPortID := va.GetPonPortID(device, port)
+
+ var vpv *VoltPortVnet
+ logger.Debugw(ctx, "Received IGMPv3 Type", log.Fields{"Type": igmpv3.Type})
+
+ if igmpv3.Type == layers.IGMPMembershipReportV3 {
+ // This is a report coming from the PON. We must be able to first find the
+ // subscriber from the VLAN tag and port and verify if the IGMP proxy is
+ // enabled for the subscriber
+ vpv, _ = va.GetVnetFromPkt(device, port, pkt)
+ if vpv == nil {
+ logger.Errorw(ctx, "Couldn't find VNET associated with port", log.Fields{"Port": port})
+ return
+ } else if !vpv.IgmpEnabled {
+ logger.Errorw(ctx, "IGMP is not activated on the port", log.Fields{"Port": port})
+ return
+ }
+ mvp := va.GetMvlanProfileByName(vpv.MvlanProfileName)
+ if mvp == nil {
+ logger.Errorw(ctx, "Igmp Packet received for Subscriber with Missing Mvlan Profile",
+ log.Fields{"Receiver": vpv.Port, "MvlanProfile": vpv.MvlanProfileName})
+ return
+ }
+ mvp.mvpLock.RLock()
+ defer mvp.mvpLock.RUnlock()
+ mvlan := mvp.Mvlan
+
+ for _, group := range igmpv3.GroupRecords {
+
+ isJoin := isIgmpJoin(group.Type, group.SourceAddresses)
+ // The subscriber is validated and now process the IGMP report
+ ig := va.GetIgmpGroup(mvlan, group.MulticastAddress)
+ if isJoin {
+ if yes := va.IsMaxChannelsCountExceeded(device, port, ponPortID, ig, group.MulticastAddress, mvp); yes {
+ logger.Warnw(ctx, "Dropping IGMP Join v3: Active channel threshold exceeded",
+ log.Fields{"PonPortID": ponPortID, "Addr": group.MulticastAddress, "MvlanProfile": vpv.MvlanProfileName})
+
+ return
+ }
+ if ig != nil {
+ // If the IGMP group is already created. just add the receiver
+ logger.Infow(ctx, "IGMP Join received for existing group", log.Fields{"Addr": group.MulticastAddress, "Port": port})
+ ig.IgmpGroupLock.Lock()
+ // Check for port state to avoid race condition where PortDown event
+ // acquired lock before packet processing
+ vd := GetApplication().GetDevice(device)
+ vp := vd.GetPort(port)
+ if vp == nil || vp.State != PortStateUp {
+ logger.Warnw(ctx, "Join received from a Port that is DOWN or not present",
+ log.Fields{"Port": port})
+ ig.IgmpGroupLock.Unlock()
+ return
+ }
+ ig.AddReceiver(device, port, group.MulticastAddress, &group, IgmpVersion3,
+ dot1Q.VLANIdentifier, dot1Q.Priority, ponPortID)
+ ig.IgmpGroupLock.Unlock()
+ } else {
+ // Create the IGMP group and then add the receiver to the group
+ logger.Infow(ctx, "IGMP Join received for new group", log.Fields{"Addr": group.MulticastAddress, "Port": port})
+ if ig := va.AddIgmpGroup(vpv.MvlanProfileName, group.MulticastAddress, device); ig != nil {
+ ig.IgmpGroupLock.Lock()
+ // Check for port state to avoid race condition where PortDown event
+ // acquired lock before packet processing
+ vd := GetApplication().GetDevice(device)
+ vp := vd.GetPort(port)
+ if vp == nil || vp.State != PortStateUp {
+ logger.Warnw(ctx, "Join received from a Port that is DOWN or not present",
+ log.Fields{"Port": port})
+ ig.IgmpGroupLock.Unlock()
+ return
+ }
+ ig.AddReceiver(device, port, group.MulticastAddress, &group, IgmpVersion3,
+ dot1Q.VLANIdentifier, dot1Q.Priority, ponPortID)
+ ig.IgmpGroupLock.Unlock()
+ } else {
+ logger.Warnw(ctx, "IGMP Group Creation Failed", log.Fields{"Addr": group.MulticastAddress})
+ }
+ }
+ } else if ig != nil {
+ logger.Infow(ctx, "IGMP Leave received for existing group", log.Fields{"Addr": group.MulticastAddress, "Port": port})
+ ig.IgmpGroupLock.Lock()
+ ig.DelReceiver(device, port, group.MulticastAddress, &group, ponPortID)
+ ig.IgmpGroupLock.Unlock()
+ if ig.NumDevicesActive() == 0 {
+ va.DelIgmpGroup(ig)
+ }
+ } else {
+ logger.Warnw(ctx, "IGMP Leave received for unknown group", log.Fields{"Addr": group.MulticastAddress})
+ }
+ }
+ } else {
+ // This must be a query on the NNI port. However, we dont make that assumption.
+ // Need to look for the IGMP group based on the VLAN in the packet as
+ // the MVLAN
+
+ //Check if mvlan profile exist for the incoming pkt vlan
+ profile, _ := va.MvlanProfilesByTag.Load(pktVlan)
+ if profile == nil {
+ logger.Errorw(ctx, "Mvlan Profile not found for incoming packet. Dropping Request", log.Fields{"Mvlan": pktVlan})
+ return
+ }
+ mvp := profile.(*MvlanProfile)
+ mvp.mvpLock.RLock()
+ defer mvp.mvpLock.RUnlock()
+
+ if net.ParseIP("0.0.0.0").Equal(igmpv3.GroupAddress) {
+ va.processIgmpQueries(device, pktVlan, IgmpVersion3)
+ } else {
+ if ig := va.GetIgmpGroup(pktVlan, igmpv3.GroupAddress); ig != nil {
+ ig.IgmpGroupLock.Lock()
+ igd, ok := ig.Devices[device]
+ if ok {
+ igd.ProcessQuery(igmpv3.GroupAddress, IgmpVersion3)
+ } else {
+ logger.Warnw(ctx, "IGMP Device not found", log.Fields{"Device": device, "Group": igmpv3.GroupAddress})
+ }
+ ig.IgmpGroupLock.Unlock()
+ }
+ }
+ }
+}
+
+// processIgmpQueries to process the igmp queries
+func (va *VoltApplication) processIgmpQueries(device string, pktVlan of.VlanType, version uint8) {
+ // This is a generic query and respond with all the groups channels in currently being viewed.
+ processquery := func(key interface{}, value interface{}) bool {
+ ig := value.(*IgmpGroup)
+ ig.IgmpGroupLock.Lock()
+ if ig.Mvlan != pktVlan {
+ ig.IgmpGroupLock.Unlock()
+ return true
+ }
+ igd, ok := ig.Devices[device]
+ if !ok {
+ logger.Warnw(ctx, "IGMP Device not found", log.Fields{"Device": device})
+ ig.IgmpGroupLock.Unlock()
+ return true
+ }
+ processQueryForEachChannel := func(key interface{}, value interface{}) bool {
+ groupAddr := key.(string)
+ igd.ProcessQuery(net.ParseIP(groupAddr), version)
+ return true
+ }
+ igd.GroupChannels.Range(processQueryForEachChannel)
+ ig.IgmpGroupLock.Unlock()
+ return true
+ }
+ va.IgmpGroups.Range(processquery)
+}
+
+// isIgmpJoin to check if it is igmp join
+func isIgmpJoin(recordType layers.IGMPv3GroupRecordType, sourceAddr []net.IP) bool {
+ var join = false
+
+ if (layers.IGMPToEx == recordType) || (layers.IGMPIsEx == recordType) {
+ join = true
+ } else if layers.IGMPBlock == recordType {
+ if len(sourceAddr) == 0 {
+ join = true
+ }
+ } else if (layers.IGMPToIn == recordType) || (layers.IGMPIsIn == recordType) || (layers.IGMPAllow == recordType) {
+ if len(sourceAddr) != 0 {
+ join = true
+ }
+ }
+ return join
+}
+
+func isIncl(recordType layers.IGMPv3GroupRecordType) bool {
+
+ if (layers.IGMPToIn == recordType) || (layers.IGMPIsIn == recordType) || (layers.IGMPAllow == recordType) {
+ return true
+ }
+ return false
+}
+
+// IgmpProcessPkt to process the IGMP packet received. The packet received brings along with it
+// the port on which the packet is received and the device the port is in.
+func (va *VoltApplication) IgmpProcessPkt(device string, port string, pkt gopacket.Packet) {
+ igmpl := pkt.Layer(layers.LayerTypeIGMP)
+ if igmpl == nil {
+ logger.Error(ctx, "Invalid IGMP packet arrived as IGMP packet")
+ return
+ }
+ if igmp, ok := igmpl.(*layers.IGMPv1or2); ok {
+ // This is an IGMPv2 packet.
+ logger.Debugw(ctx, "IGMPv2 Packet Received", log.Fields{"IPAddr": igmp.GroupAddress})
+ va.ProcessIgmpv2Pkt(device, port, pkt)
+ return
+ }
+ if igmpv3, ok := igmpl.(*layers.IGMP); ok {
+ logger.Debugw(ctx, "IGMPv3 Packet Received", log.Fields{"NumOfGroups": igmpv3.NumberOfGroupRecords})
+ va.ProcessIgmpv3Pkt(device, port, pkt)
+ }
+}
+
+// IgmpPacketInd for igmp packet indication
+func (va *VoltApplication) IgmpPacketInd(device string, port string, pkt gopacket.Packet) {
+ pt := NewIgmpPacketTask(device, port, pkt)
+ va.IgmpTasks.AddTask(pt)
+}
+
+// ------------------------------------------------------------
+// MVLAN related implemnetation
+//
+// Each MVLAN is configured with groups of multicast IPs. The idea of
+// groups is to be able to group some multicast channels into an individual
+// PON group and have a unique multicast GEM port for that set. However, in
+// the current implementation, the concept of grouping is not fully utilized.
+
+// MvlanGroup structure
+// A set of MC IPs form a group
+
+// MCGroupProxy identifies source specific multicast(SSM) config.
+type MCGroupProxy struct {
+ // Mode represents source list include/exclude
+ Mode common.MulticastSrcListMode
+ // SourceList represents list of multicast server IP addresses.
+ SourceList []net.IP
+}
+
+// MvlanGroup identifies MC group info
+type MvlanGroup struct {
+ Name string
+ Wildcard bool
+ McIPs []string
+ IsStatic bool
+}
+
+// OperInProgress type
+type OperInProgress uint8
+
+const (
+ // UpdateInProgress constant
+ UpdateInProgress OperInProgress = 2
+ // NoOp constant
+ NoOp OperInProgress = 1
+ // Nil constant
+ Nil OperInProgress = 0
+)
+
+// MvlanProfile : A set of groups of MC IPs for a MVLAN profile. It is assumed that
+// the MVLAN IP is not repeated within multiples groups and across
+// MVLAN profiles. The first match is used up on search to lcoate the
+// MVLAN profile for an MC IP
+type MvlanProfile struct {
+ Name string
+ Mvlan of.VlanType
+ PonVlan of.VlanType
+ Groups map[string]*MvlanGroup
+ Proxy map[string]*MCGroupProxy
+ Version string
+ IsPonVlanPresent bool
+ IsChannelBasedGroup bool
+ DevicesList map[string]OperInProgress //device serial number //here
+ oldGroups map[string]*MvlanGroup
+ oldProxy map[string]*MCGroupProxy
+ MaxActiveChannels uint32
+ PendingDeleteFlow map[string]map[string]bool
+ DeleteInProgress bool
+ IgmpServVersion map[string]*uint8
+ mvpLock sync.RWMutex
+ mvpFlowLock sync.RWMutex
+}
+
+// NewMvlanProfile is constructor for MVLAN profile.
+func NewMvlanProfile(name string, mvlan of.VlanType, ponVlan of.VlanType, isChannelBasedGroup bool, OLTSerialNums []string, actChannelPerPon uint32) *MvlanProfile {
+ var mvp MvlanProfile
+ mvp.Name = name
+ mvp.Mvlan = mvlan
+ mvp.PonVlan = ponVlan
+ mvp.mvpLock = sync.RWMutex{}
+ mvp.Groups = make(map[string]*MvlanGroup)
+ mvp.Proxy = make(map[string]*MCGroupProxy)
+ mvp.DevicesList = make(map[string]OperInProgress)
+ mvp.PendingDeleteFlow = make(map[string]map[string]bool)
+ mvp.IsChannelBasedGroup = isChannelBasedGroup
+ mvp.MaxActiveChannels = actChannelPerPon
+ mvp.DeleteInProgress = false
+ mvp.IgmpServVersion = make(map[string]*uint8)
+
+ if (ponVlan != of.VlanNone) && (ponVlan != 0) {
+ mvp.IsPonVlanPresent = true
+ }
+ return &mvp
+}
+
+// AddMvlanProxy for addition of groups to an MVLAN profile
+func (mvp *MvlanProfile) AddMvlanProxy(name string, proxyInfo common.MulticastGroupProxy) {
+ proxy := &MCGroupProxy{}
+ proxy.Mode = proxyInfo.Mode
+ proxy.SourceList = util.GetExpIPList(proxyInfo.SourceList)
+
+ if _, ok := mvp.Proxy[name]; !ok {
+ logger.Debugw(ctx, "Added MVLAN Proxy", log.Fields{"Name": name, "Proxy": proxy})
+ } else {
+ logger.Debugw(ctx, "Updated MVLAN Proxy", log.Fields{"Name": name, "Proxy": proxy})
+ }
+ if proxyInfo.IsStatic == common.IsStaticYes {
+ mvp.Groups[name].IsStatic = true
+ }
+ mvp.Proxy[name] = proxy
+}
+
+// AddMvlanGroup for addition of groups to an MVLAN profile
+func (mvp *MvlanProfile) AddMvlanGroup(name string, ips []string) {
+ mvg := &MvlanGroup{}
+ mvg.Name = name
+ mvg.Wildcard = len(ips) == 0
+ mvg.McIPs = ips
+ mvg.IsStatic = false
+ if _, ok := mvp.Groups[name]; !ok {
+ logger.Debugw(ctx, "Added MVLAN Group", log.Fields{"VLAN": mvp.Mvlan, "Name": name, "mvg": mvg, "IPs": mvg.McIPs})
+ } else {
+ logger.Debugw(ctx, "Updated MVLAN Group", log.Fields{"VLAN": mvp.Mvlan, "Name": name})
+ }
+ mvp.Groups[name] = mvg
+}
+
+// GetUsMatchVlan provides mvlan for US Match parameter
+func (mvp *MvlanProfile) GetUsMatchVlan() of.VlanType {
+ if mvp.IsPonVlanPresent {
+ return mvp.PonVlan
+ }
+ return mvp.Mvlan
+}
+
+// WriteToDb is utility to write Mvlan Profile Info to database
+func (mvp *MvlanProfile) WriteToDb() error {
+
+ if mvp.DeleteInProgress {
+ logger.Warnw(ctx, "Skipping Redis Update for MvlanProfile, MvlanProfile delete in progress", log.Fields{"Mvlan": mvp.Mvlan})
+ return nil
+ }
+
+ mvp.Version = database.PresentVersionMap[database.MvlanPath]
+ b, err := json.Marshal(mvp)
+ if err != nil {
+ return err
+ }
+ if err1 := db.PutMvlan(uint16(mvp.Mvlan), string(b)); err1 != nil {
+ return err1
+ }
+ return nil
+}
+
+//isChannelStatic - Returns true if the given channel is part of static group in the Mvlan Profile
+func (mvp *MvlanProfile) isChannelStatic(channel net.IP) bool {
+ for _, mvg := range mvp.Groups {
+ if mvg.IsStatic {
+ if isChannelStatic := doesIPMatch(channel, mvg.McIPs); isChannelStatic {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+//containsStaticChannels - Returns if any static channels is part of the Mvlan Profile
+func (mvp *MvlanProfile) containsStaticChannels() bool {
+ for _, mvg := range mvp.Groups {
+ if mvg.IsStatic && len(mvg.McIPs) != 0 {
+ return true
+ }
+ }
+ return false
+}
+
+//getAllStaticChannels - Returns all static channels in the Mvlan Profile
+func (mvp *MvlanProfile) getAllStaticChannels() ([]net.IP, bool) {
+ channelList := []net.IP{}
+ containsStatic := false
+ for _, mvg := range mvp.Groups {
+ if mvg.IsStatic {
+ staticChannels, _ := mvg.getAllChannels()
+ channelList = append(channelList, staticChannels...)
+ }
+ }
+ if len(channelList) > 0 {
+ containsStatic = true
+ }
+ return channelList, containsStatic
+}
+
+//getAllOldGroupStaticChannels - Returns all static channels in the Mvlan Profile
+func (mvp *MvlanProfile) getAllOldGroupStaticChannels() ([]net.IP, bool) {
+ channelList := []net.IP{}
+ containsStatic := false
+ for _, mvg := range mvp.oldGroups {
+ if mvg.IsStatic {
+ staticChannels, _ := mvg.getAllChannels()
+ channelList = append(channelList, staticChannels...)
+ }
+ }
+ if len(channelList) > 0 {
+ containsStatic = true
+ }
+ return channelList, containsStatic
+}
+
+//getAllChannels - Returns all channels in the Mvlan Profile
+func (mvg *MvlanGroup) getAllChannels() ([]net.IP, bool) {
+ channelList := []net.IP{}
+
+ if mvg == nil || len(mvg.McIPs) == 0 {
+ return []net.IP{}, false
+ }
+
+ grpChannelOrRange := mvg.McIPs
+ for _, channelOrRange := range grpChannelOrRange {
+ if strings.Contains(channelOrRange, "-") {
+ var splits = strings.Split(channelOrRange, "-")
+ ipStart := util.IP2LongConv(net.ParseIP(splits[0]))
+ ipEnd := util.IP2LongConv(net.ParseIP(splits[1]))
+
+ for i := ipStart; i <= ipEnd; i++ {
+ channelList = append(channelList, util.Long2ipConv(i))
+ }
+ } else {
+ channelList = append(channelList, net.ParseIP(channelOrRange))
+ }
+ }
+ return channelList, true
+}
+
+//SetUpdateStatus - Sets profile update status for devices
+func (mvp *MvlanProfile) SetUpdateStatus(serialNum string, status OperInProgress) {
+ if serialNum != "" {
+ mvp.DevicesList[serialNum] = status
+ return
+ }
+
+ for srNo := range mvp.DevicesList {
+ mvp.DevicesList[srNo] = status
+ }
+}
+
+//isUpdateInProgress - checking is update is in progress for the mvlan profile
+func (mvp *MvlanProfile) isUpdateInProgress() bool {
+
+ for srNo := range mvp.DevicesList {
+ if mvp.DevicesList[srNo] == UpdateInProgress {
+ return true
+ }
+ }
+ return false
+}
+
+//IsUpdateInProgressForDevice - Checks is Mvlan Profile update is is progress for the given device
+func (mvp *MvlanProfile) IsUpdateInProgressForDevice(device string) bool {
+ if vd := GetApplication().GetDevice(device); vd != nil {
+ if mvp.DevicesList[vd.SerialNum] == UpdateInProgress {
+ return true
+ }
+ }
+ return false
+}
+
+// DelFromDb to delere mvlan from database
+func (mvp *MvlanProfile) DelFromDb() {
+ _ = db.DelMvlan(uint16(mvp.Mvlan))
+}
+
+// storeMvlansMap to store mvlan map
+func (va *VoltApplication) storeMvlansMap(mvlan of.VlanType, name string, mvp *MvlanProfile) {
+ va.MvlanProfilesByTag.Store(mvlan, mvp)
+ va.MvlanProfilesByName.Store(name, mvp)
+}
+
+// deleteMvlansMap to delete mvlan map
+func (va *VoltApplication) deleteMvlansMap(mvlan of.VlanType, name string) {
+ va.MvlanProfilesByTag.Delete(mvlan)
+ va.MvlanProfilesByName.Delete(name)
+}
+
+// RestoreMvlansFromDb to read from the DB and restore all the MVLANs
+func (va *VoltApplication) RestoreMvlansFromDb() {
+ mvlans, _ := db.GetMvlans()
+ for _, mvlan := range mvlans {
+ b, ok := mvlan.Value.([]byte)
+ if !ok {
+ logger.Warn(ctx, "The value type is not []byte")
+ continue
+ }
+ var mvp MvlanProfile
+ err := json.Unmarshal(b, &mvp)
+ if err != nil {
+ logger.Warn(ctx, "Unmarshal of MVLAN failed")
+ continue
+ }
+ va.storeMvlansMap(mvp.Mvlan, mvp.Name, &mvp)
+
+ for srNo := range mvp.DevicesList {
+ if mvp.IgmpServVersion[srNo] == nil {
+ servVersion := IgmpVersion0
+ mvp.IgmpServVersion[srNo] = &servVersion
+ }
+ }
+ logger.Infow(ctx, "Restored Mvlan Profile", log.Fields{"MVPName": mvp.Name})
+ }
+}
+
+// GetMvlanProfileByTag fetches MVLAN profile based on the MC VLAN
+func (va *VoltApplication) GetMvlanProfileByTag(vlan of.VlanType) *MvlanProfile {
+ if mvp, ok := va.MvlanProfilesByTag.Load(vlan); ok {
+ return mvp.(*MvlanProfile)
+ }
+ return nil
+}
+
+// GetMvlanProfileByName fetches MVLAN profile based on the profile name.
+func (va *VoltApplication) GetMvlanProfileByName(name string) *MvlanProfile {
+ if mvp, ok := va.MvlanProfilesByName.Load(name); ok {
+ return mvp.(*MvlanProfile)
+ }
+ return nil
+}
+
+//UpdateMvlanProfile - only channel groups be updated
+func (va *VoltApplication) UpdateMvlanProfile(name string, vlan of.VlanType, groups map[string][]string, activeChannelCount int, proxy map[string]common.MulticastGroupProxy) error {
+
+ mvpIntf, ok := va.MvlanProfilesByName.Load(name)
+ if !ok {
+ logger.Error(ctx, "Update Mvlan Failed: Profile does not exist")
+ return errors.New("MVLAN profile not found")
+ }
+ mvp := mvpIntf.(*MvlanProfile)
+ // check if groups are same then just update the OLTSerial numbers, push the config on new serial numbers
+
+ existingGroup := mvp.Groups
+ existingProxy := mvp.Proxy
+ mvp.Groups = make(map[string]*MvlanGroup)
+ mvp.Proxy = make(map[string]*MCGroupProxy)
+
+ /* Need to protect groups and proxy write lock */
+ mvp.mvpLock.Lock()
+ for grpName, grpIPList := range groups {
+ mvp.AddMvlanGroup(grpName, grpIPList)
+ }
+ for grpName, proxyInfo := range proxy {
+ mvp.AddMvlanProxy(grpName, proxyInfo)
+ }
+ if _, ok := mvp.Groups[common.StaticGroup]; ok {
+ if _, yes := mvp.Proxy[common.StaticGroup]; !yes {
+ mvp.Groups[common.StaticGroup].IsStatic = true
+ }
+ }
+ prevMaxActiveChannels := mvp.MaxActiveChannels
+ if reflect.DeepEqual(mvp.Groups, existingGroup) && reflect.DeepEqual(mvp.Proxy, existingProxy) {
+ logger.Info(ctx, "No change in groups config")
+ if uint32(activeChannelCount) != mvp.MaxActiveChannels {
+ mvp.MaxActiveChannels = uint32(activeChannelCount)
+ if err := mvp.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Mvlan profile Write to DB failed", log.Fields{"ProfileName": mvp.Name})
+ }
+ if prevMaxActiveChannels != mvp.MaxActiveChannels {
+ mvp.UpdateActiveChannelSubscriberAlarm()
+ }
+ }
+ mvp.mvpLock.Unlock()
+ return nil
+ }
+ mvp.mvpLock.Unlock()
+ mvp.MaxActiveChannels = uint32(activeChannelCount)
+
+ // Status is maintained so that in the event of any crash or reboot during update,
+ // the recovery is possible once the pod is UP again
+ mvp.SetUpdateStatus("", UpdateInProgress)
+ mvp.oldGroups = existingGroup
+ mvp.oldProxy = existingProxy
+ va.storeMvlansMap(vlan, name, mvp)
+ if err := mvp.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Mvlan profile Write to DB failed", log.Fields{"ProfileName": mvp.Name})
+ }
+ if prevMaxActiveChannels != mvp.MaxActiveChannels {
+ mvp.UpdateActiveChannelSubscriberAlarm()
+ }
+
+ // The update task is added as part of Igm p task list, so that any parallel igmp pkt processing is avoided
+ // Until, the update operation is completed, the igmp pkt processing will be enqueued
+ updateTask := NewUpdateMvlanTask(mvp, "")
+ va.IgmpTasks.AddTask(updateTask)
+ return nil
+}
+
+// isDeviceInList to check if device is the list
+func isDeviceInList(serialNum string, OLTSerialNums []string) bool {
+ for _, oltSerialNum := range OLTSerialNums {
+ if serialNum == oltSerialNum {
+ return true
+ }
+ }
+ return false
+}
+
+// McastConfigKey creates the key using the olt serial number and mvlan profile id
+func McastConfigKey(oltSerialNum string, mvlanProfID string) string {
+ return oltSerialNum + "_" + mvlanProfID
+}
+
+// GetMcastConfig to get McastConfig Information by OLT and Mvlan Profile ID
+func (va *VoltApplication) GetMcastConfig(oltSerialNum string, mvlanProfID string) *McastConfig {
+ if mc, ok := va.McastConfigMap.Load(McastConfigKey(oltSerialNum, mvlanProfID)); ok {
+ return mc.(*McastConfig)
+ }
+ return nil
+}
+
+func (va *VoltApplication) storeMcastConfig(oltSerialNum string, mvlanProfID string, mcastConfig *McastConfig) {
+ va.McastConfigMap.Store(McastConfigKey(oltSerialNum, mvlanProfID), mcastConfig)
+}
+
+func (va *VoltApplication) deleteMcastConfig(oltSerialNum string, mvlanProfID string) {
+ va.McastConfigMap.Delete(McastConfigKey(oltSerialNum, mvlanProfID))
+}
+
+// AddMcastConfig for addition of a MVLAN profile
+func (va *VoltApplication) AddMcastConfig(MvlanProfileID string, IgmpProfileID string, IgmpProxyIP string, OltSerialNum string) error {
+ var mcastCfg *McastConfig
+
+ mcastCfg = va.GetMcastConfig(OltSerialNum, MvlanProfileID)
+ if mcastCfg == nil {
+ mcastCfg = &McastConfig{}
+ } else {
+ logger.Debugw(ctx, "Mcast Config already exists", log.Fields{"OltSerialNum": mcastCfg.OltSerialNum,
+ "MVLAN Profile ID": mcastCfg.MvlanProfileID})
+ }
+
+ // Update all igds available
+ mvpIntf, ok := va.MvlanProfilesByName.Load(MvlanProfileID)
+ if !ok {
+ return errors.New("MVLAN profile not found during add mcast config")
+ }
+ mvlan := mvpIntf.(*MvlanProfile).Mvlan
+
+ mcastCfg.OltSerialNum = OltSerialNum
+ mcastCfg.MvlanProfileID = MvlanProfileID
+ mcastCfg.IgmpProfileID = IgmpProfileID
+ mcastCfg.IgmpProxyIP = net.ParseIP(IgmpProxyIP)
+
+ proxyCfg := va.getIgmpProfileMap(IgmpProfileID)
+
+ iterIgmpGroups := func(key interface{}, value interface{}) bool {
+ ig := value.(*IgmpGroup)
+ if ig.Mvlan != mvlan {
+ return true
+ }
+
+ for _, igd := range ig.Devices {
+ if igd.SerialNo != OltSerialNum {
+ continue
+ }
+ igd.proxyCfg = proxyCfg
+ if IgmpProfileID == "" {
+ igd.IgmpProxyIP = &igd.proxyCfg.IgmpSourceIP
+ } else {
+ igd.IgmpProxyIP = &mcastCfg.IgmpProxyIP
+ }
+ mcastCfg.IgmpGroupDevices.Store(igd.GroupID, igd)
+ logger.Debugw(ctx, "Igd updated with proxyCfg and proxyIP", log.Fields{"name": igd.GroupName,
+ "IgmpProfileID": IgmpProfileID, "ProxyIP": mcastCfg.IgmpProxyIP})
+ }
+ return true
+ }
+ va.IgmpGroups.Range(iterIgmpGroups)
+
+ va.storeMcastConfig(OltSerialNum, MvlanProfileID, mcastCfg)
+ if err := mcastCfg.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "McastConfig Write to DB failed", log.Fields{"OltSerialNum": mcastCfg.OltSerialNum, "MvlanProfileID": mcastCfg.MvlanProfileID})
+ }
+ va.addOltToMvlan(MvlanProfileID, OltSerialNum)
+
+ return nil
+}
+
+func (va *VoltApplication) addOltToMvlan(MvlanProfileID string, OltSerialNum string) {
+ var mvp *MvlanProfile
+ if mvpIntf, ok := va.MvlanProfilesByName.Load(MvlanProfileID); ok {
+ servVersion := IgmpVersion0
+ mvp = mvpIntf.(*MvlanProfile)
+ mvp.DevicesList[OltSerialNum] = NoOp
+ mvp.IgmpServVersion[OltSerialNum] = &servVersion
+ if err := mvp.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Mvlan profile Write to DB failed", log.Fields{"ProfileName": mvp.Name})
+ }
+ mvp.pushIgmpMcastFlows(OltSerialNum)
+ }
+}
+
+func (va *VoltApplication) delOltFromMvlan(MvlanProfileID string, OltSerialNum string) {
+ var mvp *MvlanProfile
+ if mvpIntf, ok := va.MvlanProfilesByName.Load(MvlanProfileID); ok {
+ mvp = mvpIntf.(*MvlanProfile)
+ //Delete from mvp list
+ mvp.removeIgmpMcastFlows(OltSerialNum)
+ delete(mvp.DevicesList, OltSerialNum)
+ if err := mvp.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Mvlan profile Write to DB failed", log.Fields{"ProfileName": mvp.Name})
+ }
+ }
+}
+
+// DelMcastConfig for addition of a MVLAN profile
+func (va *VoltApplication) DelMcastConfig(MvlanProfileID string, IgmpProfileID string, IgmpProxyIP string, OltSerialNum string) {
+
+ va.delOltFromMvlan(MvlanProfileID, OltSerialNum)
+ va.deleteMcastConfig(OltSerialNum, MvlanProfileID)
+ _ = db.DelMcastConfig(McastConfigKey(OltSerialNum, MvlanProfileID))
+ if d := va.GetDeviceBySerialNo(OltSerialNum); d != nil {
+ if mvp := va.GetMvlanProfileByName(MvlanProfileID); mvp != nil {
+ va.RemoveGroupsFromPendingPool(d.Name, mvp.Mvlan)
+ }
+ }
+}
+
+// DelAllMcastConfig for deletion of all mcast config
+func (va *VoltApplication) DelAllMcastConfig(OltSerialNum string) error {
+
+ deleteIndividualMcastConfig := func(key interface{}, value interface{}) bool {
+ mcastCfg := value.(*McastConfig)
+ if mcastCfg.OltSerialNum == OltSerialNum {
+ va.DelMcastConfig(mcastCfg.MvlanProfileID, mcastCfg.IgmpProfileID, mcastCfg.IgmpProxyIP.String(), mcastCfg.OltSerialNum)
+ }
+ return true
+ }
+ va.McastConfigMap.Range(deleteIndividualMcastConfig)
+ return nil
+}
+
+// UpdateMcastConfig for addition of a MVLAN profile
+func (va *VoltApplication) UpdateMcastConfig(MvlanProfileID string, IgmpProfileID string, IgmpProxyIP string, OltSerialNum string) error {
+
+ mcastCfg := va.GetMcastConfig(OltSerialNum, MvlanProfileID)
+ if mcastCfg == nil {
+ logger.Warnw(ctx, "Mcast Config not found. Unable to update", log.Fields{"Mvlan Profile ID": MvlanProfileID, "OltSerialNum": OltSerialNum})
+ return nil
+ }
+
+ oldProfID := mcastCfg.IgmpProfileID
+ mcastCfg.IgmpProfileID = IgmpProfileID
+ mcastCfg.IgmpProxyIP = net.ParseIP(IgmpProxyIP)
+
+ va.storeMcastConfig(OltSerialNum, MvlanProfileID, mcastCfg)
+
+ // Update all igds
+ if oldProfID != mcastCfg.IgmpProfileID {
+ updateIgdProxyCfg := func(key interface{}, value interface{}) bool {
+ igd := value.(*IgmpGroupDevice)
+ igd.proxyCfg = va.getIgmpProfileMap(mcastCfg.IgmpProfileID)
+ if IgmpProfileID == "" {
+ igd.IgmpProxyIP = &igd.proxyCfg.IgmpSourceIP
+ } else {
+ igd.IgmpProxyIP = &mcastCfg.IgmpProxyIP
+ }
+ return true
+ }
+ mcastCfg.IgmpGroupDevices.Range(updateIgdProxyCfg)
+ }
+
+ if err := mcastCfg.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "McastConfig Write to DB failed", log.Fields{"OltSerialNum": mcastCfg.OltSerialNum, "MvlanProfileID": mcastCfg.MvlanProfileID})
+ }
+
+ return nil
+}
+
+// WriteToDb is utility to write Mcast config Info to database
+func (mc *McastConfig) WriteToDb() error {
+ mc.Version = database.PresentVersionMap[database.McastConfigPath]
+ b, err := json.Marshal(mc)
+ if err != nil {
+ return err
+ }
+ if err1 := db.PutMcastConfig(McastConfigKey(mc.OltSerialNum, mc.MvlanProfileID), string(b)); err1 != nil {
+ return err1
+ }
+ return nil
+}
+
+// RestoreMcastConfigsFromDb to read from the DB and restore Mcast configs
+func (va *VoltApplication) RestoreMcastConfigsFromDb() {
+ mcastConfigs, _ := db.GetMcastConfigs()
+ for hash, mcastConfig := range mcastConfigs {
+ b, ok := mcastConfig.Value.([]byte)
+ if !ok {
+ logger.Warn(ctx, "The value type is not []byte")
+ continue
+ }
+ var mc McastConfig
+ err := json.Unmarshal(b, &mc)
+ if err != nil {
+ logger.Warn(ctx, "Unmarshal of Mcast config failed")
+ continue
+ }
+ va.storeMcastConfig(mc.OltSerialNum, mc.MvlanProfileID, &mc)
+ logger.Infow(ctx, "Restored Mcast config", log.Fields{"OltSerialNum": mc.OltSerialNum, "MvlanProfileID": mc.MvlanProfileID, "hash": hash})
+ }
+}
+
+// AddMvlanProfile for addition of a MVLAN profile
+func (va *VoltApplication) AddMvlanProfile(name string, mvlan of.VlanType, ponVlan of.VlanType,
+ groups map[string][]string, isChannelBasedGroup bool, OLTSerialNum []string, activeChannelsPerPon int, proxy map[string]common.MulticastGroupProxy) error {
+ var mvp *MvlanProfile
+
+ if mvp = va.GetMvlanProfileByTag(mvlan); mvp != nil {
+ logger.Errorw(ctx, "Duplicate MVLAN ID configured", log.Fields{"mvlan": mvlan})
+ return errors.New("MVLAN profile with same VLANID exists")
+ }
+ if mvpIntf, ok := va.MvlanProfilesByName.Load(name); ok {
+ mvp = mvpIntf.(*MvlanProfile)
+ for _, serialNum := range OLTSerialNum {
+ if mvp.DevicesList[serialNum] != Nil {
+ //This is backup restore scenario, just update the profile
+ logger.Info(ctx, "Add Mvlan : Profile Name already exists, update-the-profile")
+ return va.UpdateMvlanProfile(name, mvlan, groups, activeChannelsPerPon, proxy)
+ }
+ }
+ }
+
+ if mvp == nil {
+ mvp = NewMvlanProfile(name, mvlan, ponVlan, isChannelBasedGroup, OLTSerialNum, uint32(activeChannelsPerPon))
+ }
+
+ va.storeMvlansMap(mvlan, name, mvp)
+
+ /* Need to protect groups and proxy write lock */
+ mvp.mvpLock.Lock()
+ for grpName, grpInfo := range groups {
+ mvp.AddMvlanGroup(grpName, grpInfo)
+ }
+ for grpName, proxyInfo := range proxy {
+ mvp.AddMvlanProxy(grpName, proxyInfo)
+ }
+ if _, ok := mvp.Groups[common.StaticGroup]; ok {
+ if _, yes := mvp.Proxy[common.StaticGroup]; !yes {
+ mvp.Groups[common.StaticGroup].IsStatic = true
+ }
+ }
+
+ logger.Debugw(ctx, "Added MVLAN Profile", log.Fields{"MVLAN": mvp.Mvlan, "PonVlan": mvp.PonVlan, "Name": mvp.Name, "Grp IPs": mvp.Groups, "IsPonVlanPresent": mvp.IsPonVlanPresent})
+ mvp.mvpLock.Unlock()
+
+ if err := mvp.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Mvlan profile Write to DB failed", log.Fields{"ProfileName": mvp.Name})
+ }
+
+ return nil
+}
+
+//pushIgmpMcastFlows - Adds all IGMP related flows (generic DS flow & static group flows)
+func (mvp *MvlanProfile) pushIgmpMcastFlows(OLTSerialNum string) {
+
+ mvp.mvpLock.RLock()
+ defer mvp.mvpLock.RUnlock()
+
+ if mvp.DevicesList[OLTSerialNum] == Nil {
+ logger.Infow(ctx, "Mvlan Profile not configure for device", log.Fields{"Device": OLTSerialNum, "Mvlan": mvp.Mvlan})
+ return
+ }
+
+ d := GetApplication().GetDeviceBySerialNo(OLTSerialNum)
+ if d == nil {
+ logger.Warnw(ctx, "Skipping Igmp & Mcast Flow processing: Device Not Found", log.Fields{"Device_SrNo": OLTSerialNum, "Mvlan": mvp.Mvlan})
+ return
+ }
+
+ p := d.GetPort(d.NniPort)
+
+ if p != nil && p.State == PortStateUp {
+ logger.Infow(ctx, "NNI Port Status is: UP & Vlan Enabled", log.Fields{"Device": d, "port": p})
+
+ //Push Igmp DS Control Flows
+ err := mvp.ApplyIgmpDSFlowForMvp(d.Name)
+ if err != nil {
+ logger.Errorw(ctx, "DS IGMP Flow Add Failed for device",
+ log.Fields{"Reason": err.Error(), "device": d.Name})
+ }
+
+ //Trigger Join for static channels
+ if channelList, containsStatic := mvp.getAllStaticChannels(); containsStatic {
+ mvp.ProcessStaticGroup(d.Name, channelList, true)
+ } else {
+ logger.Infow(ctx, "No Static Channels Present", log.Fields{"mvp": mvp.Name, "Mvlan": mvp.Mvlan})
+ }
+ }
+}
+
+/*
+//pushIgmpMcastFlowsToAllOlt - Adds all IGMP related flows (generic DS flow & static group flows) to all OLTs
+func (mvp *MvlanProfile) pushIgmpMcastFlowsToAllOlt() {
+
+ //for all devices apply igmp DS trap flow rules
+ pushIgmpFlows := func(key interface{}, value interface{}) bool {
+ d := value.(*VoltDevice)
+ p := d.GetPort(d.NniPort)
+ if p != nil && p.State == PortStateUp {
+ logger.Infow(ctx, "NNI Port Status is: UP & Vlan Enabled", log.Fields{"Device": d, "port": p})
+
+ //Push Igmp DS Control Flows
+ err := mvp.ApplyIgmpDSFlowForMvp(d.Name)
+ if err != nil {
+ logger.Errorw(ctx, "DS IGMP Flow Add Failed for device",
+ log.Fields{"Reason": err.Error(), "device": d.Name})
+ }
+
+ //Trigger Join for static channels
+ if channelList, containsStatic := mvp.getAllStaticChannels(); containsStatic {
+ mvp.ProcessStaticGroup(d.Name, channelList, true)
+ } else {
+ logger.Infow(ctx, "No Static Channels Present", log.Fields{"mvp": mvp.Name, "Mvlan": mvp.Mvlan})
+ }
+ }
+ return true
+ }
+ mvp.mvpLock.RLock()
+ defer mvp.mvpLock.RUnlock()
+ GetApplication().DevicesDisc.Range(pushIgmpFlows)
+}
+
+//removeIgmpFlows - Removes all IGMP related flows (generic DS flow)
+func (mvp *MvlanProfile) removeIgmpFlows(oltSerialNum string) {
+
+ if d := GetApplication().GetDeviceBySerialNo(oltSerialNum); d != nil {
+ p := d.GetPort(d.NniPort)
+ if p != nil {
+ logger.Infow(ctx, "NNI Port Status is: UP", log.Fields{"Device": d, "port": p})
+ err := mvp.RemoveIgmpDSFlowForMvp(d.Name)
+ if err != nil {
+ logger.Errorw(ctx, "DS IGMP Flow Del Failed", log.Fields{"Reason": err.Error(), "device": d.Name})
+ }
+ }
+ }
+}*/
+
+//removeIgmpMcastFlows - Removes all IGMP related flows (generic DS flow & static group flows)
+func (mvp *MvlanProfile) removeIgmpMcastFlows(oltSerialNum string) {
+
+ mvp.mvpLock.RLock()
+ defer mvp.mvpLock.RUnlock()
+
+ if d := GetApplication().GetDeviceBySerialNo(oltSerialNum); d != nil {
+ p := d.GetPort(d.NniPort)
+ if p != nil {
+ logger.Infow(ctx, "NNI Port Status is: UP", log.Fields{"Device": d, "port": p})
+
+ // ***Do not change the order***
+ // When Vlan is disabled, the process end is determined by the DS Igmp flag in device
+
+ //Trigger Leave for static channels
+ if channelList, containsStatic := mvp.getAllStaticChannels(); containsStatic {
+ mvp.ProcessStaticGroup(d.Name, channelList, false)
+ } else {
+ logger.Infow(ctx, "No Static Channels Present", log.Fields{"mvp": mvp.Name, "Mvlan": mvp.Mvlan})
+ }
+
+ //Remove all dynamic members for the Mvlan Profile
+ GetApplication().IgmpGroups.Range(func(key, value interface{}) bool {
+ ig := value.(*IgmpGroup)
+ if ig.Mvlan == mvp.Mvlan {
+ igd := ig.Devices[d.Name]
+ ig.DelIgmpGroupDevice(igd)
+ if ig.NumDevicesActive() == 0 {
+ GetApplication().DelIgmpGroup(ig)
+ }
+ }
+ return true
+ })
+
+ //Remove DS Igmp trap flow
+ err := mvp.RemoveIgmpDSFlowForMvp(d.Name)
+ if err != nil {
+ logger.Errorw(ctx, "DS IGMP Flow Del Failed", log.Fields{"Reason": err.Error(), "device": d.Name})
+ }
+ }
+ }
+}
+
+// ApplyIgmpDSFlowForMvp to apply Igmp DS flow for mvlan.
+func (mvp *MvlanProfile) ApplyIgmpDSFlowForMvp(device string) error {
+ va := GetApplication()
+ dIntf, ok := va.DevicesDisc.Load(device)
+ if !ok {
+ return errors.New("Device Doesn't Exist")
+ }
+ d := dIntf.(*VoltDevice)
+ mvlan := mvp.Mvlan
+
+ flowAlreadyApplied, ok := d.IgmpDsFlowAppliedForMvlan[uint16(mvlan)]
+ if !ok || !flowAlreadyApplied {
+ flows, err := mvp.BuildIgmpDSFlows(device)
+ if err == nil {
+ err = cntlr.GetController().AddFlows(d.NniPort, device, flows)
+ if err != nil {
+ logger.Warnw(ctx, "Configuring IGMP Flow for device failed ", log.Fields{"Device": device, "err": err})
+ return err
+ }
+ d.IgmpDsFlowAppliedForMvlan[uint16(mvlan)] = true
+ logger.Infow(ctx, "Updating voltDevice that IGMP DS flow as \"added\" for ",
+ log.Fields{"device": d.SerialNum, "mvlan": mvlan})
+ } else {
+ logger.Errorw(ctx, "DS IGMP Flow Add Failed", log.Fields{"Reason": err.Error(), "Mvlan": mvlan})
+ }
+ }
+
+ return nil
+}
+
+// RemoveIgmpDSFlowForMvp to remove Igmp DS flow for mvlan.
+func (mvp *MvlanProfile) RemoveIgmpDSFlowForMvp(device string) error {
+
+ va := GetApplication()
+ mvlan := mvp.Mvlan
+
+ dIntf, ok := va.DevicesDisc.Load(device)
+ if !ok {
+ return errors.New("Device Doesn't Exist")
+ }
+ d := dIntf.(*VoltDevice)
+ /* No need of strict check during DS IGMP deletion
+ flowAlreadyApplied, ok := d.IgmpDsFlowAppliedForMvlan[uint16(mvlan)]
+ if ok && flowAlreadyApplied
+ */
+ flows, err := mvp.BuildIgmpDSFlows(device)
+ if err == nil {
+ flows.ForceAction = true
+
+ err = mvp.DelFlows(d, flows)
+ if err != nil {
+ logger.Warnw(ctx, "De-Configuring IGMP Flow for device failed ", log.Fields{"Device": device, "err": err})
+ return err
+ }
+ d.IgmpDsFlowAppliedForMvlan[uint16(mvlan)] = false
+ logger.Infow(ctx, "Updating voltDevice that IGMP DS flow as \"removed\" for ",
+ log.Fields{"device": d.SerialNum, "mvlan": mvlan})
+ } else {
+ logger.Errorw(ctx, "DS IGMP Flow Del Failed", log.Fields{"Reason": err.Error()})
+ }
+
+ return nil
+}
+
+// BuildIgmpDSFlows to build Igmp DS flows for NNI port
+func (mvp *MvlanProfile) BuildIgmpDSFlows(device string) (*of.VoltFlow, error) {
+ dIntf, ok := GetApplication().DevicesDisc.Load(device)
+ if !ok {
+ return nil, errors.New("Device Doesn't Exist")
+ }
+ d := dIntf.(*VoltDevice)
+
+ logger.Infow(ctx, "Building DS IGMP Flow for NNI port", log.Fields{"vs": d.NniPort, "Mvlan": mvp.Mvlan})
+ flow := &of.VoltFlow{}
+ flow.SubFlows = make(map[uint64]*of.VoltSubFlow)
+ subFlow := of.NewVoltSubFlow()
+ subFlow.SetTableID(0)
+ subFlow.SetMatchVlan(mvp.Mvlan)
+
+ nniPort, err := GetApplication().GetNniPort(device)
+ if err != nil {
+ return nil, err
+ }
+ nniPortID, err1 := GetApplication().GetPortID(nniPort)
+ if err1 != nil {
+ return nil, errors.New("Unknown NNI outport")
+ }
+ subFlow.SetInPort(nniPortID)
+ subFlow.SetIgmpMatch()
+ subFlow.SetReportToController()
+ subFlow.Cookie = uint64(nniPortID)<<32 | uint64(mvp.Mvlan)
+ subFlow.Priority = of.IgmpFlowPriority
+
+ flow.SubFlows[subFlow.Cookie] = subFlow
+ logger.Infow(ctx, "Built DS IGMP flow", log.Fields{"cookie": subFlow.Cookie, "subflow": subFlow})
+ return flow, nil
+}
+
+//updateStaticGroups - Generates static joins & leaves for newly added and removed static channels respectively
+func (mvp *MvlanProfile) updateStaticGroups(deviceID string, added []net.IP, removed []net.IP) {
+
+ //Update static group configs for all associated devices
+ updateGroups := func(key interface{}, value interface{}) bool {
+ d := value.(*VoltDevice)
+
+ if mvp.DevicesList[d.SerialNum] == Nil {
+ logger.Infow(ctx, "Mvlan Profile not configure for device", log.Fields{"Device": d, "Profile Device List": mvp.DevicesList})
+ return true
+ }
+ //TODO if mvp.IsChannelBasedGroup {
+ mvp.ProcessStaticGroup(d.Name, added, true)
+ mvp.ProcessStaticGroup(d.Name, removed, false)
+ //}
+ return true
+ }
+
+ if deviceID != "" {
+ vd := GetApplication().GetDevice(deviceID)
+ updateGroups(deviceID, vd)
+ } else {
+ GetApplication().DevicesDisc.Range(updateGroups)
+ }
+}
+
+//updateDynamicGroups - Generates joins with updated sources for existing channels
+func (mvp *MvlanProfile) updateDynamicGroups(deviceID string, added []net.IP, removed []net.IP) {
+
+ //mvlan := mvp.Mvlan
+ va := GetApplication()
+
+ updateGroups := func(key interface{}, value interface{}) bool {
+ d := value.(*VoltDevice)
+
+ if mvp.DevicesList[d.SerialNum] == Nil {
+ logger.Infow(ctx, "Mvlan Profile not configure for device", log.Fields{"Device": d, "Profile Device List": mvp.DevicesList})
+ return true
+ }
+ for _, groupAddr := range added {
+
+ _, gName := va.GetMvlanProfileForMcIP(mvp.Name, groupAddr)
+ grpKey := mvp.generateGroupKey(gName, groupAddr.String())
+ logger.Debugw(ctx, "IGMP Group", log.Fields{"Group": grpKey, "groupAddr": groupAddr})
+ if igIntf, ok := va.IgmpGroups.Load(grpKey); ok {
+ ig := igIntf.(*IgmpGroup)
+ if igd, ok := ig.getIgmpGroupDevice(d.Name); ok {
+ if igcIntf, ok := igd.GroupChannels.Load(groupAddr.String()); ok {
+ igc := igcIntf.(*IgmpGroupChannel)
+ incl := false
+ var ip []net.IP
+ var groupModified = false
+ if _, ok := mvp.Proxy[igc.GroupName]; ok {
+ if mvp.Proxy[igc.GroupName].Mode == common.Include {
+ incl = true
+ }
+ ip = mvp.Proxy[igc.GroupName].SourceList
+ }
+ for port, igp := range igc.NewReceivers {
+ // Process the include/exclude list which may end up modifying the group
+ if change, _ := igc.ProcessSources(port, ip, incl); change {
+ groupModified = true
+ }
+ igc.ProcessMode(port, incl)
+
+ if err := igp.WriteToDb(igc.Mvlan, igc.GroupAddr, igc.Device); err != nil {
+ logger.Errorw(ctx, "Igmp group port Write to DB failed", log.Fields{"mvlan": igc.Mvlan, "GroupAddr": igc.GroupAddr})
+ }
+ }
+ // If the group is modified as this is the first receiver or due to include/exclude list modification
+ // send a report to the upstream multicast servers
+ if groupModified {
+ logger.Debug(ctx, "Group Modified and IGMP report sent to the upstream server")
+ igc.SendReport(false)
+ }
+ if err := igc.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp group channel Write to DB failed", log.Fields{"mvlan": igc.Mvlan, "GroupAddr": igc.GroupAddr})
+ }
+ }
+ }
+ }
+ }
+
+ return true
+ }
+
+ if deviceID != "" {
+ vd := GetApplication().GetDevice(deviceID)
+ updateGroups(deviceID, vd)
+ } else {
+ GetApplication().DevicesDisc.Range(updateGroups)
+ }
+}
+
+//GroupsUpdated - Handles removing of Igmp Groups, flows & group table entries for
+//channels removed as part of update
+func (mvp *MvlanProfile) GroupsUpdated(deviceID string) {
+
+ deleteChannelIfRemoved := func(key interface{}, value interface{}) bool {
+ ig := value.(*IgmpGroup)
+
+ if ig.Mvlan != mvp.Mvlan {
+ return true
+ }
+ grpName := ig.GroupName
+ logger.Infow(ctx, "###Update Cycle", log.Fields{"IG": ig.GroupName, "Addr": ig.GroupAddr})
+ //Check if group exists and remove the entire group object otherwise
+ if currentChannels := mvp.Groups[grpName]; currentChannels != nil {
+
+ if mvp.IsChannelBasedGroup {
+ channelPresent := doesIPMatch(ig.GroupAddr, currentChannels.McIPs)
+ if channelPresent || mvp.isChannelStatic(ig.GroupAddr) {
+ return true
+ }
+ } else {
+ allExistingChannels := ig.GetAllIgmpChannelForDevice(deviceID)
+ for channel := range allExistingChannels {
+ channelIP := net.ParseIP(channel)
+ channelPresent := mvp.IsChannelPresent(channelIP, currentChannels.McIPs, mvp.IsStaticGroup(ig.GroupName))
+ if channelPresent {
+ staticChannel := mvp.isChannelStatic(channelIP)
+ logger.Infow(ctx, "###Channel Comparision", log.Fields{"staticChannel": staticChannel, "Group": mvp.IsStaticGroup(ig.GroupName), "Channel": channel})
+ // Logic:
+ // If channel is Static & existing Group is also static - No migration required
+ // If channel is not Static & existing Group is also not static - No migration required
+
+ // If channel is Static and existing Group is not static - Migrate (from dynamic to static)
+ // (Channel already part of dynamic, added to static)
+
+ // If channel is not Static but existing Group is static - Migrate (from static to dynamic)
+ // (Channel removed from satic but part of dynamic)
+ if (staticChannel != mvp.IsStaticGroup(ig.GroupName)) || (ig.IsGroupStatic != mvp.IsStaticGroup(ig.GroupName)) { // Equivalent of XOR
+ ig.HandleGroupMigration(deviceID, channelIP)
+ } else {
+ if (ig.IsGroupStatic) && mvp.IsStaticGroup(ig.GroupName) {
+ if ig.GroupName != mvp.GetStaticGroupName(channelIP) {
+ ig.HandleGroupMigration(deviceID, channelIP)
+ }
+ }
+ continue
+ }
+ } else {
+ logger.Debugw(ctx, "Channel Removed", log.Fields{"Channel": channel, "Group": grpName})
+ ig.DelIgmpChannel(deviceID, net.ParseIP(channel))
+ if ig.NumDevicesActive() == 0 {
+ GetApplication().DelIgmpGroup(ig)
+ }
+ }
+ }
+ ig.IsGroupStatic = mvp.IsStaticGroup(ig.GroupName)
+ if err := ig.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp group Write to DB failed", log.Fields{"groupName": ig.GroupName})
+ }
+ return true
+ }
+ }
+ logger.Debugw(ctx, "Group Removed", log.Fields{"Channel": ig.GroupAddr, "Group": grpName, "ChannelBasedGroup": ig.IsChannelBasedGroup})
+ ig.DelIgmpGroup()
+ logger.Debugw(ctx, "Removed Igmp Group", log.Fields{"Channel": ig.GroupAddr, "Group": grpName})
+ return true
+ }
+ GetApplication().IgmpGroups.Range(deleteChannelIfRemoved)
+}
+
+// IsChannelPresent to check if channel is present
+func (mvp *MvlanProfile) IsChannelPresent(channelIP net.IP, groupChannelList []string, IsStaticGroup bool) bool {
+ // Only in case of static group, migration need to be supported.
+ // Dynamic to dynamic group migration not supported currently
+ if doesIPMatch(channelIP, groupChannelList) || mvp.isChannelStatic(channelIP) {
+ return true
+ } else if IsStaticGroup {
+ return (mvp.GetMvlanGroup(channelIP) != "")
+ }
+
+ return false
+}
+
+// GetMvlanProfileForMcIP - Get an MVLAN profile for a given MC IP. This is used when an
+// IGMP report is received from the PON port. The MVLAN profile
+// located is used to idnetify the MC VLAN used in upstream for
+// join/leave
+func (va *VoltApplication) GetMvlanProfileForMcIP(profileName string, ip net.IP) (*MvlanProfile, string) {
+ if mvpIntf, ok := va.MvlanProfilesByName.Load(profileName); ok {
+ mvp := mvpIntf.(*MvlanProfile)
+ if grpName := mvp.GetMvlanGroup(ip); grpName != "" {
+ return mvp, grpName
+ }
+ } else {
+ logger.Warnw(ctx, "Mvlan Profile not found for given profile name", log.Fields{"Profile": profileName})
+ }
+ return nil, ""
+}
+
+// GetMvlanGroup to get mvlan group
+func (mvp *MvlanProfile) GetMvlanGroup(ip net.IP) string {
+ //Check for Static Group First
+ if mvp.containsStaticChannels() {
+ grpName := mvp.GetStaticGroupName(ip)
+ if grpName != "" {
+ return grpName
+ }
+ }
+
+ for _, mvg := range mvp.Groups {
+ if mvg.Wildcard {
+ return mvg.Name
+ }
+ if doesIPMatch(ip, mvg.McIPs) {
+ return mvg.Name
+ }
+ }
+ return ""
+}
+
+// IgmpTick for igmp tick info
+func (va *VoltApplication) IgmpTick() {
+ tickCount++
+ if (tickCount % 1000) == 0 {
+ logger.Debugw(ctx, "Time @ Tick", log.Fields{"Tick": tickCount, "Time": time.Now()})
+ }
+ igmptick := func(key interface{}, value interface{}) bool {
+ ig := value.(*IgmpGroup)
+ if ig.NumDevicesActive() != 0 {
+ if tickCount%10 == ig.Hash()%10 {
+ ig.IgmpGroupLock.Lock()
+ ig.Tick()
+ ig.IgmpGroupLock.Unlock()
+ if ig.NumDevicesActive() == 0 {
+ va.DelIgmpGroup(ig)
+ }
+ }
+ }
+ return true
+ }
+ va.IgmpGroups.Range(igmptick)
+}
+
+// Tick to add Tick Task
+func (va *VoltApplication) Tick() {
+ tt := NewTickTask()
+ va.IgmpTasks.AddTask(tt)
+ // va.IgmpTick()
+}
+
+//AddIgmpProfile for addition of IGMP Profile
+func (va *VoltApplication) AddIgmpProfile(igmpProfileConfig *common.IGMPConfig) error {
+ var igmpProfile *IgmpProfile
+
+ if igmpProfileConfig.ProfileID == DefaultIgmpProfID {
+ logger.Info(ctx, "Updating default IGMP profile")
+ return va.UpdateIgmpProfile(igmpProfileConfig)
+ }
+
+ igmpProfile = va.checkIgmpProfileMap(igmpProfileConfig.ProfileID)
+ if igmpProfile == nil {
+ igmpProfile = newIgmpProfile(igmpProfileConfig)
+ } else {
+ logger.Errorw(ctx, "IGMP profile already exists", log.Fields{"IgmpProfile": igmpProfileConfig.ProfileID})
+ return errors.New("IGMP Profile already exists")
+ }
+
+ va.storeIgmpProfileMap(igmpProfileConfig.ProfileID, igmpProfile)
+
+ if err := igmpProfile.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp profile Write to DB failed", log.Fields{"profileID": igmpProfile.ProfileID})
+ }
+
+ return nil
+}
+
+func newIgmpProfile(igmpProfileConfig *common.IGMPConfig) *IgmpProfile {
+ var igmpProfile IgmpProfile
+ igmpProfile.ProfileID = igmpProfileConfig.ProfileID
+ igmpProfile.UnsolicitedTimeOut = uint32(igmpProfileConfig.UnsolicitedTimeOut)
+ igmpProfile.MaxResp = uint32(igmpProfileConfig.MaxResp)
+
+ keepAliveInterval := uint32(igmpProfileConfig.KeepAliveInterval)
+
+ //KeepAliveInterval should have a min of 10 seconds
+ if keepAliveInterval < MinKeepAliveInterval {
+ keepAliveInterval = MinKeepAliveInterval
+ logger.Infow(ctx, "Auto adjust keepAliveInterval - Value < 10", log.Fields{"Received": igmpProfileConfig.KeepAliveInterval, "Configured": keepAliveInterval})
+ }
+ igmpProfile.KeepAliveInterval = keepAliveInterval
+
+ igmpProfile.KeepAliveCount = uint32(igmpProfileConfig.KeepAliveCount)
+ igmpProfile.LastQueryInterval = uint32(igmpProfileConfig.LastQueryInterval)
+ igmpProfile.LastQueryCount = uint32(igmpProfileConfig.LastQueryCount)
+ igmpProfile.FastLeave = *igmpProfileConfig.FastLeave
+ igmpProfile.PeriodicQuery = *igmpProfileConfig.PeriodicQuery
+ igmpProfile.IgmpCos = uint8(igmpProfileConfig.IgmpCos)
+ igmpProfile.WithRAUpLink = *igmpProfileConfig.WithRAUpLink
+ igmpProfile.WithRADownLink = *igmpProfileConfig.WithRADownLink
+
+ if igmpProfileConfig.IgmpVerToServer == "2" || igmpProfileConfig.IgmpVerToServer == "v2" {
+ igmpProfile.IgmpVerToServer = "2"
+ } else {
+ igmpProfile.IgmpVerToServer = "3"
+ }
+ igmpProfile.IgmpSourceIP = net.ParseIP(igmpProfileConfig.IgmpSourceIP)
+
+ return &igmpProfile
+}
+
+// checkIgmpProfileMap to get Igmp Profile. If not found return nil
+func (va *VoltApplication) checkIgmpProfileMap(name string) *IgmpProfile {
+ if igmpProfileIntf, ok := va.IgmpProfilesByName.Load(name); ok {
+ return igmpProfileIntf.(*IgmpProfile)
+ }
+ return nil
+}
+
+// newDefaultIgmpProfile Igmp profiles with default values
+func newDefaultIgmpProfile() *IgmpProfile {
+ return &IgmpProfile{
+ ProfileID: DefaultIgmpProfID,
+ UnsolicitedTimeOut: 60,
+ MaxResp: 10, // seconds
+ KeepAliveInterval: 60, // seconds
+ KeepAliveCount: 3, // TODO - May not be needed
+ LastQueryInterval: 0, // TODO - May not be needed
+ LastQueryCount: 0, // TODO - May not be needed
+ FastLeave: true,
+ PeriodicQuery: false, // TODO - May not be needed
+ IgmpCos: 7, //p-bit value included in the IGMP packet
+ WithRAUpLink: false, // TODO - May not be needed
+ WithRADownLink: false, // TODO - May not be needed
+ IgmpVerToServer: "3",
+ IgmpSourceIP: net.ParseIP("172.27.0.1"), // This will be replaced by configuration
+ }
+}
+
+func (va *VoltApplication) resetIgmpProfileToDefault() {
+ igmpProf := va.getIgmpProfileMap(DefaultIgmpProfID)
+ defIgmpProf := newDefaultIgmpProfile()
+
+ igmpProf.UnsolicitedTimeOut = defIgmpProf.UnsolicitedTimeOut
+ igmpProf.MaxResp = defIgmpProf.MaxResp
+ igmpProf.KeepAliveInterval = defIgmpProf.KeepAliveInterval
+ igmpProf.KeepAliveCount = defIgmpProf.KeepAliveCount
+ igmpProf.LastQueryInterval = defIgmpProf.LastQueryInterval
+ igmpProf.LastQueryCount = defIgmpProf.LastQueryCount
+ igmpProf.FastLeave = defIgmpProf.FastLeave
+ igmpProf.PeriodicQuery = defIgmpProf.PeriodicQuery
+ igmpProf.IgmpCos = defIgmpProf.IgmpCos
+ igmpProf.WithRAUpLink = defIgmpProf.WithRAUpLink
+ igmpProf.WithRADownLink = defIgmpProf.WithRADownLink
+ igmpProf.IgmpVerToServer = defIgmpProf.IgmpVerToServer
+ igmpProf.IgmpSourceIP = defIgmpProf.IgmpSourceIP
+
+ if err := igmpProf.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp profile Write to DB failed", log.Fields{"profileID": igmpProf.ProfileID})
+ }
+}
+
+// getIgmpProfileMap to get Igmp Profile. If not found return default IGMP config
+func (va *VoltApplication) getIgmpProfileMap(name string) *IgmpProfile {
+ if igmpProfileIntf, ok := va.IgmpProfilesByName.Load(name); ok {
+ return igmpProfileIntf.(*IgmpProfile)
+ }
+
+ // There will be always a default igmp profile.
+ defaultIgmpProfileIntf, _ := va.IgmpProfilesByName.Load(DefaultIgmpProfID)
+ return defaultIgmpProfileIntf.(*IgmpProfile)
+}
+
+// storeIgmpProfileMap to store Igmp Profile
+func (va *VoltApplication) storeIgmpProfileMap(name string, igmpProfile *IgmpProfile) {
+ va.IgmpProfilesByName.Store(name, igmpProfile)
+}
+
+// deleteIgmpProfileMap to delete Igmp Profile
+func (va *VoltApplication) deleteIgmpProfileMap(name string) {
+ va.IgmpProfilesByName.Delete(name)
+}
+
+// WriteToDb is utility to write Igmp Config Info to database
+func (igmpProfile *IgmpProfile) WriteToDb() error {
+ igmpProfile.Version = database.PresentVersionMap[database.IgmpProfPath]
+ b, err := json.Marshal(igmpProfile)
+ if err != nil {
+ return err
+ }
+ if err1 := db.PutIgmpProfile(igmpProfile.ProfileID, string(b)); err1 != nil {
+ return err1
+ }
+ return nil
+}
+
+//DelIgmpProfile for addition of IGMP Profile
+func (va *VoltApplication) DelIgmpProfile(igmpProfileConfig *common.IGMPConfig) error {
+ // Deletion of default igmp profile is blocked from submgr. Keeping additional check for safety.
+ if igmpProfileConfig.ProfileID == DefaultIgmpProfID {
+ logger.Info(ctx, "Resetting default IGMP profile")
+ va.resetIgmpProfileToDefault()
+ return nil
+ }
+ igmpProfile := va.checkIgmpProfileMap(igmpProfileConfig.ProfileID)
+ if igmpProfile == nil {
+ logger.Warnw(ctx, "Igmp Profile not found. Unable to delete", log.Fields{"Profile ID": igmpProfileConfig.ProfileID})
+ return nil
+ }
+
+ va.deleteIgmpProfileMap(igmpProfileConfig.ProfileID)
+
+ _ = db.DelIgmpProfile(igmpProfileConfig.ProfileID)
+
+ return nil
+}
+
+//UpdateIgmpProfile for addition of IGMP Profile
+func (va *VoltApplication) UpdateIgmpProfile(igmpProfileConfig *common.IGMPConfig) error {
+ igmpProfile := va.checkIgmpProfileMap(igmpProfileConfig.ProfileID)
+ if igmpProfile == nil {
+ logger.Errorw(ctx, "Igmp Profile not found. Unable to update", log.Fields{"Profile ID": igmpProfileConfig.ProfileID})
+ return errors.New("IGMP Profile not found")
+ }
+
+ igmpProfile.ProfileID = igmpProfileConfig.ProfileID
+ igmpProfile.UnsolicitedTimeOut = uint32(igmpProfileConfig.UnsolicitedTimeOut)
+ igmpProfile.MaxResp = uint32(igmpProfileConfig.MaxResp)
+
+ keepAliveInterval := uint32(igmpProfileConfig.KeepAliveInterval)
+
+ //KeepAliveInterval should have a min of 10 seconds
+ if keepAliveInterval < MinKeepAliveInterval {
+ keepAliveInterval = MinKeepAliveInterval
+ logger.Infow(ctx, "Auto adjust keepAliveInterval - Value < 10", log.Fields{"Received": igmpProfileConfig.KeepAliveInterval, "Configured": keepAliveInterval})
+ }
+ igmpProfile.KeepAliveInterval = keepAliveInterval
+
+ igmpProfile.KeepAliveCount = uint32(igmpProfileConfig.KeepAliveCount)
+ igmpProfile.LastQueryInterval = uint32(igmpProfileConfig.LastQueryInterval)
+ igmpProfile.LastQueryCount = uint32(igmpProfileConfig.LastQueryCount)
+ igmpProfile.FastLeave = *igmpProfileConfig.FastLeave
+ igmpProfile.PeriodicQuery = *igmpProfileConfig.PeriodicQuery
+ igmpProfile.IgmpCos = uint8(igmpProfileConfig.IgmpCos)
+ igmpProfile.WithRAUpLink = *igmpProfileConfig.WithRAUpLink
+ igmpProfile.WithRADownLink = *igmpProfileConfig.WithRADownLink
+
+ if igmpProfileConfig.IgmpVerToServer == "2" || igmpProfileConfig.IgmpVerToServer == "v2" {
+ igmpProfile.IgmpVerToServer = "2"
+ } else {
+ igmpProfile.IgmpVerToServer = "3"
+ }
+
+ if igmpProfileConfig.IgmpSourceIP != "" {
+ igmpProfile.IgmpSourceIP = net.ParseIP(igmpProfileConfig.IgmpSourceIP)
+ }
+
+ if err := igmpProfile.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp profile Write to DB failed", log.Fields{"profileID": igmpProfile.ProfileID})
+ }
+
+ return nil
+}
+
+// RestoreIGMPProfilesFromDb to read from the DB and restore IGMP Profiles
+func (va *VoltApplication) RestoreIGMPProfilesFromDb() {
+ // Loading IGMP profiles
+ igmpProfiles, _ := db.GetIgmpProfiles()
+ for _, igmpProfile := range igmpProfiles {
+ b, ok := igmpProfile.Value.([]byte)
+ if !ok {
+ logger.Warn(ctx, "The value type is not []byte")
+ continue
+ }
+ var igmpProf IgmpProfile
+ err := json.Unmarshal(b, &igmpProf)
+ if err != nil {
+ logger.Warn(ctx, "Unmarshal of IGMP Profile failed")
+ continue
+ }
+ va.storeIgmpProfileMap(igmpProf.ProfileID, &igmpProf)
+ logger.Infow(ctx, "Restored Igmp Profile", log.Fields{"Conf": igmpProf})
+ }
+}
+
+// InitIgmpSrcMac for initialization of igmp source mac
+func (va *VoltApplication) InitIgmpSrcMac() {
+ srcMac, err := getPodMacAddr()
+ if err != nil {
+ igmpSrcMac = "00:11:11:11:11:11"
+ return
+ }
+ igmpSrcMac = srcMac
+}
+
+// removeIPFromList to remove ip from the list
+func removeIPFromList(s []net.IP, value net.IP) []net.IP {
+ i := 0
+ for i = 0; i < len(s); i++ {
+ if s[i].Equal(value) {
+ break
+ }
+ }
+ if i != len(s) {
+ //It means value is found in the slice
+ return append(s[0:i], s[i+1:]...)
+ }
+ return s
+}
+
+// DelMvlanProfile for deletion of a MVLAN group
+func (va *VoltApplication) DelMvlanProfile(name string) error {
+ if mvpIntf, ok := va.MvlanProfilesByName.Load(name); ok {
+ mvp := mvpIntf.(*MvlanProfile)
+
+ if len(mvp.DevicesList) == 0 {
+ mvp.DeleteInProgress = true
+ mvp.DelFromDb()
+ va.deleteMvlansMap(mvp.Mvlan, name)
+ logger.Debugw(ctx, "Deleted MVLAN Profile", log.Fields{"Name": mvp.Name})
+ } else {
+ logger.Errorw(ctx, "Unable to delete Mvlan Profile as there is still an OLT attached to it", log.Fields{"Name": mvp.Name,
+ "Device List": mvp.DevicesList})
+ return errors.New("MVLAN attached to devices")
+ }
+
+ return nil
+ }
+ logger.Errorw(ctx, "MVLAN Profile not found", log.Fields{"MvlanProfile Name": name})
+ return nil
+}
+
+// ReceiverUpInd for receiver up indication
+func (va *VoltApplication) ReceiverUpInd(device string, port string, mvpName string, vlan of.VlanType, pbits []of.PbitType) {
+ logger.Infow(ctx, "Receiver Indication: UP", log.Fields{"device": device, "port": port, "MVP": mvpName, "vlan": vlan, "pbits": pbits})
+ if mvpIntf, ok := va.MvlanProfilesByName.Load(mvpName); ok {
+ mvp := mvpIntf.(*MvlanProfile)
+ if devIntf, ok := va.DevicesDisc.Load(device); ok {
+ dev := devIntf.(*VoltDevice)
+ proxyCfg, proxyIP, _ := getIgmpProxyCfgAndIP(mvp.Mvlan, dev.SerialNum)
+ for _, pbit := range pbits {
+ sendGeneralQuery(device, port, vlan, uint8(pbit), proxyCfg, proxyIP)
+ }
+ } else {
+ logger.Warnw(ctx, "Device not found for given port", log.Fields{"device": device, "port": port})
+ }
+ } else {
+ logger.Warnw(ctx, "Mvlan Profile not found for given profileName", log.Fields{"MVP": mvpName, "vlan": vlan})
+ }
+}
+
+// sendGeneralQuery to send general query
+func sendGeneralQuery(device string, port string, cVlan of.VlanType, pbit uint8, proxyCfg *IgmpProfile, proxyIP *net.IP) {
+
+ if queryPkt, err := Igmpv2QueryPacket(NullIPAddr, cVlan, *proxyIP, pbit, proxyCfg.MaxResp); err == nil {
+ if err := cntlr.GetController().PacketOutReq(device, port, port, queryPkt, false); err != nil {
+ logger.Warnw(ctx, "General Igmpv2 Query Failed to send", log.Fields{"Device": device, "Port": port, "Packet": queryPkt, "Pbit": pbit})
+ } else {
+ logger.Debugw(ctx, "General Igmpv2 Query Sent", log.Fields{"Device": device, "Port": port, "Packet": queryPkt, "Pbit": pbit})
+ }
+ }
+ if getVersion(proxyCfg.IgmpVerToServer) == IgmpVersion3 {
+ if queryPkt, err := Igmpv3QueryPacket(NullIPAddr, cVlan, *proxyIP, pbit, proxyCfg.MaxResp); err == nil {
+ if err := cntlr.GetController().PacketOutReq(device, port, port, queryPkt, false); err != nil {
+ logger.Warnw(ctx, "General Igmpv3 Query Failed to send", log.Fields{"Device": device, "Port": port, "Packet": queryPkt, "Pbit": pbit})
+ } else {
+ logger.Debugw(ctx, "General Igmpv3 Query Sent", log.Fields{"Device": device, "Port": port, "Packet": queryPkt, "Pbit": pbit})
+ }
+ }
+ }
+}
+
+// ReceiverDownInd to send receiver down indication
+func (va *VoltApplication) ReceiverDownInd(device string, port string) {
+ logger.Infow(ctx, " Receiver Indication: DOWN", log.Fields{"device": device, "port": port})
+
+ ponPortID := va.GetPonPortID(device, port)
+
+ del := func(key interface{}, value interface{}) bool {
+ ig := value.(*IgmpGroup)
+ ig.IgmpGroupLock.Lock()
+ ig.DelReceiveronDownInd(device, port, ponPortID)
+ ig.IgmpGroupLock.Unlock()
+ if ig.NumDevicesActive() == 0 {
+ va.DelIgmpGroup(ig)
+ }
+ return true
+ }
+ va.IgmpGroups.Range(del)
+}
+
+// doesIPMatch to check if ip match with any ip from the list
+func doesIPMatch(ip net.IP, ipsOrRange []string) bool {
+ for _, ipOrRange := range ipsOrRange {
+ if strings.Contains(ipOrRange, "-") {
+ var splits = strings.Split(ipOrRange, "-")
+ ipStart := util.IP2LongConv(net.ParseIP(splits[0]))
+ ipEnd := util.IP2LongConv(net.ParseIP(splits[1]))
+ if ipEnd < ipStart {
+ return false
+ }
+ ipInt := util.IP2LongConv(ip)
+ if ipInt >= ipStart && ipInt <= ipEnd {
+ return true
+ }
+ } else if ip.Equal(net.ParseIP(ipOrRange)) {
+ return true
+ }
+ }
+ return false
+}
+
+// ProcessStaticGroup - Process Static Join/Leave Req for static channels
+func (mvp *MvlanProfile) ProcessStaticGroup(device string, groupAddresses []net.IP, isJoin bool) {
+
+ logger.Debugw(ctx, "Received Static Group Request", log.Fields{"Device": device, "Join": isJoin, "Group Address List": groupAddresses})
+
+ mvlan := mvp.Mvlan
+ va := GetApplication()
+
+ //TODO - Handle bulk add of groupAddr
+ for _, groupAddr := range groupAddresses {
+
+ ig := mvp.GetStaticIgmpGroup(groupAddr)
+ if isJoin {
+ vd := va.GetDevice(device)
+ igmpProf, _, _ := getIgmpProxyCfgAndIP(mvlan, vd.SerialNum)
+ ver := igmpProf.IgmpVerToServer
+
+ if ig == nil {
+ // First time group Creation: Create the IGMP group and then add the receiver to the group
+ logger.Infow(ctx, "Static IGMP Add received for new group", log.Fields{"Addr": groupAddr, "Port": StaticPort})
+ if ig := GetApplication().AddIgmpGroup(mvp.Name, groupAddr, device); ig != nil {
+ ig.IgmpGroupLock.Lock()
+ ig.AddReceiver(device, StaticPort, groupAddr, nil, getVersion(ver),
+ 0, 0, 0xFF)
+ ig.IgmpGroupLock.Unlock()
+ } else {
+ logger.Warnw(ctx, "Static IGMP Group Creation Failed", log.Fields{"Addr": groupAddr})
+ }
+ } else {
+ //Converting existing dynamic group to static group
+ if !mvp.IsStaticGroup(ig.GroupName) {
+ ig.updateGroupName(ig.GroupName)
+ }
+ // Update case: If the IGMP group is already created. just add the receiver
+ logger.Infow(ctx, "Static IGMP Add received for existing group", log.Fields{"Addr": groupAddr, "Port": StaticPort})
+ ig.IgmpGroupLock.Lock()
+ ig.AddReceiver(device, StaticPort, groupAddr, nil, getVersion(ver),
+ 0, 0, 0xFF)
+ ig.IgmpGroupLock.Unlock()
+ }
+ } else if ig != nil {
+ logger.Infow(ctx, "Static IGMP Del received for existing group", log.Fields{"Addr": groupAddr, "Port": StaticPort})
+
+ if ig.IsChannelBasedGroup {
+ grpName := mvp.GetMvlanGroup(ig.GroupAddr)
+ if grpName != "" {
+ ig.IgmpGroupLock.Lock()
+ ig.DelReceiver(device, StaticPort, groupAddr, nil, 0xFF)
+ ig.IgmpGroupLock.Unlock()
+ ig.updateGroupName(grpName)
+ } else {
+ ig.DelIgmpGroup()
+ }
+ } else {
+ ig.IgmpGroupLock.Lock()
+ ig.DelReceiver(device, StaticPort, groupAddr, nil, 0xFF)
+ ig.IgmpGroupLock.Unlock()
+ }
+ if ig.NumDevicesActive() == 0 {
+ GetApplication().DelIgmpGroup(ig)
+ }
+ } else {
+ logger.Warnw(ctx, "Static IGMP Del received for unknown group", log.Fields{"Addr": groupAddr})
+ }
+ }
+}
+
+//getStaticChannelDiff - return the static channel newly added and removed from existing static group
+func (mvp *MvlanProfile) getStaticChannelDiff() (newlyAdded []net.IP, removed []net.IP, common []net.IP) {
+
+ var commonChannels []net.IP
+ newChannelList, _ := mvp.getAllStaticChannels()
+ existingChannelList, _ := mvp.getAllOldGroupStaticChannels()
+ if len(existingChannelList) == 0 {
+ return newChannelList, []net.IP{}, []net.IP{}
+ }
+ for _, newChannel := range append([]net.IP{}, newChannelList...) {
+ for _, existChannel := range append([]net.IP{}, existingChannelList...) {
+
+ //Remove common channels between existing and new list
+ // The remaining in the below slices give the results
+ // Remaining in newChannelList: Newly added
+ // Remaining in existingChannelList: Removed channels
+ if existChannel.Equal(newChannel) {
+ existingChannelList = removeIPFromList(existingChannelList, existChannel)
+ newChannelList = removeIPFromList(newChannelList, newChannel)
+ commonChannels = append(commonChannels, newChannel)
+ logger.Infow(ctx, "#############Channel: "+existChannel.String()+" New: "+newChannel.String(), log.Fields{"Added": newChannelList, "Removed": existingChannelList})
+ break
+ }
+ }
+ }
+ return newChannelList, existingChannelList, commonChannels
+}
+
+//getGroupChannelDiff - return the channel newly added and removed from existing group
+func (mvp *MvlanProfile) getGroupChannelDiff(newGroup *MvlanGroup, oldGroup *MvlanGroup) (newlyAdded []net.IP, removed []net.IP, common []net.IP) {
+
+ var commonChannels []net.IP
+ newChannelList, _ := newGroup.getAllChannels()
+ existingChannelList, _ := oldGroup.getAllChannels()
+ if len(existingChannelList) == 0 {
+ return newChannelList, []net.IP{}, []net.IP{}
+ }
+ for _, newChannel := range append([]net.IP{}, newChannelList...) {
+ for _, existChannel := range append([]net.IP{}, existingChannelList...) {
+
+ //Remove common channels between existing and new list
+ // The remaining in the below slices give the results
+ // Remaining in newChannelList: Newly added
+ // Remaining in existingChannelList: Removed channels
+ if existChannel.Equal(newChannel) {
+ existingChannelList = removeIPFromList(existingChannelList, existChannel)
+ newChannelList = removeIPFromList(newChannelList, newChannel)
+ commonChannels = append(commonChannels, newChannel)
+ logger.Infow(ctx, "#############Channel: "+existChannel.String()+" New: "+newChannel.String(), log.Fields{"Added": newChannelList, "Removed": existingChannelList})
+ break
+ }
+ }
+ }
+ return newChannelList, existingChannelList, commonChannels
+}
+
+// UpdateProfile - Updates the group & member info w.r.t the mvlan profile for the given device
+func (mvp *MvlanProfile) UpdateProfile(deviceID string) {
+ logger.Infow(ctx, "Update Mvlan Profile task triggered", log.Fields{"Mvlan": mvp.Mvlan})
+ var removedStaticChannels []net.IP
+ addedStaticChannels := []net.IP{}
+ /* Taking mvpLock to protect the mvp groups and proxy */
+ mvp.mvpLock.RLock()
+ defer mvp.mvpLock.RUnlock()
+
+ serialNo := ""
+ if deviceID != "" {
+ if vd := GetApplication().GetDevice(deviceID); vd != nil {
+ serialNo = vd.SerialNum
+ if mvp.DevicesList[serialNo] != UpdateInProgress {
+ logger.Warnw(ctx, "Exiting Update Task since device not present in MvlanProfile", log.Fields{"Device": deviceID, "SerialNum": vd.SerialNum, "MvlanProfile": mvp})
+ return
+ }
+ } else {
+ logger.Errorw(ctx, "Volt Device not found. Stopping Update Mvlan Profile processing for device", log.Fields{"SerialNo": deviceID, "MvlanProfile": mvp})
+ return
+ }
+ }
+
+ //Update the groups based on static channels added & removed
+ if mvp.containsStaticChannels() {
+ addedStaticChannels, removedStaticChannels, _ = mvp.getStaticChannelDiff()
+ logger.Debugw(ctx, "Update Task - Static Group Changes", log.Fields{"Added": addedStaticChannels, "Removed": removedStaticChannels})
+
+ if len(addedStaticChannels) > 0 || len(removedStaticChannels) > 0 {
+ mvp.updateStaticGroups(deviceID, []net.IP{}, removedStaticChannels)
+ }
+ }
+ mvp.GroupsUpdated(deviceID)
+ if len(addedStaticChannels) > 0 {
+ mvp.updateStaticGroups(deviceID, addedStaticChannels, []net.IP{})
+ }
+
+ /* Need to handle if SSM params are modified for groups */
+ for key := range mvp.Groups {
+ _, _, commonChannels := mvp.getGroupChannelDiff(mvp.Groups[key], mvp.oldGroups[key])
+ if mvp.checkStaticGrpSSMProxyDiff(mvp.oldProxy[key], mvp.Proxy[key]) {
+ if mvp.Groups[key].IsStatic {
+ /* Static group proxy modified, need to trigger membership report with new mode/src-list for existing channels */
+ mvp.updateStaticGroups(deviceID, commonChannels, []net.IP{})
+ } else {
+ /* Dynamic group proxy modified, need to trigger membership report with new mode/src-list for existing channels */
+ mvp.updateDynamicGroups(deviceID, commonChannels, []net.IP{})
+ }
+ }
+ }
+
+ mvp.SetUpdateStatus(serialNo, NoOp)
+
+ if deviceID == "" || !mvp.isUpdateInProgress() {
+ mvp.oldGroups = nil
+ }
+ if err := mvp.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Mvlan profile write to DB failed", log.Fields{"ProfileName": mvp.Name})
+ }
+ logger.Debugw(ctx, "Updated MVLAN Profile", log.Fields{"VLAN": mvp.Mvlan, "Name": mvp.Name, "Grp IPs": mvp.Groups})
+}
+
+//checkStaticGrpSSMProxyDiff- return true if the proxy of oldGroup is modified in newGroup
+func (mvp *MvlanProfile) checkStaticGrpSSMProxyDiff(oldProxy *MCGroupProxy, newProxy *MCGroupProxy) bool {
+
+ if oldProxy == nil && newProxy == nil {
+ return false
+ }
+ if (oldProxy == nil && newProxy != nil) ||
+ (oldProxy != nil && newProxy == nil) {
+ return true
+ }
+
+ if oldProxy.Mode != newProxy.Mode {
+ return true
+ }
+
+ oldSrcLst := oldProxy.SourceList
+ newSrcLst := newProxy.SourceList
+ oLen := len(oldSrcLst)
+ nLen := len(newSrcLst)
+ if oLen != nLen {
+ return true
+ }
+
+ visited := make([]bool, nLen)
+
+ /* check if any new IPs added in the src list, return true if present */
+ for i := 0; i < nLen; i++ {
+ found := false
+ element := newSrcLst[i]
+ for j := 0; j < oLen; j++ {
+ if visited[j] {
+ continue
+ }
+ if element.Equal(oldSrcLst[j]) {
+ visited[j] = true
+ found = true
+ break
+ }
+ }
+ if !found {
+ return true
+ }
+ }
+
+ visited = make([]bool, nLen)
+ /* check if any IPs removed from existing src list, return true if removed */
+ for i := 0; i < oLen; i++ {
+ found := false
+ element := oldSrcLst[i]
+ for j := 0; j < nLen; j++ {
+ if visited[j] {
+ continue
+ }
+ if element.Equal(newSrcLst[j]) {
+ visited[j] = true
+ found = true
+ break
+ }
+ }
+ if !found {
+ return true
+ }
+ }
+ return false
+}
+
+// ProcessMode process the received mode and updated the igp
+func (igc *IgmpGroupChannel) ProcessMode(port string, incl bool) {
+ /* Update the mode in igp if the mode has changed */
+ igp := igc.GetReceiver(port)
+ if igp.Exclude && incl {
+ igp.Exclude = !incl
+ if igc.Exclude > 0 {
+ igc.Exclude--
+ }
+ } else if !incl && !igp.Exclude {
+ igp.Exclude = !incl
+ igc.Exclude++
+ }
+}
+
+func (ig *IgmpGroup) removeExpiredGroupFromDevice() {
+ ig.PendingPoolLock.Lock()
+ defer ig.PendingPoolLock.Unlock()
+
+ for device, timer := range ig.PendingGroupForDevice {
+
+ // To ensure no race-condition between the expiry time and the new Join,
+ // ensure the group exists in pending pool before deletion
+ groupExistsInPendingPool := true
+
+ if !time.Now().After(timer) {
+ continue
+ }
+
+ // Check if the IgmpGroup obj has no active member across any device
+ // If Yes, then this group is part of global pending pool (IgmpPendingPool), hence if expired,
+ // Remove only the IgmpGroup obj referenced to this device from global pool also.
+ if ig.NumDevicesActive() == 0 {
+ groupExistsInPendingPool = GetApplication().RemoveGroupFromPendingPool(device, ig)
+ }
+
+ // Remove the group entry from device and remove the IgmpDev Obj
+ // from IgmpGrp Pending pool
+ if groupExistsInPendingPool {
+ ig.DeleteIgmpGroupDevice(device)
+ }
+ }
+}
+
+//DeleteIgmpGroupDevice - removes the IgmpGroupDevice obj from IgmpGroup and database
+func (ig *IgmpGroup) DeleteIgmpGroupDevice(device string) {
+
+ logger.Infow(ctx, "Deleting IgmpGroupDevice from IG Pending Pool", log.Fields{"Device": device, "GroupID": ig.GroupID, "GroupName": ig.GroupName, "GroupAddr": ig.GroupAddr.String(), "PendingDevices": len(ig.Devices)})
+
+ igd := ig.Devices[device]
+ igd.DelMcGroup(true)
+ delete(ig.Devices, device)
+ delete(ig.PendingGroupForDevice, device)
+ _ = db.DelIgmpDevice(igd.Mvlan, igd.GroupName, igd.GroupAddr, igd.Device)
+
+ //If the group is not associated to any other device, then the entire Igmp Group obj itself can be removed
+ if ig.NumDevicesAll() == 0 {
+ logger.Infow(ctx, "Deleting IgmpGroup as all pending groups has expired", log.Fields{"Device": device, "GroupID": ig.GroupID, "GroupName": ig.GroupName, "GroupAddr": ig.GroupAddr.String(), "PendingDevices": len(ig.Devices)})
+ GetApplication().DelIgmpGroup(ig)
+ return
+ }
+ if err := ig.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp group Write to DB failed", log.Fields{"groupName": ig.GroupName})
+ }
+}
+
+//UpdateActiveChannelSubscriberAlarm - Updates the Active Channel Subscriber Alarm
+func (mvp *MvlanProfile) UpdateActiveChannelSubscriberAlarm() {
+ va := GetApplication()
+ logger.Debugw(ctx, "Update of Active Channel Subscriber Alarm", log.Fields{"Mvlan": mvp.Mvlan})
+ for srNo := range mvp.DevicesList {
+ d := va.GetDeviceBySerialNo(srNo)
+ if d == nil {
+ logger.Warnw(ctx, "Device info not found", log.Fields{"Device_SrNo": srNo, "Mvlan": mvp.Mvlan})
+ return
+ }
+ d.Ports.Range(func(key, value interface{}) bool {
+ //port := key.(string)
+ vp := value.(*VoltPort)
+ if vp.Type != VoltPortTypeAccess {
+ return true
+ }
+ if mvp.MaxActiveChannels > vp.ActiveChannels && vp.ChannelPerSubAlarmRaised {
+ serviceName := GetMcastServiceForSubAlarm(vp, mvp)
+ logger.Debugw(ctx, "Clearing-SendActiveChannelPerSubscriberAlarm-due-to-update", log.Fields{"ActiveChannels": vp.ActiveChannels, "ServiceName": serviceName})
+ vp.ChannelPerSubAlarmRaised = false
+ } else if mvp.MaxActiveChannels < vp.ActiveChannels && !vp.ChannelPerSubAlarmRaised {
+ /* When the max active channel count is reduced via update, we raise an alarm.
+ But the previous excess channels still exist until a leave or expiry */
+ serviceName := GetMcastServiceForSubAlarm(vp, mvp)
+ logger.Debugw(ctx, "Raising-SendActiveChannelPerSubscriberAlarm-due-to-update", log.Fields{"ActiveChannels": vp.ActiveChannels, "ServiceName": serviceName})
+ vp.ChannelPerSubAlarmRaised = true
+ }
+ return true
+ })
+ }
+}
+
+//TriggerAssociatedFlowDelete - Re-trigger delete for pending delete flows
+func (mvp *MvlanProfile) TriggerAssociatedFlowDelete(device string) bool {
+ mvp.mvpFlowLock.Lock()
+
+ cookieList := []uint64{}
+ flowMap := mvp.PendingDeleteFlow[device]
+
+ for cookie := range flowMap {
+ cookieList = append(cookieList, convertToUInt64(cookie))
+ }
+ mvp.mvpFlowLock.Unlock()
+
+ if len(cookieList) == 0 {
+ return false
+ }
+
+ for _, cookie := range cookieList {
+ if vd := GetApplication().GetDevice(device); vd != nil {
+ flow := &of.VoltFlow{}
+ flow.SubFlows = make(map[uint64]*of.VoltSubFlow)
+ subFlow := of.NewVoltSubFlow()
+ subFlow.Cookie = cookie
+ flow.SubFlows[cookie] = subFlow
+ logger.Infow(ctx, "Retriggering Vnet Delete Flow", log.Fields{"Device": device, "Mvlan": mvp.Mvlan.String(), "Cookie": cookie})
+ err := mvp.DelFlows(vd, flow)
+ if err != nil {
+ logger.Warnw(ctx, "De-Configuring IGMP Flow for device failed ", log.Fields{"Device": device, "err": err})
+ }
+ }
+ }
+ return true
+}
diff --git a/internal/pkg/application/igmptasks.go b/internal/pkg/application/igmptasks.go
new file mode 100644
index 0000000..af42562
--- /dev/null
+++ b/internal/pkg/application/igmptasks.go
@@ -0,0 +1,175 @@
+/*
+* Copyright 2022-present Open Networking Foundation
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+ */
+
+package application
+
+import (
+ "context"
+ "time"
+
+ "github.com/google/gopacket"
+)
+
+// ------------------------------------------------------------------
+// ********** Tasks
+//
+// IGMP related tasks which essentially process packets and the ticks
+// This is to serailize access to data structures and this also limits
+// the amount of CPU consumed. We can bring more capacity by running
+// more groups in parallel as we need to add parallelism
+
+// -----------------------------------------------------------------
+// ** Timer Task **
+//
+// Timer processing - Tick is a poke that the IGMP processing receives
+// from the timer thread. The entire IGMP processing receives a single
+// tick.
+
+// TickTask structure
+type TickTask struct {
+ ctx context.Context
+ taskID uint8
+ ts string
+}
+
+// NewTickTask is constructor for TickTask
+func NewTickTask() *TickTask {
+ return &TickTask{}
+}
+
+// Name to return the name of the task
+func (tt *TickTask) Name() string {
+ return "Process Tick"
+}
+
+// TaskID to return the task id
+func (tt *TickTask) TaskID() uint8 {
+ return tt.taskID
+}
+
+// Timestamp to return the timestamp of task
+func (tt *TickTask) Timestamp() string {
+ return tt.ts
+}
+
+// Stop to stop the task
+func (tt *TickTask) Stop() {
+}
+
+// Start to start the task
+func (tt *TickTask) Start(ctx context.Context, taskID uint8) error {
+ tt.taskID = taskID
+ tt.ctx = ctx
+ GetApplication().IgmpTick()
+ return nil
+}
+
+// ---------------------------------------------------------------
+// ** Packet processing Task **
+//
+//
+
+// IgmpPacketTask structure
+type IgmpPacketTask struct {
+ ctx context.Context
+ taskID uint8
+ Device string
+ Port string
+ Pkt gopacket.Packet
+ ts string
+}
+
+// NewIgmpPacketTask is the constructor for IgmpPacketTask
+func NewIgmpPacketTask(device string, port string, pkt gopacket.Packet) *IgmpPacketTask {
+ var pt IgmpPacketTask
+ pt.Device = device
+ pt.Port = port
+ pt.Pkt = pkt
+ pt.ts = (time.Now()).Format(time.RFC3339Nano)
+ return &pt
+}
+
+// Name to return name of the task
+func (pt *IgmpPacketTask) Name() string {
+ return "Igmp Packet Task"
+}
+
+// TaskID to return the task id
+func (pt *IgmpPacketTask) TaskID() uint8 {
+ return pt.taskID
+}
+
+// Timestamp to return the timestamp for the task
+func (pt *IgmpPacketTask) Timestamp() string {
+ return pt.ts
+}
+
+// Stop to stop the task
+func (pt *IgmpPacketTask) Stop() {
+}
+
+// Start to start the task
+func (pt *IgmpPacketTask) Start(ctx context.Context, taskID uint8) error {
+ pt.taskID = taskID
+ pt.ctx = ctx
+ GetApplication().IgmpProcessPkt(pt.Device, pt.Port, pt.Pkt)
+ return nil
+}
+
+// UpdateMvlanTask structure
+type UpdateMvlanTask struct {
+ ctx context.Context
+ taskID uint8
+ DeviceID string
+ mvp *MvlanProfile
+ ts string
+}
+
+// NewUpdateMvlanTask is the constructor for UpdateMvlanTask
+func NewUpdateMvlanTask(mvp *MvlanProfile, deviceID string) *UpdateMvlanTask {
+ var mt UpdateMvlanTask
+ mt.mvp = mvp
+ mt.DeviceID = deviceID
+ mt.ts = (time.Now()).Format(time.RFC3339Nano)
+ return &mt
+}
+
+// Name to retun the name of the task
+func (mt *UpdateMvlanTask) Name() string {
+ return "Update Mvlan Task"
+}
+
+// TaskID to return the task id of the task
+func (mt *UpdateMvlanTask) TaskID() uint8 {
+ return mt.taskID
+}
+
+// Timestamp to return the timestamp of the task
+func (mt *UpdateMvlanTask) Timestamp() string {
+ return mt.ts
+}
+
+// Stop to stop the task
+func (mt *UpdateMvlanTask) Stop() {
+}
+
+// Start to start the task
+func (mt *UpdateMvlanTask) Start(ctx context.Context, taskID uint8) error {
+ mt.taskID = taskID
+ mt.ctx = ctx
+ mvp := mt.mvp
+ mvp.UpdateProfile(mt.DeviceID)
+ return nil
+}
diff --git a/internal/pkg/application/major_upgrade.go b/internal/pkg/application/major_upgrade.go
new file mode 100644
index 0000000..13c3762
--- /dev/null
+++ b/internal/pkg/application/major_upgrade.go
@@ -0,0 +1,618 @@
+/*
+* Copyright 2022-present Open Networking Foundation
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+ */
+
+package application
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "voltha-go-controller/internal/pkg/types"
+ "sync"
+
+ "github.com/google/gopacket/layers"
+
+ "voltha-go-controller/database"
+ "github.com/opencord/voltha-lib-go/v7/pkg/log"
+)
+
+const (
+ //MigrationComplete Represents the Migration Complete
+ MigrationComplete = "Completed"
+ //MigrationInProgress Represents the Migration Inprogress
+ MigrationInProgress = "InProgress"
+ //MigrationFailed Represents the Migration Failed
+ MigrationFailed = "Failed"
+ // StatusNone for no operations
+ StatusNone = "NONE"
+ //ModuleToBeDeleted - module where old version is deleted
+ ModuleToBeDeleted = "ModuleToBeDeleted"
+)
+
+//DataMigration represents the Verison and Status info for Major Version Upgrade.
+type DataMigration struct {
+ Version string
+ Status string
+ ModuleVer map[string]string // eg. "service": "v1"
+}
+
+type paramsMigrationFunc func([]byte) string
+
+//map to store conversion functions
+var migrationMap = map[string]paramsMigrationFunc{
+ database.ServicePath: MigrateServices,
+ database.DevicePath: MigrateDevices,
+ database.DevicePortPath: MigrateDevicePorts,
+ database.DeviceFlowPath: MigrateDeviceFlows,
+ database.DeviceGroupPath: MigrateDeviceGroups,
+ database.DeviceMeterPath: MigrateDeviceMeters,
+ database.VnetPath: MigrateVnets,
+ database.VpvPath: MigrateVpvs,
+ database.MvlanPath: MigrateMvlans,
+ database.MeterPath: MigrateMeters,
+ database.IgmpConfPath: MigrateIgmpConfs,
+ database.IgmpGroupPath: MigrateIgmpGroups,
+ database.IgmpDevicePath: MigrateIgmpDevices,
+ database.IgmpChannelPath: MigrateIgmpChannels,
+ database.IgmpPortPath: MigrateIgmpPorts,
+ database.IgmpProfPath: MigrateIgmpProfs,
+ database.McastConfigPath: MigrateMcastConfs,
+ database.LogLevelPath: MigrateLogLevels,
+ database.HealthPath: MigrateHealth,
+ database.PonCounterPath: MigratePonCounters,
+ database.ChannelCounterPath: MigrateChannelCounters,
+ database.ServiceCounterPath: MigrateServiceCounters,
+ database.NbDevicePath: MigrateNbDevices,
+ database.DeviceFlowHashPath: MigrateDeviceFlowHash,
+}
+
+// WriteToDb write a meter profile to DB
+func (md *DataMigration) WriteToDb() error {
+ b, err := json.Marshal(md)
+ if err != nil {
+ return err
+ }
+ if err1 := db.PutMigrationInfo(string(b)); err1 != nil {
+ return err1
+ }
+ return nil
+}
+
+// DelFromDb delete a meter profile from DB
+func (md *DataMigration) DelFromDb() {
+ if err := db.DelMigrationInfo(); err != nil {
+ logger.Warnw(ctx, "DelMigrationInfo Failed", log.Fields{"Error": err})
+ }
+}
+
+// GetMigrationInfo to get data migration info
+func GetMigrationInfo(dmInfo *DataMigration) error {
+ var migrationInfo string
+ var err error
+ if db == nil {
+ db = database.GetDatabase()
+ }
+ if migrationInfo, err = db.GetMigrationInfo(); err != nil {
+ return err
+ }
+ err = json.Unmarshal([]byte(migrationInfo), &dmInfo)
+ if err != nil {
+ logger.Warn(ctx, "Unmarshal of migrationinfo failed")
+ return err
+ }
+ return nil
+}
+
+// CheckIfMigrationRequired Checks if Migration is Completed
+// Only Data Migration and Reboot would be handled in the Below function
+// When Roll back happens just Delete of DB keys has to happen
+// which will be done once delete key request is received from MSM
+func CheckIfMigrationRequired(ctx context.Context) bool {
+ Migrate := new(DataMigration)
+ var NoDataInDB bool
+ err := GetMigrationInfo(Migrate)
+ logger.Debug(ctx, "Migration data", log.Fields{"DataMigration": Migrate})
+ // No DB entry represents N verison Bring Up for the First time
+ if err != nil {
+ NoDataInDB = true
+ logger.Error(ctx, "Failed to read the Migration Data from DB ")
+ }
+ // Covers N verison bringup and Reboot Senarios
+ if NoDataInDB {
+ logger.Info(ctx, "Data Migration Not Required")
+ Migrate.Version = database.PresentVersion
+ Migrate.Status = MigrationComplete
+ Migrate.ModuleVer = database.PresentVersionMap
+ if err := Migrate.WriteToDb(); err != nil {
+ logger.Error(ctx, "DB Write failed for Migration Path", log.Fields{"error": err})
+ }
+ //MigrateProbestatus has to be Updated to Complete when No Migration is Required
+ logger.Debug(ctx, "Migration Probe Status", log.Fields{"Migration Probe": Migrate.Status})
+ //probe.UpdateDBMigrationStatus(ctx, true)
+ return false
+ // Migration required when vgc moves to Higher Versions
+ } else if Migrate.ModuleVer == nil {
+ // This case will hit when DataMigration is present with old schema
+ // and DataMigration schema has changed.
+ // In this case compare previous and current version configured in the models.
+ for key, currVer := range database.PresentVersionMap {
+ if currVer > database.PreviousVersionMap[key] {
+ logger.Infow(ctx, "DB Migration needed for", log.Fields{"comp": key})
+ return true
+ }
+ }
+ } else {
+ var isVersionChanged bool
+ // Compare the current version with previous version present in DB.
+ // This case will also hit in case of POD restart.
+ for key, currVer := range database.PresentVersionMap {
+ if dbVer := Migrate.ModuleVer[key]; dbVer != "" {
+ if currVer > dbVer {
+ logger.Infow(ctx, "DB Migration needed for", log.Fields{"comp": key})
+ isVersionChanged = true
+ }
+ }
+ }
+ database.DBVersionMap = Migrate.ModuleVer // Store DB data
+
+ if isVersionChanged {
+ return true
+ }
+ }
+
+ // In case Service Reboots/Rolls Back then Probe Success to MSM
+ logger.Debug(ctx, "Migration Probe Status", log.Fields{"Migration Probe": Migrate.Status})
+ //probe.UpdateDBMigrationStatus(ctx, true)
+ return false
+}
+
+// InitiateDataMigration Migrates the DB data
+// depending on the bool value returned by CheckIfMigrationDone
+func InitiateDataMigration(ctx context.Context) {
+ var err error
+ Migrate := new(DataMigration)
+ var migrationWG sync.WaitGroup
+
+ //Keeping it outside to avoid race condition where the
+ // wait check is reached before the go toutine for data migraiton is triggered
+ migrationWG.Add(1)
+
+ go func() {
+ logger.Debug(ctx, "Started Go Routine for data migration")
+ err = MigrateDBData()
+ if err != nil {
+ logger.Error(ctx, "Failed to Migrate the Data", log.Fields{"error": err})
+ Migrate.Status = MigrationFailed
+ if err := Migrate.WriteToDb(); err != nil {
+ logger.Error(ctx, "DB Write failed to Migration Path", log.Fields{"error": err})
+ }
+ }
+ logger.Debug(ctx, "Completed Go Routine for data migration")
+ migrationWG.Done()
+
+ Migrate.Version = database.PresentVersion
+ Migrate.Status = MigrationInProgress
+ Migrate.ModuleVer = database.PresentVersionMap
+ if err = Migrate.WriteToDb(); err != nil {
+ logger.Error(ctx, "DB Write failed for Migration Path", log.Fields{"error": err})
+ return
+ }
+ }()
+ // Failure Senario can be Exceptions, incase of panic Update the status as failed
+ defer func() {
+ if err := recover(); err != nil {
+ logger.Error(ctx, "Migration failure due to Exception happend", log.Fields{"reason": err})
+ Migrate.Status = MigrationFailed
+ if err := Migrate.WriteToDb(); err != nil {
+ logger.Error(ctx, "DB Write failed for Migration Path", log.Fields{"error": err})
+ }
+ //probe.UpdateDBMigrationStatus(ctx, false)
+ return
+ }
+ }()
+ // Wait for all the Db data migration to complete
+ migrationWG.Wait()
+ //probe.UpdateDBMigrationStatus(ctx, true)
+ Migrate.Status = MigrationComplete
+ if err := Migrate.WriteToDb(); err != nil {
+ logger.Error(ctx, "DB Write failed for Migration Path", log.Fields{"error": err})
+ }
+ logger.Info(ctx, "Migration completed successfully", log.Fields{"Status": Migrate.Status})
+}
+
+// MigrateDBData to migrate database data
+func MigrateDBData() error {
+
+ var err error
+ for module, currentVersion := range database.PresentVersionMap {
+ if currentVersion == database.DBVersionMap[module] {
+ logger.Infow(ctx, "No Data Migration required for module", log.Fields{"Table": module, "Version": currentVersion})
+ continue
+ }
+
+ if _, ok := migrationMap[module]; ok {
+ switch module {
+ case database.DeviceFlowPath,
+ database.DevicePortPath,
+ database.DeviceMeterPath,
+ database.DeviceGroupPath,
+ database.DeviceFlowHashPath:
+ err = FetchAndMigrateDeviceDBData(module)
+ default:
+ err = FetchAndMigrateDBData(module)
+ }
+ } else {
+ logger.Infow(ctx, "No Data Migration handling found for module", log.Fields{"Table": module, "Version": currentVersion})
+ }
+
+ if err != nil {
+ logger.Errorw(ctx, "Error in data migration", log.Fields{"Module": module})
+ return err
+ }
+ }
+ return nil
+}
+
+//FetchAndMigrateDeviceDBData fetchs the data from database and migrte the same to latest versions and store ot back ot database
+func FetchAndMigrateDeviceDBData(module string) error {
+ logger.Error(ctx, "Data Migration not implemented for Device DB Data")
+ return nil
+}
+
+//FetchAndMigrateDBData fetchs the data from database and migrte the same to latest versions and store ot back ot database
+func FetchAndMigrateDBData(module string) error {
+
+ previousPath := database.GetModuleKeypath(module, database.PreviousVersionMap[module])
+ dbPathKeysValueMap, err := db.List(previousPath)
+ if err != nil {
+ logger.Error(ctx, "failed to Fetch the Keys from Redis", log.Fields{"error": err})
+ //No return required, Data might not be present in DB
+ return nil
+ }
+ if len(dbPathKeysValueMap) == 0 {
+ logger.Debug(ctx, "No data present in DB for the path", log.Fields{"dbPath": module})
+ return nil
+ }
+
+ // Fetch each Path from previous version and store to present version after data migration changes
+ for hash, value := range dbPathKeysValueMap {
+ logger.Debug(ctx, "DB path", log.Fields{"hash": hash})
+ //convert the value to a specific type based on the dbPath
+ b, ok := value.Value.([]byte)
+ if !ok {
+ logger.Error(ctx, "The value type is not []byte")
+ return errors.New("Error-in-migration")
+ }
+
+ presentParams := migrationMap[module](b)
+ logger.Infow(ctx, "Migrated data", log.Fields{"presentParams": presentParams})
+ if "" == presentParams {
+ logger.Error(ctx, "Error in migrating data\n")
+ return errors.New("Error-in-migration")
+ } else if ModuleToBeDeleted == presentParams {
+ return nil
+ }
+ presentPath := database.GetKeyPath(module) + hash
+ logger.Infow(ctx, "Before writing to DB", log.Fields{"presentParams": presentParams})
+ if err := db.Put(presentPath, presentParams); err != nil {
+ logger.Error(ctx, "Update Params failed", log.Fields{"key": presentPath, "presentparams": presentParams})
+ return err
+ }
+ }
+ return nil
+}
+
+//MigrateServices modifyies the old data as per current version requirement and updates the database
+func MigrateServices(data []byte) string {
+ var vs VoltService
+ var updatedData, updatedData1 []byte
+ var vsmap map[string]interface{}
+ var err1 error
+
+ err := json.Unmarshal(data, &vsmap)
+ if err != nil {
+ logger.Warn(ctx, "Unmarshal of VPV failed", log.Fields{"error": err})
+ return ""
+ }
+ // changes to handle change in data type of MacLearning parameter
+ if updatedData1, err1 = json.Marshal(&vsmap); err1 != nil {
+ logger.Warnw(ctx, "Marshal of Service failed", log.Fields{"Error": err1.Error()})
+ return ""
+ }
+
+ if err2 := json.Unmarshal(updatedData1, &vs); err != nil {
+ logger.Warnw(ctx, "Unmarshal-failed", log.Fields{"err": err2})
+ return ""
+ }
+
+ if vsmap["MacLearning"] == true {
+ vs.MacLearning = Learn
+
+ }
+
+ //Migration
+ vs.PendingFlows = make(map[string]bool)
+ vs.AssociatedFlows = make(map[string]bool)
+ vs.DeleteInProgress = false
+ vs.PonPort = 0xFF
+ if updatedData, err = json.Marshal(vs); err != nil {
+ logger.Warnw(ctx, "Marshal of Service failed", log.Fields{"Error": err.Error()})
+ return ""
+ }
+ logger.Infow(ctx, "Service Migrated", log.Fields{"Service": vs.Name, "PresentVersion": database.PresentVersionMap[database.ServicePath]})
+ return string(updatedData)
+}
+
+//MigrateDevices modifyies the old data as per current version requirement and updates the database
+func MigrateDevices(data []byte) string {
+ logger.Error(ctx, "Data Migration not implemented for Devices")
+ return ""
+}
+
+//MigrateDevicePorts modifyies the old data as per current version requirement and updates the database
+func MigrateDevicePorts(data []byte) string {
+ logger.Error(ctx, "Data Migration not implemented for Ports")
+ return ""
+}
+
+//MigrateDeviceFlows modifyies the old data as per current version requirement and updates the database
+func MigrateDeviceFlows(data []byte) string {
+ logger.Error(ctx, "Data Migration not implemented for Flows")
+ return ""
+}
+
+//MigrateDeviceGroups modifyies the old data as per current version requirement and updates the database
+func MigrateDeviceGroups(data []byte) string {
+ logger.Error(ctx, "Data Migration not implemented for Groups")
+ return ""
+}
+
+//MigrateDeviceMeters modifyies the old data as per current version requirement and updates the database
+func MigrateDeviceMeters(data []byte) string {
+ logger.Error(ctx, "Data Migration not implemented for Meters")
+ return ""
+}
+
+//MigrateDeviceFlowHash modifyies the old data as per current version requirement and updates the database
+func MigrateDeviceFlowHash(data []byte) string {
+ logger.Error(ctx, "Data Migration not implemented for FlowHash")
+ return ""
+}
+
+//MigrateVnets modifyies the old data as per current version requirement and updates the database
+func MigrateVnets(data []byte) string {
+
+ var vnet VoltVnet
+ var updatedData []byte
+
+ err := json.Unmarshal(data, &vnet)
+ if err != nil {
+ logger.Warn(ctx, "Unmarshal of VNET failed", log.Fields{"error": err})
+ return ""
+ }
+
+ if vnet.SVlanTpid == 0 {
+ vnet.SVlanTpid = layers.EthernetTypeDot1Q
+ }
+ // MacLeanring parameter was not stored in vnets in 2.7 release.
+ if vnet.DhcpRelay || vnet.ArpLearning {
+ vnet.MacLearning = Learn
+ } else if !vnet.DhcpRelay && !vnet.ArpLearning {
+ vnet.MacLearning = MacLearningNone
+ }
+ vnet.PendingDeleteFlow = make(map[string]map[string]bool)
+ vnet.DeleteInProgress = false
+ if updatedData, err = json.Marshal(vnet); err != nil {
+ logger.Warnw(ctx, "Marshal of Vnet failed", log.Fields{"Error": err.Error()})
+ return ""
+ }
+ logger.Infow(ctx, "Vnet Migrated", log.Fields{"Vnet Name": vnet.Name, "PresentVersion": database.PresentVersionMap[database.VnetPath]})
+ return string(updatedData)
+}
+
+//MigrateVpvs modifyies the old data as per current version requirement and updates the database
+func MigrateVpvs(data []byte) string {
+ var vpv VoltPortVnet
+ var updatedData, updatedData1 []byte
+ var vpvmap map[string]interface{}
+ var err1 error
+ var usFlowsApplied, dsFlowsApplied bool
+
+ err := json.Unmarshal(data, &vpvmap)
+ if err != nil {
+ logger.Warn(ctx, "Unmarshal of VPV failed", log.Fields{"error": err})
+ return ""
+ }
+ // changes to handle change in data type of MacLearning parameter
+ if updatedData1, err1 = json.Marshal(&vpvmap); err1 != nil {
+ logger.Warnw(ctx, "Marshal of Service failed", log.Fields{"Error": err1.Error()})
+ return ""
+ }
+
+ if err2 := json.Unmarshal(updatedData1, &vpv); err != nil {
+ logger.Warnw(ctx, "Unmarshal-failed", log.Fields{"err": err2})
+
+ }
+
+ if vpvmap["MacLearning"] == true {
+ vpv.MacLearning = Learn
+
+ }
+ if vpvmap["UsFlowsApplied"] == true {
+ usFlowsApplied = true
+ }
+
+ if vpvmap["DsFlowsApplied"] == true {
+ dsFlowsApplied = true
+ }
+
+ if usFlowsApplied && dsFlowsApplied {
+ vpv.FlowsApplied = true
+ }
+ //Migration
+ if vpv.SVlanTpid == 0 {
+ vpv.SVlanTpid = layers.EthernetTypeDot1Q
+ }
+ vpv.VnetName = VnetKey(vpv.SVlan, vpv.CVlan, vpv.UniVlan)
+ vpv.PendingDeleteFlow = make(map[string]bool)
+ vpv.PonPort = 0xFF
+
+ if updatedData, err = json.Marshal(vpv); err != nil {
+ logger.Warnw(ctx, "Marshal of VPV failed", log.Fields{"Error": err.Error()})
+ return ""
+ }
+ logger.Infow(ctx, "VPV Migrated", log.Fields{"Device": vpv.Device, "port": vpv.Port, "SVlan": vpv.SVlan,
+ "CVlan": vpv.CVlan, "UniVlan": vpv.UniVlan, "PresentVersion": database.PresentVersionMap[database.VpvPath]})
+ return string(updatedData)
+}
+
+//MigrateMvlans modifyies the old data as per current version requirement and updates the database
+func MigrateMvlans(data []byte) string {
+ var mvp MvlanProfile
+ var updatedData []byte
+
+ err := json.Unmarshal(data, &mvp)
+ if err != nil {
+ logger.Warn(ctx, "Unmarshal of VPV failed")
+ return ""
+ }
+ // Mvlan Migration
+ mvp.IgmpServVersion = make(map[string]*uint8)
+ for srNo := range mvp.DevicesList {
+ var servVersion uint8
+ mvp.IgmpServVersion[srNo] = &servVersion
+ }
+
+ if updatedData, err = json.Marshal(mvp); err != nil {
+ logger.Warnw(ctx, "Marshal of Mvlan Profile failed", log.Fields{"Error": err.Error()})
+ return ""
+ }
+ logger.Infow(ctx, "Mvlan Profile Migrated", log.Fields{"MvlanProfileName": mvp.Name, "PresentVersion": database.PresentVersionMap[database.MvlanPath]})
+ return string(updatedData)
+}
+
+//MigrateMeters modifyies the old data as per current version requirement and updates the database
+func MigrateMeters(data []byte) string {
+ logger.Error(ctx, "Data Migration not implemented for Meters")
+ return ""
+}
+
+//MigrateIgmpConfs modifyies the old data as per current version requirement and updates the database
+func MigrateIgmpConfs(data []byte) string {
+ var igmpProfile IgmpProfile
+
+ err := json.Unmarshal(data, &igmpProfile)
+ if err != nil {
+ logger.Warn(ctx, "Unmarshal of IGMP failed")
+ return ""
+ }
+ if err := igmpProfile.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp profile Write to DB failed", log.Fields{"profileID": igmpProfile.ProfileID})
+ }
+
+ logger.Infow(ctx, "Igmp Conf Migrated", log.Fields{"Profile": igmpProfile, "PresentVersion": database.PresentVersionMap[database.VpvPath]})
+ return ModuleToBeDeleted
+}
+
+//MigrateIgmpGroups modifyies the old data as per current version requirement and updates the database
+func MigrateIgmpGroups(data []byte) string {
+ logger.Error(ctx, "Data Migration not implemented for IGMP Groups")
+ return ""
+}
+
+//MigrateIgmpDevices modifyies the old data as per current version requirement and updates the database
+func MigrateIgmpDevices(data []byte) string {
+ logger.Error(ctx, "Data Migration not implemented for IGMP Device")
+ return ""
+}
+
+//MigrateIgmpChannels modifyies the old data as per current version requirement and updates the database
+func MigrateIgmpChannels(data []byte) string {
+ logger.Error(ctx, "Data Migration not implemented for IGMP Channels")
+ return ""
+}
+
+//MigrateIgmpPorts modifyies the old data as per current version requirement and updates the database
+func MigrateIgmpPorts(data []byte) string {
+ logger.Error(ctx, "Data Migration not implemented for IGMP Ports")
+ return ""
+}
+
+//MigrateIgmpProfs modifyies the old data as per current version requirement and updates the database
+func MigrateIgmpProfs(data []byte) string {
+ logger.Error(ctx, "Data Migration not implemented for IGMP Profs")
+ return ""
+}
+
+//MigrateMcastConfs modifyies the old data as per current version requirement and updates the database
+func MigrateMcastConfs(data []byte) string {
+ logger.Error(ctx, "Data Migration not implemented for Mcast Confs")
+ return ""
+}
+
+//MigrateLogLevels modifyies the old data as per current version requirement and updates the database
+func MigrateLogLevels(data []byte) string {
+ logger.Error(ctx, "Data Migration not implemented for Log Levels")
+ return ""
+}
+
+//MigrateHealth modifyies the old data as per current version requirement and updates the database
+func MigrateHealth(data []byte) string {
+ logger.Error(ctx, "Data Migration not implemented for Health")
+ return ""
+}
+
+//MigratePonCounters modifyies the old data as per current version requirement and updates the database
+func MigratePonCounters(data []byte) string {
+ logger.Error(ctx, "Data Migration not implemented for Pon Counters")
+ return ""
+}
+
+//MigrateChannelCounters modifyies the old data as per current version requirement and updates the database
+func MigrateChannelCounters(data []byte) string {
+ logger.Error(ctx, "Data Migration not implemented for Channel Counters")
+ return ""
+}
+
+//MigrateServiceCounters modifyies the old data as per current version requirement and updates the database
+func MigrateServiceCounters(data []byte) string {
+ logger.Error(ctx, "Data Migration not implemented for Service Counters")
+ return ""
+}
+
+//MigrateNbDevices modifyies the old data as per current version requirement and updates the database
+func MigrateNbDevices(data []byte) string {
+ logger.Error(ctx, "Data Migration not implemented for NB Devices")
+ return ""
+}
+
+//MigrateFlowHash modifyies the old data as per current version requirement and updates the database
+func MigrateFlowHash(data []byte) string {
+ logger.Error(ctx, "Data Migration not implemented for FLow Hash")
+ return ""
+}
+
+//DeleteDbPathKeys Deleted the paths from DB
+func DeleteDbPathKeys(keyPath string) error {
+ logger.Debug(ctx, "Deleting paths for version", log.Fields{"Path": keyPath})
+
+ // Delete all the keys
+ err := db.DeleteAll(keyPath)
+ if err != nil && err.Error() != common.ErrEntryNotFound.Error() {
+ logger.Error(ctx, "Delete Key failed", log.Fields{"error": err})
+ return err
+ }
+ return nil
+}
diff --git a/internal/pkg/application/meters.go b/internal/pkg/application/meters.go
new file mode 100644
index 0000000..4b5fd71
--- /dev/null
+++ b/internal/pkg/application/meters.go
@@ -0,0 +1,331 @@
+/*
+* Copyright 2022-present Open Networking Foundation
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+ */
+
+package application
+
+import (
+ "encoding/json"
+ "errors"
+ "sync"
+
+ cntlr "voltha-go-controller/internal/pkg/controller"
+ "voltha-go-controller/database"
+ "voltha-go-controller/internal/pkg/of"
+ "github.com/opencord/voltha-lib-go/v7/pkg/log"
+)
+
+// VoltShaperConfig is shaper profile configuration structure
+type VoltShaperConfig struct {
+ Name string
+ BurstSize uint32
+}
+
+// VoltBwConfig is bandwidth profile configuration structure
+type VoltBwConfig struct {
+ Name string
+ Fir uint32
+ Air uint32
+ Eir uint32
+}
+
+// VoltBandwidthProf is bandwidth profile stored at VGC
+type VoltBandwidthProf struct {
+ VoltBwConfig
+}
+
+// VoltShaperProf is shaper profile stored at VGC
+type VoltShaperProf struct {
+ VoltShaperConfig
+}
+
+// VoltMeterProf is meter profile stored at VGC
+type VoltMeterProf struct {
+ VoltMeter
+}
+
+// MeterMgr structure
+type MeterMgr struct {
+ Meters sync.Map
+ MetersByID sync.Map
+ LastMeterID uint32
+}
+
+// Init to initialize MeterMgr
+func (m *MeterMgr) Init() {
+ m.LastMeterID = 0
+}
+
+// VoltMeter : A VOLT meter is a combination of BW and shaper profiles
+// The ID is generated by the VOLT application
+type VoltMeter struct {
+ Name string
+ ID uint32
+ Fir uint32
+ Air uint32
+ Eir uint32
+ BurstSize uint32
+ AssociatedServices uint32
+ Version string
+ Cir uint32
+ Cbs uint32
+ Pir uint32
+ Pbs uint32
+ Gir uint32
+ Ebs uint32
+}
+
+// WriteToDb to write a meter profile to DB
+func (vm *VoltMeter) WriteToDb() error {
+ vm.Version = database.PresentVersionMap[database.MeterPath]
+ b, err := json.Marshal(vm)
+ if err != nil {
+ return err
+ }
+ if err1 := db.PutMeter(vm.Name, string(b)); err1 != nil {
+ return err1
+ }
+ return nil
+}
+
+// DelFromDb to delete a meter profile from DB
+func (vm *VoltMeter) DelFromDb() {
+ _ = db.DelMeter(vm.Name)
+}
+
+// GetMeterByName to get meter by name
+func (m *MeterMgr) GetMeterByName(name string) (*VoltMeter, bool) {
+ meter, ok := m.Meters.Load(name)
+ logger.Infow(ctx, "Meter Obtained Name", log.Fields{"Meter": meter})
+ if ok {
+ return meter.(*VoltMeter), ok
+ }
+ return nil, ok
+}
+
+// GetMeterByID to get meter by ID
+func (m *MeterMgr) GetMeterByID(id uint32) (*VoltMeter, bool) {
+ meter, ok := m.MetersByID.Load(id)
+ logger.Infow(ctx, "Meter Obtained ID", log.Fields{"Meter": meter})
+ if ok {
+ return meter.(*VoltMeter), ok
+ }
+ return nil, ok
+}
+
+// AddMeter to add meter
+func (m *MeterMgr) AddMeter(meter *VoltMeter) {
+ m.Meters.Store(meter.Name, meter)
+ m.MetersByID.Store(meter.ID, meter)
+ logger.Infow(ctx, "Meter Added/Updated", log.Fields{"Meter": meter, "Name": meter.Name, "Id": meter.ID})
+}
+
+// DelMeter to delete meter
+func (m *MeterMgr) DelMeter(meter *VoltMeter) {
+ m.Meters.Delete(meter.Name)
+ m.MetersByID.Delete(meter.ID)
+ logger.Infow(ctx, "Meter Deleted", log.Fields{"Meter": meter, "Name": meter.Name, "Id": meter.ID})
+}
+
+// AddToDevice to add meter to the device
+func (vm *VoltMeter) AddToDevice(port string, device string, aggVM *VoltMeter) {
+ logger.Debugw(ctx, "Adding Meter To Device", log.Fields{"Id": vm.ID, "Device": device, "Port": port})
+ meter := of.NewMeter(vm.ID)
+ // meter.AddBand(vm.Air, vm.BurstSize)
+ // meter.AddBand(vm.Eir, vm.BurstSize)
+ // if aggVM != nil {
+ // meter.AddBand(aggVM.Air, aggVM.BurstSize)
+ // meter.AddBand(aggVM.Eir, aggVM.BurstSize)
+ // }
+
+ //Community VGC Impl
+
+ //Set Cir
+ if vm.Cir != 0 {
+ meter.AddBand(vm.Cir, vm.Cbs)
+ }
+
+ //Set Air to 0 if both air & gir are set
+ if vm.Air != 0 && vm.Gir != 0 {
+ vm.Air = 0
+ }
+
+ //Set Pir & Pbs
+ var pir uint32
+ var pbs uint32
+ if vm.Pir != 0 {
+ pir = vm.Pir
+ } else {
+ pir = vm.Eir + vm.Cir + vm.Gir + vm.Air
+ }
+
+ if vm.Pbs != 0 {
+ pbs = vm.Pbs
+ } else {
+ pbs = vm.Ebs + vm.Cbs
+ }
+ meter.AddBand(pir, pbs)
+
+ //Set Gir
+ if vm.Gir != 0 {
+ meter.AddBand(vm.Gir, 0)
+ }
+
+ logger.Infow(ctx, "Meter Config", log.Fields{"Cir": vm.Cir, "Air": vm.Air, "Pir": vm.Pir, "Gir": vm.Gir, "Eir": vm.Eir})
+ logger.Infow(ctx, "Meter Burst Config", log.Fields{"Cbs": vm.Cbs, "Pbs": vm.Pbs})
+ logger.Infow(ctx, "Meter Burst Oper", log.Fields{"Pir": pir, "Pbs": pbs})
+ //Set Air
+ // Air is used in place of Gir only if Gir is
+ // not present and Air is not 0
+ if vm.Air != 0 {
+ meter.AddBand(vm.Air, 0)
+ }
+
+ logger.Debug(ctx, "Total Bands are", log.Fields{"meter": *meter})
+ if err := cntlr.GetController().ModMeter(port, device, of.MeterCommandAdd, meter); err != nil {
+ logger.Warnw(ctx, "Add meter to device Failed", log.Fields{"Id": vm.ID, "meter": *meter, "Error": err})
+ }
+}
+
+// AddMeterToDevice to add meter to the device
+func (m *MeterMgr) AddMeterToDevice(port string, device string, meterID uint32, aggMeterID uint32) {
+ var aggVM *VoltMeter
+ vm, err := m.GetMeterByProfID(meterID)
+ if err == nil {
+ if 0 != aggMeterID { //Assuming valid meter id will never be 0
+ if aggVM, err = m.GetMeterByProfID(aggMeterID); err != nil {
+ logger.Warnw(ctx, "Aggregated Meter not found", log.Fields{"Id": aggMeterID})
+ }
+ }
+ vm.AddToDevice(port, device, aggVM)
+ } else {
+ logger.Warnw(ctx, "Meter not found", log.Fields{"Id": meterID})
+ }
+}
+
+// RestoreMetersFromDb to read from the DB and restore all the services
+func (m *MeterMgr) RestoreMetersFromDb() {
+ // VNETS must be learnt first
+ logger.Infow(ctx, "LastMeterID on restart", log.Fields{"LastMeterID": m.LastMeterID})
+ ms, _ := db.GetMeters()
+ for _, mt := range ms {
+ b, ok := mt.Value.([]byte)
+ if !ok {
+ logger.Warn(ctx, "The value type is not []byte")
+ continue
+ }
+ var meter VoltMeter
+ err := json.Unmarshal(b, &meter)
+ if err != nil {
+ logger.Warn(ctx, "Unmarshal of meter profile failed")
+ continue
+ }
+ logger.Infow(ctx, "Retrieved Meter", log.Fields{"Meter": meter.Name})
+ m.AddMeter(&meter)
+ if meter.ID > m.LastMeterID {
+ m.LastMeterID = meter.ID
+ }
+ }
+ logger.Infow(ctx, "LastMeterID on reading DB", log.Fields{"LastMeterID": m.LastMeterID})
+}
+
+// AddMeterProf to add the meter profile name as key
+func (va *VoltApplication) AddMeterProf(cfg VoltMeter) {
+
+ mm := &va.MeterMgr
+ if _, ok := mm.GetMeterByName(cfg.Name); ok {
+ logger.Warnw(ctx, "Meter profile exists", log.Fields{"Name": cfg.Name})
+ return
+ }
+
+ mm.LastMeterID++
+ //FIX-ME: Hardcoded the meter-id temp till meter delete is introduced
+ //Restriction: Only one meter profile should be used across all services
+ // id := uint32(1) //mm.LastMeterId
+ id := mm.LastMeterID
+ cfg.ID = id
+ mm.AddMeter(&cfg)
+ if err := cfg.WriteToDb(); err != nil {
+ logger.Warnw(ctx, "MeterProf Write to DB Failed", log.Fields{"MeterConfig": cfg, "Error": err})
+ }
+}
+
+// UpdateMeterProf to update the meter profile
+func (va *VoltApplication) UpdateMeterProf(cfg VoltMeter) {
+ mm := &va.MeterMgr
+ if _, ok := mm.GetMeterByName(cfg.Name); !ok {
+ logger.Warnw(ctx, "Meter profile does not exist", log.Fields{"Name": cfg.Name})
+ return
+ }
+ mm.AddMeter(&cfg)
+ if err := cfg.WriteToDb(); err != nil {
+ logger.Warnw(ctx, "MeterProf Write to DB Failed", log.Fields{"MeterConfig": cfg, "Error": err})
+ }
+}
+
+// GetMeterByProfID to get a meter based on the identities of bandwidth profile and shaper
+// profile names.
+func (m *MeterMgr) GetMeterByProfID(id uint32) (*VoltMeter, error) {
+ if mtr, ok := m.GetMeterByID(id); ok {
+ return mtr, nil
+ }
+ return nil, errors.New("Meter Missing")
+}
+
+// GetMeter to get a meter based on the identities of bandwidth profile and shaper
+// profile names.
+func (m *MeterMgr) GetMeter(meterID string) (*VoltMeter, error) {
+ if mt, ok := m.GetMeterByName(meterID); ok {
+ return mt, nil
+ }
+ return nil, errors.New("Meter Missing")
+}
+
+// DeleteFromDevice to delete meter from the device
+func (vm *VoltMeter) DeleteFromDevice(port string, device string) {
+
+ meter := of.NewMeter(vm.ID)
+
+ logger.Debugw(ctx, "Delete meter from device", log.Fields{"Id": vm.ID, "meter": *meter})
+ if err := cntlr.GetController().ModMeter(port, device, of.MeterCommandDel, meter); err != nil {
+ logger.Warnw(ctx, "Delete meter from device Failed", log.Fields{"Id": vm.ID, "meter": *meter, "Error": err})
+ }
+}
+
+// DelMeterProf to delete meter profile
+func (va *VoltApplication) DelMeterProf(name string) error {
+ mm := &va.MeterMgr
+ if _, ok := mm.GetMeterByName(name); !ok {
+ logger.Warnw(ctx, "Meter profile does not exist", log.Fields{"Name": name})
+ return errors.New("Meter profile doesn't exist")
+ }
+ cfg, _ := mm.GetMeterByName(name)
+ if cfg.AssociatedServices != 0 {
+ logger.Warnw(ctx, "Mismatch in submgr and vgc oeter profile service reference",
+ log.Fields{"MeterProfile": name, "serviceCount": cfg.AssociatedServices})
+ return errors.New("Service reference is not 0")
+ }
+ //TODO : delete from all devices
+ delmeterFromDevice := func(key interface{}, value interface{}) bool {
+ device := key.(string)
+ port, _ := GetApplication().GetNniPort(device)
+ cfg.DeleteFromDevice(port, device)
+ return true
+ }
+ va.DevicesDisc.Range(delmeterFromDevice)
+ cfg.DelFromDb()
+ //Delete meter from device will be invoked by caller separately
+ mm.DelMeter(cfg)
+ return nil
+}
diff --git a/internal/pkg/application/minor_upgrade.go b/internal/pkg/application/minor_upgrade.go
new file mode 100644
index 0000000..8953470
--- /dev/null
+++ b/internal/pkg/application/minor_upgrade.go
@@ -0,0 +1,247 @@
+/*
+* Copyright 2022-present Open Networking Foundation
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+ */
+
+package application
+
+import (
+ "errors"
+ "net"
+ "voltha-go-controller/internal/pkg/types"
+
+ "strings"
+
+ "github.com/google/gopacket/layers"
+ "voltha-go-controller/database"
+ "github.com/opencord/voltha-lib-go/v7/pkg/log"
+)
+
+type paramsUpdationFunc func(hash string, value interface{}) error
+
+//map to store conversion functions
+var updationMap = map[string]paramsUpdationFunc{
+ database.VnetPath: updateVnets,
+ database.VpvPath: updateVpvs,
+ database.ServicePath: updateServices,
+ database.MvlanPath: updateMvlans,
+ database.IgmpGroupPath: updateIgmpGroups,
+ database.IgmpDevicePath: updateIgmpDevices,
+ database.IgmpProfPath: updateIgmpProfiles,
+}
+
+// UpdateDbData to update database data
+func UpdateDbData(dbPath, hash string, value interface{}) error {
+ if migrationFunc, ok := updationMap[dbPath]; ok {
+ err := migrationFunc(hash, value)
+ if err != nil {
+ logger.Error(ctx, "Error in migrating data\n")
+ return errors.New("Error-in-migration")
+ }
+ }
+ return nil
+}
+
+//This function modifyies the old data as per current version requirement and also
+//returns the new path on which the modified data has to be written
+func updateServices(hash string, value interface{}) error {
+ param := value.(*VoltService)
+ param.VnetID = VnetKey(param.SVlan, param.CVlan, param.UniVlan)
+ return nil
+}
+
+//This function modifyies the old data as per current version requirement and also
+//returns the new path on which the modified data has to be written
+func updateVnets(hash string, value interface{}) error {
+ param := value.(*VoltVnet)
+ newKey := VnetKey(param.SVlan, param.CVlan, param.UniVlan)
+ if newKey != hash {
+ //Delete the older key
+ _ = db.DelVnet(hash)
+ } else {
+ //Update SVlan Tag Protocol id param with default valud if not present
+ if param.SVlanTpid == 0 {
+ param.SVlanTpid = layers.EthernetTypeDot1Q
+ }
+ }
+ param.Name = newKey
+ if param.DevicesList == nil || len(param.DevicesList) == 0 {
+ param.DevicesList = append(param.DevicesList, "") //Empty OLT serial number as of now since submgr won't have proper serial num
+ }
+ return nil
+}
+
+//This function modifyies the old data as per current version requirement and also
+//returns the new path on which the modified data has to be written
+func updateVpvs(hash string, value interface{}) error {
+
+ //var param VoltPortVnet
+ param := value.(*VoltPortVnet)
+
+ //Update SVlan Tag Protocol id param with default valud if not present
+ if param.SVlanTpid == 0 {
+ param.SVlanTpid = layers.EthernetTypeDot1Q
+ }
+
+ if strings.Count(hash, "-") > 1 {
+ logger.Info(ctx, "Already upgraded")
+ return nil
+ }
+
+ //Add the vpv under new path
+ param.WriteToDb()
+ //delete the older path
+ fullPath := database.BasePath + database.VpvPath + hash
+ if err := db.Del(fullPath); err != nil {
+ logger.Errorw(ctx, "Vpv Delete from DB failed", log.Fields{"Error": err, "key": fullPath})
+ }
+ return nil
+}
+
+func updateMvlans(hash string, value interface{}) error {
+ param := value.(*MvlanProfile)
+ if param.DevicesList == nil || len(param.DevicesList) == 0 {
+ param.DevicesList = make(map[string]OperInProgress) //Empty OLT serial number as of now since submgr won't have proper serial num
+ if err := param.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Mvlan profile write to DB failed", log.Fields{"ProfileName": param.Name})
+ }
+
+ }
+ if _, ok := param.Groups[common.StaticGroup]; ok {
+ param.Groups[common.StaticGroup].IsStatic = true
+ }
+ return nil
+}
+
+//This function modifyies the old Igmp Group data as per current version requirement and also
+//returns the new path on which the modified data has to be written
+func updateIgmpGroups(hash string, value interface{}) error {
+
+ ig := value.(*IgmpGroup)
+ logger.Infow(ctx, "Group Data Migration", log.Fields{"ig": ig, "GroupAddr": ig.GroupAddr, "hash": hash})
+ if ig.GroupAddr == nil {
+ ig.GroupAddr = net.ParseIP("0.0.0.0")
+ }
+ if err := ig.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp group Write to DB failed", log.Fields{"groupName": ig.GroupName})
+ }
+
+ return nil
+}
+
+//This function modifyies the old Igmp Device data as per current version requirement and also
+//returns the new path on which the modified data has to be written
+func updateIgmpDevices(hash string, value interface{}) error {
+ igd := value.(*IgmpGroupDevice)
+ logger.Infow(ctx, "Group Device Migration", log.Fields{"igd": igd, "GroupAddr": igd.GroupAddr, "hash": hash})
+ if igd.GroupAddr == nil {
+ igd.GroupAddr = net.ParseIP("0.0.0.0")
+ }
+ if err := igd.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp group device Write to DB failed", log.Fields{"Device": igd.Device,
+ "GroupName": igd.GroupName, "GroupAddr": igd.GroupAddr.String()})
+ }
+
+ return nil
+}
+
+//This function modifyies the old Igmp Profile data as per current version requirement and also
+//returns the new path on which the modified data has to be written
+func updateIgmpProfiles(hash string, value interface{}) error {
+ igmpProfile := value.(*IgmpProfile)
+ logger.Infow(ctx, "IGMP Profile Migration", log.Fields{"igmpProfile": igmpProfile, "hash": hash})
+ return nil
+}
+
+func (ig *IgmpGroup) migrateIgmpDevices() {
+
+ devices, _ := db.GetPrevIgmpDevices(ig.Mvlan, ig.GroupName)
+ logger.Infow(ctx, "Migratable Devices", log.Fields{"Devices": devices})
+ for _, device := range devices {
+ b, ok := device.Value.([]byte)
+ if !ok {
+ logger.Warn(ctx, "The value type is not []byte")
+ continue
+ }
+ if igd, err := NewIgmpGroupDeviceFromBytes(b); err == nil {
+ key := database.BasePath + database.IgmpDevicePath + igd.Mvlan.String() + "/" + igd.GroupName + "/" + igd.Device
+ logger.Infow(ctx, "Deleting old entry", log.Fields{"Path": key, "igd": igd})
+ if err := db.Del(key); err != nil {
+ logger.Errorw(ctx, "Igmp Group Delete from DB failed", log.Fields{"Error": err, "key": key})
+ }
+ if err := UpdateDbData(database.IgmpDevicePath, key, igd); err != nil {
+ logger.Warnw(ctx, "Group Device Migration failed", log.Fields{"IGD": igd, "Error": err})
+ } else {
+ logger.Infow(ctx, "Group Device Migrated", log.Fields{"IGD": igd})
+ }
+ } else {
+ logger.Warnw(ctx, "Unable to decode device from database", log.Fields{"str": string(b)})
+ }
+ }
+}
+
+func (igd *IgmpGroupDevice) migrateIgmpChannels() {
+
+ channels, _ := db.GetPrevIgmpChannels(igd.GroupName, igd.Device)
+ logger.Infow(ctx, "Migratable Channels", log.Fields{"Channels": channels})
+ for _, channel := range channels {
+
+ b, ok := channel.Value.([]byte)
+ if !ok {
+ logger.Warn(ctx, "The value type is not []byte")
+ continue
+ }
+ if igc, err := NewIgmpGroupChannelFromBytes(b); err == nil {
+ key := database.BasePath + database.IgmpChannelPath + igc.GroupName + "/" + igc.Device + "/" + igc.GroupAddr.String()
+ logger.Infow(ctx, "Deleting old entry", log.Fields{"Path": key, "igc": igc})
+ if err := db.Del(key); err != nil {
+ logger.Errorw(ctx, "Igmp Group Delete from DB failed", log.Fields{"Error": err, "key": key})
+ }
+ if err := igc.WriteToDb(); err != nil {
+ logger.Errorw(ctx, "Igmp group channel Write to DB failed", log.Fields{"mvlan": igc.Mvlan, "GroupAddr": igc.GroupAddr})
+ }
+
+ logger.Infow(ctx, "Group Channel Migrated", log.Fields{"IGD": igc})
+ } else {
+ logger.Warnw(ctx, "Unable to decode channel from database", log.Fields{"str": string(b)})
+ }
+ }
+}
+
+func (igc *IgmpGroupChannel) migrateIgmpPorts() {
+
+ ports, _ := db.GetPrevIgmpRcvrs(igc.GroupAddr, igc.Device)
+ logger.Infow(ctx, "Migratable Ports", log.Fields{"Ports": ports})
+ for _, port := range ports {
+
+ b, ok := port.Value.([]byte)
+ if !ok {
+ logger.Warn(ctx, "The value type is not []byte")
+ continue
+ }
+ if igp, err := NewIgmpGroupPortFromBytes(b); err == nil {
+ key := database.BasePath + database.IgmpPortPath + igc.GroupAddr.String() + "/" + igc.Device + "/" + igp.Port
+ logger.Infow(ctx, "Deleting old entry", log.Fields{"Key": key, "Igp": igp})
+ if err := db.Del(key); err != nil {
+ logger.Errorw(ctx, "Igmp Group port Delete from DB failed", log.Fields{"Error": err, "key": key})
+ }
+ if err := igp.WriteToDb(igc.Mvlan, igc.GroupAddr, igc.Device); err != nil {
+ logger.Errorw(ctx, "Igmp group port Write to DB failed", log.Fields{"mvlan": igc.Mvlan, "GroupAddr": igc.GroupAddr})
+ }
+
+ logger.Infow(ctx, "Group Port Migrated", log.Fields{"IGD": igp})
+ } else {
+ logger.Warnw(ctx, "Unable to decode port from database", log.Fields{"str": string(b)})
+ }
+ }
+}
diff --git a/internal/pkg/application/pppoeia.go b/internal/pkg/application/pppoeia.go
new file mode 100644
index 0000000..8b5a214
--- /dev/null
+++ b/internal/pkg/application/pppoeia.go
@@ -0,0 +1,618 @@
+/*
+* Copyright 2022-present Open Networking Foundation
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package application
+
+import (
+ "context"
+ "errors"
+ "net"
+ "time"
+
+ "github.com/google/gopacket"
+ "github.com/google/gopacket/layers"
+
+ cntlr "voltha-go-controller/internal/pkg/controller"
+ "voltha-go-controller/internal/pkg/of"
+ "voltha-go-controller/internal/pkg/util"
+ "github.com/opencord/voltha-lib-go/v7/pkg/log"
+)
+
+// PppoeIaState type
+type PppoeIaState uint8
+
+const (
+ // PppoeIaStateNone constant
+ PppoeIaStateNone PppoeIaState = iota
+ // PppoeIaStatePADI constant
+ PppoeIaStatePADI
+ // PppoeIaStatePADO constant
+ PppoeIaStatePADO
+ // PppoeIaStatePADR constant
+ PppoeIaStatePADR
+ // PppoeIaStatePADS constant
+ PppoeIaStatePADS
+ // PppoeIaStatePADT constant
+ PppoeIaStatePADT
+)
+
+const (
+ // PPPoEVendorID constant
+ PPPoEVendorID uint32 = 0x0DE9
+ // TYPECIRCUITID constant
+ TYPECIRCUITID byte = 0x01
+ // TYPEREMOTEID constant
+ TYPEREMOTEID byte = 0x02
+ // TYPEMINDATAUS constant
+ TYPEMINDATAUS byte = 0x83
+ // TYPEMINDATADS constant
+ TYPEMINDATADS byte = 0x84
+ // TYPEMAXDATAUS constant
+ TYPEMAXDATAUS byte = 0x87
+ // TYPEMAXDATADS constant
+ TYPEMAXDATADS byte = 0x88
+)
+
+var (
+ // DSLATTRVendorID is PPPoEVendorID in byte format
+ DSLATTRVendorID = util.Uint32ToByte(PPPoEVendorID)
+)
+
+// IPppoeIaSession interface
+type IPppoeIaSession interface {
+ GetCircuitID() []byte
+ GetRemoteID() []byte
+ GetNniVlans() (uint16, uint16)
+ GetPppoeIaState() PppoeIaState
+ SetPppoeIaState(PppoeIaState)
+ SetMacAddr(net.HardwareAddr)
+}
+
+// PppoeIaRelayVnet : The PppoeIa relay sessions are stored in a map to be retrieved from when
+// a response is received from the network. The map uses the VLANs and the
+// the MAC address as key to finding the service
+// PppoeIa Relay Virtual Network hosts a set of PppoeIa relay sessions that belong
+// to the network. It supports two VLANs as its identify. If a single VLAN or
+// no VLAN is to be used, those two should be passed as 4096 (VlanNone)
+type PppoeIaRelayVnet struct {
+ OuterVlan uint16
+ InnerVlan uint16
+ sessions *util.ConcurrentMap //map[[6]byte]IPppoeIaSession
+}
+
+// PppoeIaNetworks : PppoeIa Networks hosts different PppoeIa networks that in turn hold the PppoeIa
+// sessions
+type PppoeIaNetworks struct {
+ Networks *util.ConcurrentMap //map[uint32]*PppoeIaRelayVnet
+}
+
+// NewPppoeIaRelayVnet is constructor for a PppoeIa Relay Virtual network
+func NewPppoeIaRelayVnet(outerVlan uint16, innerVlan uint16) *PppoeIaRelayVnet {
+ var drv PppoeIaRelayVnet
+
+ drv.OuterVlan = outerVlan
+ drv.InnerVlan = innerVlan
+ drv.sessions = util.NewConcurrentMap() //make(map[[6]byte]IPppoeIaSession)
+ return &drv
+}
+
+// AddPppoeIaRelayVnet add pppoeia relay vnet
+func (dn *PppoeIaNetworks) AddPppoeIaRelayVnet(outerVlan uint16, innerVlan uint16) *PppoeIaRelayVnet {
+ comboVlan := uint32(outerVlan)<<16 + uint32(innerVlan)
+ if drv, ok := dn.Networks.Get(comboVlan); ok {
+ return drv.(*PppoeIaRelayVnet)
+ }
+ drv := NewPppoeIaRelayVnet(outerVlan, innerVlan)
+ dn.Networks.Set(comboVlan, drv)
+ return drv
+}
+
+// NewPppoeIaNetworks is constructor for PppoeIa network
+func NewPppoeIaNetworks() *PppoeIaNetworks {
+ var dn PppoeIaNetworks
+ dn.Networks = util.NewConcurrentMap() //make(map[uint32]*PppoeIaRelayVnet)
+ return &dn
+}
+
+// AddPppoeIaSession to add pppoeia session
+func (dn *PppoeIaNetworks) AddPppoeIaSession(pkt gopacket.Packet, session IPppoeIaSession) {
+ var key [6]byte
+ ethl := pkt.Layer(layers.LayerTypeEthernet)
+ eth, _ := ethl.(*layers.Ethernet)
+ addr := eth.SrcMAC
+ copy(key[:], addr[0:6])
+ drv := dn.AddPppoeIaRelayVnet(session.GetNniVlans())
+ drv.sessions.Set(key, session)
+}
+
+// DelPppoeIaSession to delete pppoeia session
+func (dn *PppoeIaNetworks) DelPppoeIaSession(pkt gopacket.Packet, session IPppoeIaSession) {
+ var key [6]byte
+ ethl := pkt.Layer(layers.LayerTypeEthernet)
+ eth, _ := ethl.(*layers.Ethernet)
+ addr := eth.SrcMAC
+ if len(addr) != 6 {
+ logger.Errorw(ctx, "Invalid MAC address", log.Fields{"Addr": addr})
+ return
+ }
+ copy(key[:], addr[0:6])
+ drv := dn.AddPppoeIaRelayVnet(session.GetNniVlans())
+ drv.sessions.Remove(key)
+}
+
+// delPppoeIaSessions to delete pppoeia sessions
+func delPppoeIaSessions(addr net.HardwareAddr, outervlan of.VlanType, innervlan of.VlanType) {
+
+ var key [6]byte
+ if addr == nil || !NonZeroMacAddress(addr) {
+ logger.Warnw(ctx, "Invalid MAC address", log.Fields{"Addr": addr})
+ return
+ }
+ copy(key[:], addr[0:6])
+ drv := pppoeIaNws.AddPppoeIaRelayVnet(uint16(outervlan), uint16(innervlan))
+ drv.sessions.Remove(key)
+ logger.Infow(ctx, "PppoeIa Sessions deleted", log.Fields{"MAC": addr})
+}
+
+// GetPppoeIaSession to get pppoeia sessions
+func (dn *PppoeIaNetworks) GetPppoeIaSession(outerVlan uint16, innerVlan uint16, addr net.HardwareAddr) (IPppoeIaSession, error) {
+ var key [6]byte
+ if len(addr) != 6 {
+ logger.Errorw(ctx, "Invalid MAC address", log.Fields{"Addr": addr})
+ return nil, errors.New("Invalid MAC address")
+ }
+ copy(key[:], addr[0:6])
+ drv := dn.AddPppoeIaRelayVnet(outerVlan, innerVlan)
+ logger.Infow(ctx, "Key for PPPoE session", log.Fields{"Key": key})
+ if session, ok := drv.sessions.Get(key); ok {
+ return session.(IPppoeIaSession), nil
+ }
+ return nil, ErrSessionDoNotExist
+}
+
+// GetVnetForNni to get vnet for nni port
+func GetVnetForNni(addr net.HardwareAddr, cvlan of.VlanType, svlan of.VlanType, pbit uint8) (*VoltPortVnet, error) {
+
+ var err error
+ var session IPppoeIaSession
+ logger.Infow(ctx, "Mac Obtained MAC: ", log.Fields{"Addr": addr})
+ if session, err = pppoeIaNws.GetPppoeIaSession(uint16(svlan), uint16(cvlan), addr); err != nil {
+ logger.Errorw(ctx, "PPPoE Session retrieval failed", log.Fields{"Error": err})
+ if err == ErrSessionDoNotExist {
+ logger.Info(ctx, "Finding matching VPV from packet")
+ vpvs, err1 := GetApplication().GetVpvsForDsPkt(cvlan, svlan, addr, pbit)
+ if len(vpvs) == 1 {
+ return vpvs[0], nil
+ }
+ return nil, err1
+ }
+ return nil, err
+ }
+
+ if session != nil {
+ vpv, ok := session.(*VoltPortVnet)
+
+ if ok {
+ logger.Infow(ctx, "Session Exist: VPV found", log.Fields{"VPV": vpv})
+ return vpv, nil
+ }
+ }
+ logger.Error(ctx, "PPPoE Session retrieved of wrong type")
+ return nil, errors.New("The session retrieved of wrong type")
+}
+
+// AddIaOption : Addition of PppoeIa Option 82 which codes circuit-id and remote-id
+// into the packet. This happens as the request is relayed to the
+// PppoeIa servers on the NNI
+func AddIaOption(svc *VoltService, pppoe *layers.PPPoE) {
+
+ //NOTE : both cID and rID should not be empty if this function is called
+ var data []byte
+ cID := svc.GetCircuitID()
+ rID := svc.RemoteID
+
+ if len(cID) != 0 || len(rID) != 0 || svc.isDataRateAttrPresent() {
+ data = append(data, DSLATTRVendorID...)
+ }
+
+ logger.Debugw(ctx, "Vendor Info", log.Fields{"Data": data})
+
+ if len(cID) != 0 {
+ data = append(data, TYPECIRCUITID)
+ data = append(data, byte(len(cID)))
+ data = append(data, cID...)
+ }
+ if len(rID) != 0 {
+ data = append(data, TYPEREMOTEID)
+ data = append(data, byte(len(rID)))
+ data = append(data, rID...)
+ }
+
+ if svc.isDataRateAttrPresent() {
+ minDrUs := util.Uint32ToByte(svc.MinDataRateUs)
+ data = append(data, TYPEMINDATAUS)
+ data = append(data, byte(len(minDrUs)))
+ data = append(data, minDrUs...)
+
+ minDrDs := util.Uint32ToByte(svc.MinDataRateDs)
+ data = append(data, TYPEMINDATADS)
+ data = append(data, byte(len(minDrDs)))
+ data = append(data, minDrDs...)
+
+ maxDrUs := util.Uint32ToByte(svc.MaxDataRateUs)
+ data = append(data, TYPEMAXDATAUS)
+ data = append(data, byte(len(maxDrUs)))
+ data = append(data, maxDrUs...)
+
+ maxDrDs := util.Uint32ToByte(svc.MaxDataRateDs)
+ data = append(data, TYPEMAXDATADS)
+ data = append(data, byte(len(maxDrDs)))
+ data = append(data, maxDrDs...)
+ }
+ option := layers.NewPPPoEOption(layers.PPPoEOptVendorSpecific, data)
+ pppoe.Options = append(pppoe.Options, option)
+}
+
+// DelIaOption for deletion of IA option from the packet received on the NNI interface.
+func DelIaOption(pppoe *layers.PPPoE) {
+ for index, option := range pppoe.Options {
+ if option.Type == layers.PPPoEOptVendorSpecific {
+ pppoe.Options = append(pppoe.Options[0:index], pppoe.Options[index+1:]...)
+ return
+ }
+ }
+}
+
+// ProcessDsPppoeIaPacket : This function processes DS PppoeIa packet received on the NNI port.
+// The services are attached to the access ports. Thus, the PppoeIa
+// session is derived from the list of PppoeIa sessions stored in the
+// common map. The key for retrieval includes the VLAN tags in the
+// the packet and the MAC address of the client.
+func (va *VoltApplication) ProcessDsPppoeIaPacket(device string, port string, pkt gopacket.Packet) {
+
+ // Retrieve the layers to build the outgoing packet. It is not
+ // possible to add/remove layers to the existing packet and thus
+ // the lyayers are extracted to build the outgoing packet
+ eth := pkt.Layer(layers.LayerTypeEthernet).(*layers.Ethernet)
+ pppoe := pkt.Layer(layers.LayerTypePPPoE).(*layers.PPPoE)
+
+ logger.Infow(ctx, "Processing Southbound DS PppoeIa packet", log.Fields{"Port": port, "Type": pppoe.Code})
+
+ // Retrieve the priority and drop eligible flags from the
+ // packet received
+ var priority uint8
+ var dropEligible bool
+ dot1ql := pkt.Layer(layers.LayerTypeDot1Q)
+ if dot1ql != nil {
+ dot1q := dot1ql.(*layers.Dot1Q)
+ priority = dot1q.Priority
+ dropEligible = dot1q.DropEligible
+ }
+
+ pktInnerlan, pktOuterlan := GetVlansFromPacket(pkt)
+ vpv, err := GetVnetForNni(eth.DstMAC, pktInnerlan, pktOuterlan, priority)
+ if err != nil {
+ logger.Errorw(ctx, "VNET couldn't be found for NNI", log.Fields{"Error": err})
+ return
+ }
+
+ // Do not modify pppoe header if vnet's mac_learning type is not PPPoE-IA.
+ if vpv.PppoeIa {
+ // Delete the IA option that may be included in the response
+ DelIaOption(pppoe)
+ if pppoe.Code == layers.PPPoECodePADO {
+ vpv.SetPppoeIaState(PppoeIaStatePADO)
+ } else if pppoe.Code == layers.PPPoECodePADS {
+ vpv.SetPppoeIaState(PppoeIaStatePADS)
+ } else if pppoe.Code == layers.PPPoECodePADT {
+ vpv.SetPppoeIaState(PppoeIaStatePADT)
+ }
+ vpv.WriteToDb()
+ }
+ // Create the outgoing bufer and set the checksum in the packet
+ buff := gopacket.NewSerializeBuffer()
+ opts := gopacket.SerializeOptions{
+ FixLengths: true,
+ ComputeChecksums: true,
+ }
+
+ cTagType := layers.EthernetTypePPPoEDiscovery
+ eth.EthernetType = layers.EthernetTypeDot1Q
+ priority = vpv.GetRemarkedPriority(priority)
+
+ var pktLayers []gopacket.SerializableLayer
+ pktLayers = append(pktLayers, eth)
+
+ var qVlans []of.VlanType
+ var qVlanLayers []gopacket.SerializableLayer
+
+ if vpv.AllowTransparent {
+ vlanThreshold := 2
+ // In case of ONU_CVLAN or OLT_SVLAN, the DS pkts have single configured vlan
+ // In case of ONU_CVLAN_OLT_SVLAN or OLT_CVLAN_OLT_SVLAN, the DS pkts have 2 configured vlan
+ // Based on that, the no. of vlans should be ignored to get only transparent vlans
+ if vpv.VlanControl == ONUCVlan || vpv.VlanControl == OLTSVlan || vpv.VlanControl == None {
+ vlanThreshold = 1
+ }
+ nxtLayer := layers.EthernetTypeDot1Q
+ if vlans := GetVlans(pkt); len(vlans) > vlanThreshold {
+ qVlans = vlans[vlanThreshold:]
+ cTagType = layers.EthernetTypeDot1Q
+ }
+ for i, qVlan := range qVlans {
+ vlan := uint16(qVlan)
+ if i == (len(qVlans) - 1) {
+ nxtLayer = layers.EthernetTypePPPoEDiscovery
+ }
+ qdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: vlan, DropEligible: dropEligible, Type: nxtLayer}
+ qVlanLayers = append(qVlanLayers, qdot1q)
+ }
+ }
+
+ switch vpv.VlanControl {
+ case ONUCVlanOLTSVlan:
+ cdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: uint16(vpv.CVlan), DropEligible: dropEligible, Type: cTagType}
+ pktLayers = append(pktLayers, cdot1q)
+ case ONUCVlan,
+ None:
+ sdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: uint16(vpv.SVlan), DropEligible: dropEligible, Type: cTagType}
+ pktLayers = append(pktLayers, sdot1q)
+ case OLTCVlanOLTSVlan,
+ OLTSVlan:
+ udot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: uint16(vpv.UniVlan), DropEligible: dropEligible, Type: cTagType}
+ pktLayers = append(pktLayers, udot1q)
+ default:
+ logger.Errorw(ctx, "Invalid Vlan Control Option", log.Fields{"Value": vpv.VlanControl})
+ return
+ }
+
+ pktLayers = append(pktLayers, qVlanLayers...)
+ pktLayers = append(pktLayers, pppoe)
+
+ logger.Debugw(ctx, "Layers Count", log.Fields{"Count": len(pktLayers)})
+ if err := gopacket.SerializeMultiLayers(buff, opts, pktLayers); err != nil {
+ logger.Errorw(ctx, "Packet Serialization Failed", log.Fields{"Reason": err.Error()})
+ return
+ }
+
+ if err := cntlr.GetController().PacketOutReq(device, vpv.Port, port, buff.Bytes(), false); err != nil {
+ logger.Warnw(ctx, "PacketOutReq Failed", log.Fields{"Device": device, "Error": err})
+ }
+}
+
+// ProcessUsPppoeIaPacket : The US PppoeIa packet is identified the PppoeIa OP in the packet. A request is considered upstream
+// and the service associated with the packet is located by the port and VLANs in the packet
+func (va *VoltApplication) ProcessUsPppoeIaPacket(device string, port string, pkt gopacket.Packet) {
+ // We received the packet on an access port and the service for the packet can be
+ // gotten from the port and the packet
+ vpv, svc := va.GetVnetFromPkt(device, port, pkt)
+ if vpv == nil {
+ logger.Errorw(ctx, "VNET couldn't be found from packet", log.Fields{"Device": device, "Port": port})
+ return
+ }
+
+ outport, _ := va.GetNniPort(device)
+ if outport == "" || outport == "0" {
+ logger.Errorw(ctx, "NNI Port not found for device. Dropping Packet", log.Fields{"NNI": outport})
+ return
+ }
+
+ //Add PPPoE session for reference so that the DS pkts can be processed and re-directed
+ pppoeIaNws.AddPppoeIaSession(pkt, vpv)
+
+ // Extract the layers in the packet to prepare the outgoing packet
+ // We use the layers to build the outgoing packet from scratch as
+ // the packet received can't be modified to add/remove layers
+ eth := pkt.Layer(layers.LayerTypeEthernet).(*layers.Ethernet)
+ pppoe := pkt.Layer(layers.LayerTypePPPoE).(*layers.PPPoE)
+ msgType := pppoe.Code
+ logger.Infow(ctx, "Processing Southbound US PppoeIa packet", log.Fields{"Device": device, "Port": port, "Type": pppoe.Code})
+
+ AddIaOption(svc, pppoe)
+
+ // Learn the 8021P values from the packet received
+ var priority uint8
+ dropEligible := false
+ dot1ql := pkt.Layer(layers.LayerTypeDot1Q)
+ if dot1ql != nil {
+ dot1q := dot1ql.(*layers.Dot1Q)
+ priority = dot1q.Priority
+ dropEligible = dot1q.DropEligible
+ }
+
+ if vpv.PppoeIa {
+ //Maintain the session MAC as learnt MAC, since MAC is required for deletion of PPPoE session
+ if msgType == layers.PPPoECodePADI || msgType == layers.PPPoECodePADR {
+ if !util.MacAddrsMatch(vpv.MacAddr, eth.SrcMAC) {
+ expectedPort := va.GetMacInPortMap(eth.SrcMAC)
+ if expectedPort != "" && expectedPort != vpv.Port {
+ logger.Errorw(ctx, "mac-learnt-from-different-port-ignoring-pppoe-message",
+ log.Fields{"MsgType": msgType, "ExpectedPort": expectedPort, "ReceivedPort": vpv.Port, "LearntMacAdrr": vpv.MacAddr, "NewMacAdrr": eth.SrcMAC.String()})
+ return
+ }
+ }
+ vpv.SetMacAddr(eth.SrcMAC)
+ }
+
+ if pppoe.Code == layers.PPPoECodePADI {
+ vpv.SetPppoeIaState(PppoeIaStatePADI)
+ } else if pppoe.Code == layers.PPPoECodePADR {
+ vpv.SetPppoeIaState(PppoeIaStatePADR)
+ }
+ vpv.WriteToDb()
+ }
+
+ buff := gopacket.NewSerializeBuffer()
+ opts := gopacket.SerializeOptions{
+ FixLengths: true,
+ ComputeChecksums: true,
+ }
+
+ cTagType := layers.EthernetTypePPPoEDiscovery
+ outerVlan, innerVlan := vpv.GetNniVlans()
+ logger.Debugw(ctx, "Vnet Vlans", log.Fields{"Svlan": outerVlan, "Cvlan": innerVlan})
+ eth.EthernetType = vpv.SVlanTpid
+
+ var pktLayers []gopacket.SerializableLayer
+ pktLayers = append(pktLayers, eth)
+
+ var qVlans []of.VlanType
+ var qVlanLayers []gopacket.SerializableLayer
+
+ if vpv.AllowTransparent {
+ nxtLayer := layers.EthernetTypeDot1Q
+ if vlans := GetVlans(pkt); len(vlans) > 1 {
+ qVlans = vlans[1:]
+ logger.Debugw(ctx, "Q Vlans", log.Fields{"Vlan List": qVlans})
+ cTagType = layers.EthernetTypeDot1Q
+ }
+ for i, qVlan := range qVlans {
+ vlan := uint16(qVlan)
+ if i == (len(qVlans) - 1) {
+ nxtLayer = layers.EthernetTypePPPoEDiscovery
+ }
+ qdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: vlan, DropEligible: dropEligible, Type: nxtLayer}
+ qVlanLayers = append(qVlanLayers, qdot1q)
+ }
+ }
+
+ switch vpv.VlanControl {
+ case ONUCVlanOLTSVlan,
+ OLTCVlanOLTSVlan:
+ sdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: outerVlan, DropEligible: dropEligible, Type: layers.EthernetTypeDot1Q}
+ pktLayers = append(pktLayers, sdot1q)
+ cdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: innerVlan, DropEligible: dropEligible, Type: cTagType}
+ pktLayers = append(pktLayers, cdot1q)
+ case ONUCVlan,
+ OLTSVlan,
+ None:
+ cdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: outerVlan, DropEligible: dropEligible, Type: cTagType}
+ pktLayers = append(pktLayers, cdot1q)
+ default:
+ logger.Errorw(ctx, "Invalid Vlan Control Option", log.Fields{"Value": vpv.VlanControl})
+ return
+ }
+
+ pktLayers = append(pktLayers, qVlanLayers...)
+ pktLayers = append(pktLayers, pppoe)
+ logger.Debugw(ctx, "Layers Count", log.Fields{"Count": len(pktLayers)})
+ if err := gopacket.SerializeMultiLayers(buff, opts, pktLayers); err != nil {
+ return
+ }
+
+ // Now the packet constructed is output towards the switch to be emitted on
+ // the NNI port
+ if err := cntlr.GetController().PacketOutReq(device, outport, port, buff.Bytes(), false); err != nil {
+ logger.Warnw(ctx, "PacketOutReq Failed", log.Fields{"Device": device, "Error": err})
+ }
+
+}
+
+// ProcessPPPoEIaPacket to process Pppoeia packet
+func (va *VoltApplication) ProcessPPPoEIaPacket(device string, port string, pkt gopacket.Packet) {
+ // Make some error checks before proceeding
+ pppoel := pkt.Layer(layers.LayerTypePPPoE)
+ if pppoel == nil {
+ return
+ }
+ _, ok := pppoel.(*layers.PPPoE)
+ if !ok {
+ return
+ }
+
+ // Let us assess the direction of the packet. We can do so by the port
+ // which is more reliable or do by the PPPoE code which is less reliable
+ isUs := true
+ if nni, _ := GetApplication().GetNniPort(device); nni == port {
+ isUs = false
+ }
+
+ // This is a valid PPPoE packet and can be processed
+ if isUs {
+ // This is treated as an upstream packet in the VOLT application
+ // as VOLT serves access subscribers who use DHCP to acquire IP
+ // address and these packets go upstream to the network
+ va.ProcessUsPppoeIaPacket(device, port, pkt)
+ } else {
+ // This is a downstream packet
+ va.ProcessDsPppoeIaPacket(device, port, pkt)
+ }
+}
+
+// ProcessPPPoEPacket to process Pppoe packet
+func (va *VoltApplication) ProcessPPPoEPacket(device string, port string, pkt gopacket.Packet) {
+ dpt := NewPppoeIaPacketTask(pkt, device, port)
+ va.pppoeTasks.AddTask(dpt)
+}
+
+// pppoeIaNws : The DHCP relay application is maintained within the structures below
+var pppoeIaNws *PppoeIaNetworks
+
+func init() {
+ pppoeIaNws = NewPppoeIaNetworks()
+ RegisterPacketHandler(PPPOE, ProcessPPPoEPacket)
+}
+
+// ProcessPPPoEPacket : CallBack function registered with application to handle PPPoE packetIn
+func ProcessPPPoEPacket(device string, port string, pkt gopacket.Packet) {
+ GetApplication().ProcessPPPoEPacket(device, port, pkt)
+}
+
+// PppoeIaPacketTask : Task to add or delete flows of a service
+type PppoeIaPacketTask struct {
+ taskID uint8
+ ctx context.Context
+ pkt gopacket.Packet
+ device string
+ port string
+ timestamp string
+}
+
+// NewPppoeIaPacketTask constructor for PppoeIaPacketTask
+func NewPppoeIaPacketTask(pkt gopacket.Packet, dev string, port string) *PppoeIaPacketTask {
+ var dpt PppoeIaPacketTask
+ dpt.pkt = pkt
+ dpt.device = dev
+ dpt.port = port
+ dpt.timestamp = (time.Now()).Format(time.RFC3339Nano)
+ return &dpt
+}
+
+// Name to return name for PppoeIaPacketTask
+func (dpt *PppoeIaPacketTask) Name() string {
+ return "DHCP Packet Task"
+}
+
+// TaskID to return task id for PppoeIaPacketTask
+func (dpt *PppoeIaPacketTask) TaskID() uint8 {
+ return dpt.taskID
+}
+
+// Timestamp to return timestamp for PppoeIaPacketTask
+func (dpt *PppoeIaPacketTask) Timestamp() string {
+ return dpt.timestamp
+}
+
+// Stop to stop the PppoeIaPacketTask
+func (dpt *PppoeIaPacketTask) Stop() {
+}
+
+// Start to start PppoeIaPacketTask
+func (dpt *PppoeIaPacketTask) Start(ctx context.Context, taskID uint8) error {
+ dpt.taskID = taskID
+ dpt.ctx = ctx
+ GetApplication().ProcessPPPoEIaPacket(dpt.device, dpt.port, dpt.pkt)
+ return nil
+}
diff --git a/internal/pkg/application/service.go b/internal/pkg/application/service.go
new file mode 100644
index 0000000..e90b948
--- /dev/null
+++ b/internal/pkg/application/service.go
@@ -0,0 +1,1978 @@
+/*
+* Copyright 2022-present Open Networking Foundation
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package application
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "net"
+ "reflect"
+ infraerrorCodes "voltha-go-controller/internal/pkg/errorcodes"
+ "sort"
+ "strconv"
+ "strings"
+ "sync"
+
+ "github.com/google/gopacket/layers"
+
+ "voltha-go-controller/internal/pkg/controller"
+ cntlr "voltha-go-controller/internal/pkg/controller"
+ "voltha-go-controller/database"
+ "voltha-go-controller/internal/pkg/of"
+ "voltha-go-controller/internal/pkg/util"
+ errorCodes "voltha-go-controller/internal/pkg/errorcodes"
+ "github.com/opencord/voltha-lib-go/v7/pkg/log"
+)
+
+const (
+ // DSLAttrEnabled constant
+ DSLAttrEnabled string = "ENABLED"
+)
+
+// VoltServiceCfg structure
+// Name - Uniquely identifies a service across the entire application
+// UniVlan - The VLAN of the packets entering the UNI of ONU
+// CVlan - The VLAN to transalate to/from on the PON link
+// SVlan - The outer VLAN to be used on the NNI of OLT.
+// - In general, 4096 is used as NO VLAN for all the above
+// SVlanTpid - SVlan Tag Protocl Identifier
+// Pbits - Each bit of uint8 represents one p-bit. MSB is pbit 7
+// DhcpRelay - Whether it is turned on/off
+// CircuitId - The circuit id to be used with DHCP relay. Unused otherwise
+// RemoveId - Same as above
+// Port - The access port for the service. Each service has a single access
+// port. The converse is not always true
+// MacLearning - If MAC learning is turned on, the MAC address learned from the
+// the service activation is used in programming flows
+// MacAddress - The MAC hardware address learnt on the UNI interface
+// MacAddresses - Not yet implemented. To be used to learn more MAC addresses
+type VoltServiceCfg struct {
+ Name string
+ UniVlan of.VlanType
+ CVlan of.VlanType
+ SVlan of.VlanType
+ SVlanTpid layers.EthernetType
+ MacAddr net.HardwareAddr
+ Pbits []of.PbitType
+ DsRemarkPbitsMap map[int]int // Ex: Remark case {0:0,1:0} and No-remark case {1:1}
+ TechProfileID uint16
+ CircuitID string
+ RemoteID []byte
+ Port string
+ PonPort uint32
+ MacLearning MacLearningType
+ IsOption82Disabled bool
+ IgmpEnabled bool
+ McastService bool
+ ONTEtherTypeClassification int
+ VlanControl VlanControl
+ UsMeterProfile string
+ DsMeterProfile string
+ AggDsMeterProfile string
+ VnetID string
+ MvlanProfileName string
+ RemoteIDType string
+ SchedID int
+ AllowTransparent bool
+ EnableMulticastKPI bool
+ DataRateAttr string
+ MinDataRateUs uint32
+ MinDataRateDs uint32
+ MaxDataRateUs uint32
+ MaxDataRateDs uint32
+
+ Trigger ServiceTrigger
+}
+
+// VoltServiceOper structure
+type VoltServiceOper struct {
+ //MacLearning bool
+ //MacAddr net.HardwareAddr
+ Device string
+ Ipv4Addr net.IP
+ Ipv6Addr net.IP
+
+ UsMeterID uint32
+ DsMeterID uint32
+ AggDsMeterID uint32
+
+ //Multiservice-Fix
+ UsHSIAFlowsApplied bool
+ DsHSIAFlowsApplied bool
+ UsDhcpFlowsApplied bool
+ DsDhcpFlowsApplied bool
+ IgmpFlowsApplied bool
+ Icmpv6FlowsApplied bool
+
+ ServiceLock sync.RWMutex `json:"-"`
+ PendingFlows map[string]bool
+ AssociatedFlows map[string]bool
+ DeleteInProgress bool
+ ForceDelete bool
+ BwAvailInfo string
+
+ UpdateInProgress bool
+ Metadata interface{}
+}
+
+// VoltService structure
+type VoltService struct {
+ VoltServiceCfg
+ VoltServiceOper
+ Version string
+}
+
+//ServiceTrigger - Service activation trigger
+type ServiceTrigger int
+
+const (
+ //NBActivate - Service added due to NB Action
+ NBActivate ServiceTrigger = 0
+ //ServiceVlanUpdate - Service added due to Svlan Update
+ ServiceVlanUpdate ServiceTrigger = 1
+)
+
+// AppMutexes structure
+type AppMutexes struct {
+ ServiceDataMutex sync.Mutex `json:"-"`
+ VnetMutex sync.Mutex `json:"-"`
+}
+
+//MigrateServiceMetadata - migrate services request metadata
+type MigrateServiceMetadata struct {
+ NewVnetID string
+ RequestID string
+}
+
+// AppMutex variable
+var AppMutex AppMutexes
+
+// NewVoltService for constructor for volt service
+func NewVoltService(cfg *VoltServiceCfg) *VoltService {
+ var vs VoltService
+ vs.VoltServiceCfg = *cfg
+ vs.UsHSIAFlowsApplied = false
+ vs.DsHSIAFlowsApplied = false
+ vs.DeleteInProgress = false
+ //vs.MacAddr, _ = net.ParseMAC("00:00:00:00:00:00")
+
+ vs.MacAddr = cfg.MacAddr
+ vs.Ipv4Addr = net.ParseIP("0.0.0.0")
+ vs.Ipv6Addr = net.ParseIP("::")
+ vs.PendingFlows = make(map[string]bool)
+ vs.AssociatedFlows = make(map[string]bool)
+ return &vs
+}
+
+// WriteToDb commit a service to the DB if service delete is not in-progress
+func (vs *VoltService) WriteToDb() {
+
+ vs.ServiceLock.RLock()
+ defer vs.ServiceLock.RUnlock()
+
+ if vs.DeleteInProgress {
+ logger.Warnw(ctx, "Skipping Redis Update for Service, Service delete in progress", log.Fields{"Service": vs.Name})
+ return
+ }
+ vs.ForceWriteToDb()
+}
+
+//ForceWriteToDb force commit a service to the DB
+func (vs *VoltService) ForceWriteToDb() {
+ b, err := json.Marshal(vs)
+
+ if err != nil {
+ logger.Errorw(ctx, "Json Marshal Failed for Service", log.Fields{"Service": vs.Name})
+ return
+ }
+ if err1 := db.PutService(vs.Name, string(b)); err1 != nil {
+ logger.Errorw(ctx, "DB write oper failed for Service", log.Fields{"Service": vs.Name})
+ }
+}
+
+// isDataRateAttrPresent to check if data attribute is present
+func (vs *VoltService) isDataRateAttrPresent() bool {
+ return vs.DataRateAttr == DSLAttrEnabled
+}
+
+// DelFromDb delete a service from DB
+func (vs *VoltService) DelFromDb() {
+ logger.Debugw(ctx, "Deleting Service from DB", log.Fields{"Name": vs.Name})
+ //TODO - Need to understand and delete the second call
+ //Calling twice has worked though don't know why
+ _ = db.DelService(vs.Name)
+ _ = db.DelService(vs.Name)
+}
+
+// MatchesVlans find the service that matches the VLANs. In this case it is
+// purely based on CVLAN. The CVLAN can sufficiently be used to
+// match a service
+func (vs *VoltService) MatchesVlans(vlans []of.VlanType) bool {
+ if len(vlans) != 1 {
+ return false
+ }
+
+ if vlans[0] == vs.CVlan {
+ return true
+ }
+ return false
+}
+
+// MatchesPbits allows matching a service to a pbit. This is used
+// to search for a service matching the pbits, typically to identify
+// attributes for other flows such as DHCP, IGMP, etc.
+func (vs *VoltService) MatchesPbits(pbits []of.PbitType) bool {
+ for _, pbit := range pbits {
+ for _, pb := range vs.Pbits {
+ if pb == pbit {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+// IsPbitExist allows matching a service to a pbit. This is used
+// to search for a service matching the pbit
+func (vs *VoltService) IsPbitExist(pbit of.PbitType) bool {
+ for _, pb := range vs.Pbits {
+ if pb == pbit {
+ return true
+ }
+ }
+ return false
+}
+
+// AddHsiaFlows - Adds US & DS HSIA Flows for the service
+func (vs *VoltService) AddHsiaFlows() {
+ if err := vs.AddUsHsiaFlows(); err != nil {
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vs.triggerServiceFailureInd(statusCode, statusMessage)
+ }
+ if err := vs.AddDsHsiaFlows(); err != nil {
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vs.triggerServiceFailureInd(statusCode, statusMessage)
+ }
+}
+
+//DelHsiaFlows - Deletes US & DS HSIA Flows for the service
+func (vs *VoltService) DelHsiaFlows() {
+ if err := vs.DelUsHsiaFlows(); err != nil {
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vs.triggerServiceFailureInd(statusCode, statusMessage)
+ }
+
+ if err := vs.DelDsHsiaFlows(); err != nil {
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vs.triggerServiceFailureInd(statusCode, statusMessage)
+ }
+}
+
+// AddUsHsiaFlows - Add US HSIA Flows for the service
+func (vs *VoltService) AddUsHsiaFlows() error {
+
+ if vs.DeleteInProgress || vs.UpdateInProgress {
+ logger.Errorw(ctx, "Ignoring US HSIA Flow Push, Service deleteion In-Progress", log.Fields{"Device": vs.Device, "Service": vs.Name})
+ return nil
+ }
+
+ va := GetApplication()
+ logger.Infow(ctx, "Configuring US HSIA Service Flows", log.Fields{"ServiceName": vs.Name})
+ if !vs.UsHSIAFlowsApplied || vgcRebooted {
+ device, err := va.GetDeviceFromPort(vs.Port)
+ if err != nil {
+ logger.Errorw(ctx, "Error Getting Device", log.Fields{"Reason": err.Error()})
+ return errorCodes.ErrDeviceNotFound
+ } else if device.State != controller.DeviceStateUP {
+ logger.Warnw(ctx, "Device state Down. Ignoring US HSIA Flow Push", log.Fields{"Service": vs.Name, "Port": vs.Port})
+ return nil
+ }
+
+ vs.Device = device.Name
+ va.AddMeterToDevice(vs.Port, device.Name, vs.UsMeterID, 0)
+ va.AddMeterToDevice(vs.Port, device.Name, vs.DsMeterID, vs.AggDsMeterID)
+
+ logger.Infow(ctx, "Adding HSIA flows", log.Fields{"Name": vs.Name})
+ pBits := vs.Pbits
+
+ //If no pbits configured for service, hence add PbitNone for flows
+ if len(vs.Pbits) == 0 {
+ pBits = append(pBits, PbitMatchNone)
+ }
+ for _, pbits := range pBits {
+ usflows, err := vs.BuildUsHsiaFlows(pbits)
+ if err != nil {
+ logger.Errorw(ctx, "Error Building HSIA US flows", log.Fields{"Reason": err.Error()})
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vs.triggerServiceFailureInd(statusCode, statusMessage)
+ continue
+ }
+ usflows.MigrateCookie = vgcRebooted
+ if err := vs.AddFlows(device, usflows); err != nil {
+ logger.Errorw(ctx, "Error adding HSIA US flows", log.Fields{"Reason": err.Error()})
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vs.triggerServiceFailureInd(statusCode, statusMessage)
+ }
+ }
+ vs.UsHSIAFlowsApplied = true
+ logger.Infow(ctx, "Pushed US HSIA Service Flows", log.Fields{"ServiceName": vs.Name})
+ }
+ vs.WriteToDb()
+ return nil
+}
+
+// AddDsHsiaFlows - Add DS HSIA Flows for the service
+func (vs *VoltService) AddDsHsiaFlows() error {
+ if vs.DeleteInProgress {
+ logger.Errorw(ctx, "Ignoring DS HSIA Flow Push, Service deleteion In-Progress", log.Fields{"Device": vs.Device, "Service": vs.Name})
+ return nil
+ }
+
+ va := GetApplication()
+ logger.Infow(ctx, "Configuring DS HSIA Service Flows", log.Fields{"ServiceName": vs.Name})
+ if !vs.DsHSIAFlowsApplied || vgcRebooted {
+ device, err := va.GetDeviceFromPort(vs.Port)
+ if err != nil {
+ logger.Errorw(ctx, "Error Getting Device", log.Fields{"Reason": err.Error()})
+ return errorCodes.ErrDeviceNotFound
+ } else if device.State != controller.DeviceStateUP {
+ logger.Warnw(ctx, "Device state Down. Ignoring DS HSIA Flow Push", log.Fields{"Service": vs.Name, "Port": vs.Port})
+ return nil
+ }
+
+ va.AddMeterToDevice(vs.Port, device.Name, vs.DsMeterID, vs.AggDsMeterID)
+ logger.Infow(ctx, "Adding HSIA flows", log.Fields{"Name": vs.Name})
+
+ //If no pbits configured for service, hence add PbitNone for flows
+ if len(vs.DsRemarkPbitsMap) == 0 {
+ dsflows, err := vs.BuildDsHsiaFlows(of.PbitType(of.PbitMatchNone))
+ if err != nil {
+ logger.Errorw(ctx, "Error Building HSIA DS flows", log.Fields{"Reason": err.Error()})
+ return err
+ }
+ dsflows.MigrateCookie = vgcRebooted
+ if err = vs.AddFlows(device, dsflows); err != nil {
+ logger.Errorw(ctx, "Failed to add HSIA DS flows", log.Fields{"Reason": err})
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vs.triggerServiceFailureInd(statusCode, statusMessage)
+ }
+ } else {
+ // if all 8 p-bits are to be remarked to one-pbit, configure all-to-one remarking flow
+ if _, ok := vs.DsRemarkPbitsMap[int(of.PbitMatchAll)]; ok {
+ dsflows, err := vs.BuildDsHsiaFlows(of.PbitType(of.PbitMatchAll))
+ if err != nil {
+ logger.Errorw(ctx, "Error Building HSIA DS flows", log.Fields{"Reason": err.Error()})
+ return err
+ }
+ logger.Debug(ctx, "Add-one-match-all-pbit-flow")
+ dsflows.MigrateCookie = vgcRebooted
+ if err := vs.AddFlows(device, dsflows); err != nil {
+ logger.Errorw(ctx, "Failed to add HSIA DS flows", log.Fields{"Reason": err})
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vs.triggerServiceFailureInd(statusCode, statusMessage)
+ }
+ } else {
+ for matchPbit := range vs.DsRemarkPbitsMap {
+ dsflows, err := vs.BuildDsHsiaFlows(of.PbitType(matchPbit))
+ if err != nil {
+ logger.Errorw(ctx, "Error Building HSIA DS flows", log.Fields{"Reason": err.Error()})
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vs.triggerServiceFailureInd(statusCode, statusMessage)
+ continue
+ }
+ dsflows.MigrateCookie = vgcRebooted
+ if err := vs.AddFlows(device, dsflows); err != nil {
+ logger.Errorw(ctx, "Failed to Add HSIA DS flows", log.Fields{"Reason": err})
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vs.triggerServiceFailureInd(statusCode, statusMessage)
+ }
+ }
+ }
+ }
+ vs.DsHSIAFlowsApplied = true
+ logger.Infow(ctx, "Pushed DS HSIA Service Flows", log.Fields{"ServiceName": vs.Name})
+ }
+ vs.WriteToDb()
+ return nil
+}
+
+// DelUsHsiaFlows - Deletes US HSIA Flows for the service
+func (vs *VoltService) DelUsHsiaFlows() error {
+
+ logger.Infow(ctx, "Removing US HSIA Services", log.Fields{"Services": vs.Name})
+ if vs.UsHSIAFlowsApplied || vgcRebooted {
+ device, err := GetApplication().GetDeviceFromPort(vs.Port)
+ if err != nil {
+ logger.Errorw(ctx, "Error Getting Device", log.Fields{"Reason": err.Error()})
+ return errorCodes.ErrDeviceNotFound
+ }
+
+ logger.Infow(ctx, "Removing HSIA flows", log.Fields{"Name": vs.Name})
+ pBits := vs.Pbits
+
+ //If no pbits configured for service, hence add PbitNone for flows
+ if len(vs.Pbits) == 0 {
+ pBits = append(pBits, PbitMatchNone)
+ }
+ for _, pbits := range pBits {
+ usflows, err := vs.BuildUsHsiaFlows(pbits)
+ if err != nil {
+ logger.Errorw(ctx, "Error Building HSIA US flows", log.Fields{"Reason": err.Error()})
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vs.triggerServiceFailureInd(statusCode, statusMessage)
+ continue
+ }
+ usflows.MigrateCookie = vgcRebooted
+ if err = vs.DelFlows(device, usflows); err != nil {
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vs.triggerServiceFailureInd(statusCode, statusMessage)
+ }
+ }
+ vs.UsHSIAFlowsApplied = false
+ }
+ vs.WriteToDb()
+ return nil
+}
+
+// DelDsHsiaFlows - Deletes DS HSIA Flows for the service
+func (vs *VoltService) DelDsHsiaFlows() error {
+
+ logger.Infow(ctx, "Removing DS HSIA Services", log.Fields{"Services": vs.Name})
+ if vs.DsHSIAFlowsApplied || vgcRebooted {
+ device, err := GetApplication().GetDeviceFromPort(vs.Port)
+ if err != nil {
+ logger.Errorw(ctx, "Error Getting Device", log.Fields{"Reason": err.Error()})
+ return errorCodes.ErrDeviceNotFound
+ }
+
+ logger.Infow(ctx, "Removing HSIA flows", log.Fields{"Name": vs.Name})
+ var matchPbit int
+ //If no pbits configured for service, hence add PbitNone for flows
+ if len(vs.DsRemarkPbitsMap) == 0 {
+ dsflows, err := vs.BuildDsHsiaFlows(of.PbitType(PbitMatchNone))
+ if err != nil {
+ logger.Errorw(ctx, "Error Building HSIA DS flows", log.Fields{"Reason": err.Error()})
+ return err
+ }
+ dsflows.MigrateCookie = vgcRebooted
+ if err = vs.DelFlows(device, dsflows); err != nil {
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vs.triggerServiceFailureInd(statusCode, statusMessage)
+ }
+ } else if _, ok := vs.DsRemarkPbitsMap[int(PbitMatchAll)]; ok {
+ dsflows, err := vs.BuildDsHsiaFlows(of.PbitType(int(PbitMatchAll)))
+ if err != nil {
+ logger.Errorw(ctx, "Error Building HSIA DS flows", log.Fields{"Reason": err.Error()})
+ return err
+ }
+ dsflows.MigrateCookie = vgcRebooted
+ if err = vs.DelFlows(device, dsflows); err != nil {
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vs.triggerServiceFailureInd(statusCode, statusMessage)
+ }
+ } else {
+ for matchPbit = range vs.DsRemarkPbitsMap {
+ dsflows, err := vs.BuildDsHsiaFlows(of.PbitType(matchPbit))
+ if err != nil {
+ logger.Errorw(ctx, "Error Building HSIA DS flows", log.Fields{"Reason": err.Error()})
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vs.triggerServiceFailureInd(statusCode, statusMessage)
+ continue
+ }
+ dsflows.MigrateCookie = vgcRebooted
+ if err = vs.DelFlows(device, dsflows); err != nil {
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vs.triggerServiceFailureInd(statusCode, statusMessage)
+ }
+ }
+ }
+ vs.DsHSIAFlowsApplied = false
+ }
+ logger.Infow(ctx, "Deleted HSIA DS flows from DB successfuly", log.Fields{"ServiceName": vs.Name})
+ // Post HSIA configuration success indication on message bus
+ vs.WriteToDb()
+ return nil
+}
+
+// BuildDsHsiaFlows build the DS HSIA flows
+// Called for add/delete HSIA flows
+func (vs *VoltService) BuildDsHsiaFlows(pbits of.PbitType) (*of.VoltFlow, error) {
+ flow := &of.VoltFlow{}
+ flow.SubFlows = make(map[uint64]*of.VoltSubFlow)
+
+ // Get the out and in ports for the flows
+ device, err := GetApplication().GetDeviceFromPort(vs.Port)
+ if err != nil {
+ return nil, errorCodes.ErrDeviceNotFound
+ }
+ inport, _ := GetApplication().GetPortID(device.NniPort)
+ outport, _ := GetApplication().GetPortID(vs.Port)
+ // PortName and PortID to be used for validation of port before flow pushing
+ flow.PortID = outport
+ flow.PortName = vs.Port
+ allowTransparent := 0
+ if vs.AllowTransparent {
+ allowTransparent = 1
+ }
+
+ // initialized actnPbit to 0 for cookie genration backward compatibility.
+ var actnPbit of.PbitType
+ remarkPbit, remarkExists := vs.DsRemarkPbitsMap[int(pbits)]
+
+ generateDSCookie := func(vlan of.VlanType, valToShift uint64) uint64 {
+ //| 12-bit cvlan/UniVlan | 4 bits action pbit | <32-bits uniport>| 16-bits HSIA mask OR flow mask OR pbit |
+ cookie := uint64(vlan)<<52 + uint64(actnPbit)<<48 + uint64(outport)<<16 | of.HsiaFlowMask
+ cookie = cookie | of.DsFlowMask
+ cookie = cookie + (valToShift << 4) + uint64(pbits)
+ return cookie
+ }
+
+ l2ProtoValue, err := GetMetadataForL2Protocol(vs.SVlanTpid)
+ if err != nil {
+ logger.Errorw(ctx, "DS HSIA flow push failed: Invalid SvlanTpid", log.Fields{"SvlanTpid": vs.SVlanTpid, "Service": vs.Name})
+ return nil, err
+ }
+
+ // Add Table-0 flow that deals with the outer VLAN in pOLT
+ {
+ subflow1 := of.NewVoltSubFlow()
+ subflow1.SetTableID(0)
+ subflow1.SetGoToTable(1)
+ subflow1.SetInPort(inport)
+
+ if pbits != PbitMatchNone {
+ subflow1.SetMatchPbit(pbits)
+ }
+
+ if remarkExists && (of.PbitType(remarkPbit) != pbits) {
+ subflow1.SetPcp(of.PbitType(remarkPbit))
+ // match & action pbits are different, set remark-pbit action
+ actnPbit = of.PbitType(remarkPbit)
+ // mask remark p-bit to 4bits
+ actnPbit = actnPbit & 0x0F
+ }
+
+ if err := vs.setDSMatchActionVlanT0(subflow1); err != nil {
+ return nil, err
+ }
+ logger.Info(ctx, "HSIA DS flows MAC Learning & MAC", log.Fields{"ML": vs.MacLearning, "Mac": vs.MacAddr})
+ if NonZeroMacAddress(vs.MacAddr) {
+ subflow1.SetMatchDstMac(vs.MacAddr)
+ }
+ subflow1.Priority = of.HsiaFlowPriority
+ subflow1.SetMeterID(vs.DsMeterID)
+
+ /* WriteMetaData 8 Byte(uint64) usage:
+ | Byte8 | Byte7 | Byte6 | Byte5 | Byte4 | Byte3 | Byte2 | Byte1 |
+ | reserved | reserved | TpID | TpID | uinID | uniID | uniID | uniID | */
+ metadata := uint64(vs.CVlan)<<48 + uint64(vs.TechProfileID)<<32 + uint64(outport)
+ subflow1.SetWriteMetadata(metadata)
+
+ /* TableMetaData 8 Byte(uint64) Voltha usage: (Considering MSB bit as 63rd bit and LSB bit as 0th bit)
+ | Byte8 | Byte7 | Byte6 | Byte5 | Byte4 | Byte3 | Byte2 | Byte1 |
+ | 0000 | 00 | 0 | 0 | 00000000 | 00000000 | 0000 0000 | 00000000 | 00000000 | 00000000 | 00000000|
+ | reserved | svlanTpID | Buff us | AT | schedID | schedID | onteth vlanCtrl | unitag | unitag | ctag | ctag | */
+
+ //TODO-COMM:
+ /* TableMetaData 8 Byte(uint64) Community usage: (Considering MSB bit as 63rd bit and LSB bit as 0th bit)
+ | Byte8 | Byte7 | Byte6 | Byte5 | Byte4 | Byte3 | Byte2 | Byte1 |
+ | 0000 | 00 | 0 | 0 | 00000000 | 00000000 | 0000 0000 | 00000000 | 00000000 | 00000000 | 00000000|
+ | reserved | svlanTpID | Buff us | AT | schedID | schedID | onteth vlanCtrl | ctag | ctag | ctag | ctag | */
+
+ metadata = uint64(l2ProtoValue)<<58 | uint64(allowTransparent)<<56 | uint64(vs.SchedID)<<40 | uint64(vs.ONTEtherTypeClassification)<<36 | uint64(vs.VlanControl)<<32 | uint64(vs.CVlan)
+ subflow1.SetTableMetadata(metadata)
+ // TODO - We are using cookie as key and must come up with better cookie
+ // allocation algorithm
+ /**
+ * Cokies may clash when -
+ * on same uni-port we have two sub-service
+ * 1. U=10, C=100, S=310, p-bit=4 - VLAN_Control = OLT_CVLAN_OLT_SVLAN
+ * 2. U=10, C=10, S=320, p-bit=4 - VLAN_control = ONU_CVLAN_ONU_SVLAN
+ * However, this p-bit re-use will not be allowed by sub-mgr.
+ */
+ if vs.VlanControl == OLTCVlanOLTSVlan {
+ /**
+ * The new cookie generation is only for OLT_CVLAN_OLT_SVLAN case (TEF residential case) within a UNI.
+ * After vgc upgrade, if we have to deactivate an already existing TEF residential service, then we have to
+ * use old cookie.
+ */
+ subflow1.Cookie = generateDSCookie(vs.UniVlan, 0)
+ if vgcRebooted {
+ subflow1.OldCookie = generateDSCookie(vs.CVlan, 0)
+ }
+ } else {
+ // In case of Olt_Svlan , CVLAN=UNIVLAN so cookie can be constructed with CVLAN as well
+ subflow1.Cookie = generateDSCookie(vs.CVlan, 0)
+ }
+
+ flow.SubFlows[subflow1.Cookie] = subflow1
+ logger.Infow(ctx, "Building downstream HSIA flow for T0", log.Fields{"cookie": subflow1.Cookie,
+ "subflow": subflow1})
+ }
+
+ //Add Table-1 flow that deals with inner VLAN at the ONU
+ {
+ subflow2 := of.NewVoltSubFlow()
+ subflow2.SetTableID(1)
+ subflow2.SetInPort(inport)
+ if NonZeroMacAddress(vs.MacAddr) {
+ subflow2.SetMatchDstMac(vs.MacAddr)
+ }
+
+ if err := vs.setDSMatchActionVlanT1(subflow2); err != nil {
+ return nil, err
+ }
+ if pbits != PbitMatchNone {
+ subflow2.SetMatchPbit(pbits)
+ }
+
+ if remarkExists && (of.PbitType(remarkPbit) != pbits) {
+ subflow2.SetPcp(of.PbitType(remarkPbit))
+ }
+
+ subflow2.SetOutPort(outport)
+ subflow2.SetMeterID(vs.DsMeterID)
+
+ // refer Table-0 flow generation for byte information
+ metadata := uint64(vs.CVlan)<<48 + uint64(vs.TechProfileID)<<32 + uint64(outport)
+ subflow2.SetWriteMetadata(metadata)
+
+ // Table-1 and inport is NNI: It is a DS flow for ONU, add uniport in metadata to make it unique
+ if util.IsNniPort(inport) {
+ metadata = uint64(outport)
+ } else {
+ // refer Table-0 flow generation for byte information
+ metadata = uint64(l2ProtoValue)<<58 | uint64(allowTransparent)<<56 | uint64(vs.SchedID)<<40 | uint64(vs.ONTEtherTypeClassification)<<36 | uint64(vs.VlanControl)<<32 | uint64(vs.CVlan)
+ }
+ subflow2.SetTableMetadata(metadata)
+ // Setting of Cookie - TODO - Improve the allocation algorithm
+ if vs.VlanControl == OLTCVlanOLTSVlan {
+ /**
+ * The new cookie generation is only for OLT_CVLAN_OLT_SVLAN case (TEF residential case) within a UNI.
+ * After vgc upgrade, if we have to deactivate an already existing TEF residential service, then we have to
+ * use old cookie.
+ */
+ subflow2.Cookie = generateDSCookie(vs.UniVlan, 1)
+ if vgcRebooted {
+ subflow2.OldCookie = generateDSCookie(vs.CVlan, 1)
+ }
+ } else {
+ // In case of Olt_Svlan , CVLAN=UNIVLAN so cookie can be constructed with CVLAN as well
+ subflow2.Cookie = generateDSCookie(vs.CVlan, 1)
+ }
+
+ subflow2.Priority = of.HsiaFlowPriority
+ flow.SubFlows[subflow2.Cookie] = subflow2
+ logger.Infow(ctx, "Building downstream HSIA flow for T1", log.Fields{"cookie": subflow2.Cookie,
+ "subflow": subflow2})
+ }
+
+ return flow, nil
+}
+
+// BuildUsHsiaFlows build the US HSIA flows
+// Called for add/delete HSIA flows
+func (vs *VoltService) BuildUsHsiaFlows(pbits of.PbitType) (*of.VoltFlow, error) {
+ flow := &of.VoltFlow{}
+ flow.SubFlows = make(map[uint64]*of.VoltSubFlow)
+
+ // Get the out and in ports for the flows
+ device, err := GetApplication().GetDeviceFromPort(vs.Port)
+ if err != nil {
+ return nil, errorCodes.ErrDeviceNotFound
+ }
+ outport, _ := GetApplication().GetPortID(device.NniPort)
+ inport, _ := GetApplication().GetPortID(vs.Port)
+ // PortName and PortID to be used for validation of port before flow pushing
+ flow.PortID = inport
+ flow.PortName = vs.Port
+ allowTransparent := 0
+ reqBwInfo := 0
+ if vs.AllowTransparent {
+ allowTransparent = 1
+ }
+ if vs.BwAvailInfo == "" {
+ reqBwInfo = 1
+ }
+
+ // Add Table-0 flow that deals with the inner VLAN in ONU
+ {
+ subflow1 := of.NewVoltSubFlow()
+ subflow1.SetTableID(0)
+ subflow1.SetGoToTable(1)
+ subflow1.SetInPort(inport)
+
+ if pbits != PbitMatchNone {
+ subflow1.SetMatchPbit(pbits)
+ }
+ if err := vs.setUSMatchActionVlanT0(subflow1); err != nil {
+ return nil, err
+ }
+ subflow1.SetMeterID(vs.UsMeterID)
+
+ /* WriteMetaData 8 Byte(uint64) usage:
+ | Byte8 | Byte7 | Byte6 | Byte5 | Byte4 | Byte3 | Byte2 | Byte1 |
+ | reserved | reserved | TpID | TpID | uinID | uniID | uniID | uniID | */
+ metadata := uint64(vs.CVlan)<<48 + uint64(vs.TechProfileID)<<32 + uint64(outport)
+ subflow1.SetWriteMetadata(metadata)
+
+ /* TableMetaData 8 Byte(uint64) usage: (Considering MSB bit as 63rd bit and LSB bit as 0th bit)
+ | Byte8 | Byte7 | Byte6 | Byte5 | Byte4 | Byte3 | Byte2 | Byte1 |
+ | 000 | 0 | 00 | 0 | 0 | 00000000 | 00000000 | 0000 0000 | 00000000 | 00000000 | 00000000 | 00000000|
+ | reserved | reqBwInfo | svlanTpID | Buff us | AT | schedID | schedID | onteth vlanCtrl | unitag | unitag | ctag | ctag | */
+ metadata = uint64(reqBwInfo)<<60 | uint64(allowTransparent)<<56 | uint64(vs.SchedID)<<40 | uint64(vs.ONTEtherTypeClassification)<<36 | uint64(vs.VlanControl)<<32 | uint64(vs.CVlan)
+
+ // // In case of MAC Learning enabled voltha will buffer the US flow installation.
+ // if NonZeroMacAddress(vs.MacAddr) {
+ // subflow1.SetMatchSrcMac(vs.MacAddr)
+ // } else if vs.MacLearning != MacLearning {
+ // metadata |= 1 << 57
+ // logger.Infow(ctx, "Buffer us flow at adapter", log.Fields{"metadata": metadata})
+ // }
+ subflow1.SetTableMetadata(metadata)
+ if vs.VlanControl == OLTCVlanOLTSVlan {
+ /**
+ * The new cookie generation is only for OLT_CVLAN_OLT_SVLAN case (TEF residential case) within a UNI.
+ * After vgc upgrade, if we have to deactivate an already existing TEF residential service, then we have to
+ * use old cookie.
+ */
+ subflow1.Cookie = vs.generateUSCookie(vs.UniVlan, 0, inport, pbits)
+ if vgcRebooted {
+ subflow1.OldCookie = vs.generateUSCookie(vs.CVlan, 0, inport, pbits)
+ }
+ } else {
+ // In case of Olt_Svlan , CVLAN=UNIVLAN so cookie can be constructed with CVLAN as well
+ subflow1.Cookie = vs.generateUSCookie(vs.CVlan, 0, inport, pbits)
+ }
+ subflow1.Priority = of.HsiaFlowPriority
+ flow.SubFlows[subflow1.Cookie] = subflow1
+ logger.Infow(ctx, "Building upstream HSIA flow for T0", log.Fields{"cookie": subflow1.Cookie, "subflow": subflow1})
+ }
+
+ //Add Table-1 flow that deals with the outer vlan in pOLT
+ {
+ subflow2 := of.NewVoltSubFlow()
+ subflow2.SetTableID(1)
+ subflow2.SetInPort(inport)
+
+ if pbits != PbitMatchNone {
+ subflow2.SetMatchPbit(pbits)
+ }
+
+ if err := vs.setUSMatchActionVlanT1(subflow2); err != nil {
+ return nil, err
+ }
+ subflow2.SetInPort(inport)
+ subflow2.SetOutPort(outport)
+ subflow2.SetMeterID(vs.UsMeterID)
+
+ // refer Table-0 flow generation for byte information
+ metadata := uint64(vs.CVlan)<<48 + uint64(vs.TechProfileID)<<32 + uint64(outport)
+ subflow2.SetWriteMetadata(metadata)
+
+ // refer Table-0 flow generation for byte information
+ metadata = uint64(reqBwInfo)<<60 | uint64(allowTransparent)<<56 | uint64(vs.SchedID)<<40 | uint64(vs.ONTEtherTypeClassification)<<36 | uint64(vs.VlanControl)<<32 | uint64(vs.CVlan)
+ // // In case of MAC Learning enabled voltha will buffer the US flow installation.
+ // if NonZeroMacAddress(vs.MacAddr) {
+ // subflow2.SetMatchSrcMac(vs.MacAddr)
+ // } else if vs.MacLearning != MacLearningNone {
+ // metadata |= 1 << 57
+ // logger.Infow(ctx, "Buffer us flow at adapter", log.Fields{"metadata": metadata})
+ // }
+ subflow2.SetTableMetadata(metadata)
+ if vs.VlanControl == OLTCVlanOLTSVlan {
+ /**
+ * The new cookie generation is only for OLT_CVLAN_OLT_SVLAN case (TEF residential case) within a UNI.
+ * After vgc upgrade, if we have to deactivate an already existing TEF residential service, then we have to
+ * use old cookie.
+ */
+ subflow2.Cookie = vs.generateUSCookie(vs.UniVlan, 1, inport, pbits)
+ if vgcRebooted {
+ subflow2.OldCookie = vs.generateUSCookie(vs.CVlan, 1, inport, pbits)
+ }
+ } else {
+ // In case of Olt_Svlan , CVLAN=UNIVLAN so cookie can be constructed with CVLAN as well
+ subflow2.Cookie = vs.generateUSCookie(vs.CVlan, 1, inport, pbits)
+ }
+ subflow2.Priority = of.HsiaFlowPriority
+
+ flow.SubFlows[subflow2.Cookie] = subflow2
+ logger.Infow(ctx, "Building upstream HSIA flow for T1", log.Fields{"cookie": subflow2.Cookie, "subflow": subflow2})
+ }
+
+ return flow, nil
+}
+
+func (vs *VoltService) generateUSCookie(vlan of.VlanType, valToShift uint64, inport uint32, pbits of.PbitType) uint64 {
+ //| 12-bit cvlan/UniVlan | 4 bits empty | <32-bits uniport>| 16-bits HSIA mask OR flow mask OR pbit |
+ cookie := uint64(vlan)<<52 + uint64(inport)<<16 | of.HsiaFlowMask
+ cookie = cookie | of.UsFlowMask
+ cookie = cookie + (valToShift << 4) + uint64(pbits)
+ return cookie
+}
+
+// setUSMatchActionVlanT1 - Sets the Match & Action w.r.t Vlans for US Table-1
+// based on different Vlan Controls
+func (vs *VoltService) setUSMatchActionVlanT1(flow *of.VoltSubFlow) error {
+ switch vs.VlanControl {
+ case None:
+ flow.SetMatchVlan(vs.SVlan)
+ case ONUCVlanOLTSVlan:
+ flow.SetMatchVlan(vs.CVlan)
+ flow.SetPushVlan(vs.SVlan, vs.SVlanTpid)
+ case OLTCVlanOLTSVlan:
+ flow.SetMatchVlan(vs.UniVlan)
+ flow.SetSetVlan(vs.CVlan)
+ flow.SetPushVlan(vs.SVlan, vs.SVlanTpid)
+ case ONUCVlan:
+ flow.SetMatchVlan(vs.SVlan)
+ case OLTSVlan:
+ if vs.UniVlan != of.VlanAny && vs.UniVlan != of.VlanNone {
+ flow.SetMatchVlan(vs.UniVlan)
+ flow.SetSetVlan(vs.SVlan)
+ } else if vs.UniVlan != of.VlanNone {
+ flow.SetMatchVlan(vs.UniVlan)
+ flow.SetPushVlan(vs.SVlan, layers.EthernetTypeDot1Q)
+ } else {
+ flow.SetPushVlan(vs.SVlan, layers.EthernetTypeDot1Q)
+ }
+ default:
+ logger.Errorw(ctx, "Invalid Vlan Control Option", log.Fields{"Value": vs.VlanControl})
+ return errorCodes.ErrInvalidParamInRequest
+ }
+ return nil
+}
+
+// setDSMatchActionVlanT0 - Sets the Match & Action w.r.t Vlans for DS Table-0
+// based on different Vlan Controls
+func (vs *VoltService) setDSMatchActionVlanT0(flow *of.VoltSubFlow) error {
+ switch vs.VlanControl {
+ case None:
+ flow.SetMatchVlan(vs.SVlan)
+ case ONUCVlanOLTSVlan:
+ flow.SetMatchVlan(vs.SVlan)
+ flow.SetPopVlan()
+ case OLTCVlanOLTSVlan:
+ flow.SetMatchVlan(vs.SVlan)
+ flow.SetPopVlan()
+ flow.SetSetVlan(vs.UniVlan)
+ case ONUCVlan:
+ flow.SetMatchVlan(vs.SVlan)
+ case OLTSVlan:
+ flow.SetMatchVlan(vs.SVlan)
+ if vs.UniVlan != of.VlanNone && vs.UniVlan != of.VlanAny {
+ flow.SetSetVlan(vs.UniVlan)
+ } else {
+ flow.SetPopVlan()
+ }
+ default:
+ logger.Errorw(ctx, "Invalid Vlan Control Option", log.Fields{"Value": vs.VlanControl})
+ return errorCodes.ErrInvalidParamInRequest
+ }
+ return nil
+}
+
+// setUSMatchActionVlanT0 - Sets the Match & Action w.r.t Vlans for US Table-0
+// based on different Vlan Controls
+func (vs *VoltService) setUSMatchActionVlanT0(flow *of.VoltSubFlow) error {
+ switch vs.VlanControl {
+ case None:
+ flow.SetMatchVlan(vs.SVlan)
+ case ONUCVlanOLTSVlan:
+ if vs.UniVlan != of.VlanNone {
+ flow.SetMatchVlan(vs.UniVlan)
+ flow.SetSetVlan(vs.CVlan)
+ } else {
+ flow.SetPushVlan(vs.CVlan, layers.EthernetTypeDot1Q)
+ }
+ case OLTCVlanOLTSVlan:
+ flow.SetMatchVlan(vs.UniVlan)
+ case ONUCVlan:
+ if vs.UniVlan != of.VlanNone {
+ flow.SetMatchVlan(vs.UniVlan)
+ flow.SetSetVlan(vs.SVlan)
+ } else {
+ flow.SetPushVlan(vs.SVlan, layers.EthernetTypeDot1Q)
+ }
+ case OLTSVlan:
+ flow.SetMatchVlan(vs.UniVlan)
+ default:
+ logger.Errorw(ctx, "Invalid Vlan Control Option", log.Fields{"Value": vs.VlanControl})
+ return errorCodes.ErrInvalidParamInRequest
+ }
+ return nil
+}
+
+// setDSMatchActionVlanT1 - Sets the Match & Action w.r.t Vlans for DS Table-1
+// based on different Vlan Controls
+func (vs *VoltService) setDSMatchActionVlanT1(flow *of.VoltSubFlow) error {
+ switch vs.VlanControl {
+ case None:
+ flow.SetMatchVlan(vs.SVlan)
+ case ONUCVlanOLTSVlan:
+ flow.SetMatchVlan(vs.CVlan)
+ if vs.UniVlan != of.VlanNone {
+ flow.SetSetVlan(vs.UniVlan)
+ } else {
+ flow.SetPopVlan()
+ }
+ case OLTCVlanOLTSVlan:
+ flow.SetMatchVlan(vs.UniVlan)
+ case ONUCVlan:
+ flow.SetMatchVlan(vs.SVlan)
+ if vs.UniVlan != of.VlanNone {
+ flow.SetSetVlan(vs.UniVlan)
+ } else {
+ flow.SetPopVlan()
+ }
+ case OLTSVlan:
+ flow.SetMatchVlan(vs.UniVlan)
+ default:
+ logger.Errorw(ctx, "Invalid Vlan Control Option", log.Fields{"Value": vs.VlanControl})
+ return errorCodes.ErrInvalidParamInRequest
+ }
+ return nil
+}
+
+// SvcUpInd for service up indication
+func (vs *VoltService) SvcUpInd() {
+ vs.AddHsiaFlows()
+}
+
+// SvcDownInd for service down indication
+func (vs *VoltService) SvcDownInd() {
+ vs.DelHsiaFlows()
+}
+
+// SetIpv4Addr to set ipv4 address
+func (vs *VoltService) SetIpv4Addr(addr net.IP) {
+ vs.Ipv4Addr = addr
+}
+
+// SetIpv6Addr to set ipv6 address
+func (vs *VoltService) SetIpv6Addr(addr net.IP) {
+ vs.Ipv6Addr = addr
+}
+
+// SetMacAddr to set mac address
+func (vs *VoltService) SetMacAddr(addr net.HardwareAddr) {
+ vs.MacAddr = addr
+}
+
+// ----------------------------------------------
+// VOLT Application - Related to services
+// ---------------------------------------------
+// ---------------------------------------------------------------
+// Service CRUD functions. These are exposed to the overall binary
+// to be invoked from the point where the CRUD operations are received
+// from the external entities
+
+// AddService : A service in the context of VOLT is a subscriber or service of a
+// subscriber which is uniquely identified by a combination of MAC
+// address, VLAN tags, 802.1p bits. However, in the context of the
+// current implementation, a service is an entity that is identified by a
+// unique L2 (MAC address + VLANs) or unique L3 (VLANs + IP address)
+// FUNC: Add Service
+func (va *VoltApplication) AddService(cfg VoltServiceCfg, oper *VoltServiceOper) error {
+ var mmUs, mmDs *VoltMeter
+ var err error
+
+ //Take the Device lock only in case of NB add request.
+ // Allow internal adds since internal add happen only under
+ // 1. Restore Service from DB
+ // 2. Service Migration
+ if oper == nil {
+ if svc := va.GetService(cfg.Name); svc != nil {
+ logger.Warnw(ctx, "Service Already Exists. Ignoring Add Service Request", log.Fields{"Name": cfg.Name})
+ return errors.New("Service Already Exists")
+ }
+ }
+
+ logger.Infow(ctx, "Service to be configured", log.Fields{"Cfg": cfg})
+ // Service doesn't exist. So create it and add to the port
+ vs := NewVoltService(&cfg)
+ if oper != nil {
+ vs.UsHSIAFlowsApplied = oper.UsHSIAFlowsApplied
+ vs.DsHSIAFlowsApplied = oper.DsHSIAFlowsApplied
+ vs.Ipv4Addr = oper.Ipv4Addr
+ vs.Ipv6Addr = oper.Ipv6Addr
+ vs.MacLearning = cfg.MacLearning
+ vs.PendingFlows = oper.PendingFlows
+ vs.AssociatedFlows = oper.AssociatedFlows
+ vs.DeleteInProgress = oper.DeleteInProgress
+ vs.BwAvailInfo = oper.BwAvailInfo
+ vs.Device = oper.Device
+ } else {
+
+ //Sorting Pbit from highest
+ sort.Slice(vs.Pbits, func(i, j int) bool {
+ return vs.Pbits[i] > vs.Pbits[j]
+ })
+ logger.Infow(ctx, "Sorted Pbits", log.Fields{"Pbits": vs.Pbits})
+ }
+ logger.Infow(ctx, "VolthService...", log.Fields{"vs": vs.Name})
+
+ // The bandwidth and shaper profile combined into meter
+ if mmDs, err = va.GetMeter(cfg.DsMeterProfile); err == nil {
+ vs.DsMeterID = mmDs.ID
+ } else {
+ return errors.New("DownStream meter profile not found")
+ }
+
+ // The aggregated downstream meter profile
+ // if mmAg, err = va.GetMeter(cfg.AggDsMeterProfile); err == nil {
+ // vs.AggDsMeterID = mmAg.ID
+ // } else {
+ // return errors.New("Aggregated meter profile not found")
+ // }
+
+ // if cfg.AggDsMeterProfile == cfg.UsMeterProfile {
+ // vs.UsMeterID = mmAg.ID
+ // } else {
+ // The bandwidth and shaper profile combined into meter
+ if mmUs, err = va.GetMeter(cfg.UsMeterProfile); err == nil {
+ vs.UsMeterID = mmUs.ID
+ } else {
+ return errors.New("Upstream meter profile not found")
+ }
+ //}
+
+ AppMutex.ServiceDataMutex.Lock()
+ defer AppMutex.ServiceDataMutex.Unlock()
+
+ // Add the service to the VNET
+ vnet := va.GetVnet(cfg.SVlan, cfg.CVlan, cfg.UniVlan)
+ if vnet != nil {
+ if vpv := va.GetVnetByPort(vs.Port, cfg.SVlan, cfg.CVlan, cfg.UniVlan); vpv != nil {
+ vpv.VpvLock.Lock()
+ vpv.AddSvc(vs)
+ vpv.VpvLock.Unlock()
+ } else {
+ va.AddVnetToPort(vs.Port, vnet, vs)
+ }
+ } else {
+ logger.Error(ctx, "VNET-does-not-exist-for-service", log.Fields{"ServiceName": cfg.Name})
+ return errors.New("VNET doesn't exist")
+ }
+
+ vs.Version = database.PresentVersionMap[database.ServicePath]
+ // Add the service to the volt application
+ va.ServiceByName.Store(vs.Name, vs)
+ vs.WriteToDb()
+
+ if nil == oper {
+
+ if !vs.UsHSIAFlowsApplied {
+ vs.triggerServiceInProgressInd()
+ }
+
+ //Update meter profiles service count if service is being added from northbound
+ mmDs.AssociatedServices++
+ va.UpdateMeterProf(*mmDs)
+ if mmUs != nil {
+ mmUs.AssociatedServices++
+ va.UpdateMeterProf(*mmUs)
+ }
+ //mmAg.AssociatedServices++
+ //va.UpdateMeterProf(*mmAg)
+ logger.Debug(ctx, "northbound-service-add-sucessful", log.Fields{"ServiceName": vs.Name})
+ }
+
+ logger.Warnw(ctx, "Added Service to DB", log.Fields{"Name": vs.Name, "Port": (vs.Port), "ML": vs.MacLearning})
+ return nil
+}
+
+//DelServiceWithPrefix - Deletes service with the provided prefix.
+// Added for DT/TT usecase with sadis replica interface
+func (va *VoltApplication) DelServiceWithPrefix(prefix string) {
+ va.ServiceByName.Range(func(key, value interface{}) bool {
+ srvName := key.(string)
+ vs := value.(*VoltService)
+ if strings.Contains(srvName, prefix) {
+ va.DelService(srvName, true, nil, false)
+
+ vnetName := strconv.FormatUint(uint64(vs.SVlan), 10) + "-"
+ vnetName = vnetName + strconv.FormatUint(uint64(vs.CVlan), 10) + "-"
+ vnetName = vnetName + strconv.FormatUint(uint64(vs.UniVlan), 10)
+
+ if err := va.DelVnet(vnetName, ""); err != nil {
+ logger.Warnw(ctx, "Delete Vnet Failed", log.Fields{"Name": vnetName, "Error": err})
+ }
+ }
+ return true
+ })
+}
+
+// DelService delete a service form the application
+func (va *VoltApplication) DelService(name string, forceDelete bool, newSvc *VoltServiceCfg, serviceMigration bool) {
+
+ AppMutex.ServiceDataMutex.Lock()
+ defer AppMutex.ServiceDataMutex.Unlock()
+
+ logger.Warnw(ctx, "Delete Service Request", log.Fields{"Service": name, "ForceDelete": forceDelete, "serviceMigration": serviceMigration})
+ var noFlowsPresent bool
+
+ vsIntf, ok := va.ServiceByName.Load(name)
+ if !ok {
+ logger.Warnw(ctx, "Service doesn't exist", log.Fields{"ServiceName": name})
+ return
+ }
+ vs := vsIntf.(*VoltService)
+ vpv := va.GetVnetByPort(vs.Port, vs.SVlan, vs.CVlan, vs.UniVlan)
+ if vpv == nil {
+ logger.Errorw(ctx, "Vpv Not found for Service", log.Fields{"vs": vs.Name})
+ return
+ }
+
+ //Set this to avoid race-condition during flow result processing
+ vs.DeleteInProgress = true
+ vs.ForceDelete = forceDelete
+ vs.ForceWriteToDb()
+
+ if len(vs.AssociatedFlows) == 0 {
+ noFlowsPresent = true
+ }
+ vpv.VpvLock.Lock()
+ defer vpv.VpvLock.Unlock()
+
+ vs.DelHsiaFlows()
+
+ if vpv.IgmpEnabled {
+ va.ReceiverDownInd(vpv.Device, vpv.Port)
+ }
+ logger.Infow(ctx, "Delete Service from VPV", log.Fields{"VPV_Port": vpv.Port, "VPV_SVlan": vpv.SVlan, "VPV_CVlan": vpv.CVlan, "VPV_UniVlan": vpv.UniVlan, "ServiceName": name})
+ vpv.DelService(vs)
+ if vpv.servicesCount.Load() == 0 {
+ va.DelVnetFromPort(vs.Port, vpv)
+ }
+
+ // Delete the service immediately in case of Force Delete
+ // This will be enabled when profile reconciliation happens after restore
+ // of backedup data
+ if vs.ForceDelete {
+ vs.DelFromDb()
+ GetApplication().ServiceByName.Delete(vs.Name)
+ logger.Warnw(ctx, "Deleted service from DB/Cache successfully", log.Fields{"serviceName": vs.Name})
+ }
+
+ meterProfiles := make(map[*VoltMeter]bool)
+
+ if nil != newSvc {
+ logger.Infow(ctx, "Old Service meter profiles", log.Fields{"AGG": vs.AggDsMeterProfile, "DS": vs.DsMeterProfile, "US": vs.UsMeterProfile})
+ logger.Infow(ctx, "New Service meter profiles", log.Fields{"AGG": newSvc.AggDsMeterProfile, "DS": newSvc.DsMeterProfile, "US": newSvc.UsMeterProfile})
+ }
+ skipMeterDeletion := false
+ if aggMeter, ok := va.MeterMgr.GetMeterByID(vs.AggDsMeterID); ok {
+ if nil != newSvc && aggMeter.Name == newSvc.AggDsMeterProfile {
+ skipMeterDeletion = true
+ }
+
+ meterProfiles[aggMeter] = skipMeterDeletion
+ skipMeterDeletion = false
+ }
+ if dsMeter, ok := va.MeterMgr.GetMeterByID(vs.DsMeterID); ok {
+ if nil != newSvc && dsMeter.Name == newSvc.DsMeterProfile {
+ skipMeterDeletion = true
+ }
+ meterProfiles[dsMeter] = skipMeterDeletion
+ skipMeterDeletion = false
+ }
+ if vs.AggDsMeterID != vs.UsMeterID {
+ if usMeter, ok := va.MeterMgr.GetMeterByID(vs.UsMeterID); ok {
+ if nil != newSvc && usMeter.Name == newSvc.UsMeterProfile {
+ skipMeterDeletion = true
+ }
+ meterProfiles[usMeter] = skipMeterDeletion
+ }
+ }
+
+ for meter, skipMeterDeletion := range meterProfiles {
+ if nil == meter {
+ logger.Debug(ctx, "Null meter found, continuing")
+ continue
+ }
+ if meter.AssociatedServices > 0 {
+ meter.AssociatedServices--
+ if meter.AssociatedServices == 0 && !skipMeterDeletion {
+ logger.Info(ctx, "Meter should be deleted now\n", log.Fields{"MeterID": meter})
+ va.UpdateMeterProf(*meter)
+ }
+ }
+ }
+
+ if noFlowsPresent || vs.ForceDelete {
+ vs.CheckAndDeleteService()
+ }
+
+ //Delete the per service counter too
+ va.ServiceCounters.Delete(name)
+ if vs.IgmpEnabled && vs.EnableMulticastKPI {
+ _ = db.DelAllServiceChannelCounter(name)
+ }
+}
+
+//AddFlows - Adds the flow to the service
+// Triggers flow addition after registering for flow indication event
+func (vs *VoltService) AddFlows(device *VoltDevice, flow *of.VoltFlow) error {
+
+ // Using locks instead of concurrent map for PendingFlows to avoid
+ // race condition during flow response indication processing
+ vs.ServiceLock.Lock()
+ defer vs.ServiceLock.Unlock()
+
+ for cookie := range flow.SubFlows {
+ cookie := strconv.FormatUint(cookie, 10)
+ fe := &FlowEvent{
+ eType: EventTypeServiceFlowAdded,
+ device: device.Name,
+ cookie: cookie,
+ eventData: vs,
+ }
+ device.RegisterFlowAddEvent(cookie, fe)
+ vs.PendingFlows[cookie] = true
+ }
+ return cntlr.GetController().AddFlows(vs.Port, device.Name, flow)
+}
+
+//FlowInstallSuccess - Called when corresponding service flow installation is success
+// If no more pending flows, HSIA indication wil be triggered
+func (vs *VoltService) FlowInstallSuccess(cookie string, bwAvailInfo of.BwAvailDetails) {
+ if vs.DeleteInProgress {
+ logger.Warnw(ctx, "Skipping Flow Add Success Notification. Service deletion in-progress", log.Fields{"Cookie": cookie, "Service": vs.Name})
+ return
+ }
+ vs.ServiceLock.Lock()
+
+ if _, ok := vs.PendingFlows[cookie]; !ok {
+ logger.Errorw(ctx, "Flow Add Success for unknown Cookie", log.Fields{"Service": vs.Name, "Cookie": cookie})
+ vs.ServiceLock.Unlock()
+ return
+ }
+
+ delete(vs.PendingFlows, cookie)
+ vs.AssociatedFlows[cookie] = true
+ vs.ServiceLock.Unlock()
+ var prevBwAvail, presentBwAvail string
+ if bwAvailInfo.PrevBw != "" && bwAvailInfo.PresentBw != "" {
+ prevBwAvail = bwAvailInfo.PrevBw
+ presentBwAvail = bwAvailInfo.PresentBw
+ vs.BwAvailInfo = prevBwAvail + "," + presentBwAvail
+ logger.Debug(ctx, "Bandwidth-value-formed", log.Fields{"BwAvailInfo": vs.BwAvailInfo})
+ }
+ vs.WriteToDb()
+
+ if len(vs.PendingFlows) == 0 && vs.DsHSIAFlowsApplied {
+
+ device, err := GetApplication().GetDeviceFromPort(vs.Port)
+ if err != nil {
+ logger.Errorw(ctx, "Error Getting Device. Dropping HSIA Success indication to NB", log.Fields{"Reason": err.Error(), "Service": vs.Name, "Port": vs.Port})
+ return
+ } else if device.State != controller.DeviceStateUP {
+ logger.Warnw(ctx, "Device state Down. Dropping HSIA Success indication to NB", log.Fields{"Service": vs.Name, "Port": vs.Port})
+ return
+ }
+
+ if vs.Trigger == ServiceVlanUpdate {
+ vs.Trigger = NBActivate
+ defer vs.WriteToDb()
+ }
+ logger.Infow(ctx, "All Flows installed for Service", log.Fields{"Service": vs.Name})
+ return
+ }
+ logger.Infow(ctx, "Processed Service Flow Add Success Indication", log.Fields{"Cookie": cookie, "Service": vs.Name, "DsFlowsApplied": vs.DsHSIAFlowsApplied})
+}
+
+//FlowInstallFailure - Called when corresponding service flow installation is failed
+// Trigger service failure indication to NB
+func (vs *VoltService) FlowInstallFailure(cookie string, errorCode uint32, errReason string) {
+ vs.ServiceLock.RLock()
+
+ if _, ok := vs.PendingFlows[cookie]; !ok {
+ logger.Errorw(ctx, "Flow Add Failure for unknown Cookie", log.Fields{"Service": vs.Name, "Cookie": cookie})
+ vs.ServiceLock.RUnlock()
+ return
+ }
+ vs.ServiceLock.RUnlock()
+ logger.Errorw(ctx, "HSIA Flow Add Failure Notification", log.Fields{"uniPort": vs.Port, "Cookie": cookie, "Service": vs.Name, "ErrorCode": errorCode, "ErrorReason": errReason})
+ vs.triggerServiceFailureInd(errorCode, errReason)
+}
+
+//DelFlows - Deletes the flow from the service
+// Triggers flow deletion after registering for flow indication event
+func (vs *VoltService) DelFlows(device *VoltDevice, flow *of.VoltFlow) error {
+
+ if !vs.ForceDelete {
+ // Using locks instead of concurrent map for AssociatedFlows to avoid
+ // race condition during flow response indication processing
+ vs.ServiceLock.Lock()
+ defer vs.ServiceLock.Unlock()
+
+ for cookie := range flow.SubFlows {
+ cookie := strconv.FormatUint(cookie, 10)
+ fe := &FlowEvent{
+ eType: EventTypeServiceFlowRemoved,
+ cookie: cookie,
+ eventData: vs,
+ }
+ device.RegisterFlowDelEvent(cookie, fe)
+ }
+ }
+ return cntlr.GetController().DelFlows(vs.Port, device.Name, flow)
+}
+
+//CheckAndDeleteService - remove service from DB is there are no pending flows to be removed
+func (vs *VoltService) CheckAndDeleteService() {
+ if vs.DeleteInProgress && len(vs.AssociatedFlows) == 0 && !vs.DsHSIAFlowsApplied {
+ vs.DelFromDb()
+ GetApplication().ServiceByName.Delete(vs.Name)
+ logger.Warnw(ctx, "Deleted service from DB/Cache successfully", log.Fields{"serviceName": vs.Name})
+ }
+}
+
+//FlowRemoveSuccess - Called when corresponding service flow removal is success
+// If no more associated flows, DelHSIA indication wil be triggered
+func (vs *VoltService) FlowRemoveSuccess(cookie string) {
+
+ // if vs.DeleteInProgress {
+ // logger.Warnw(ctx, "Skipping Flow Remove Success Notification. Service deletion in-progress", log.Fields{"Cookie": cookie, "Service": vs.Name})
+ // return
+ // }
+ vs.ServiceLock.Lock()
+ logger.Infow(ctx, "Processing Service Flow Remove Success Indication", log.Fields{"Cookie": cookie, "Service": vs.Name, "Associated Flows": vs.AssociatedFlows, "DsFlowsApplied": vs.DsHSIAFlowsApplied})
+
+ if _, ok := vs.AssociatedFlows[cookie]; ok {
+ delete(vs.AssociatedFlows, cookie)
+ } else if _, ok := vs.PendingFlows[cookie]; ok {
+ logger.Errorw(ctx, "Service Flow Remove: Cookie Present in Pending Flow list. No Action", log.Fields{"Service": vs.Name, "Cookie": cookie, "AssociatedFlows": vs.AssociatedFlows, "PendingFlows": vs.PendingFlows})
+ } else {
+ logger.Errorw(ctx, "Service Flow Remove Success for unknown Cookie", log.Fields{"Service": vs.Name, "Cookie": cookie, "AssociatedFlows": vs.AssociatedFlows, "PendingFlows": vs.PendingFlows})
+ }
+
+ vs.ServiceLock.Unlock()
+
+ vs.WriteToDb()
+
+ if len(vs.AssociatedFlows) == 0 && !vs.DsHSIAFlowsApplied {
+
+ device := GetApplication().GetDevice(vs.Device)
+ if device == nil {
+ logger.Errorw(ctx, "Error Getting Device. Dropping DEL_HSIA Success indication to NB", log.Fields{"Service": vs.Name, "Port": vs.Port})
+ return
+ } else if device.State != controller.DeviceStateUP {
+ logger.Warnw(ctx, "Device state Down. Dropping DEL_HSIA Success indication to NB", log.Fields{"Service": vs.Name, "Port": vs.Port})
+ return
+ }
+
+ if vs.UpdateInProgress {
+ vs.updateVnetProfile(vs.Device)
+ //Not sending DEL_HSIA Indication since it wil be generated internally by SubMgr
+ return
+ }
+ logger.Infow(ctx, "All Flows removed for Service. Triggering Service De-activation Success indication to NB", log.Fields{"Service": vs.Name, "DeleteFlag": vs.DeleteInProgress})
+ vs.CheckAndDeleteService()
+
+ return
+ }
+ logger.Infow(ctx, "Processed Service Flow Remove Success Indication", log.Fields{"Cookie": cookie, "Service": vs.Name, "Associated Flows": vs.AssociatedFlows, "DsFlowsApplied": vs.DsHSIAFlowsApplied})
+}
+
+//FlowRemoveFailure - Called when corresponding service flow installation is failed
+// Trigger service failure indication to NB
+func (vs *VoltService) FlowRemoveFailure(cookie string, errorCode uint32, errReason string) {
+ vs.ServiceLock.RLock()
+
+ if _, ok := vs.AssociatedFlows[cookie]; !ok {
+ logger.Errorw(ctx, "Flow Failure for unknown Cookie", log.Fields{"Service": vs.Name, "Cookie": cookie})
+ vs.ServiceLock.RUnlock()
+ return
+ }
+ if vs.DeleteInProgress {
+ delete(vs.AssociatedFlows, cookie)
+ }
+ vs.ServiceLock.RUnlock()
+ logger.Errorw(ctx, "Service Flow Remove Failure Notification", log.Fields{"uniPort": vs.Port, "Cookie": cookie, "Service": vs.Name, "ErrorCode": errorCode, "ErrorReason": errReason})
+
+ vs.triggerServiceFailureInd(errorCode, errReason)
+ vs.CheckAndDeleteService()
+}
+
+func (vs *VoltService) triggerServiceFailureInd(errorCode uint32, errReason string) {
+ device, err := GetApplication().GetDeviceFromPort(vs.Port)
+ if err != nil {
+ logger.Errorw(ctx, "Error Getting Device. Dropping DEL_HSIA Failure indication to NB", log.Fields{"Reason": err.Error(), "Service": vs.Name, "Port": vs.Port})
+ return
+ } else if device.State != controller.DeviceStateUP {
+ logger.Warnw(ctx, "Device state Down. Dropping DEL_HSIA Failure indication to NB", log.Fields{"Service": vs.Name, "Port": vs.Port})
+ return
+ }
+}
+
+// RestoreSvcsFromDb read from the DB and restore all the services
+func (va *VoltApplication) RestoreSvcsFromDb() {
+ // VNETS must be learnt first
+ vss, _ := db.GetServices()
+ for _, vs := range vss {
+ b, ok := vs.Value.([]byte)
+ if !ok {
+ logger.Warn(ctx, "The value type is not []byte")
+ continue
+ }
+ var vvs VoltService
+ err := json.Unmarshal(b, &vvs)
+ if err != nil {
+ logger.Warn(ctx, "Unmarshal of VNET failed")
+ continue
+ }
+ logger.Debugw(ctx, "Retrieved Service", log.Fields{"Service": vvs.VoltServiceCfg})
+ if err := va.AddService(vvs.VoltServiceCfg, &vvs.VoltServiceOper); err != nil {
+ logger.Warnw(ctx, "Add New Service Failed", log.Fields{"Service": vvs.Name, "Error": err})
+ }
+
+ if vvs.VoltServiceOper.DeleteInProgress {
+ va.ServicesToDelete[vvs.VoltServiceCfg.Name] = true
+ logger.Warnw(ctx, "Service (restored) to be deleted", log.Fields{"Service": vvs.Name})
+ }
+ }
+}
+
+// GetService to get service
+func (va *VoltApplication) GetService(name string) *VoltService {
+ if vs, ok := va.ServiceByName.Load(name); ok {
+ return vs.(*VoltService)
+ }
+ return nil
+}
+
+// GetCircuitID to get circuit id
+func (vs *VoltService) GetCircuitID() []byte {
+ return []byte(vs.CircuitID)
+}
+
+// GetRemoteID to get remote id
+func (vs *VoltService) GetRemoteID() []byte {
+ return []byte(vs.RemoteID)
+}
+
+// IPAssigned to check if ip is assigned
+func (vs *VoltService) IPAssigned() bool {
+ if vs.Ipv4Addr != nil && !vs.Ipv4Addr.Equal(net.ParseIP("0.0.0.0")) {
+ return true
+ } else if vs.Ipv6Addr != nil && !vs.Ipv6Addr.Equal(net.ParseIP("0:0:0:0:0:0:0:0")) {
+ return true
+ }
+ return false
+}
+
+// GetServiceNameFromCookie to get service name from cookie
+func (va *VoltApplication) GetServiceNameFromCookie(cookie uint64, portName string, pbit uint8, device string, tableMetadata uint64) *VoltService {
+
+ var vlan uint64
+ vlanControl := (tableMetadata >> 32) & 0xF
+
+ if vlanControl == uint64(OLTCVlanOLTSVlan) {
+ // Fetching UniVlan for vlanControl OLTCVLANOLTSVLAN
+ vlan = (tableMetadata >> 16) & 0xFFFF
+ } else {
+ //Fetching CVlan for other vlanControl
+ vlan = cookie >> 52
+ }
+ logger.Infow(ctx, "Configured Params", log.Fields{"VlanControl": vlanControl, "vlan": vlan})
+ var vlans []of.VlanType
+ vlans = append(vlans, of.VlanType(vlan))
+ service := GetApplication().GetServiceFromCvlan(device, portName, vlans, uint8(pbit))
+ if nil != service {
+ logger.Info(ctx, "Service Found for", log.Fields{"serviceName": service.Name, "portName": portName, "ctag": vlan})
+ } else {
+ logger.Errorw(ctx, "No Service for", log.Fields{"portName": portName, "ctag": vlan, "Pbit": pbit, "device": device, "VlanControl": vlanControl})
+ }
+ return service
+}
+
+//MigrateServicesReqStatus - update vnet request status
+type MigrateServicesReqStatus string
+
+const (
+ //MigrateSrvsReqInit constant
+ MigrateSrvsReqInit MigrateServicesReqStatus = "Init"
+ //MigrateSrvsReqDeactTriggered constant
+ MigrateSrvsReqDeactTriggered MigrateServicesReqStatus = "Profiles Deactivated"
+ //MigrateSrvsReqCompleted constant
+ MigrateSrvsReqCompleted MigrateServicesReqStatus = "Update Complete"
+)
+
+//MigrateServicesRequest - update vnet request params
+type MigrateServicesRequest struct {
+ ID string
+ OldVnetID string
+ NewVnetID string
+ ServicesList map[string]bool
+ DeviceID string
+ Status MigrateServicesReqStatus
+ MigrateServicesLock sync.RWMutex
+}
+
+func newMigrateServicesRequest(id string, oldVnetID string, newVnetID string, serviceMap map[string]bool, deviceID string) *MigrateServicesRequest {
+
+ var msr MigrateServicesRequest
+ msr.OldVnetID = oldVnetID
+ msr.NewVnetID = newVnetID
+ msr.ID = id
+ msr.ServicesList = serviceMap
+ msr.DeviceID = deviceID
+ msr.Status = MigrateSrvsReqInit
+ return &msr
+}
+
+//GetMsrKey - generates migrate service request key
+func (msr *MigrateServicesRequest) GetMsrKey() string {
+ return msr.OldVnetID + "-" + msr.ID
+}
+
+// //isRequestComplete - return if all request has been processed and completed
+// // RequestProcessed indicates that all the profile de-activation has been triggered
+// // And the associated profiles indicates the profiles awaiting results
+// func (msr *MigrateServicesRequest) isRequestComplete() bool {
+// //return edr.RequestProcessed && (len(edr.AssociatedProfiles) == 0)
+// return (len(edr.AssociatedProfiles) == 0)
+// }
+
+//WriteToDB - writes the udpate vnet request details ot DB
+func (msr *MigrateServicesRequest) WriteToDB() {
+ logger.Debugw(ctx, "Adding Migrate Service Request to DB", log.Fields{"OldVnet": msr.OldVnetID, "NewVnet": msr.NewVnetID, "Device": msr.DeviceID, "RequestID": msr.ID, "ServiceCount": len(msr.ServicesList)})
+ if b, err := json.Marshal(msr); err == nil {
+ if err = db.PutMigrateServicesReq(msr.DeviceID, msr.GetMsrKey(), string(b)); err != nil {
+ logger.Warnw(ctx, "PutMigrateServicesReq Failed", log.Fields{"OldVnet": msr.OldVnetID, "NewVnet": msr.NewVnetID,
+ "Device": msr.DeviceID, "Error": err})
+ }
+ }
+}
+
+//MigrateServices - updated vnet profile for services
+func (va *VoltApplication) MigrateServices(serialNum string, reqID string, oldVnetID, newVnetID string, serviceList []string) error {
+
+ logger.Warnw(ctx, "Migrate Serviec Request Received", log.Fields{"SerialNum": serialNum, "RequestID": reqID, "OldVnet": oldVnetID, "NewVnet": newVnetID, "ServiceList": serviceList})
+ if _, ok := va.VnetsByName.Load(oldVnetID); !ok {
+ return errors.New("Old Vnet Id not found")
+ }
+ if _, ok := va.VnetsByName.Load(newVnetID); !ok {
+ return errors.New("New Vnet Id not found")
+ }
+
+ d := va.GetDeviceBySerialNo(serialNum)
+ if d == nil {
+ logger.Errorw(ctx, "Error Getting Device", log.Fields{"SerialNum": serialNum})
+ return errorCodes.ErrDeviceNotFound
+ }
+
+ serviceMap := make(map[string]bool)
+
+ for _, service := range serviceList {
+ serviceMap[service] = false
+ }
+ msr := newMigrateServicesRequest(reqID, oldVnetID, newVnetID, serviceMap, d.Name)
+ msr.WriteToDB()
+
+ d.AddMigratingServices(msr)
+ go msr.ProcessMigrateServicesProfRequest()
+ return nil
+}
+
+//ProcessMigrateServicesProfRequest - collects all associated profiles
+func (msr *MigrateServicesRequest) ProcessMigrateServicesProfRequest() {
+ va := GetApplication()
+ for srv, processed := range msr.ServicesList {
+
+ //Indicates new service is already created and only deletion of old one is pending
+ if processed {
+ va.DelService(srv, true, nil, true)
+ msr.serviceMigrated(srv)
+ continue
+ }
+
+ logger.Infow(ctx, "Migrate Service Triggering", log.Fields{"Service": srv})
+ if vsIntf, ok := va.ServiceByName.Load(srv); ok {
+ vs := vsIntf.(*VoltService)
+ vpv := va.GetVnetByPort(vs.Port, vs.SVlan, vs.CVlan, vs.UniVlan)
+ if vpv == nil {
+ logger.Errorw(ctx, "Vpv Not found for Service", log.Fields{"vs": vs.Name, "port": vs.Port, "Vnet": vs.VnetID})
+ continue
+ }
+ logger.Infow(ctx, "Migrating Service", log.Fields{"Service": vs.Name, "UsFlowApplied": vs.UsHSIAFlowsApplied})
+ vpv.Blocked = true
+
+ // setDeactTrigger := func(key, value interface{}) bool {
+ // vs := value.(*VoltService)
+ vs.ServiceLock.Lock()
+ vs.UpdateInProgress = true
+ metadata := &MigrateServiceMetadata{
+ NewVnetID: msr.NewVnetID,
+ RequestID: msr.ID,
+ }
+ vs.Metadata = metadata
+ vs.ServiceLock.Unlock()
+
+ //vpv flows will be removed when last service is removed from it and
+ // new vpv flows will be installed when new service is added
+ if vs.UsHSIAFlowsApplied {
+ vpv.DelTrapFlows()
+ vs.DelHsiaFlows()
+ logger.Info(ctx, "Remove Service Flows Triggered", log.Fields{"Service": srv, "US": vs.UsHSIAFlowsApplied, "DS": vs.DsHSIAFlowsApplied})
+ } else {
+ vs.updateVnetProfile(msr.DeviceID)
+ }
+ } else {
+ logger.Warnw(ctx, "Migrate Service Failed: Service Not Found", log.Fields{"Service": srv, "Vnet": msr.OldVnetID})
+ }
+ }
+}
+
+//AddMigratingServices - store msr info to device obj
+func (d *VoltDevice) AddMigratingServices(msr *MigrateServicesRequest) {
+
+ var msrMap *util.ConcurrentMap
+ if msrMapIntf, ok := d.MigratingServices.Get(msr.OldVnetID); !ok {
+ msrMap = util.NewConcurrentMap()
+ } else {
+ msrMap = msrMapIntf.(*util.ConcurrentMap)
+ }
+
+ msrMap.Set(msr.ID, msr)
+ logger.Infow(ctx, "1: MsrListLen", log.Fields{"Len": msrMap.Length(), "Vnet": msr.OldVnetID})
+
+ d.MigratingServices.Set(msr.OldVnetID, msrMap)
+ logger.Infow(ctx, "1: DeviceMsr", log.Fields{"Device": d.Name, "Vnet": msr.OldVnetID, "Len": d.MigratingServices.Length()})
+
+}
+
+//getMigrateServicesRequest - fetches msr info from device
+func (va *VoltApplication) getMigrateServicesRequest(deviceID string, oldVnetID string, requestID string) *MigrateServicesRequest {
+ if vd := va.GetDevice(deviceID); vd != nil {
+ logger.Infow(ctx, "2: DeviceMsr", log.Fields{"Device": deviceID, "Vnet": oldVnetID, "Len": vd.MigratingServices.Length()})
+ if msrListIntf, ok := vd.MigratingServices.Get(oldVnetID); ok {
+ msrList := msrListIntf.(*util.ConcurrentMap)
+ logger.Infow(ctx, "2: MsrListLen", log.Fields{"Len": msrList.Length(), "Vnet": oldVnetID})
+ if msrObj, ok := msrList.Get(requestID); ok {
+ return msrObj.(*MigrateServicesRequest)
+ }
+
+ }
+ }
+ logger.Errorw(ctx, "Device Not Found", log.Fields{"Device": deviceID})
+ return nil
+}
+
+//updateMigrateServicesRequest - Updates the device with updated msr
+func (va *VoltApplication) updateMigrateServicesRequest(deviceID string, oldVnetID string, requestID string, msr *MigrateServicesRequest) {
+ if vd := va.GetDevice(deviceID); vd != nil {
+ if msrList, ok := vd.MigratingServices.Get(oldVnetID); ok {
+ if _, ok := msrList.(*util.ConcurrentMap).Get(requestID); ok {
+ msrList.(*util.ConcurrentMap).Set(requestID, msr)
+ }
+ }
+ }
+}
+
+//updateVnetProfile - Called on flow process completion
+// Removes old service and creates new VPV & service with udpated vnet profile
+func (vs *VoltService) updateVnetProfile(deviceID string) {
+
+ logger.Info(ctx, "Update Vnet Profile Triggering", log.Fields{"Service": vs.Name, "US": vs.UsHSIAFlowsApplied, "DS": vs.DsHSIAFlowsApplied})
+
+ nvs := VoltService{}
+ nvs.VoltServiceCfg = vs.VoltServiceCfg
+ nvs.Device = vs.Device
+ nvs.Ipv4Addr = vs.Ipv4Addr
+ nvs.Ipv6Addr = vs.Ipv6Addr
+ nvs.UsMeterID = vs.UsMeterID
+ nvs.DsMeterID = vs.DsMeterID
+ nvs.AggDsMeterID = vs.AggDsMeterID
+ nvs.UsHSIAFlowsApplied = vs.UsHSIAFlowsApplied
+ nvs.DsHSIAFlowsApplied = vs.DsHSIAFlowsApplied
+ nvs.UsDhcpFlowsApplied = vs.UsDhcpFlowsApplied
+ nvs.DsDhcpFlowsApplied = vs.DsDhcpFlowsApplied
+ nvs.IgmpFlowsApplied = vs.IgmpFlowsApplied
+ nvs.Icmpv6FlowsApplied = vs.Icmpv6FlowsApplied
+ nvs.PendingFlows = vs.PendingFlows
+ nvs.AssociatedFlows = vs.AssociatedFlows
+ nvs.DeleteInProgress = vs.DeleteInProgress
+ nvs.ForceDelete = vs.ForceDelete
+ nvs.BwAvailInfo = vs.BwAvailInfo
+ nvs.UpdateInProgress = vs.UpdateInProgress
+
+ if nvs.DeleteInProgress {
+ logger.Warnw(ctx, "Skipping Service Migration. Service Delete in Progress", log.Fields{"Device": deviceID, "Service": vs.Name, "Vnet": vs.VnetID})
+ return
+ }
+
+ metadata := vs.Metadata.(*MigrateServiceMetadata)
+ oldVnetID := vs.VnetID
+ nvs.VnetID = metadata.NewVnetID
+ id := metadata.RequestID
+ oldSrvName := vs.Name
+
+ if metadata == nil || metadata.NewVnetID == "" {
+ logger.Errorw(ctx, "Migrate Service Metadata not found. Dropping vnet profile update request", log.Fields{"Service": vs.Name, "Vnet": vs.VnetID})
+ return
+ }
+
+ //First add the new service and then only delete the old service
+ // Since if post del service in case of pod crash or reboot, the service data will be lost
+ va := GetApplication()
+ msr := va.getMigrateServicesRequest(deviceID, oldVnetID, id)
+ vnets := strings.Split(metadata.NewVnetID, "-")
+ svlan, _ := strconv.Atoi(vnets[0])
+ nvs.SVlan = of.VlanType(svlan)
+ nvs.UpdateInProgress = false
+ nvs.Metadata = nil
+ nvs.Trigger = ServiceVlanUpdate
+
+ svcName := vs.Port + "-" + strconv.FormatUint(uint64(nvs.SVlan), 10) + "-"
+ svcName = svcName + strconv.FormatUint(uint64(vs.CVlan), 10) + "-"
+ nvs.Name = svcName + strconv.FormatUint(uint64(vs.TechProfileID), 10)
+
+ //TODO:Nav Pass a copy, not the pointer
+ logger.Infow(ctx, "Add New Service Triggering", log.Fields{"Service": nvs.Name, "US": nvs.UsHSIAFlowsApplied, "DS": nvs.DsHSIAFlowsApplied, "DelFlag": nvs.DeleteInProgress})
+ if err := va.AddService(nvs.VoltServiceCfg, &nvs.VoltServiceOper); err != nil {
+ logger.Warnw(ctx, "Add New Service Failed", log.Fields{"Service": nvs.Name, "Error": err})
+ }
+ logger.Infow(ctx, "Add New Service Triggered", log.Fields{"Service": nvs.Name, "US": nvs.UsHSIAFlowsApplied, "DS": nvs.DsHSIAFlowsApplied, "DelFlag": nvs.DeleteInProgress})
+
+ msr.ServicesList[oldSrvName] = true
+ va.updateMigrateServicesRequest(deviceID, oldVnetID, id, msr)
+ msr.WriteToDB()
+
+ logger.Infow(ctx, "Del Old Service Triggering", log.Fields{"Service": oldSrvName, "US": vs.UsHSIAFlowsApplied, "DS": vs.DsHSIAFlowsApplied, "DelFlag": vs.DeleteInProgress})
+ va.DelService(oldSrvName, true, nil, true)
+ logger.Infow(ctx, "Del Old Service Triggered", log.Fields{"Service": oldSrvName, "US": vs.UsHSIAFlowsApplied, "DS": vs.DsHSIAFlowsApplied, "DelFlag": vs.DeleteInProgress})
+ msr.serviceMigrated(oldSrvName)
+}
+
+//serviceMigrated - called on successful service updation
+// Removes the service entry from servicelist and deletes the request on process completion
+func (msr *MigrateServicesRequest) serviceMigrated(serviceName string) {
+
+ msr.MigrateServicesLock.Lock()
+ defer msr.MigrateServicesLock.Unlock()
+
+ delete(msr.ServicesList, serviceName)
+
+ if len(msr.ServicesList) == 0 {
+ _ = db.DelMigrateServicesReq(msr.DeviceID, msr.GetMsrKey())
+ return
+ }
+ msr.WriteToDB()
+ //TODO:Nav - Need for any Response to SubMgr?
+}
+
+//TriggerPendingMigrateServicesReq - trigger pending service request
+func (va *VoltApplication) TriggerPendingMigrateServicesReq(device string) {
+ va.FetchAndProcessAllMigrateServicesReq(device, storeAndProcessMigrateSrvRequest)
+}
+
+//FetchAndProcessAllMigrateServicesReq - fetch all pending migrate services req from DB and process based on provided func
+func (va *VoltApplication) FetchAndProcessAllMigrateServicesReq(device string, msrAction func(*MigrateServicesRequest)) {
+
+ msrList, _ := db.GetAllMigrateServicesReq(device)
+ for _, msr := range msrList {
+ b, ok := msr.Value.([]byte)
+ if !ok {
+ logger.Warn(ctx, "The value type is not []byte")
+ continue
+ }
+ msr := va.createMigrateServicesFromString(b)
+ msrAction(msr)
+ logger.Warnw(ctx, "Triggering Pending Migrate Services Req", log.Fields{"OldVnet": msr.OldVnetID, "NewVnet": msr.NewVnetID, "Device": device, "PendingProfiles": len(msr.ServicesList)})
+
+ }
+}
+
+// createMigrateServicesFromString to create Service from string
+func (va *VoltApplication) createMigrateServicesFromString(b []byte) *MigrateServicesRequest {
+ var msr MigrateServicesRequest
+ if err := json.Unmarshal(b, &msr); err == nil {
+ logger.Debugw(ctx, "Adding Migrate Services Request From Db", log.Fields{"Vlan": msr.OldVnetID})
+
+ } else {
+ logger.Warn(ctx, "Unmarshal failed")
+ }
+ return &msr
+}
+
+//storeAndProcessMigrateSrvRequest - stores the msr info in device obj and triggers req
+func storeAndProcessMigrateSrvRequest(msr *MigrateServicesRequest) {
+ d := GetApplication().GetDevice(msr.DeviceID)
+ d.AddMigratingServices(msr)
+ msr.ProcessMigrateServicesProfRequest()
+}
+
+//forceUpdateAllServices - force udpate services with new vnet profile
+func forceUpdateAllServices(msr *MigrateServicesRequest) {
+ for srv := range msr.ServicesList {
+ if vsIntf, ok := GetApplication().ServiceByName.Load(srv); ok {
+ vsIntf.(*VoltService).updateVnetProfile(msr.DeviceID)
+ }
+ }
+ _ = db.DelMigrateServicesReq(msr.DeviceID, msr.GetMsrKey())
+}
+
+//DeepEqualServicecfg - checks if the given service cfgs are same
+func (va *VoltApplication) DeepEqualServicecfg(evs *VoltServiceCfg, nvs *VoltServiceCfg) bool {
+ if nvs.Name != evs.Name {
+ return false
+ }
+ if nvs.UniVlan != evs.UniVlan {
+ return false
+ }
+ if nvs.CVlan != evs.CVlan {
+ return false
+ }
+ if nvs.SVlan != evs.SVlan {
+ return false
+ }
+ if nvs.SVlanTpid != 0 && nvs.SVlanTpid != evs.SVlanTpid {
+ return false
+ }
+ if !util.IsPbitSliceSame(nvs.Pbits, evs.Pbits) {
+ return false
+ }
+ if !reflect.DeepEqual(nvs.DsRemarkPbitsMap, evs.DsRemarkPbitsMap) {
+ return false
+ }
+ if nvs.TechProfileID != evs.TechProfileID {
+ return false
+ }
+ if nvs.CircuitID != evs.CircuitID {
+ return false
+ }
+ if !bytes.Equal(nvs.RemoteID, evs.RemoteID) {
+ return false
+ }
+ if nvs.Port != evs.Port {
+ return false
+ }
+ if nvs.PonPort != evs.PonPort {
+ return false
+ }
+ if evs.MacLearning == MacLearningNone && !util.MacAddrsMatch(nvs.MacAddr, evs.MacAddr) {
+ return false
+ }
+ if nvs.IsOption82Disabled != evs.IsOption82Disabled {
+ return false
+ }
+ if nvs.IgmpEnabled != evs.IgmpEnabled {
+ return false
+ }
+ if nvs.McastService != evs.McastService {
+ return false
+ }
+ if nvs.ONTEtherTypeClassification != evs.ONTEtherTypeClassification {
+ return false
+ }
+ if nvs.UsMeterProfile != evs.UsMeterProfile {
+ return false
+ }
+ if nvs.DsMeterProfile != evs.DsMeterProfile {
+ return false
+ }
+ if nvs.AggDsMeterProfile != evs.AggDsMeterProfile {
+ return false
+ }
+ if nvs.VnetID != evs.VnetID {
+ return false
+ }
+ if nvs.MvlanProfileName != evs.MvlanProfileName {
+ return false
+ }
+ if nvs.RemoteIDType != evs.RemoteIDType {
+ return false
+ }
+ if nvs.SchedID != evs.SchedID {
+ return false
+ }
+ if nvs.AllowTransparent != evs.AllowTransparent {
+ return false
+ }
+ if nvs.EnableMulticastKPI != evs.EnableMulticastKPI {
+ return false
+ }
+ if nvs.DataRateAttr != evs.DataRateAttr {
+ return false
+ }
+ if nvs.MinDataRateUs != evs.MinDataRateUs {
+ return false
+ }
+ if nvs.MinDataRateDs != evs.MinDataRateDs {
+ return false
+ }
+ if nvs.MaxDataRateUs != evs.MaxDataRateUs {
+ return false
+ }
+ if nvs.MaxDataRateDs != evs.MaxDataRateDs {
+ return false
+ }
+
+ return true
+}
+
+//TriggerAssociatedFlowDelete - re-trigger service flow delete for pending delete flows
+func (vs *VoltService) TriggerAssociatedFlowDelete() bool {
+
+ //Clear the Flows flag if already set
+ //This case happens only in case of some race condition
+ if vs.UsHSIAFlowsApplied {
+ if err := vs.DelUsHsiaFlows(); err != nil {
+ logger.Errorw(ctx, "DelUsHsiaFlows Failed", log.Fields{"Device": vs.Device, "Service": vs.Name, "Error": err})
+ }
+ }
+
+ if vs.DsHSIAFlowsApplied {
+ if err := vs.DelDsHsiaFlows(); err != nil {
+ logger.Errorw(ctx, "DelDsHsiaFlows Failed", log.Fields{"Device": vs.Device, "Service": vs.Name, "Error": err})
+ }
+ }
+
+ vs.ServiceLock.Lock()
+ cookieList := []uint64{}
+ for cookie := range vs.AssociatedFlows {
+ cookieList = append(cookieList, convertToUInt64(cookie))
+ }
+ vs.ServiceLock.Unlock()
+
+ if len(cookieList) == 0 {
+ return false
+ }
+
+ //Trigger Flow Delete
+ for _, cookie := range cookieList {
+ if vd := GetApplication().GetDevice(vs.Device); vd != nil {
+ flow := &of.VoltFlow{}
+ flow.SubFlows = make(map[uint64]*of.VoltSubFlow)
+ subFlow := of.NewVoltSubFlow()
+ subFlow.Cookie = cookie
+ flow.SubFlows[cookie] = subFlow
+ logger.Infow(ctx, "Retriggering Service Delete Flow", log.Fields{"Device": vs.Device, "Service": vs.Name, "Cookie": cookie})
+ if err := vs.DelFlows(vd, flow); err != nil {
+ logger.Errorw(ctx, "DelFlows Failed", log.Fields{"Device": vs.Device, "Service": vs.Name, "Cookie": cookie, "Error": err})
+ }
+ }
+ }
+ return true
+}
+
+//triggerServiceInProgressInd - Indication is generated when Service is not provisioned after add serviec req from NB
+func (vs *VoltService) triggerServiceInProgressInd() {
+}
diff --git a/internal/pkg/application/timer.go b/internal/pkg/application/timer.go
new file mode 100644
index 0000000..1a80cb5
--- /dev/null
+++ b/internal/pkg/application/timer.go
@@ -0,0 +1,70 @@
+/*
+* Copyright 2022-present Open Networking Foundation
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+ */
+
+package application
+
+import (
+ "time"
+)
+
+//TimerType - type of timer used
+type TimerType string
+
+const (
+ tickTimer TimerType = "TickTimer"
+ pendingPoolTimer TimerType = "PendingPoolTimer"
+)
+
+var timerMap = map[TimerType]bool{
+ tickTimer: false,
+ pendingPoolTimer: false,
+}
+
+var timerChannels = make(map[TimerType](chan bool))
+
+// TimerCfg structure
+type TimerCfg struct {
+ tick time.Duration
+}
+
+// Start to start timer
+func (va *VoltApplication) Start(cfg TimerCfg, timerType TimerType) {
+ if timerMap[timerType] {
+ logger.Warn(ctx, "Duplicate Timer!!! Timer already running")
+ return
+ }
+ timerMap[timerType] = true
+ timerChannels[timerType] = make(chan bool)
+ for {
+ select {
+ case <-time.After(cfg.tick):
+ switch timerType {
+ case tickTimer:
+ va.Tick()
+ case pendingPoolTimer:
+ va.removeExpiredGroups()
+ }
+ case <- timerChannels[timerType]:
+ return
+ }
+ }
+}
+
+// StopTimer to stop timers
+func StopTimer() {
+ for _, ch := range timerChannels {
+ ch <- true
+ }
+}
diff --git a/internal/pkg/application/util.go b/internal/pkg/application/util.go
new file mode 100644
index 0000000..866e12b
--- /dev/null
+++ b/internal/pkg/application/util.go
@@ -0,0 +1,60 @@
+/*
+* Copyright 2022-present Open Networking Foundation
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+ */
+
+package application
+
+import (
+ "errors"
+ "strconv"
+
+ "github.com/google/gopacket/layers"
+)
+
+const (
+ //EtherType8100 - EtherType dot1q
+ EtherType8100 uint8 = 0
+ //EtherType88a8 - EtherType dot1ad
+ EtherType88a8 uint8 = 1
+ //EtherType9100 - EtherType dot1ad doubleTag
+ EtherType9100 uint8 = 2
+ //EtherType9200 - EtherType dot1q doubleTag
+ EtherType9200 uint8 = 3
+)
+
+//GetMetadataForL2Protocol - returns metadata value for provide ethertype
+func GetMetadataForL2Protocol(etherType layers.EthernetType) (uint8, error) {
+ switch etherType {
+ case layers.EthernetTypeDot1Q:
+ return EtherType8100, nil
+ case layers.EthernetTypeQinQ:
+ return EtherType88a8, nil
+ case layers.EthernetTypeDot1QDoubleTag:
+ return EtherType9100, nil
+ case layers.EthernetTypeQinQDoubleTag:
+ return EtherType9200, nil
+ default:
+ return 0, errors.New("EtherType not supported")
+ }
+}
+
+func convertToUInt64(data string) uint64 {
+
+ value, err := strconv.ParseUint(data, 10, 64)
+ if err != nil {
+ return 0
+ }
+ return value
+
+}
diff --git a/internal/pkg/application/vnets.go b/internal/pkg/application/vnets.go
new file mode 100644
index 0000000..cf2d1fc
--- /dev/null
+++ b/internal/pkg/application/vnets.go
@@ -0,0 +1,3138 @@
+/*
+* Copyright 2022-present Open Networking Foundation
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+ */
+
+package application
+
+import (
+ "encoding/json"
+ "errors"
+ "net"
+ infraerrorCodes "voltha-go-controller/internal/pkg/errorcodes"
+ "strconv"
+ "sync"
+ "time"
+
+ "github.com/google/gopacket"
+ "github.com/google/gopacket/layers"
+ "go.uber.org/atomic"
+
+ "voltha-go-controller/internal/pkg/controller"
+ cntlr "voltha-go-controller/internal/pkg/controller"
+ "voltha-go-controller/database"
+ "voltha-go-controller/internal/pkg/of"
+ "voltha-go-controller/internal/pkg/util"
+ errorCodes "voltha-go-controller/internal/pkg/errorcodes"
+ "github.com/opencord/voltha-lib-go/v7/pkg/log"
+)
+
+const (
+ // ICMPv6ArpGroupID constant
+ ICMPv6ArpGroupID uint32 = 1
+
+ // Radisys vendor id constant
+ Radisys string = "Radisys"
+)
+
+var (
+ //BroadcastMAC - Broadcast MAC Address
+ BroadcastMAC, _ = net.ParseMAC("ff:ff:ff:ff:ff:ff")
+)
+
+// NonZeroMacAddress utility to identify if the MAC address is non-zero.
+// We use zero MAC address as an unset MAC address
+func NonZeroMacAddress(h net.HardwareAddr) bool {
+ for i := 0; i < 6; i++ {
+ if h[i] != 0 {
+ return true
+ }
+ }
+ return false
+}
+
+// VNET package manages the different virtual networks that are part of the
+// the network. In the case of VOLT, the networks can be single tagged or
+// double tagged networks. In addition, the networks may be used for unicast
+// and multicast traffic. The unicast traffic further has two models, the
+// 1:1 and N:1 model. In case of a 1:1 model, the outer tag is same for many
+// subscribers and the inner tag is unique to each subscriber for the same
+// outer tag. The N:1 uses the same inner and outer tags, or for that matter
+// a single tag that can also be shared by subscribers. The VNET implementation
+// manages all these possibilities and the associated configuration.
+
+const (
+ // PbitMatchNone constant
+ PbitMatchNone of.PbitType = 8
+ // PbitMatchAll constant
+ PbitMatchAll of.PbitType = 0xFF
+)
+
+// SVlan - Value of the outer tag if double tagged or the only tag if single
+// tagged
+// SVlanTpid - SVlan Tag Protocol Identifier
+// CVlan - Value of the inner tag. Set to VlanNone if single tagged
+// DhcpRelay - Set to true if the DHCP relay is enabled on the virtual network
+// MacLearning - Set to true if the flows should include MAC address
+// UsDhcpPbit - The pbit used for US DHCP packets
+// DsDhcpPbit - The pbit used for DS DHCP packets
+
+// VnetConfig structure
+type VnetConfig struct {
+ Name string
+ SVlan of.VlanType
+ CVlan of.VlanType
+ UniVlan of.VlanType
+ SVlanTpid layers.EthernetType
+ DhcpRelay bool
+ ArpLearning bool
+ MacLearning MacLearningType
+ PppoeIa bool
+ ONTEtherTypeClassification int
+ VlanControl VlanControl
+ Encapsulation string
+ UsDhcpPbit []of.PbitType
+ DsDhcpPbit []of.PbitType
+ UsIGMPPbit []of.PbitType
+ DsIGMPPbit []of.PbitType
+ DevicesList []string //List of serial number of devices on which this vnet is applied
+ AllowTransparent bool
+ CtrlPktPbitRemark map[of.PbitType]of.PbitType
+}
+
+// VnetOper structure
+type VnetOper struct {
+ PendingDeleteFlow map[string]map[string]bool
+ DeleteInProgress bool
+ PendingDeviceToDelete string
+ VnetLock sync.RWMutex `json:"-"`
+ VnetPortLock sync.RWMutex `json:"-"`
+ AssociatedPorts map[string]bool `json:"-"`
+}
+
+// VoltVnet sructure
+type VoltVnet struct {
+ VnetConfig
+ VnetOper
+ Version string
+}
+
+const (
+ // EncapsulationPPPoEIA constant
+ EncapsulationPPPoEIA string = "PPPoE-IA"
+ // EncapsulationPPPoE constant
+ EncapsulationPPPoE string = "PPPoE"
+ // EncapsulationIPoE constant
+ EncapsulationIPoE string = "IPoE"
+)
+
+// NewVoltVnet is constructor for the VNET structure
+func NewVoltVnet(cfg VnetConfig) *VoltVnet {
+ var vv VoltVnet
+ vv.VnetConfig = cfg
+ if vv.PendingDeleteFlow == nil {
+ vv.PendingDeleteFlow = make(map[string]map[string]bool)
+ }
+ vv.DeleteInProgress = false
+ if cfg.Encapsulation == EncapsulationPPPoEIA {
+ vv.PppoeIa = true
+ }
+ vv.AssociatedPorts = make(map[string]bool)
+ return &vv
+}
+
+//associatePortToVnet - associate a port to Vnet
+func (vv *VoltVnet) associatePortToVnet(port string) {
+ vv.VnetPortLock.Lock()
+ if vv.AssociatedPorts == nil {
+ vv.AssociatedPorts = make(map[string]bool)
+ }
+ vv.AssociatedPorts[port] = true
+ vv.VnetPortLock.Unlock()
+}
+
+//disassociatePortFromVnet - disassociate a port from Vnet and return true if the association map is empty
+func (vv *VoltVnet) disassociatePortFromVnet(device string, port string) {
+ vv.VnetPortLock.Lock()
+ delete(vv.AssociatedPorts, port)
+ logger.Infow(ctx, "Disassociated Port from Vnet", log.Fields{"Device": device, "Port": port, "Vnet": vv.Name, "PendingDeleteFlow": vv.PendingDeleteFlow, "AssociatedPorts": vv.AssociatedPorts, "DeleteFlag": vv.DeleteInProgress})
+ vv.VnetPortLock.Unlock()
+
+ if vv.DeleteInProgress {
+ if !vv.isAssociatedPortsPresent() {
+ if len(vv.PendingDeleteFlow[device]) == 0 {
+ logger.Warnw(ctx, "Deleting Vnet", log.Fields{"Name": vv.Name})
+ GetApplication().deleteVnetConfig(vv)
+ _ = db.DelVnet(vv.Name)
+ } else {
+ logger.Warnw(ctx, "Skipping Del Vnet", log.Fields{"Name": vv.Name, "PendingDeleteFlow": vv.PendingDeleteFlow})
+ }
+ } else {
+ vv.VnetPortLock.RLock()
+ logger.Warnw(ctx, "Skipping Del Vnet", log.Fields{"Name": vv.Name, "AssociatedPorts": vv.AssociatedPorts})
+ vv.VnetPortLock.RUnlock()
+ }
+ }
+}
+
+func (vv *VoltVnet) isAssociatedPortsPresent() bool {
+ vv.VnetPortLock.RLock()
+ defer vv.VnetPortLock.RUnlock()
+ return len(vv.AssociatedPorts) != 0
+}
+
+// WriteToDb commit the VNET to the database
+func (vv *VoltVnet) WriteToDb() {
+
+ if vv.DeleteInProgress {
+ logger.Warnw(ctx, "Skipping Redis Update for Vnet, Vnet delete in progress", log.Fields{"Vnet": vv.Name})
+ return
+ }
+ vv.ForceWriteToDb()
+}
+
+//ForceWriteToDb force commit a vnet to the DB
+func (vv *VoltVnet) ForceWriteToDb() {
+ vv.VnetPortLock.RLock()
+ defer vv.VnetPortLock.RUnlock()
+ vv.Version = database.PresentVersionMap[database.VnetPath]
+ logger.Debugw(ctx, "Updating VNET....", log.Fields{"vnet": vv})
+ if b, err := json.Marshal(vv); err == nil {
+ if err:= db.PutVnet(vv.Name, string(b)); err != nil {
+ logger.Warnw(ctx, "Add Vnet to DB failed", log.Fields{"vnet name": vv.Name, "Error": err})
+ }
+ }
+}
+
+// VnetKey creates the key using the two VLAN tags
+// We append the two VLAN tags to create a single key
+func VnetKey(otag of.VlanType, itag of.VlanType, utag of.VlanType) string {
+ return strconv.Itoa(int(otag)) + "-" + strconv.Itoa(int(itag)) + "-" + strconv.Itoa(int(utag))
+}
+
+// GetVnet get VNET configuration related functionality associated with VOLT application
+func (va *VoltApplication) GetVnet(otag of.VlanType, itag of.VlanType, utag of.VlanType) *VoltVnet {
+ // When matching VNET, it is expected to match first just the outer
+ // tag, and then the combination to make sure there is no conflict
+ // for the new configuration.
+ if vnet, ok := va.VnetsByTag.Load(VnetKey(otag, of.VlanNone, utag)); ok {
+ return vnet.(*VoltVnet)
+ }
+ if vnet, ok := va.VnetsByTag.Load(VnetKey(otag, itag, utag)); ok {
+ return vnet.(*VoltVnet)
+ }
+ return nil
+}
+
+// The VNET may also be assigned name for easier references. For now,
+// the VNET is mainly identified by the two VLANs.
+
+// GetVnetByName to get vnet by name
+func (va *VoltApplication) GetVnetByName(name string) *VoltVnet {
+ if vnet, ok := va.VnetsByName.Load(name); ok {
+ return vnet.(*VoltVnet)
+ }
+ return nil
+}
+
+// storeVnetConfig to store vnet config
+func (va *VoltApplication) storeVnetConfig(cfg VnetConfig, vv *VoltVnet) {
+
+ var vnetMap *util.ConcurrentMap
+
+ va.VnetsByTag.Store(VnetKey(cfg.SVlan, cfg.CVlan, cfg.UniVlan), vv)
+ va.VnetsByName.Store(cfg.Name, vv)
+
+ if vnetMapIntf, ok := va.VnetsBySvlan.Get(vv.SVlan); !ok {
+ vnetMap = util.NewConcurrentMap()
+ } else {
+ vnetMap = vnetMapIntf.(*util.ConcurrentMap)
+ }
+ vnetMap.Set(vv, true)
+ va.VnetsBySvlan.Set(vv.SVlan, vnetMap)
+}
+
+// deleteVnetConfig to delete vnet config
+func (va *VoltApplication) deleteVnetConfig(vnet *VoltVnet) {
+ va.VnetsByTag.Delete(VnetKey(vnet.SVlan, vnet.CVlan, vnet.UniVlan))
+ va.VnetsByName.Delete(vnet.Name)
+
+ if vnetMapIntf, ok := va.VnetsBySvlan.Get(vnet.SVlan); ok {
+ vnetMap := vnetMapIntf.(*util.ConcurrentMap)
+ vnetMap.Remove(vnet)
+ va.VnetsBySvlan.Set(vnet.SVlan, vnetMap)
+ }
+}
+
+// AddVnet to add a VNET to the list of VNETs configured.
+func (va *VoltApplication) AddVnet(cfg VnetConfig, oper *VnetOper) error {
+
+ AppMutex.VnetMutex.Lock()
+ var vv *VoltVnet
+ devicesToHandle := []string{}
+ vv = va.GetVnetByName(cfg.Name)
+ if vv != nil {
+ //Could be for another OLT or could be case of backup-restore
+ for _, serialNum := range cfg.DevicesList {
+ if isDeviceInList(serialNum, vv.DevicesList) {
+ //This is backup restore scenario, just update the profile
+ logger.Info(ctx, "Add Vnet : Profile Name already exists with OLT, update-the-profile")
+ continue
+ }
+ devicesToHandle = append(devicesToHandle, serialNum)
+ }
+ if len(devicesToHandle) == 0 {
+ logger.Debug(ctx, "Ignoring Duplicate VNET by name ", log.Fields{"Vnet": cfg.Name})
+ AppMutex.VnetMutex.Unlock()
+ return nil
+ }
+ }
+
+ if vv == nil {
+ vv = NewVoltVnet(cfg)
+ if oper != nil {
+ vv.PendingDeleteFlow = oper.PendingDeleteFlow
+ vv.DeleteInProgress = oper.DeleteInProgress
+ vv.AssociatedPorts = oper.AssociatedPorts
+ vv.PendingDeviceToDelete = oper.PendingDeviceToDelete
+ }
+ devicesToHandle = append(devicesToHandle, cfg.DevicesList...)
+ } else {
+ vv.DevicesList = append(vv.DevicesList, devicesToHandle...)
+ }
+
+ va.storeVnetConfig(cfg, vv)
+ vv.WriteToDb()
+
+ logger.Infow(ctx, "Added VNET TO DB", log.Fields{"cfg": cfg, "devicesToHandle": devicesToHandle})
+
+ //va.PushDevFlowForVlan(vv)
+ AppMutex.VnetMutex.Unlock()
+ return nil
+}
+
+// DelVnet to delete a VNET from the list of VNETs configured
+func (va *VoltApplication) DelVnet(name, deviceSerialNum string) error {
+ logger.Infow(ctx, "Deleting Vnet", log.Fields{"Vnet": name})
+ AppMutex.VnetMutex.Lock()
+ if vnetIntf, ok := va.VnetsByName.Load(name); ok {
+ vnet := vnetIntf.(*VoltVnet)
+ //Delete from mvp list
+ vnet.DevicesList = util.RemoveFromSlice(vnet.DevicesList, deviceSerialNum)
+
+ va.DeleteDevFlowForVlanFromDevice(vnet, deviceSerialNum)
+ if len(vnet.DevicesList) == 0 {
+ vnet.DeleteInProgress = true
+ vnet.PendingDeviceToDelete = deviceSerialNum
+ vnet.ForceWriteToDb()
+ vnet.VnetPortLock.RLock()
+ if len(vnet.PendingDeleteFlow) == 0 && !vnet.isAssociatedPortsPresent() {
+ logger.Warnw(ctx, "Deleting Vnet", log.Fields{"Name": vnet.Name, "AssociatedPorts": vnet.AssociatedPorts, "PendingDelFlows": vnet.PendingDeleteFlow})
+ va.deleteVnetConfig(vnet)
+ _ = db.DelVnet(vnet.Name)
+ } else {
+ logger.Warnw(ctx, "Skipping Del Vnet", log.Fields{"Name": vnet.Name, "AssociatedPorts": vnet.AssociatedPorts, "PendingDelFlows": vnet.PendingDeleteFlow})
+ }
+ vnet.VnetPortLock.RUnlock()
+ } else {
+ //Update the devicelist in db
+ vnet.WriteToDb()
+ }
+ }
+ //TODO: if no vnets are present on device remove icmpv6 group from device
+ AppMutex.VnetMutex.Unlock()
+ return nil
+}
+
+// UpdateVnet to update the VNET with associated service count
+func (va *VoltApplication) UpdateVnet(vv *VoltVnet) error {
+ va.storeVnetConfig(vv.VnetConfig, vv)
+ vv.WriteToDb()
+ logger.Infow(ctx, "Updated VNET TO DB", log.Fields{"vv": vv.VnetConfig})
+ return nil
+}
+
+// ------------------------------------------------------------
+// Manifestation of a VNET on a port is handled below
+// ------------------------------------------------------------
+//
+// The VNET on a port handles everything that is done for a VNET
+// such as DHCP relay state machine, MAC addresses, IP addresses
+// learnt, so on.
+
+// DhcpStatus type
+type DhcpStatus uint8
+
+const (
+ // DhcpStatusNone constant
+ DhcpStatusNone DhcpStatus = 0
+ // DhcpStatusAcked constant
+ DhcpStatusAcked DhcpStatus = 1
+ // DhcpStatusNacked constant
+ DhcpStatusNacked DhcpStatus = 2
+ // EthTypeNone constant
+ EthTypeNone int = 0
+ // EthTypeIPoE constant
+ EthTypeIPoE int = 1
+ // EthTypePPPoE constant
+ EthTypePPPoE int = 2
+)
+
+// VoltPortVnet structure
+type VoltPortVnet struct {
+ Device string
+ Port string
+ PonPort uint32
+ VnetName string
+ SVlan of.VlanType
+ CVlan of.VlanType
+ UniVlan of.VlanType
+ SVlanTpid layers.EthernetType
+ DhcpRelay bool
+ ArpRelay bool
+ PppoeIa bool
+ MacLearning MacLearningType
+ DhcpStatus DhcpStatus
+ DhcpExpiryTime time.Time
+ Dhcp6ExpiryTime time.Time
+ FlowsApplied bool
+ services sync.Map
+ servicesCount *atomic.Uint64
+ Ipv4Addr net.IP
+ Ipv6Addr net.IP
+ MacAddr net.HardwareAddr
+ LearntMacAddr net.HardwareAddr
+ CircuitID []byte //Will not be used
+ RemoteID []byte //Will not be used
+ IsOption82Disabled bool //Will not be used
+ RelayState DhcpRelayState
+ PPPoeState PppoeIaState
+ RelayStatev6 Dhcpv6RelayState
+ IgmpEnabled bool
+ IgmpFlowsApplied bool
+ McastService bool
+ ONTEtherTypeClassification int
+ VlanControl VlanControl
+ MvlanProfileName string
+ Version string
+ McastTechProfileID uint16
+ McastPbit of.PbitType
+ McastUsMeterID uint32
+ AllowTransparent bool
+ VpvLock sync.Mutex `json:"-"`
+ SchedID int
+ DHCPv6DUID [MaxLenDhcpv6DUID]byte
+ PendingFlowLock sync.RWMutex `json:"-"`
+ PendingDeleteFlow map[string]bool
+ DeleteInProgress bool
+ Blocked bool
+ DhcpPbit of.PbitType
+}
+
+//VlanControl vlan control type
+type VlanControl uint8
+
+const (
+ // None constant
+ // ONU and OLT will passthrough UNIVLAN as is to BNG
+ None VlanControl = iota
+
+ // ONUCVlanOLTSVlan constant
+ // Tagged traffic, ONU will replace UNIVLAN with CVLAN and OLT will add SVLAN
+ // Untagged traffic, ONU will add CVLAN and OLT will add SVLAN
+ ONUCVlanOLTSVlan
+
+ // OLTCVlanOLTSVlan constant
+ // Tagged traffic, ONU will passthrough UNIVLAN as is to OLT and
+ // OLT will replace UNIVLAN with CVLAN and add SVLAN
+ OLTCVlanOLTSVlan
+
+ // ONUCVlan constant
+ // Tagged traffic, ONU will replace UNIVLAN with CVLAN
+ // Untagged traffic, ONU will add CVLAN
+ ONUCVlan
+
+ // OLTSVlan constant
+ // UnTagged traffic, OLT will add the SVLAN
+ OLTSVlan
+)
+
+// NewVoltPortVnet is constructor for VoltPortVnet
+func NewVoltPortVnet(vnet *VoltVnet) *VoltPortVnet {
+ var vpv VoltPortVnet
+
+ vpv.VnetName = vnet.Name
+ vpv.SVlan = vnet.SVlan
+ vpv.CVlan = vnet.CVlan
+ vpv.UniVlan = vnet.UniVlan
+ vpv.SVlanTpid = vnet.SVlanTpid
+ vpv.DhcpRelay = vnet.DhcpRelay
+ vpv.DhcpStatus = DhcpStatusNone
+ vpv.PPPoeState = PppoeIaStateNone
+ vpv.ArpRelay = vnet.ArpLearning
+ vpv.PppoeIa = vnet.PppoeIa
+ vpv.VlanControl = vnet.VlanControl
+ vpv.ONTEtherTypeClassification = vnet.ONTEtherTypeClassification
+ vpv.AllowTransparent = vnet.AllowTransparent
+ vpv.FlowsApplied = false
+ vpv.IgmpEnabled = false
+ vpv.MacAddr, _ = net.ParseMAC("00:00:00:00:00:00")
+ vpv.LearntMacAddr, _ = net.ParseMAC("00:00:00:00:00:00")
+ // for OLTCVLAN SVLAN=CVLAN, UNIVLAN can differ.
+ if vpv.VlanControl == ONUCVlan {
+ vpv.CVlan = vpv.SVlan
+ }
+ // for OLTSVLAN CVLAN=UNIVLAN , SVLAN can differ,
+ // hence assigning UNIVLAN to CVLAN, so that ONU will transparently forward the packet.
+ if vpv.VlanControl == OLTSVlan {
+ vpv.CVlan = vpv.UniVlan
+ }
+ vpv.servicesCount = atomic.NewUint64(0)
+ vpv.SchedID = 0
+ vpv.PendingDeleteFlow = make(map[string]bool)
+ vpv.DhcpPbit = vnet.UsDhcpPbit[0]
+ return &vpv
+}
+
+func (vpv *VoltPortVnet) setDevice(device string) {
+
+ if vpv.Device != device && vpv.Device != "" {
+ GetApplication().DisassociateVpvsFromDevice(device, vpv)
+ //TEMP:
+ vpv.printAssociatedVPVs(false)
+ }
+
+ logger.Infow(ctx, "Associating VPV and Device", log.Fields{"Device": vpv.Device, "Port": vpv.Port, "SVlan": vpv.SVlan})
+
+ vpv.Device = device
+ GetApplication().AssociateVpvsToDevice(device, vpv)
+ //TEMP:
+ vpv.printAssociatedVPVs(true)
+}
+
+//TODO - Nav - Temp
+func (vpv *VoltPortVnet) printAssociatedVPVs(add bool) {
+ logger.Infow(ctx, "Start----Printing all associated VPV", log.Fields{"Device": vpv.Device, "Add": add})
+ if vMap := GetApplication().GetAssociatedVpvsForDevice(vpv.Device, vpv.SVlan); vMap != nil {
+ vMap.Range(func(key, value interface{}) bool {
+ vpvEntry := key.(*VoltPortVnet)
+ logger.Infow(ctx, "Associated VPVs", log.Fields{"SVlan": vpvEntry.SVlan, "CVlan": vpvEntry.CVlan, "UniVlan": vpvEntry.UniVlan})
+ return true
+ })
+ }
+ logger.Infow(ctx, "End----Printing all associated VPV", log.Fields{"Device": vpv.Device, "Add": add})
+
+}
+
+// GetCircuitID : The interface to be satisfied by the VoltPortVnet to be a DHCP relay
+// session is implemented below. The main functions still remain in
+// the service.go file.
+func (vpv *VoltPortVnet) GetCircuitID() []byte {
+ return []byte(vpv.CircuitID)
+}
+
+// GetRemoteID to get remote id
+func (vpv *VoltPortVnet) GetRemoteID() []byte {
+ return []byte(vpv.RemoteID)
+}
+
+// GetDhcpState to get dhcp state
+func (vpv *VoltPortVnet) GetDhcpState() DhcpRelayState {
+ return vpv.RelayState
+}
+
+// SetDhcpState to set the dhcp state
+func (vpv *VoltPortVnet) SetDhcpState(state DhcpRelayState) {
+ vpv.RelayState = state
+}
+
+// GetPppoeIaState to get pppoeia state
+func (vpv *VoltPortVnet) GetPppoeIaState() PppoeIaState {
+ return vpv.PPPoeState
+}
+
+// SetPppoeIaState to set pppoeia state
+func (vpv *VoltPortVnet) SetPppoeIaState(state PppoeIaState) {
+ vpv.PPPoeState = state
+}
+
+// GetDhcpv6State to get dhcpv6 state
+func (vpv *VoltPortVnet) GetDhcpv6State() Dhcpv6RelayState {
+ return vpv.RelayStatev6
+}
+
+// SetDhcpv6State to set dhcpv6 state
+func (vpv *VoltPortVnet) SetDhcpv6State(state Dhcpv6RelayState) {
+ vpv.RelayStatev6 = state
+}
+
+// DhcpResultInd for dhcp result indication
+func (vpv *VoltPortVnet) DhcpResultInd(res *layers.DHCPv4) {
+ vpv.ProcessDhcpResult(res)
+}
+
+// Dhcpv6ResultInd for dhcpv6 result indication
+func (vpv *VoltPortVnet) Dhcpv6ResultInd(ipv6Addr net.IP, leaseTime uint32) {
+ vpv.ProcessDhcpv6Result(ipv6Addr, leaseTime)
+}
+
+// GetNniVlans to get nni vlans
+func (vpv *VoltPortVnet) GetNniVlans() (uint16, uint16) {
+ switch vpv.VlanControl {
+ case ONUCVlanOLTSVlan,
+ OLTCVlanOLTSVlan:
+ return uint16(vpv.SVlan), uint16(vpv.CVlan)
+ case ONUCVlan,
+ None:
+ return uint16(vpv.SVlan), uint16(of.VlanNone)
+ case OLTSVlan:
+ return uint16(vpv.SVlan), uint16(of.VlanNone)
+ }
+ return uint16(of.VlanNone), uint16(of.VlanNone)
+}
+
+// GetService to get service
+func (vpv *VoltPortVnet) GetService(name string) (*VoltService, bool) {
+ service, ok := vpv.services.Load(name)
+ if ok {
+ return service.(*VoltService), ok
+ }
+ return nil, ok
+}
+
+// AddService to add service
+func (vpv *VoltPortVnet) AddService(service *VoltService) {
+ vpv.services.Store(service.Name, service)
+ vpv.servicesCount.Inc()
+ logger.Infow(ctx, "Service added/updated to VPV", log.Fields{"Port": vpv.Port, "SVLAN": vpv.SVlan, "CVLAN": vpv.CVlan, "UNIVlan": vpv.UniVlan, "Service": service.Name, "Count": vpv.servicesCount.Load()})
+}
+
+// DelService to delete service
+func (vpv *VoltPortVnet) DelService(service *VoltService) {
+ vpv.services.Delete(service.Name)
+ vpv.servicesCount.Dec()
+
+ // If the only Igmp Enabled service is removed, remove the Igmp trap flow along with it
+ if service.IgmpEnabled {
+ if err := vpv.DelIgmpFlows(); err != nil {
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vpv.FlowInstallFailure("VGC processing failure", statusCode, statusMessage)
+ }
+
+ vpv.IgmpEnabled = false
+ }
+ logger.Infow(ctx, "Service deleted from VPV", log.Fields{"Port": vpv.Port, "SVLAN": vpv.SVlan, "CVLAN": vpv.CVlan, "UNIVlan": vpv.UniVlan, "Service": service.Name, "Count": vpv.servicesCount.Load()})
+}
+
+// ProcessDhcpResult to process dhcp results
+func (vpv *VoltPortVnet) ProcessDhcpResult(res *layers.DHCPv4) {
+ msgType := DhcpMsgType(res)
+ if msgType == layers.DHCPMsgTypeAck {
+ vpv.ProcessDhcpSuccess(res)
+ } else if msgType == layers.DHCPMsgTypeNak {
+ vpv.DhcpStatus = DhcpStatusNacked
+ }
+ vpv.WriteToDb()
+}
+
+// ProcessDhcpSuccess : Learn the IPv4 address allocated to the services and update the
+// the services with the same. This also calls for adding flows
+// for the services as the DHCP procedure is completed
+func (vpv *VoltPortVnet) ProcessDhcpSuccess(res *layers.DHCPv4) {
+ vpv.DhcpStatus = DhcpStatusAcked
+ vpv.Ipv4Addr, _ = GetIpv4Addr(res)
+ logger.Infow(ctx, "Received IPv4 Address", log.Fields{"IP Address": vpv.Ipv4Addr.String()})
+ logger.Infow(ctx, "Services Configured", log.Fields{"Count": vpv.servicesCount.Load()})
+
+ vpv.services.Range(vpv.updateIPv4AndProvisionFlows)
+ vpv.ProcessDhcpv4Options(res)
+}
+
+// ProcessDhcpv4Options : Currently we process lease time and store the validity of the
+// IP address allocated.
+func (vpv *VoltPortVnet) ProcessDhcpv4Options(res *layers.DHCPv4) {
+ for _, o := range res.Options {
+ switch o.Type {
+ case layers.DHCPOptLeaseTime:
+ leasetime := GetIPv4LeaseTime(o)
+ vpv.DhcpExpiryTime = time.Now().Add((time.Duration(leasetime) * time.Second))
+ logger.Infow(ctx, "Lease Expiry Set", log.Fields{"Time": vpv.DhcpExpiryTime})
+ }
+ }
+}
+
+// ProcessDhcpv6Result : Read the IPv6 address allocated to the device and store it on the
+// VNET. The same IPv6 address is also passed to the services. When a
+// service is fetched all the associated information such as MAC address,
+// IPv4 address and IPv6 addresses can be provided.
+func (vpv *VoltPortVnet) ProcessDhcpv6Result(ipv6Addr net.IP, leaseTime uint32) {
+ // TODO: Status based hanlding of flows
+ vpv.Dhcp6ExpiryTime = time.Now().Add((time.Duration(leaseTime) * time.Second))
+ vpv.Ipv6Addr = ipv6Addr
+
+ vpv.services.Range(vpv.updateIPv6AndProvisionFlows)
+ vpv.WriteToDb()
+}
+
+// AddSvcUsMeterToDevice to add service upstream meter info to device
+func AddSvcUsMeterToDevice(key, value interface{}) bool {
+ svc := value.(*VoltService)
+ logger.Info(ctx, "Adding upstream meter profile to device", log.Fields{"ServiceName": svc.Name})
+ if device, _ := GetApplication().GetDeviceFromPort(svc.Port); device != nil {
+ GetApplication().AddMeterToDevice(svc.Port, device.Name, svc.UsMeterID, 0)
+ return true
+ }
+ logger.Errorw(ctx, "Dropping US Meter request: Device not found", log.Fields{"Service": svc})
+ return false
+}
+
+// PushFlowsForPortVnet - triggers flow construction and push for provided VPV
+func (vpv *VoltPortVnet) PushFlowsForPortVnet(d *VoltDevice) {
+
+ vp := d.GetPort(vpv.Port)
+
+ //Ignore if UNI port is not found or not UP
+ if vp == nil || vp.State != PortStateUp {
+ logger.Warnw(ctx, "Ignoring Vlan UP Ind for VPV: Port Not Found/Ready", log.Fields{"Port": vp})
+ return
+ }
+
+ if vpv.PonPort != 0xFF && vpv.PonPort != vp.PonPort {
+ logger.Errorw(ctx, "UNI port discovered on wrong PON Port. Dropping Flow Configuration for VPV", log.Fields{"Device": d.Name, "Port": vpv.Port, "DetectedPon": vp.PonPort, "ExpectedPon": vpv.PonPort, "Vnet": vpv.VnetName})
+ return
+ }
+
+ //Disable the flag so that flows can be pushed again
+ // vpv.IgmpFlowsApplied = false
+ // vpv.DsFlowsApplied = false
+ // vpv.UsFlowsApplied = false
+ vpv.VpvLock.Lock()
+ vpv.PortUpInd(d, vpv.Port)
+ vpv.VpvLock.Unlock()
+}
+
+// PortUpInd : When a port transistions to UP state, the indication is passed
+// on to this module via the application. We read the VNET configuration
+// again here to apply the latest configuration if the configuration
+// changed. Thus, a reboot of ONT forces the new configuration to get
+// applied.
+func (vpv *VoltPortVnet) PortUpInd(device *VoltDevice, port string) {
+
+ if vpv.DeleteInProgress {
+ logger.Errorw(ctx, "Ignoring VPV Port UP Ind, VPV deleteion In-Progress", log.Fields{"Device": device, "Port": port, "Vnet": vpv.VnetName})
+ return
+ }
+ vpv.setDevice(device.Name)
+ logger.Infow(ctx, "Port UP Ind, pushing flows for the port", log.Fields{"Device": device, "Port": port, "VnetDhcp": vpv.DhcpRelay, "McastService": vpv.McastService})
+
+ nni, _ := GetApplication().GetNniPort(device.Name)
+ if nni == "" {
+ logger.Warnw(ctx, "Ignoring Vnet Port UP indication: NNI is unavailable", log.Fields{"Port": vpv.Port, "Device": device.Name})
+ return
+ }
+
+ if vp := device.GetPort(port); vp != nil {
+
+ if vpv.PonPort != 0xFF && vpv.PonPort != vp.PonPort {
+ logger.Errorw(ctx, "UNI port discovered on wrong PON Port. Dropping Flow Config for VPV", log.Fields{"Device": device.Name, "Port": port, "DetectedPon": vp.PonPort, "ExpectedPon": vpv.PonPort, "Vnet": vpv.VnetName})
+ return
+ }
+ }
+
+ if vpv.Blocked {
+ logger.Errorw(ctx, "VPV Bocked for Processing. Ignoring flow push request", log.Fields{"Port": vpv.Port, "Vnet": vpv.VnetName})
+ return
+ }
+
+ if vpv.DhcpRelay || vpv.ArpRelay || vpv.PppoeIa {
+ // If MAC Learning is true if no MAC is configured, push DS/US DHCP, US HSIA flows without MAC.
+ // DS HSIA flows are installed after learning the MAC.
+ logger.Infow(ctx, "Port Up - Trap Flows", log.Fields{"Device": device.Name, "Port": port})
+ // no HSIA flows for multicast service
+ if !vpv.McastService {
+ vpv.services.Range(AddUsHsiaFlows)
+ }
+ vpv.AddTrapFlows()
+ if vpv.MacLearning == MacLearningNone || NonZeroMacAddress(vpv.MacAddr) {
+ logger.Infow(ctx, "Port Up - DS Flows", log.Fields{"Device": device.Name, "Port": port})
+ // US & DS DHCP, US HSIA flows are already installed
+ // install only DS HSIA flow here.
+ // no HSIA flows for multicast service
+ if !vpv.McastService {
+ vpv.services.Range(AddDsHsiaFlows)
+ }
+ }
+
+ } else {
+ // DHCP relay is not configured. This implies that the service must use
+ // 1:1 and does not require MAC learning. In a completely uncommon but
+ // plausible case, the MAC address can be learnt from N:1 without DHCP
+ // relay by configuring any unknown MAC address to be reported. This
+ // however is not seen as a real use case.
+ logger.Infow(ctx, "Port Up - Service Flows", log.Fields{"Device": device.Name, "Port": port})
+ if !vpv.McastService {
+ vpv.services.Range(AddUsHsiaFlows)
+ }
+ vpv.AddTrapFlows()
+ if !vpv.McastService {
+ vpv.services.Range(AddDsHsiaFlows)
+ }
+ }
+
+ // Process IGMP proxy - install IGMP trap rules before DHCP trap rules
+ if vpv.IgmpEnabled {
+ logger.Infow(ctx, "Port Up - IGMP Flows", log.Fields{"Device": device.Name, "Port": port})
+ vpv.services.Range(AddSvcUsMeterToDevice)
+ if err := vpv.AddIgmpFlows(); err != nil {
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vpv.FlowInstallFailure("VGC processing failure", statusCode, statusMessage)
+ }
+
+ if vpv.McastService {
+ vpv.services.Range(PostAccessConfigSuccessInd)
+ }
+ }
+
+ vpv.WriteToDb()
+}
+
+// PortDownInd : When the port status changes to down, we delete all configured flows
+// The same indication is also passed to the services enqueued for them
+// to take appropriate actions
+func (vpv *VoltPortVnet) PortDownInd(device string, port string) {
+
+ logger.Infow(ctx, "VPV Port DOWN Ind, deleting all flows for services",
+ log.Fields{"service count": vpv.servicesCount.Load()})
+
+ //vpv.services.Range(DelAllFlows)
+ vpv.DelTrapFlows()
+ vpv.DelHsiaFlows()
+ vpv.WriteToDb()
+ vpv.ClearServiceCounters()
+}
+
+// SetMacAddr : The MAC address is set when a MAC address is learnt through the
+// packets received from the network. Currently, DHCP packets are
+// only packets we learn the MAC address from
+func (vpv *VoltPortVnet) SetMacAddr(addr net.HardwareAddr) {
+
+ //Store Learnt MAC address and return if MACLearning is not enabled
+ vpv.LearntMacAddr = addr
+ if vpv.MacLearning == MacLearningNone || !NonZeroMacAddress(addr) ||
+ (NonZeroMacAddress(vpv.MacAddr) && vpv.MacLearning == Learn) {
+ return
+ }
+
+ // Compare the two MAC addresses to see if it is same
+ // If they are same, we just return. If not, we perform
+ // actions to address the change in MAC address
+ //if NonZeroMacAddress(vpv.MacAddr) && !util.MacAddrsMatch(vpv.MacAddr, addr) {
+ if !util.MacAddrsMatch(vpv.MacAddr, addr) {
+ expectedPort := GetApplication().GetMacInPortMap(addr)
+ if expectedPort != "" && expectedPort != vpv.Port {
+ logger.Errorw(ctx, "mac-learnt-from-different-port-ignoring-setmacaddr",
+ log.Fields{"ExpectedPort": expectedPort, "ReceivedPort": vpv.Port, "LearntMacAdrr": vpv.MacAddr, "NewMacAdrr": addr.String()})
+ return
+ }
+ if NonZeroMacAddress(vpv.MacAddr) {
+ logger.Warnw(ctx, "MAC Address Changed. Remove old flows (if added) and re-add with updated MAC", log.Fields{"UpdatedMAC": addr})
+
+ // The newly learnt MAC address is different than earlier one.
+ // The existing MAC based HSIA flows need to be undone as the device
+ // may have been changed
+ // Atleast one HSIA flow should be present in adapter to retain the TP and GEM
+ // hence delete one after the other
+ vpv.services.Range(DelUsHsiaFlows)
+ vpv.MacAddr = addr
+ vpv.services.Range(vpv.setLearntMAC)
+ vpv.services.Range(AddUsHsiaFlows)
+ vpv.services.Range(DelDsHsiaFlows)
+ GetApplication().DeleteMacInPortMap(vpv.MacAddr)
+ } else {
+ vpv.MacAddr = addr
+ vpv.services.Range(vpv.setLearntMAC)
+ logger.Infow(ctx, "MAC Address learnt from DHCP or ARP", log.Fields{"Learnt MAC": addr.String(), "Port": vpv.Port})
+ }
+ GetApplication().UpdateMacInPortMap(vpv.MacAddr, vpv.Port)
+ } else {
+ logger.Infow(ctx, "Leant MAC Address is same", log.Fields{"Learnt MAC": addr.String(), "Port": vpv.Port})
+ }
+
+ _, err := GetApplication().GetDeviceFromPort(vpv.Port)
+ if err != nil {
+ logger.Warnw(ctx, "Not pushing Service Flows: Error Getting Device", log.Fields{"Reason": err.Error()})
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vpv.FlowInstallFailure("VGC processing failure", statusCode, statusMessage)
+ return
+ }
+ // Ds Hsia flows has to be pushed
+ if vpv.FlowsApplied {
+ // no HSIA flows for multicast service
+ if !vpv.McastService {
+ vpv.services.Range(AddDsHsiaFlows)
+ }
+ }
+ vpv.WriteToDb()
+}
+
+// MatchesVlans : If the VNET matches both S and C VLANs, return true. Else, return false
+func (vpv *VoltPortVnet) MatchesVlans(svlan of.VlanType, cvlan of.VlanType, univlan of.VlanType) bool {
+ if vpv.SVlan != svlan || vpv.CVlan != cvlan || vpv.UniVlan != univlan {
+ return false
+ }
+ return true
+}
+
+// MatchesCvlan : If the VNET matches CVLAN, return true. Else, return false
+func (vpv *VoltPortVnet) MatchesCvlan(cvlan []of.VlanType) bool {
+ if len(cvlan) != 1 && !vpv.AllowTransparent {
+ return false
+ }
+ if vpv.CVlan != cvlan[0] {
+ return false
+ }
+ return true
+}
+
+// MatchesPriority : If the VNET matches priority of the incoming packet with any service, return true. Else, return false
+func (vpv *VoltPortVnet) MatchesPriority(priority uint8) *VoltService {
+
+ var service *VoltService
+ pbitFound := false
+ matchpbitsFunc := func(key, value interface{}) bool {
+ svc := value.(*VoltService)
+ for _, pbit := range svc.Pbits {
+ if uint8(pbit) == priority {
+ logger.Infow(ctx, "Pbit match found with service",
+ log.Fields{"Pbit": priority, "serviceName": svc.Name})
+ pbitFound = true
+ service = svc
+ return false //Returning false to stop the Range loop
+ }
+ }
+ return true
+ }
+ _ = pbitFound
+ vpv.services.Range(matchpbitsFunc)
+ return service
+}
+
+// GetRemarkedPriority : If the VNET matches priority of the incoming packet with any service, return true. Else, return false
+func (vpv *VoltPortVnet) GetRemarkedPriority(priority uint8) uint8 {
+
+ dsPbit := uint8(0)
+ matchpbitsFunc := func(key, value interface{}) bool {
+ svc := value.(*VoltService)
+ if remarkPbit, ok := svc.DsRemarkPbitsMap[int(priority)]; ok {
+ logger.Infow(ctx, "Pbit match found with service",
+ log.Fields{"Pbit": priority, "serviceName": svc.Name, "remarkPbit": remarkPbit})
+ dsPbit = uint8(remarkPbit)
+ return false //Returning false to stop the Range loop
+ }
+ // When no remarking info is available, remark the incoming pbit
+ // to highest pbit configured for the subscriber (across all subservices associated)
+ svcPbit := uint8(svc.Pbits[0])
+ if svcPbit > dsPbit {
+ dsPbit = svcPbit
+ }
+ return true
+ }
+ vpv.services.Range(matchpbitsFunc)
+ logger.Debugw(ctx, "Remarked Pbit Value", log.Fields{"Incoming": priority, "Remarked": dsPbit})
+ return dsPbit
+}
+
+// AddSvc adds a service on the VNET on a port. The addition is
+// triggered when NB requests for service addition
+func (vpv *VoltPortVnet) AddSvc(svc *VoltService) {
+
+ //vpv.services = append(vpv.services, svc)
+ vpv.AddService(svc)
+ logger.Debugw(ctx, "Added Service to VPV", log.Fields{"Num of SVCs": vpv.servicesCount.Load(), "SVC": svc})
+
+ // Learn the circuit-id and remote-id from the service
+ // TODO: There must be a better way of doing this. This
+ // may be explored
+ if svc.IgmpEnabled {
+ vpv.IgmpEnabled = true
+ }
+ // first time service activation MacLearning will have default value as None.
+ // to handle reciliency if anythng other then None we should retain it .
+ if svc.MacLearning == MacLearningNone {
+ if !vpv.DhcpRelay && !vpv.ArpRelay {
+ svc.MacLearning = MacLearningNone
+ } else if vpv.MacLearning == Learn {
+ svc.MacLearning = Learn
+ } else if vpv.MacLearning == ReLearn {
+ svc.MacLearning = ReLearn
+ }
+ }
+
+ //TODO: Temp Change - Need to address MAC Learning flow issues completely
+ if (svc.MacLearning == Learn || svc.MacLearning == ReLearn) && NonZeroMacAddress(vpv.MacAddr) {
+ svc.MacAddr = vpv.MacAddr
+ } else if vpv.servicesCount.Load() == 1 {
+ vpv.MacAddr = svc.MacAddr
+ }
+
+ vpv.MacLearning = svc.MacLearning
+ vpv.PonPort = svc.PonPort
+ logger.Debugw(ctx, "Added MAC to VPV", log.Fields{"MacLearning": vpv.MacLearning, "VPV": vpv})
+ //Reconfigure Vlans based on Vlan Control type
+ svc.VlanControl = vpv.VlanControl
+ // for OLTCVLAN SVLAN=CVLAN, UNIVLAN can differ.
+ if vpv.VlanControl == ONUCVlan {
+ svc.CVlan = svc.SVlan
+ }
+ // for OLTSVLAN CVLAN=UNIVLAN , SVLAN can differ,
+ // hence assigning UNIVLAN to CVLAN, so that ONU will transparently forward the packet.
+ if vpv.VlanControl == OLTSVlan {
+ svc.CVlan = svc.UniVlan
+ }
+ if svc.McastService {
+ vpv.McastService = true
+ vpv.McastTechProfileID = svc.TechProfileID
+ //Assumption: Only one Pbit for mcast service
+ vpv.McastPbit = svc.Pbits[0]
+ vpv.McastUsMeterID = svc.UsMeterID
+ vpv.SchedID = svc.SchedID
+ }
+ svc.ONTEtherTypeClassification = vpv.ONTEtherTypeClassification
+ svc.AllowTransparent = vpv.AllowTransparent
+ svc.SVlanTpid = vpv.SVlanTpid
+
+ //Ensure configuring the mvlan profile only once
+ //One subscriber cannot have multiple mvlan profiles. Only the first configuration is valid
+ if svc.MvlanProfileName != "" {
+ if vpv.MvlanProfileName == "" {
+ vpv.MvlanProfileName = svc.MvlanProfileName
+ } else {
+ logger.Warnw(ctx, "Mvlan Profile already configured for subscriber. Ignoring new Mvlan", log.Fields{"Existing Mvlan": vpv.MvlanProfileName, "New Mvlan": svc.MvlanProfileName})
+ }
+ }
+
+ _, err := GetApplication().GetDeviceFromPort(vpv.Port)
+ if err != nil {
+ logger.Warnw(ctx, "Not pushing Service Flows: Error Getting Device", log.Fields{"Reason": err.Error()})
+ //statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ //TODO-COMM: vpv.FlowInstallFailure("VGC processing failure", statusCode, statusMessage)
+ return
+ }
+
+ //Push Service Flows if DHCP relay is not configured
+ //or already DHCP flows are configured for the VPV
+ //to which the serivce is associated
+ if vpv.FlowsApplied {
+ if NonZeroMacAddress(vpv.MacAddr) || svc.MacLearning == MacLearningNone {
+ svc.AddHsiaFlows()
+ } else {
+ if err:= svc.AddUsHsiaFlows(); err != nil {
+ logger.Warnw(ctx, "Add US hsia flow failed", log.Fields{"service": svc.Name, "Error": err})
+ }
+ }
+ }
+
+ //Assumption: Igmp will be enabled only for one service and SubMgr ensure the same
+ // When already the port is UP and provisioned a service without igmp, then trap flows for subsequent
+ // service with Igmp Enabled needs to be installed
+ if svc.IgmpEnabled && vpv.FlowsApplied {
+ logger.Infow(ctx, "Add Service - IGMP Flows", log.Fields{"Device": vpv.Device, "Port": vpv.Port})
+ if err := vpv.AddIgmpFlows(); err != nil {
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vpv.FlowInstallFailure("VGC processing failure", statusCode, statusMessage)
+ }
+
+ if vpv.McastService {
+ //For McastService, send Service Activated indication once IGMP US flow is pushed
+ vpv.services.Range(PostAccessConfigSuccessInd)
+ }
+ }
+ vpv.WriteToDb()
+}
+
+// setLearntMAC to set learnt mac
+func (vpv *VoltPortVnet) setLearntMAC(key, value interface{}) bool {
+ svc := value.(*VoltService)
+ svc.SetMacAddr(vpv.MacAddr)
+ svc.WriteToDb()
+ return true
+}
+
+// PostAccessConfigSuccessInd for posting access config success indication
+func PostAccessConfigSuccessInd(key, value interface{}) bool {
+ return true
+}
+
+// updateIPv4AndProvisionFlows to update ipv4 and provisional flows
+func (vpv *VoltPortVnet) updateIPv4AndProvisionFlows(key, value interface{}) bool {
+ svc := value.(*VoltService)
+ logger.Info(ctx, "Updating Ipv4 address for service", log.Fields{"ServiceName": svc.Name})
+ svc.SetIpv4Addr(vpv.Ipv4Addr)
+ svc.WriteToDb()
+
+ return true
+}
+
+// updateIPv6AndProvisionFlows to update ipv6 and provisional flow
+func (vpv *VoltPortVnet) updateIPv6AndProvisionFlows(key, value interface{}) bool {
+ svc := value.(*VoltService)
+ svc.SetIpv6Addr(vpv.Ipv6Addr)
+ svc.WriteToDb()
+
+ return true
+}
+
+// AddUsHsiaFlows to add upstream hsia flows
+func AddUsHsiaFlows(key, value interface{}) bool {
+ svc := value.(*VoltService)
+ if err:= svc.AddUsHsiaFlows(); err != nil {
+ logger.Warnw(ctx, "Add US hsia flow failed", log.Fields{"service": svc.Name, "Error": err})
+ }
+ return true
+}
+
+// AddDsHsiaFlows to add downstream hsia flows
+func AddDsHsiaFlows(key, value interface{}) bool {
+ svc := value.(*VoltService)
+ if err:= svc.AddDsHsiaFlows(); err != nil {
+ logger.Warnw(ctx, "Add DS hsia flow failed", log.Fields{"service": svc.Name, "Error": err})
+ }
+ return true
+}
+
+// ClearFlagsInService to clear the flags used in service
+func ClearFlagsInService(key, value interface{}) bool {
+ svc := value.(*VoltService)
+ svc.ServiceLock.Lock()
+ svc.IgmpFlowsApplied = false
+ svc.DsDhcpFlowsApplied = false
+ svc.DsHSIAFlowsApplied = false
+ svc.Icmpv6FlowsApplied = false
+ svc.UsHSIAFlowsApplied = false
+ svc.UsDhcpFlowsApplied = false
+ svc.PendingFlows = make(map[string]bool)
+ svc.AssociatedFlows = make(map[string]bool)
+ svc.ServiceLock.Unlock()
+ svc.WriteToDb()
+ logger.Debugw(ctx, "Cleared Flow Flags for service", log.Fields{"name": svc.Name})
+ return true
+}
+
+// DelDsHsiaFlows to delete hsia flows
+func DelDsHsiaFlows(key, value interface{}) bool {
+ svc := value.(*VoltService)
+ if err:= svc.DelDsHsiaFlows(); err != nil {
+ logger.Warnw(ctx, "Delete DS hsia flow failed", log.Fields{"service": svc.Name, "Error": err})
+ }
+ return true
+}
+
+// DelUsHsiaFlows to delete upstream hsia flows
+func DelUsHsiaFlows(key, value interface{}) bool {
+ svc := value.(*VoltService)
+ if err:= svc.DelUsHsiaFlows(); err != nil {
+ logger.Warnw(ctx, "Delete US hsia flow failed", log.Fields{"service": svc.Name, "Error": err})
+ }
+ return true
+}
+
+// ClearServiceCounters to clear the service counters
+func ClearServiceCounters(key, value interface{}) bool {
+ svc := value.(*VoltService)
+ //Delete the per service counter too
+ GetApplication().ServiceCounters.Delete(svc.Name)
+ if svc.IgmpEnabled && svc.EnableMulticastKPI {
+ _ = db.DelAllServiceChannelCounter(svc.Name)
+ }
+ return true
+}
+
+//AddTrapFlows - Adds US & DS Trap flows
+func (vpv *VoltPortVnet) AddTrapFlows() {
+
+ if !vpv.FlowsApplied || vgcRebooted {
+ if vpv.DhcpRelay {
+ if err := vpv.AddUsDhcpFlows(); err != nil {
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vpv.FlowInstallFailure("VGC processing failure", statusCode, statusMessage)
+ }
+ if err := vpv.AddDsDhcpFlows(); err != nil {
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vpv.FlowInstallFailure("VGC processing failure", statusCode, statusMessage)
+ }
+ logger.Infow(ctx, "ICMPv6 MC Group modification will not be triggered to rwcore for ",
+ log.Fields{"port": vpv.Port})
+ //vpv.updateICMPv6McGroup(true)
+ } else if vpv.ArpRelay {
+ if err := vpv.AddUsArpFlows(); err != nil {
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vpv.FlowInstallFailure("VGC processing failure", statusCode, statusMessage)
+ }
+ logger.Info(ctx, "ARP trap rules not added in downstream direction")
+
+ } else if vpv.PppoeIa {
+ if err := vpv.AddUsPppoeFlows(); err != nil {
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vpv.FlowInstallFailure("VGC processing failure", statusCode, statusMessage)
+ }
+ if err := vpv.AddDsPppoeFlows(); err != nil {
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vpv.FlowInstallFailure("VGC processing failure", statusCode, statusMessage)
+ }
+ }
+ vpv.FlowsApplied = true
+ vpv.WriteToDb()
+ }
+}
+
+//DelTrapFlows - Removes all US & DS DHCP, IGMP trap flows.
+func (vpv *VoltPortVnet) DelTrapFlows() {
+
+ // Delete HSIA & DHCP flows before deleting IGMP flows
+ if vpv.FlowsApplied || vgcRebooted {
+ if vpv.DhcpRelay {
+ if err:= vpv.DelUsDhcpFlows(); err != nil {
+ logger.Warnw(ctx, "Delete US hsia flow failed", log.Fields{"port": vpv.Port, "SVlan": vpv.SVlan, "CVlan": vpv.CVlan,
+ "UniVlan": vpv.UniVlan, "Error": err})
+ }
+ logger.Infow(ctx, "ICMPv6 MC Group modification will not be triggered to rwcore for ",
+ log.Fields{"port": vpv.Port})
+ if err := vpv.DelDsDhcpFlows(); err != nil {
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vpv.FlowInstallFailure("VGC processing failure", statusCode, statusMessage)
+ }
+ //vpv.updateICMPv6McGroup(false)
+ } else if vpv.ArpRelay {
+ if err := vpv.DelUsArpFlows(); err != nil {
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vpv.FlowInstallFailure("VGC processing failure", statusCode, statusMessage)
+ }
+ } else if vpv.PppoeIa {
+ if err := vpv.DelUsPppoeFlows(); err != nil {
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vpv.FlowInstallFailure("VGC processing failure", statusCode, statusMessage)
+ }
+ if err := vpv.DelDsPppoeFlows(); err != nil {
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vpv.FlowInstallFailure("VGC processing failure", statusCode, statusMessage)
+ }
+ }
+ vpv.FlowsApplied = false
+ vpv.WriteToDb()
+ }
+ if err:= vpv.DelIgmpFlows(); err != nil {
+ logger.Warnw(ctx, "Delete igmp flow failed", log.Fields{"port": vpv.Port, "SVlan": vpv.SVlan, "CVlan": vpv.CVlan,
+ "UniVlan": vpv.UniVlan, "Error": err})
+ }
+}
+
+// DelHsiaFlows deletes the service flows
+func (vpv *VoltPortVnet) DelHsiaFlows() {
+ // no HSIA flows for multicast service
+ if !vpv.McastService {
+ vpv.services.Range(DelUsHsiaFlows)
+ vpv.services.Range(DelDsHsiaFlows)
+ }
+}
+
+//ClearServiceCounters - Removes all igmp counters for a service
+func (vpv *VoltPortVnet) ClearServiceCounters() {
+ //send flows deleted indication to submgr
+ vpv.services.Range(ClearServiceCounters)
+}
+
+// AddUsDhcpFlows pushes the DHCP flows to the VOLTHA via the controller
+func (vpv *VoltPortVnet) AddUsDhcpFlows() error {
+ var vd *VoltDevice
+ device := vpv.Device
+
+ if vd = GetApplication().GetDevice(device); vd != nil {
+ if vd.State != controller.DeviceStateUP {
+ logger.Errorw(ctx, "Skipping US DHCP Flow Push - Device state DOWN", log.Fields{"Port": vpv.Port, "SVLAN": vpv.SVlan, "CVLAN": vpv.CVlan, "UNIVlan": vpv.UniVlan, "device": device})
+ return nil
+ }
+ } else {
+ logger.Errorw(ctx, "US DHCP Flow Push Failed- Device not found", log.Fields{"Port": vpv.Port, "SVLAN": vpv.SVlan, "CVLAN": vpv.CVlan, "UNIVlan": vpv.UniVlan, "device": device})
+ return errorCodes.ErrDeviceNotFound
+ }
+
+ flows, err := vpv.BuildUsDhcpFlows()
+ if err == nil {
+ logger.Debugw(ctx, "Adding US DHCP flows", log.Fields{"Device": device})
+ if err1 := vpv.PushFlows(vd, flows); err1 != nil {
+ //push ind here ABHI
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err1)
+ vpv.FlowInstallFailure("VGC processing failure", statusCode, statusMessage)
+ }
+ } else {
+ logger.Errorw(ctx, "US DHCP Flow Add Failed", log.Fields{"Reason": err.Error(), "Device": device})
+ //push ind here ABHI
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vpv.FlowInstallFailure("VGC processing failure", statusCode, statusMessage)
+
+ }
+ /*
+ flows, err = vpv.BuildUsDhcp6Flows()
+ if err == nil {
+ logger.Debugw(ctx, "Adding US DHCP6 flows", log.Fields{"Device": device})
+ if err1 := vpv.PushFlows(vd, flows); err1 != nil {
+ //pussh ind here ABHI
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err1)
+ vpv.FlowInstallFailure("VGC processing failure", statusCode, statusMessage)
+
+ }
+ } else {
+ logger.Errorw(ctx, "US DHCP6 Flow Add Failed", log.Fields{"Reason": err.Error(), "Device": device})
+ //push ind here ABHI
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vpv.FlowInstallFailure("VGC processing failure", statusCode, statusMessage)
+
+ }*/
+ return nil
+}
+
+// AddDsDhcpFlows function pushes the DHCP flows to the VOLTHA via the controller
+func (vpv *VoltPortVnet) AddDsDhcpFlows() error {
+
+ var vd *VoltDevice
+ device := vpv.Device
+
+ if vd = GetApplication().GetDevice(device); vd != nil {
+ if vd.State != controller.DeviceStateUP {
+ logger.Errorw(ctx, "Skipping DS DHCP Flow Push - Device state DOWN", log.Fields{"Port": vpv.Port, "SVLAN": vpv.SVlan, "CVLAN": vpv.CVlan, "UNIVlan": vpv.UniVlan, "device": device})
+ return nil
+ }
+ } else {
+ logger.Errorw(ctx, "DS DHCP Flow Push Failed- Device not found", log.Fields{"Port": vpv.Port, "SVLAN": vpv.SVlan, "CVLAN": vpv.CVlan, "UNIVlan": vpv.UniVlan, "device": device})
+ return errorCodes.ErrDeviceNotFound
+ }
+ if GetApplication().GetVendorID() != Radisys && vd.GlobalDhcpFlowAdded {
+ return nil
+ }
+
+ flows, err := vpv.BuildDsDhcpFlows()
+ if err == nil {
+ if err1 := vpv.PushFlows(vd, flows); err1 != nil {
+ //push ind here and procced
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err1)
+ vpv.FlowInstallFailure("VGC processing failure", statusCode, statusMessage)
+
+ }
+ } else {
+ logger.Errorw(ctx, "DS DHCP Flow Add Failed", log.Fields{"Reason": err.Error()})
+ //send ind here and proceed
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vpv.FlowInstallFailure("VGC processing failure", statusCode, statusMessage)
+
+ }
+ /*
+ flows, err = vpv.BuildDsDhcp6Flows()
+ if err == nil {
+ if err1 := vpv.PushFlows(vd, flows); err1 != nil {
+ //push ind and proceed
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err1)
+ vpv.FlowInstallFailure("VGC processing failure", statusCode, statusMessage)
+
+ }
+ } else {
+ logger.Errorw(ctx, "DS DHCP6 Flow Add Failed", log.Fields{"Reason": err.Error()})
+ //Send ind here and proceed
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vpv.FlowInstallFailure("VGC processing failure", statusCode, statusMessage)
+
+ }*/
+ if GetApplication().GetVendorID() != Radisys {
+ vd.GlobalDhcpFlowAdded = true
+ }
+ return nil
+}
+
+// DelDhcpFlows deletes both US & DS DHCP flows applied for this Vnet instantiated on the port
+func (vpv *VoltPortVnet) DelDhcpFlows() {
+ if err := vpv.DelUsDhcpFlows(); err != nil {
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vpv.FlowInstallFailure("VGC processing failure", statusCode, statusMessage)
+ }
+
+ if err := vpv.DelDsDhcpFlows(); err != nil {
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vpv.FlowInstallFailure("VGC processing failure", statusCode, statusMessage)
+ }
+}
+
+// DelUsDhcpFlows delete the DHCP flows applied for this Vnet instantiated on the port
+// Write the status of the VPV to the DB once the delete is scheduled
+// for dispatch
+func (vpv *VoltPortVnet) DelUsDhcpFlows() error {
+ device, err := GetApplication().GetDeviceFromPort(vpv.Port)
+ if err != nil {
+ return err
+ }
+
+ err = vpv.delDhcp4Flows(device)
+ if err != nil {
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vpv.FlowInstallFailure("VGC processing failure", statusCode, statusMessage)
+ }
+ /*
+ err = vpv.delDhcp6Flows(device)
+ if err != nil {
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vpv.FlowInstallFailure("VGC processing failure", statusCode, statusMessage)
+ }*/
+ return nil
+}
+
+func (vpv *VoltPortVnet) delDhcp4Flows(device *VoltDevice) error {
+ flows, err := vpv.BuildUsDhcpFlows()
+ if err == nil {
+ return vpv.RemoveFlows(device, flows)
+ }
+ logger.Errorw(ctx, "US DHCP Flow Delete Failed", log.Fields{"Reason": err.Error()})
+ return err
+}
+/*
+func (vpv *VoltPortVnet) delDhcp6Flows(device *VoltDevice) error {
+ flows, err := vpv.BuildUsDhcp6Flows()
+ if err == nil {
+ return vpv.RemoveFlows(device, flows)
+ }
+ logger.Errorw(ctx, "US DHCP6 Flow Delete Failed", log.Fields{"Reason": err.Error()})
+ return err
+
+}*/
+
+// DelDsDhcpFlows delete the DHCP flows applied for this Vnet instantiated on the port
+// Write the status of the VPV to the DB once the delete is scheduled
+// for dispatch
+func (vpv *VoltPortVnet) DelDsDhcpFlows() error {
+ device, err := GetApplication().GetDeviceFromPort(vpv.Port)
+ if err != nil {
+ return err
+ }
+ err = vpv.delDsDhcp4Flows(device)
+ if err != nil {
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vpv.FlowInstallFailure("VGC processing failure", statusCode, statusMessage)
+ }
+ /*
+ err = vpv.delDsDhcp6Flows(device)
+ if err != nil {
+ statusCode, statusMessage := infraerrorCodes.GetErrorInfo(err)
+ vpv.FlowInstallFailure("VGC processing failure", statusCode, statusMessage)
+ }*/
+ return nil
+}
+
+func (vpv *VoltPortVnet) delDsDhcp4Flows(device *VoltDevice) error {
+ flows, err := vpv.BuildDsDhcpFlows()
+ if err == nil {
+ return vpv.RemoveFlows(device, flows)
+ }
+ logger.Errorw(ctx, "DS DHCP Flow Delete Failed", log.Fields{"Reason": err.Error()})
+ return err
+}
+
+/*
+func (vpv *VoltPortVnet) delDsDhcp6Flows(device *VoltDevice) error {
+ flows, err := vpv.BuildDsDhcp6Flows()
+ if err == nil {
+ return vpv.RemoveFlows(device, flows)
+ }
+ logger.Errorw(ctx, "DS DHCP6 Flow Delete Failed", log.Fields{"Reason": err.Error()})
+ return err
+}*/
+
+// AddUsArpFlows pushes the ARP flows to the VOLTHA via the controller
+func (vpv *VoltPortVnet) AddUsArpFlows() error {
+
+ var vd *VoltDevice
+ device := vpv.Device
+ if vd = GetApplication().GetDevice(device); vd != nil {
+ if vd.State != controller.DeviceStateUP {
+ logger.Errorw(ctx, "Skipping US ARP Flow Push - Device state DOWN", log.Fields{"Port": vpv.Port, "SVLAN": vpv.SVlan, "CVLAN": vpv.CVlan, "UNIVlan": vpv.UniVlan, "device": device})
+ return nil
+ }
+ } else {
+ logger.Errorw(ctx, "US ARP Flow Push Failed- Device not found", log.Fields{"Port": vpv.Port, "SVLAN": vpv.SVlan, "CVLAN": vpv.CVlan, "UNIVlan": vpv.UniVlan, "device": device})
+ return errorCodes.ErrDeviceNotFound
+ }
+
+ flows, err := vpv.BuildUsArpFlows()
+ if err == nil {
+ logger.Debugw(ctx, "Adding US ARP flows", log.Fields{"Device": device})
+ if err1 := vpv.PushFlows(vd, flows); err1 != nil {
+ return err1
+ }
+ } else {
+ logger.Errorw(ctx, "US ARP Flow Add Failed", log.Fields{"Reason": err.Error(), "Device": device})
+ return err
+ }
+ return nil
+}
+
+// DelUsArpFlows delete the ARP flows applied for this Vnet instantiated on the port
+// Write the status of the VPV to the DB once the delete is scheduled
+// for dispatch
+func (vpv *VoltPortVnet) DelUsArpFlows() error {
+ device, err := GetApplication().GetDeviceFromPort(vpv.Port)
+ if err != nil {
+ return err
+ }
+ flows, err := vpv.BuildUsArpFlows()
+ if err == nil {
+ return vpv.RemoveFlows(device, flows)
+ }
+ logger.Errorw(ctx, "US ARP Flow Delete Failed", log.Fields{"Reason": err.Error()})
+ return err
+}
+
+// AddUsPppoeFlows pushes the PPPoE flows to the VOLTHA via the controller
+func (vpv *VoltPortVnet) AddUsPppoeFlows() error {
+ logger.Debugw(ctx, "Adding US PPPoE flows", log.Fields{"STAG": vpv.SVlan, "CTAG": vpv.CVlan, "Device": vpv.Device})
+
+ var vd *VoltDevice
+ device := vpv.Device
+
+ if vd = GetApplication().GetDevice(device); vd != nil {
+ if vd.State != controller.DeviceStateUP {
+ logger.Errorw(ctx, "Skipping US PPPoE Flow Push - Device state DOWN", log.Fields{"Port": vpv.Port, "SVLAN": vpv.SVlan, "CVLAN": vpv.CVlan, "UNIVlan": vpv.UniVlan, "device": device})
+ return nil
+ }
+ } else {
+ logger.Errorw(ctx, "US PPPoE Flow Push Failed- Device not found", log.Fields{"Port": vpv.Port, "SVLAN": vpv.SVlan, "CVLAN": vpv.CVlan, "UNIVlan": vpv.UniVlan, "device": device})
+ return errorCodes.ErrDeviceNotFound
+ }
+
+ if flows, err := vpv.BuildUsPppoeFlows(); err == nil {
+ logger.Debugw(ctx, "Adding US PPPoE flows", log.Fields{"Device": device})
+
+ if err1 := vpv.PushFlows(vd, flows); err1 != nil {
+ return err1
+ }
+ } else {
+ logger.Errorw(ctx, "US PPPoE Flow Add Failed", log.Fields{"Reason": err.Error(), "Device": device})
+ return err
+ }
+ return nil
+}
+
+// AddDsPppoeFlows to add downstream pppoe flows
+func (vpv *VoltPortVnet) AddDsPppoeFlows() error {
+ logger.Debugw(ctx, "Adding DS PPPoE flows", log.Fields{"STAG": vpv.SVlan, "CTAG": vpv.CVlan, "Device": vpv.Device})
+ var vd *VoltDevice
+ device := vpv.Device
+
+ if vd = GetApplication().GetDevice(device); vd != nil {
+ if vd.State != controller.DeviceStateUP {
+ logger.Errorw(ctx, "Skipping DS PPPoE Flow Push - Device state DOWN", log.Fields{"Port": vpv.Port, "SVLAN": vpv.SVlan, "CVLAN": vpv.CVlan, "UNIVlan": vpv.UniVlan, "device": device})
+ return nil
+ }
+ } else {
+ logger.Errorw(ctx, "DS PPPoE Flow Push Failed- Device not found", log.Fields{"Port": vpv.Port, "SVLAN": vpv.SVlan, "CVLAN": vpv.CVlan, "UNIVlan": vpv.UniVlan, "device": device})
+ return errorCodes.ErrDeviceNotFound
+ }
+
+ flows, err := vpv.BuildDsPppoeFlows()
+ if err == nil {
+
+ if err1 := vpv.PushFlows(vd, flows); err1 != nil {
+ return err1
+ }
+ } else {
+ logger.Errorw(ctx, "DS PPPoE Flow Add Failed", log.Fields{"Reason": err.Error()})
+ return err
+ }
+ return nil
+}
+
+// DelUsPppoeFlows delete the PPPoE flows applied for this Vnet instantiated on the port
+// Write the status of the VPV to the DB once the delete is scheduled
+// for dispatch
+func (vpv *VoltPortVnet) DelUsPppoeFlows() error {
+ logger.Debugw(ctx, "Deleting US PPPoE flows", log.Fields{"STAG": vpv.SVlan, "CTAG": vpv.CVlan, "Device": vpv.Device})
+ device, err := GetApplication().GetDeviceFromPort(vpv.Port)
+ if err != nil {
+ return err
+ }
+ flows, err := vpv.BuildUsPppoeFlows()
+ if err == nil {
+ return vpv.RemoveFlows(device, flows)
+ }
+ logger.Errorw(ctx, "US PPPoE Flow Delete Failed", log.Fields{"Reason": err.Error()})
+ return err
+}
+
+// DelDsPppoeFlows delete the PPPoE flows applied for this Vnet instantiated on the port
+// Write the status of the VPV to the DB once the delete is scheduled
+// for dispatch
+func (vpv *VoltPortVnet) DelDsPppoeFlows() error {
+ logger.Debugw(ctx, "Deleting DS PPPoE flows", log.Fields{"STAG": vpv.SVlan, "CTAG": vpv.CVlan, "Device": vpv.Device})
+ device, err := GetApplication().GetDeviceFromPort(vpv.Port)
+ if err != nil {
+ return err
+ }
+ flows, err := vpv.BuildDsPppoeFlows()
+ if err == nil {
+ return vpv.RemoveFlows(device, flows)
+ }
+ logger.Errorw(ctx, "DS PPPoE Flow Delete Failed", log.Fields{"Reason": err.Error()})
+ return err
+}
+
+// AddIgmpFlows function pushes the IGMP flows to the VOLTHA via the controller
+func (vpv *VoltPortVnet) AddIgmpFlows() error {
+
+ if !vpv.IgmpFlowsApplied || vgcRebooted {
+ if vpv.MvlanProfileName == "" {
+ logger.Info(ctx, "Mvlan Profile not configured. Ignoring Igmp trap flow")
+ return nil
+ }
+ device, err := GetApplication().GetDeviceFromPort(vpv.Port)
+ if err != nil {
+ logger.Errorw(ctx, "Error getting device from port", log.Fields{"Port": vpv.Port, "Reason": err.Error()})
+ return err
+ } else if device.State != controller.DeviceStateUP {
+ logger.Warnw(ctx, "Device state Down. Ignoring US IGMP Flow Push", log.Fields{"Port": vpv.Port, "SVLAN": vpv.SVlan, "CVLAN": vpv.CVlan, "UNIVlan": vpv.UniVlan})
+ return nil
+ }
+ flows, err := vpv.BuildIgmpFlows()
+ if err == nil {
+ for cookie := range flows.SubFlows {
+ if vd := GetApplication().GetDevice(device.Name); vd != nil {
+ cookie := strconv.FormatUint(cookie, 10)
+ fe := &FlowEvent{
+ eType: EventTypeUsIgmpFlowAdded,
+ cookie: cookie,
+ eventData: vpv,
+ }
+ vd.RegisterFlowAddEvent(cookie, fe)
+ }
+ }
+ if err1 := cntlr.GetController().AddFlows(vpv.Port, device.Name, flows); err1 != nil {
+ return err1
+ }
+ } else {
+ logger.Errorw(ctx, "IGMP Flow Add Failed", log.Fields{"Reason": err.Error()})
+ return err
+ }
+ vpv.IgmpFlowsApplied = true
+ vpv.WriteToDb()
+ }
+ return nil
+}
+
+// DelIgmpFlows delete the IGMP flows applied for this Vnet instantiated on the port
+// Write the status of the VPV to the DB once the delete is scheduled
+// for dispatch
+func (vpv *VoltPortVnet) DelIgmpFlows() error {
+
+ if vpv.IgmpFlowsApplied || vgcRebooted {
+ device, err := GetApplication().GetDeviceFromPort(vpv.Port)
+ if err != nil {
+ logger.Errorw(ctx, "Error getting device from port", log.Fields{"Port": vpv.Port, "Reason": err.Error()})
+ return err
+ }
+ flows, err := vpv.BuildIgmpFlows()
+ if err == nil {
+ if err1 := vpv.RemoveFlows(device, flows); err1 != nil {
+ return err1
+ }
+ } else {
+ logger.Errorw(ctx, "IGMP Flow Add Failed", log.Fields{"Reason": err.Error()})
+ return err
+ }
+ vpv.IgmpFlowsApplied = false
+ vpv.WriteToDb()
+ }
+ return nil
+}
+
+// BuildUsDhcpFlows builds the US DHCP relay flows for a subscriber
+// The flows included by this function cover US only as the DS is
+// created either automatically by the VOLTHA or at the device level
+// earlier
+func (vpv *VoltPortVnet) BuildUsDhcpFlows() (*of.VoltFlow, error) {
+ flow := &of.VoltFlow{}
+ flow.SubFlows = make(map[uint64]*of.VoltSubFlow)
+
+ logger.Infow(ctx, "Building US DHCP flow", log.Fields{"Port": vpv.Port})
+ subFlow := of.NewVoltSubFlow()
+ subFlow.SetTableID(0)
+
+ if GetApplication().GetVendorID() == Radisys {
+ if err := vpv.setUsMatchVlan(subFlow); err != nil {
+ return nil, err
+ }
+ } else {
+ subFlow.SetMatchVlan(vpv.UniVlan)
+ subFlow.SetSetVlan(vpv.CVlan)
+ }
+ subFlow.SetUdpv4Match()
+ subFlow.SrcPort = 68
+ subFlow.DstPort = 67
+ uniport, err := GetApplication().GetPortID(vpv.Port)
+ if err != nil {
+ logger.Errorw(ctx, "Failed to fetch uni port from vpv", log.Fields{"error": err, "port": vpv.Port})
+ return nil, err
+ }
+ subFlow.SetInPort(uniport)
+ // PortName and PortID to be used for validation of port before flow pushing
+ flow.PortID = uniport
+ flow.PortName = vpv.Port
+ subFlow.SetReportToController()
+
+ // Set techprofile, meterid of first service
+ vpv.services.Range(func(key, value interface{}) bool {
+ svc := value.(*VoltService)
+ writemetadata := uint64(svc.TechProfileID) << 32
+ subFlow.SetWriteMetadata(writemetadata)
+ subFlow.SetMeterID(svc.UsMeterID)
+ return false
+ })
+
+ subFlow.SetPcp(vpv.DhcpPbit)
+ // metadata := uint64(uniport)
+ // subFlow.SetWriteMetadata(metadata)
+ allowTransparent := 0
+ if vpv.AllowTransparent {
+ allowTransparent = 1
+ }
+ metadata := uint64(allowTransparent)<<56 | uint64(vpv.ONTEtherTypeClassification)<<36 | uint64(vpv.VlanControl)<<32 | uint64(vpv.UniVlan)<<16 | uint64(vpv.CVlan)
+ subFlow.SetTableMetadata(metadata)
+
+ //| 12-bit cvlan | 4 bits empty | <32-bits uniport>| 16-bits dhcp mask or flow mask |
+ subFlow.Cookie = uint64(vpv.CVlan)<<52 | uint64(uniport)<<16 | of.DhcpArpFlowMask | of.UsFlowMask
+ subFlow.Priority = of.DhcpFlowPriority
+
+ flow.SubFlows[subFlow.Cookie] = subFlow
+ logger.Infow(ctx, "Built US DHCP flow ", log.Fields{"cookie": subFlow.Cookie, "flow": flow})
+ return flow, nil
+}
+
+// BuildDsDhcpFlows to build the downstream dhcp flows
+func (vpv *VoltPortVnet) BuildDsDhcpFlows() (*of.VoltFlow, error) {
+
+ logger.Infow(ctx, "Building DS DHCP flow", log.Fields{"Port": vpv.Port, "ML": vpv.MacLearning, "Mac": vpv.MacAddr})
+ flow := &of.VoltFlow{}
+ flow.SubFlows = make(map[uint64]*of.VoltSubFlow)
+ subFlow := of.NewVoltSubFlow()
+ subFlow.SetTableID(0)
+ // If dhcp trap rule is global rule, No need to match on vlan
+ if GetApplication().GetVendorID() == Radisys {
+ vpv.setDsMatchVlan(subFlow)
+ }
+ subFlow.SetUdpv4Match()
+ subFlow.SrcPort = 67
+ subFlow.DstPort = 68
+ uniport, _ := GetApplication().GetPortID(vpv.Port)
+ nni, err := GetApplication().GetNniPort(vpv.Device)
+ if err != nil {
+ return nil, err
+ }
+ nniport, err := GetApplication().GetPortID(nni)
+ if err != nil {
+ return nil, err
+ }
+ subFlow.SetInPort(nniport)
+ // PortName and PortID to be used for validation of port before flow pushing
+ flow.PortID = uniport
+ flow.PortName = vpv.Port
+ // metadata := uint64(uniport)
+ // subFlow.SetWriteMetadata(metadata)
+ allowTransparent := 0
+ if vpv.AllowTransparent {
+ allowTransparent = 1
+ }
+ metadata := uint64(allowTransparent)<<56 | uint64(vpv.ONTEtherTypeClassification)<<36 | uint64(vpv.VlanControl)<<32 | uint64(vpv.UniVlan)<<16 | uint64(vpv.CVlan)
+ subFlow.SetTableMetadata(metadata)
+ subFlow.SetReportToController()
+ //| 12-bit cvlan | 4 bits empty | <32-bits uniport>| 16-bits dhcp mask or flow mask |
+ subFlow.Cookie = uint64(vpv.CVlan)<<52 | uint64(uniport)<<16 | of.DhcpArpFlowMask | of.DsFlowMask
+ subFlow.Priority = of.DhcpFlowPriority
+
+ flow.SubFlows[subFlow.Cookie] = subFlow
+ logger.Infow(ctx, "Built DS DHCP flow ", log.Fields{"cookie": subFlow.Cookie, "Flow": flow})
+
+ return flow, nil
+}
+
+// BuildUsDhcp6Flows to trap the DHCPv6 packets to be reported to the
+// application.
+func (vpv *VoltPortVnet) BuildUsDhcp6Flows() (*of.VoltFlow, error) {
+ flow := &of.VoltFlow{}
+ flow.SubFlows = make(map[uint64]*of.VoltSubFlow)
+
+ logger.Infow(ctx, "Building US DHCPv6 flow", log.Fields{"Port": vpv.Port})
+ subFlow := of.NewVoltSubFlow()
+ subFlow.SetTableID(0)
+
+ subFlow.SetMatchVlan(vpv.UniVlan)
+ subFlow.SetSetVlan(vpv.CVlan)
+ subFlow.SetUdpv6Match()
+ subFlow.SrcPort = 546
+ subFlow.DstPort = 547
+ uniport, err := GetApplication().GetPortID(vpv.Port)
+ if err != nil {
+ return nil, err
+ }
+ // Set techprofile, meterid of first service
+ vpv.services.Range(func(key, value interface{}) bool {
+ svc := value.(*VoltService)
+ writemetadata := uint64(svc.TechProfileID) << 32
+ subFlow.SetWriteMetadata(writemetadata)
+ subFlow.SetMeterID(svc.UsMeterID)
+ return false
+ })
+ subFlow.SetInPort(uniport)
+ // PortName and PortID to be used for validation of port before flow pushing
+ flow.PortID = uniport
+ flow.PortName = vpv.Port
+ //subFlow.SetMeterId(vpv.UsDhcpMeterId)
+ // metadata := uint64(uniport)
+ // subFlow.SetWriteMetadata(metadata)
+ allowTransparent := 0
+ if vpv.AllowTransparent {
+ allowTransparent = 1
+ }
+ metadata := uint64(allowTransparent)<<56 | uint64(vpv.ONTEtherTypeClassification)<<36 | uint64(vpv.VlanControl)<<32 | uint64(vpv.UniVlan)<<16 | uint64(vpv.CVlan)
+ subFlow.SetTableMetadata(metadata)
+ subFlow.SetReportToController()
+ //| 12-bit cvlan | 4 bits empty | <32-bits uniport>| 16-bits dhcp mask or flow mask |
+ subFlow.Cookie = uint64(vpv.CVlan)<<52 | uint64(uniport)<<16 | of.Dhcpv6FlowMask | of.UsFlowMask
+ subFlow.Priority = of.DhcpFlowPriority
+
+ flow.SubFlows[subFlow.Cookie] = subFlow
+ logger.Infow(ctx, "Built US DHCPv6 flow", log.Fields{"cookie": subFlow.Cookie, "flow": flow})
+ return flow, nil
+}
+
+// BuildDsDhcp6Flows to trap the DHCPv6 packets to be reported to the
+// application.
+func (vpv *VoltPortVnet) BuildDsDhcp6Flows() (*of.VoltFlow, error) {
+ logger.Infow(ctx, "Building DS DHCPv6 flow", log.Fields{"Port": vpv.Port, "ML": vpv.MacLearning, "Mac": vpv.MacAddr})
+
+ flow := &of.VoltFlow{}
+ flow.SubFlows = make(map[uint64]*of.VoltSubFlow)
+ subFlow := of.NewVoltSubFlow()
+ subFlow.SetTableID(0)
+
+ vpv.setDsMatchVlan(subFlow)
+ subFlow.SetUdpv6Match()
+ subFlow.SrcPort = 547
+ subFlow.DstPort = 547
+ uniport, _ := GetApplication().GetPortID(vpv.Port)
+ nni, err := GetApplication().GetNniPort(vpv.Device)
+ if err != nil {
+ return nil, err
+ }
+ nniport, err := GetApplication().GetPortID(nni)
+ if err != nil {
+ return nil, err
+ }
+ subFlow.SetInPort(nniport)
+ // PortName and PortID to be used for validation of port before flow pushing
+ flow.PortID = uniport
+ flow.PortName = vpv.Port
+ // metadata := uint64(uniport)
+ // subFlow.SetWriteMetadata(metadata)
+ allowTransparent := 0
+ if vpv.AllowTransparent {
+ allowTransparent = 1
+ }
+ metadata := uint64(allowTransparent)<<56 | uint64(vpv.ONTEtherTypeClassification)<<36 | uint64(vpv.VlanControl)<<32 | uint64(vpv.UniVlan)<<16 | uint64(vpv.CVlan)
+ subFlow.SetTableMetadata(metadata)
+ subFlow.SetReportToController()
+ //| 12-bit cvlan | 4 bits empty | <32-bits uniport>| 16-bits dhcp mask or flow mask |
+ subFlow.Cookie = uint64(vpv.CVlan)<<52 | uint64(uniport)<<16 | of.Dhcpv6FlowMask | of.DsFlowMask
+ subFlow.Priority = of.DhcpFlowPriority
+
+ flow.SubFlows[subFlow.Cookie] = subFlow
+ logger.Infow(ctx, "Built DS DHCPv6 flow", log.Fields{"cookie": subFlow.Cookie, "flow": flow})
+ return flow, nil
+}
+
+// BuildUsArpFlows builds the US ARP relay flows for a subscriber
+// The flows included by this function cover US only as the DS is
+// created either automatically by the VOLTHA or at the device level
+// earlier
+func (vpv *VoltPortVnet) BuildUsArpFlows() (*of.VoltFlow, error) {
+ flow := &of.VoltFlow{}
+ flow.SubFlows = make(map[uint64]*of.VoltSubFlow)
+
+ logger.Infow(ctx, "Building US ARP flow", log.Fields{"Port": vpv.Port})
+ subFlow := of.NewVoltSubFlow()
+ subFlow.SetTableID(0)
+
+ if vpv.MacLearning == MacLearningNone && NonZeroMacAddress(vpv.MacAddr) {
+ subFlow.SetMatchSrcMac(vpv.MacAddr)
+ }
+
+ subFlow.SetMatchDstMac(BroadcastMAC)
+ if err := vpv.setUsMatchVlan(subFlow); err != nil {
+ return nil, err
+ }
+ subFlow.SetArpMatch()
+ uniport, err := GetApplication().GetPortID(vpv.Port)
+ if err != nil {
+ return nil, err
+ }
+ subFlow.SetInPort(uniport)
+ // PortName and PortID to be used for validation of port before flow pushing
+ flow.PortID = uniport
+ flow.PortName = vpv.Port
+ subFlow.SetReportToController()
+ allowTransparent := 0
+ if vpv.AllowTransparent {
+ allowTransparent = 1
+ }
+ metadata := uint64(uniport)
+ subFlow.SetWriteMetadata(metadata)
+ metadata = uint64(allowTransparent)<<56 | uint64(vpv.ONTEtherTypeClassification)<<36 | uint64(vpv.VlanControl)<<32 | uint64(vpv.UniVlan)<<16 | uint64(vpv.CVlan)
+ subFlow.SetTableMetadata(metadata)
+ subFlow.Cookie = uint64(vpv.CVlan)<<52 | uint64(uniport)<<32 | of.DhcpArpFlowMask | of.UsFlowMask
+ subFlow.Priority = of.ArpFlowPriority
+
+ flow.SubFlows[subFlow.Cookie] = subFlow
+ logger.Infow(ctx, "Built US ARP flow ", log.Fields{"cookie": subFlow.Cookie, "flow": flow})
+ return flow, nil
+}
+
+// setUsMatchVlan to set upstream match vlan
+func (vpv *VoltPortVnet) setUsMatchVlan(flow *of.VoltSubFlow) error {
+ switch vpv.VlanControl {
+ case None:
+ flow.SetMatchVlan(vpv.SVlan)
+ case ONUCVlanOLTSVlan:
+ flow.SetMatchVlan(vpv.CVlan)
+ case OLTCVlanOLTSVlan:
+ flow.SetMatchVlan(vpv.UniVlan)
+ //flow.SetSetVlan(vpv.CVlan)
+ case ONUCVlan:
+ flow.SetMatchVlan(vpv.SVlan)
+ case OLTSVlan:
+ flow.SetMatchVlan(vpv.UniVlan)
+ //flow.SetSetVlan(vpv.SVlan)
+ default:
+ logger.Errorw(ctx, "Invalid Vlan Control Option", log.Fields{"Value": vpv.VlanControl})
+ return errorCodes.ErrInvalidParamInRequest
+ }
+ return nil
+}
+
+// BuildUsPppoeFlows to build upstream pppoe flows
+func (vpv *VoltPortVnet) BuildUsPppoeFlows() (*of.VoltFlow, error) {
+
+ flow := &of.VoltFlow{}
+ flow.SubFlows = make(map[uint64]*of.VoltSubFlow)
+ logger.Infow(ctx, "Building US PPPoE flow", log.Fields{"Port": vpv.Port})
+ subFlow := of.NewVoltSubFlow()
+ subFlow.SetTableID(0)
+
+ if vpv.MacLearning == MacLearningNone && NonZeroMacAddress(vpv.MacAddr) {
+ subFlow.SetMatchSrcMac(vpv.MacAddr)
+ }
+
+ if err := vpv.setUsMatchVlan(subFlow); err != nil {
+ return nil, err
+ }
+ subFlow.SetPppoeDiscoveryMatch()
+ uniport, err := GetApplication().GetPortID(vpv.Port)
+ if err != nil {
+ return nil, err
+ }
+ subFlow.SetInPort(uniport)
+ subFlow.SetReportToController()
+ // PortName and PortID to be used for validation of port before flow pushing
+ flow.PortID = uniport
+ flow.PortName = vpv.Port
+
+ allowTransparent := 0
+ if vpv.AllowTransparent {
+ allowTransparent = 1
+ }
+ metadata := uint64(uniport)
+ subFlow.SetWriteMetadata(metadata)
+
+ metadata = uint64(allowTransparent)<<56 | uint64(vpv.ONTEtherTypeClassification)<<36 | uint64(vpv.VlanControl)<<32 | uint64(vpv.UniVlan)<<16 | uint64(vpv.CVlan)
+ subFlow.SetTableMetadata(metadata)
+
+ //| 12-bit cvlan | 4 bits empty | <32-bits uniport>| 16-bits pppoe mask or flow mask |
+ subFlow.Cookie = uint64(vpv.CVlan)<<52 | uint64(uniport)<<16 | of.PppoeFlowMask | of.UsFlowMask
+ subFlow.Priority = of.PppoeFlowPriority
+
+ flow.SubFlows[subFlow.Cookie] = subFlow
+ logger.Infow(ctx, "Built US PPPoE flow ", log.Fields{"cookie": subFlow.Cookie, "flow": flow})
+ return flow, nil
+}
+
+// BuildDsPppoeFlows to build downstream pppoe flows
+func (vpv *VoltPortVnet) BuildDsPppoeFlows() (*of.VoltFlow, error) {
+
+ logger.Infow(ctx, "Building DS PPPoE flow", log.Fields{"Port": vpv.Port, "ML": vpv.MacLearning, "Mac": vpv.MacAddr})
+ flow := &of.VoltFlow{}
+ flow.SubFlows = make(map[uint64]*of.VoltSubFlow)
+ subFlow := of.NewVoltSubFlow()
+ subFlow.SetTableID(0)
+
+ vpv.setDsMatchVlan(subFlow)
+ subFlow.SetPppoeDiscoveryMatch()
+
+ if NonZeroMacAddress(vpv.MacAddr) {
+ subFlow.SetMatchDstMac(vpv.MacAddr)
+ }
+
+ uniport, _ := GetApplication().GetPortID(vpv.Port)
+ nni, err := GetApplication().GetNniPort(vpv.Device)
+ if err != nil {
+ return nil, err
+ }
+ nniport, err := GetApplication().GetPortID(nni)
+ if err != nil {
+ return nil, err
+ }
+ subFlow.SetInPort(nniport)
+ // PortName and PortID to be used for validation of port before flow pushing
+ flow.PortID = uniport
+ flow.PortName = vpv.Port
+ metadata := uint64(uniport)
+ subFlow.SetWriteMetadata(metadata)
+ allowTransparent := 0
+ if vpv.AllowTransparent {
+ allowTransparent = 1
+ }
+ metadata = uint64(allowTransparent)<<56 | uint64(vpv.ONTEtherTypeClassification)<<36 | uint64(vpv.VlanControl)<<32 | uint64(vpv.UniVlan)<<16 | uint64(vpv.CVlan)
+ subFlow.SetTableMetadata(metadata)
+ subFlow.SetReportToController()
+ //| 12-bit cvlan | 4 bits empty | <32-bits uniport>| 16-bits dhcp mask or flow mask |
+ subFlow.Cookie = uint64(vpv.CVlan)<<52 | uint64(uniport)<<16 | of.PppoeFlowMask | of.DsFlowMask
+ subFlow.Priority = of.PppoeFlowPriority
+
+ flow.SubFlows[subFlow.Cookie] = subFlow
+ logger.Infow(ctx, "Built DS DHCP flow ", log.Fields{"cookie": subFlow.Cookie, "Flow": flow})
+ return flow, nil
+}
+
+// setDsMatchVlan to set downstream match vlan
+func (vpv *VoltPortVnet) setDsMatchVlan(flow *of.VoltSubFlow) {
+ switch vpv.VlanControl {
+ case None:
+ flow.SetMatchVlan(vpv.SVlan)
+ case ONUCVlanOLTSVlan,
+ OLTCVlanOLTSVlan,
+ ONUCVlan,
+ OLTSVlan:
+ flow.SetMatchVlan(vpv.SVlan)
+ default:
+ logger.Errorw(ctx, "Invalid Vlan Control Option", log.Fields{"Value": vpv.VlanControl})
+ }
+}
+
+// BuildIgmpFlows builds the US IGMP flows for a subscriber. IGMP requires flows only
+// in the US direction.
+func (vpv *VoltPortVnet) BuildIgmpFlows() (*of.VoltFlow, error) {
+ logger.Infow(ctx, "Building US IGMP Flow", log.Fields{"Port": vpv.Port})
+ mvp := GetApplication().GetMvlanProfileByName(vpv.MvlanProfileName)
+ if mvp == nil {
+ return nil, errors.New("Mvlan Profile configured not found")
+ }
+ mvlan := mvp.GetUsMatchVlan()
+ flow := &of.VoltFlow{}
+ flow.SubFlows = make(map[uint64]*of.VoltSubFlow)
+ subFlow := of.NewVoltSubFlow()
+ subFlow.SetTableID(0)
+
+ if GetApplication().GetVendorID() == Radisys {
+ if err := vpv.setUsMatchVlan(subFlow); err != nil {
+ return nil, err
+ }
+ } else {
+ subFlow.SetMatchVlan(vpv.UniVlan)
+ subFlow.SetSetVlan(vpv.CVlan)
+ }
+
+ uniport, err := GetApplication().GetPortID(vpv.Port)
+ if err != nil {
+ return nil, err
+ }
+ subFlow.SetInPort(uniport)
+ // PortName and PortID to be used for validation of port before flow pushing
+ flow.PortID = uniport
+ flow.PortName = vpv.Port
+
+ if vpv.MacLearning == MacLearningNone && NonZeroMacAddress(vpv.MacAddr) {
+ subFlow.SetMatchSrcMac(vpv.MacAddr)
+ }
+ logger.Infow(ctx, "Mvlan", log.Fields{"mvlan": mvlan})
+ //metadata := uint64(mvlan)
+
+ if vpv.McastService {
+ metadata := uint64(vpv.McastUsMeterID)
+ metadata = metadata | uint64(vpv.McastTechProfileID)<<32
+ subFlow.SetMatchPbit(vpv.McastPbit)
+ subFlow.SetMeterID(vpv.McastUsMeterID)
+ subFlow.SetWriteMetadata(metadata)
+ } else {
+ // Set techprofile, meterid of first service
+ vpv.services.Range(func(key, value interface{}) bool {
+ svc := value.(*VoltService)
+ writemetadata := uint64(svc.TechProfileID) << 32
+ subFlow.SetWriteMetadata(writemetadata)
+ subFlow.SetMeterID(svc.UsMeterID)
+ return false
+ })
+ }
+
+ allowTransparent := 0
+ if vpv.AllowTransparent {
+ allowTransparent = 1
+ }
+ metadata := uint64(allowTransparent)<<56 | uint64(vpv.SchedID)<<40 | uint64(vpv.ONTEtherTypeClassification)<<36 | uint64(vpv.VlanControl)<<32 | uint64(vpv.UniVlan)<<16 | uint64(vpv.CVlan)
+ subFlow.SetTableMetadata(metadata)
+ subFlow.SetIgmpMatch()
+ subFlow.SetReportToController()
+ //| 16 bits empty | <32-bits uniport>| 16-bits igmp mask or flow mask |
+ subFlow.Cookie = uint64(uniport)<<16 | of.IgmpFlowMask | of.UsFlowMask
+ subFlow.Priority = of.IgmpFlowPriority
+
+ flow.SubFlows[subFlow.Cookie] = subFlow
+ logger.Infow(ctx, "Built US IGMP flow ", log.Fields{"cookie": subFlow.Cookie, "flow": flow})
+ return flow, nil
+}
+
+// WriteToDb for writing to database
+func (vpv *VoltPortVnet) WriteToDb() {
+ if vpv.DeleteInProgress {
+ logger.Warnw(ctx, "Skipping Redis Update for VPV, VPV delete in progress", log.Fields{"Vnet": vpv.VnetName, "Port": vpv.Port})
+ return
+ }
+ vpv.ForceWriteToDb()
+}
+
+//ForceWriteToDb force commit a VPV to the DB
+func (vpv *VoltPortVnet) ForceWriteToDb() {
+ vpv.PendingFlowLock.RLock()
+ defer vpv.PendingFlowLock.RUnlock()
+ vpv.Version = database.PresentVersionMap[database.VpvPath]
+ if b, err := json.Marshal(vpv); err == nil {
+ if err := db.PutVpv(vpv.Port, uint16(vpv.SVlan), uint16(vpv.CVlan), uint16(vpv.UniVlan), string(b)); err != nil {
+ logger.Warnw(ctx, "VPV write to DB failed", log.Fields{"port": vpv.Port, "SVlan": vpv.SVlan, "CVlan": vpv.CVlan,
+ "UniVlan": vpv.UniVlan, "Error": err})
+ }
+ }
+}
+
+// DelFromDb for deleting from database
+func (vpv *VoltPortVnet) DelFromDb() {
+ logger.Debugw(ctx, "Deleting VPV from DB", log.Fields{"Port": vpv.Port, "SVLAN": vpv.SVlan, "CVLAN": vpv.CVlan})
+ _ = db.DelVpv(vpv.Port, uint16(vpv.SVlan), uint16(vpv.CVlan), uint16(vpv.UniVlan))
+}
+
+// ClearAllServiceFlags to clear all service flags
+func (vpv *VoltPortVnet) ClearAllServiceFlags() {
+ vpv.services.Range(ClearFlagsInService)
+}
+
+// ClearAllVpvFlags to clear all vpv flags
+func (vpv *VoltPortVnet) ClearAllVpvFlags() {
+ vpv.PendingFlowLock.Lock()
+ vpv.FlowsApplied = false
+ vpv.IgmpFlowsApplied = false
+ vpv.PendingDeleteFlow = make(map[string]bool)
+ vpv.PendingFlowLock.Unlock()
+ vpv.WriteToDb()
+ logger.Debugw(ctx, "Cleared Flow Flags for VPV",
+ log.Fields{"device": vpv.Device, "port": vpv.Port,
+ "svlan": vpv.SVlan, "cvlan": vpv.CVlan, "univlan": vpv.UniVlan})
+}
+
+// CreateVpvFromString to create vpv from string
+func (va *VoltApplication) CreateVpvFromString(b []byte, hash string) {
+ var vpv VoltPortVnet
+ if err := json.Unmarshal(b, &vpv); err == nil {
+ vnetsByPortsSliceIntf, ok := va.VnetsByPort.Load(vpv.Port)
+ if !ok {
+ va.VnetsByPort.Store(vpv.Port, []*VoltPortVnet{})
+ vnetsByPortsSliceIntf = []*VoltPortVnet{}
+ }
+ vpv.servicesCount = atomic.NewUint64(0)
+ vnetsByPortsSlice := vnetsByPortsSliceIntf.([]*VoltPortVnet)
+ vnetsByPortsSlice = append(vnetsByPortsSlice, &vpv)
+ va.VnetsByPort.Store(vpv.Port, vnetsByPortsSlice)
+ va.UpdateMacInPortMap(vpv.MacAddr, vpv.Port)
+ if vnet := va.GetVnetByName(vpv.VnetName); vnet != nil {
+ vnet.associatePortToVnet(vpv.Port)
+ }
+
+ if vpv.DeleteInProgress {
+ va.VoltPortVnetsToDelete[&vpv] = true
+ logger.Warnw(ctx, "VPV (restored) to be deleted", log.Fields{"Port": vpv.Port, "Vnet": vpv.VnetName})
+ }
+ logger.Debugw(ctx, "Added VPV from string", log.Fields{"port": vpv.Port, "svlan": vpv.SVlan, "cvlan": vpv.CVlan, "univlan": vpv.UniVlan})
+ }
+}
+
+// RestoreVpvsFromDb to restore vpvs from database
+func (va *VoltApplication) RestoreVpvsFromDb() {
+ // VNETS must be learnt first
+ vpvs, _ := db.GetVpvs()
+ for hash, vpv := range vpvs {
+ b, ok := vpv.Value.([]byte)
+ if !ok {
+ logger.Warn(ctx, "The value type is not []byte")
+ continue
+ }
+ va.CreateVpvFromString(b, hash)
+ }
+}
+
+// GetVnetByPort : VNET related functionality of VOLT Application here on.
+// Get the VNET from a port. The port identity is passed as device and port identities in string.
+// The identity of the VNET is the SVLAN and the CVLAN. Only if the both match the VLAN
+// is assumed to have matched. TODO: 1:1 should be treated differently and needs to be addressed
+func (va *VoltApplication) GetVnetByPort(port string, svlan of.VlanType, cvlan of.VlanType, univlan of.VlanType) *VoltPortVnet {
+ if _, ok := va.VnetsByPort.Load(port); !ok {
+ return nil
+ }
+ vpvs, _ := va.VnetsByPort.Load(port)
+ for _, vpv := range vpvs.([]*VoltPortVnet) {
+ if vpv.MatchesVlans(svlan, cvlan, univlan) {
+ return vpv
+ }
+ }
+ return nil
+}
+
+// AddVnetToPort to add vnet to port
+func (va *VoltApplication) AddVnetToPort(port string, vvnet *VoltVnet, vs *VoltService) *VoltPortVnet {
+ // The VNET is not on the port and is to be added
+ logger.Debugw(ctx, "Adding VNET to Port", log.Fields{"Port": port, "VNET": vvnet.Name})
+ vpv := NewVoltPortVnet(vvnet)
+ vpv.MacLearning = vvnet.MacLearning
+ vpv.Port = port
+ vvnet.associatePortToVnet(port)
+ if _, ok := va.VnetsByPort.Load(port); !ok {
+ va.VnetsByPort.Store(port, []*VoltPortVnet{})
+ }
+ vpvsIntf, _ := va.VnetsByPort.Load(port)
+ vpvs := vpvsIntf.([]*VoltPortVnet)
+ vpvs = append(vpvs, vpv)
+ va.VnetsByPort.Store(port, vpvs)
+ va.UpdateMacInPortMap(vpv.MacAddr, vpv.Port)
+
+ vpv.VpvLock.Lock()
+ defer vpv.VpvLock.Unlock()
+
+ // Add the service that is causing the VNET to be added to the port
+ vpv.AddSvc(vs)
+
+ // Process the PORT UP if the port is already up
+ d, err := va.GetDeviceFromPort(port)
+ if err == nil {
+ vpv.setDevice(d.Name)
+ p := d.GetPort(port)
+ if p != nil {
+
+ if vs.PonPort != 0xFF && vs.PonPort != p.PonPort {
+ logger.Errorw(ctx, "UNI port discovered on wrong PON Port. Dropping Flow Push for VPV", log.Fields{"Device": d.Name, "Port": port, "DetectedPon": p.PonPort, "ExpectedPon": vs.PonPort, "Vnet": vpv.VnetName})
+ } else {
+ logger.Infow(ctx, "Checking UNI port state", log.Fields{"State": p.State})
+ if d.State == controller.DeviceStateUP && p.State == PortStateUp {
+ vpv.PortUpInd(d, port)
+ }
+ }
+ }
+ }
+ vpv.WriteToDb()
+ return vpv
+}
+
+// DelVnetFromPort for deleting vnet from port
+func (va *VoltApplication) DelVnetFromPort(port string, vpv *VoltPortVnet) {
+
+ //Delete DHCP Session
+ delDhcpSessions(vpv.LearntMacAddr, vpv.SVlan, vpv.CVlan, vpv.DHCPv6DUID)
+
+ //Delete PPPoE session
+ delPppoeIaSessions(vpv.LearntMacAddr, vpv.SVlan, vpv.CVlan)
+
+ //Delete Mac from MacPortMap
+ va.DeleteMacInPortMap(vpv.MacAddr)
+
+ //Delete VPV
+ vpvsIntf, ok := va.VnetsByPort.Load(port)
+ if !ok {
+ return
+ }
+ vpvs := vpvsIntf.([]*VoltPortVnet)
+ for i, lvpv := range vpvs {
+ if lvpv == vpv {
+ logger.Debugw(ctx, "Deleting VPV from port", log.Fields{"Port": vpv.Port, "SVLAN": vpv.SVlan, "CVLAN": vpv.CVlan,
+ "UNIVLAN": vpv.UniVlan})
+
+ vpvs = append(vpvs[0:i], vpvs[i+1:]...)
+
+ vpv.DeleteInProgress = true
+ vpv.ForceWriteToDb()
+
+ va.VnetsByPort.Store(port, vpvs)
+ vpv.DelTrapFlows()
+ vpv.DelHsiaFlows()
+ va.DisassociateVpvsFromDevice(vpv.Device, vpv)
+ vpv.PendingFlowLock.RLock()
+ if len(vpv.PendingDeleteFlow) == 0 {
+ vpv.DelFromDb()
+ }
+ if vnet := va.GetVnetByName(vpv.VnetName); vnet != nil {
+ vnet.disassociatePortFromVnet(vpv.Device, vpv.Port)
+ }
+ vpv.PendingFlowLock.RUnlock()
+ return
+ }
+ }
+}
+
+// RestoreVnetsFromDb to restore vnet from port
+func (va *VoltApplication) RestoreVnetsFromDb() {
+ // VNETS must be learnt first
+ vnets, _ := db.GetVnets()
+ for _, net := range vnets {
+ b, ok := net.Value.([]byte)
+ if !ok {
+ logger.Warn(ctx, "The value type is not []byte")
+ continue
+ }
+ var vnet VoltVnet
+ err := json.Unmarshal(b, &vnet)
+ if err != nil {
+ logger.Warn(ctx, "Unmarshal of VNET failed")
+ continue
+ }
+ logger.Debugw(ctx, "Retrieved VNET", log.Fields{"VNET": vnet.VnetConfig})
+ if err := va.AddVnet(vnet.VnetConfig, &vnet.VnetOper); err != nil {
+ logger.Warnw(ctx, "Add Vnet Failed", log.Fields{"Config": vnet.VnetConfig, "Error": err})
+ }
+
+ if vnet.DeleteInProgress {
+ va.VnetsToDelete[vnet.Name] = true
+ logger.Warnw(ctx, "Vnet (restored) to be deleted", log.Fields{"Vnet": vnet.Name})
+ }
+
+ }
+}
+
+// GetServiceFromCvlan : Locate a service based on the packet received. The packet contains VLANs that
+// are used as the key to locate the service. If more than one service is on the
+// same port (essentially a UNI of ONU), the services must be separated by different
+// CVLANs
+func (va *VoltApplication) GetServiceFromCvlan(device, port string, vlans []of.VlanType, priority uint8) *VoltService {
+ // Fetch the device first to make sure the device exists
+ dIntf, ok := va.DevicesDisc.Load(device)
+ if !ok {
+ return nil
+ }
+ d := dIntf.(*VoltDevice)
+
+ // If the port is NNI port, the services dont exist on it. The svc then
+ // must be obtained from a different context and is not included here
+ if port == d.NniPort {
+ return nil
+ }
+
+ //To return the matched service
+ var service *VoltService
+
+ // This is an access port and the port should have all the associated
+ // services which can be uniquely identified by the VLANs in the packet
+ vnets, ok := va.VnetsByPort.Load(port)
+
+ if !ok {
+ logger.Debugw(ctx, "No Vnets for port", log.Fields{"Port": port})
+ return nil
+ }
+ logger.Debugw(ctx, "Matching for VLANs", log.Fields{"VLANs": vlans, "Priority": priority})
+ for _, vnet := range vnets.([]*VoltPortVnet) {
+ logger.Infow(ctx, "Vnet", log.Fields{"Vnet": vnet})
+ switch vnet.VlanControl {
+ case ONUCVlanOLTSVlan:
+ service = vnet.MatchesPriority(priority)
+ if vnet.MatchesCvlan(vlans) && service != nil {
+ return service
+ }
+ case ONUCVlan,
+ None:
+ service = vnet.MatchesPriority(priority)
+ // In case of DHCP Flow - cvlan == VlanNone
+ // In case of HSIA Flow - cvlan == Svlan
+ if len(vlans) == 1 && (vlans[0] == vnet.SVlan || vlans[0] == of.VlanNone) && service != nil {
+ return service
+ }
+ case OLTCVlanOLTSVlan,
+ OLTSVlan:
+ service = vnet.MatchesPriority(priority)
+ if len(vlans) == 1 && vlans[0] == vnet.UniVlan && service != nil {
+ return service
+ }
+ default:
+ logger.Errorw(ctx, "Invalid Vlan Control Option", log.Fields{"Value": vnet.VlanControl})
+ }
+ }
+ return nil
+}
+
+// GetVnetFromFields : Locate a service based on the packet received. The packet contains VLANs that
+// are used as the key to locate the service. If more than one service is on the
+// same port (essentially a UNI of ONU), the services must be separated by different
+// CVLANs
+func (va *VoltApplication) GetVnetFromFields(device string, port string, vlans []of.VlanType, priority uint8) (*VoltPortVnet, *VoltService) {
+ // Fetch the device first to make sure the device exists
+ dIntf, ok := va.DevicesDisc.Load(device)
+ if !ok {
+ return nil, nil
+ }
+ d := dIntf.(*VoltDevice)
+
+ // If the port is NNI port, the services dont exist on it. The svc then
+ // must be obtained from a different context and is not included here
+ if port == d.NniPort {
+ return nil, nil
+ }
+
+ //To return the matched service
+ var service *VoltService
+
+ // This is an access port and the port should have all the associated
+ // services which can be uniquely identified by the VLANs in the packet
+ if vnets, ok := va.VnetsByPort.Load(port); ok {
+ logger.Debugw(ctx, "Matching for VLANs", log.Fields{"VLANs": vlans, "Priority": priority})
+ for _, vnet := range vnets.([]*VoltPortVnet) {
+ logger.Infow(ctx, "Vnet", log.Fields{"Vnet": vnet})
+ switch vnet.VlanControl {
+ case ONUCVlanOLTSVlan:
+ service = vnet.MatchesPriority(priority)
+ if vnet.MatchesCvlan(vlans) && service != nil {
+ return vnet, service
+ }
+ case ONUCVlan,
+ None:
+ service = vnet.MatchesPriority(priority)
+ if (len(vlans) == 1 || vnet.AllowTransparent) && vlans[0] == vnet.SVlan && service != nil {
+ return vnet, service
+ }
+ case OLTCVlanOLTSVlan,
+ OLTSVlan:
+ service = vnet.MatchesPriority(priority)
+ if (len(vlans) == 1 || vnet.AllowTransparent) && vlans[0] == vnet.UniVlan && service != nil {
+ return vnet, service
+ }
+ default:
+ logger.Errorw(ctx, "Invalid Vlan Control Option", log.Fields{"Value": vnet.VlanControl})
+ }
+ }
+ }
+ return nil, nil
+}
+
+// GetVnetFromPkt : Locate a service based on the packet received. The packet contains VLANs that
+// are used as the key to locate the service. If more than one service is on the
+// same port (essentially a UNI of ONU), the services must be separated by different
+// CVLANs
+func (va *VoltApplication) GetVnetFromPkt(device string, port string, pkt gopacket.Packet) (*VoltPortVnet, *VoltService) {
+ vlans := GetVlans(pkt)
+ priority := GetPriority(pkt)
+ return va.GetVnetFromFields(device, port, vlans, priority)
+}
+
+// PushDevFlowForVlan to push icmpv6 flows for vlan
+func (va *VoltApplication) PushDevFlowForVlan(vnet *VoltVnet) {
+ logger.Infow(ctx, "PushDevFlowForVlan", log.Fields{"SVlan": vnet.SVlan, "CVlan": vnet.CVlan})
+ pushflow := func(key interface{}, value interface{}) bool {
+ device := value.(*VoltDevice)
+ if !isDeviceInList(device.SerialNum, vnet.DevicesList) {
+ logger.Info(ctx, "Device not present in vnet device list", log.Fields{"Device": device.SerialNum})
+ return true
+ }
+ if device.State != controller.DeviceStateUP {
+ logger.Errorw(ctx, "Push Dev Flows Failed - Device state DOWN", log.Fields{"Port": device.NniPort, "Vlan": vnet.SVlan, "device": device})
+ return true
+ }
+ if applied, ok := device.VlanPortStatus.Load(uint16(vnet.SVlan)); !ok || !applied.(bool) {
+ logger.Errorw(ctx, "Push Dev Flows Failed - Vlan not enabled yet", log.Fields{"Port": device.NniPort, "Vlan": vnet.SVlan})
+ return true
+ }
+
+ if vnetListIntf, ok := device.ConfiguredVlanForDeviceFlows.Get(VnetKey(vnet.SVlan, vnet.CVlan, 0)); ok {
+ vnetList := vnetListIntf.(*util.ConcurrentMap)
+ vnetList.Set(vnet.Name, true)
+ device.ConfiguredVlanForDeviceFlows.Set(VnetKey(vnet.SVlan, vnet.CVlan, 0), vnetList)
+ logger.Infow(ctx, "Flow already pushed for these Vlans. Adding profile to list", log.Fields{"SVlan": vnet.SVlan, "CVlan": vnet.CVlan, "vnetList-len": vnetList.Length()})
+ return true
+ }
+ logger.Debugw(ctx, "Configuring Dev Flows Group for device ", log.Fields{"Device": device})
+ err := ProcessIcmpv6McGroup(device.Name, false)
+ if err != nil {
+ logger.Warnw(ctx, "Configuring Dev Flows Group for device failed ", log.Fields{"Device": device.Name, "err": err})
+ return true
+ }
+ if portID, err := va.GetPortID(device.NniPort); err == nil {
+ if state, _ := cntlr.GetController().GetPortState(device.Name, device.NniPort); state != cntlr.PortStateUp {
+ logger.Warnw(ctx, "Skipping Dev Flow Configuration - Port Down", log.Fields{"Device": device})
+ return true
+ }
+
+ //Pushing ICMPv6 Flow
+ flow := BuildICMPv6Flow(portID, vnet)
+ err = cntlr.GetController().AddFlows(device.NniPort, device.Name, flow)
+ if err != nil {
+ logger.Warnw(ctx, "Configuring ICMPv6 Flow for device failed ", log.Fields{"Device": device.Name, "err": err})
+ return true
+ }
+ logger.Infow(ctx, "ICMPv6 Flow Added to Queue", log.Fields{"flow": flow})
+
+ // Pushing ARP Flow
+ flow = BuildDSArpFlow(portID, vnet)
+ err = cntlr.GetController().AddFlows(device.NniPort, device.Name, flow)
+ if err != nil {
+ logger.Warnw(ctx, "Configuring ARP Flow for device failed ", log.Fields{"Device": device.Name, "err": err})
+ return true
+ }
+ logger.Infow(ctx, "ARP Flow Added to Queue", log.Fields{"flow": flow})
+
+ vnetList := util.NewConcurrentMap()
+ vnetList.Set(vnet.Name, true)
+ device.ConfiguredVlanForDeviceFlows.Set(VnetKey(vnet.SVlan, vnet.CVlan, 0), vnetList)
+ }
+ return true
+ }
+ va.DevicesDisc.Range(pushflow)
+}
+
+// PushDevFlowForDevice to push icmpv6 flows for device
+func (va *VoltApplication) PushDevFlowForDevice(device *VoltDevice) {
+ logger.Infow(ctx, "PushDevFlowForDevice", log.Fields{"device": device})
+
+ logger.Debugw(ctx, "Configuring ICMPv6 Group for device ", log.Fields{"Device": device.Name})
+ err := ProcessIcmpv6McGroup(device.Name, false)
+ if err != nil {
+ logger.Warnw(ctx, "Configuring ICMPv6 Group for device failed ", log.Fields{"Device": device.Name, "err": err})
+ return
+ }
+ pushicmpv6 := func(key, value interface{}) bool {
+ vnet := value.(*VoltVnet)
+ if vnetListIntf, ok := device.ConfiguredVlanForDeviceFlows.Get(VnetKey(vnet.SVlan, vnet.CVlan, 0)); ok {
+ vnetList := vnetListIntf.(*util.ConcurrentMap)
+ vnetList.Set(vnet.Name, true)
+ device.ConfiguredVlanForDeviceFlows.Set(VnetKey(vnet.SVlan, vnet.CVlan, 0), vnetList)
+ logger.Infow(ctx, "Flow already pushed for these Vlans. Adding profile to list", log.Fields{"SVlan": vnet.SVlan, "CVlan": vnet.CVlan, "vnetList-len": vnetList.Length()})
+ return true
+ }
+ nniPortID, err := va.GetPortID(device.NniPort)
+ if err != nil {
+ logger.Errorw(ctx, "Push ICMPv6 Failed - Failed to get NNI Port Id", log.Fields{"Port": device.NniPort, "Reason": err.Error})
+ }
+ if applied, ok := device.VlanPortStatus.Load(uint16(vnet.SVlan)); !ok || !applied.(bool) {
+ logger.Warnw(ctx, "Push ICMPv6 Failed - Vlan not enabled yet", log.Fields{"Port": device.NniPort, "Vlan": vnet.SVlan})
+ return true
+ }
+ flow := BuildICMPv6Flow(nniPortID, vnet)
+ err = cntlr.GetController().AddFlows(device.NniPort, device.Name, flow)
+ if err != nil {
+ logger.Warnw(ctx, "Configuring ICMPv6 Flow for device failed ", log.Fields{"Device": device.Name, "err": err})
+ return true
+ }
+ logger.Infow(ctx, "ICMP Flow Added to Queue", log.Fields{"flow": flow})
+
+ flow = BuildDSArpFlow(nniPortID, vnet)
+ err = cntlr.GetController().AddFlows(device.NniPort, device.Name, flow)
+ if err != nil {
+ logger.Warnw(ctx, "Configuring ARP Flow for device failed ", log.Fields{"Device": device.Name, "err": err})
+ return true
+ }
+ logger.Infow(ctx, "ARP Flow Added to Queue", log.Fields{"flow": flow})
+
+ vnetList := util.NewConcurrentMap()
+ vnetList.Set(vnet.Name, true)
+ device.ConfiguredVlanForDeviceFlows.Set(VnetKey(vnet.SVlan, vnet.CVlan, 0), vnetList)
+ return true
+ }
+ va.VnetsByName.Range(pushicmpv6)
+}
+
+// DeleteDevFlowForVlan to delete icmpv6 flow for vlan
+func (va *VoltApplication) DeleteDevFlowForVlan(vnet *VoltVnet) {
+ logger.Infow(ctx, "DeleteDevFlowForVlan", log.Fields{"SVlan": vnet.SVlan, "CVlan": vnet.CVlan})
+ delflows := func(key interface{}, value interface{}) bool {
+ device := value.(*VoltDevice)
+
+ if vnetListIntf, ok := device.ConfiguredVlanForDeviceFlows.Get(VnetKey(vnet.SVlan, vnet.CVlan, 0)); ok {
+ vnetList := vnetListIntf.(*util.ConcurrentMap)
+ vnetList.Remove(vnet.Name)
+ device.ConfiguredVlanForDeviceFlows.Set(VnetKey(vnet.SVlan, vnet.CVlan, 0), vnetList)
+ if vnetList.Length() != 0 {
+ logger.Warnw(ctx, "Similar VNet associated to diff service. Not removing ICMPv6 flow", log.Fields{"SVlan": vnet.SVlan, "CVlan": vnet.CVlan, "vnetList-len": vnetList.Length()})
+ return true
+ }
+ }
+ if portID, err := va.GetPortID(device.NniPort); err == nil {
+ if state, _ := cntlr.GetController().GetPortState(device.Name, device.NniPort); state != cntlr.PortStateUp {
+ logger.Warnw(ctx, "Skipping ICMPv6 Flow Deletion - Port Down", log.Fields{"Device": device})
+ return true
+ }
+ //Pushing ICMPv6 Flow
+ flow := BuildICMPv6Flow(portID, vnet)
+ flow.ForceAction = true
+ err := vnet.RemoveFlows(device, flow)
+ if err != nil {
+ logger.Warnw(ctx, "De-Configuring ICMPv6 Flow for device failed ", log.Fields{"Device": device.Name, "err": err})
+ return true
+ }
+ logger.Infow(ctx, "ICMPv6 Flow Delete Added to Queue", log.Fields{"flow": flow})
+
+ //Pushing ARP Flow
+ flow = BuildDSArpFlow(portID, vnet)
+ flow.ForceAction = true
+ err = vnet.RemoveFlows(device, flow)
+ if err != nil {
+ logger.Warnw(ctx, "De-Configuring ARP Flow for device failed ", log.Fields{"Device": device.Name, "err": err})
+ return true
+ }
+ logger.Infow(ctx, "ARP Flow Delete Added to Queue", log.Fields{"flow": flow})
+
+ device.ConfiguredVlanForDeviceFlows.Remove(VnetKey(vnet.SVlan, vnet.CVlan, 0))
+ }
+ return true
+ }
+ va.DevicesDisc.Range(delflows)
+}
+
+// DeleteDevFlowForDevice to delete icmpv6 flow for device
+func (va *VoltApplication) DeleteDevFlowForDevice(device *VoltDevice) {
+ logger.Infow(ctx, "DeleteDevFlowForDevice", log.Fields{"Device": device})
+ delicmpv6 := func(key, value interface{}) bool {
+ vnet := value.(*VoltVnet)
+ if vnetListIntf, ok := device.ConfiguredVlanForDeviceFlows.Get(VnetKey(vnet.SVlan, vnet.CVlan, 0)); ok {
+ vnetList := vnetListIntf.(*util.ConcurrentMap)
+ vnetList.Remove(vnet.Name)
+ device.ConfiguredVlanForDeviceFlows.Set(VnetKey(vnet.SVlan, vnet.CVlan, 0), vnetList)
+ if vnetList.Length() != 0 {
+ logger.Warnw(ctx, "Similar VNet associated to diff service. Not removing ICMPv6 flow", log.Fields{"SVlan": vnet.SVlan, "CVlan": vnet.CVlan, "vnetList-len": vnetList.Length()})
+ return true
+ }
+ } else {
+ logger.Warnw(ctx, "ICMPv6 Flow map entry not found for Vnet", log.Fields{"Vnet": vnet.VnetConfig})
+ return true
+ }
+ nniPortID, err := va.GetPortID(device.NniPort)
+ if err != nil {
+ logger.Errorw(ctx, "Delete ICMPv6 Failed - Failed to get NNI Port Id", log.Fields{"Port": device.NniPort, "Reason": err.Error})
+ }
+ flow := BuildICMPv6Flow(nniPortID, vnet)
+ flow.ForceAction = true
+ err = vnet.RemoveFlows(device, flow)
+ if err != nil {
+ logger.Warnw(ctx, "De-Configuring ICMPv6 Flow for device failed ", log.Fields{"Device": device.Name, "err": err})
+ return true
+ }
+
+ flow = BuildDSArpFlow(nniPortID, vnet)
+ flow.ForceAction = true
+ err = vnet.RemoveFlows(device, flow)
+ if err != nil {
+ logger.Warnw(ctx, "De-Configuring ARP Flow for device failed ", log.Fields{"Device": device.Name, "err": err})
+ return true
+ }
+
+ device.ConfiguredVlanForDeviceFlows.Remove(VnetKey(vnet.SVlan, vnet.CVlan, 0))
+ logger.Infow(ctx, "ICMP Flow Delete Added to Queue", log.Fields{"flow": flow})
+ return true
+ }
+ va.VnetsByName.Range(delicmpv6)
+ logger.Debugw(ctx, "De-Configuring ICMPv6 Group for device ", log.Fields{"Device": device.Name})
+ err := ProcessIcmpv6McGroup(device.Name, true)
+ if err != nil {
+ logger.Warnw(ctx, "De-Configuring ICMPv6 Group on device failed ", log.Fields{"Device": device.Name, "err": err})
+ return
+ }
+}
+
+// DeleteDevFlowForVlanFromDevice to delete icmpv6 flow for vlan from device
+func (va *VoltApplication) DeleteDevFlowForVlanFromDevice(vnet *VoltVnet, deviceSerialNum string) {
+ logger.Infow(ctx, "DeleteDevFlowForVlanFromDevice", log.Fields{"Device-serialNum": deviceSerialNum, "SVlan": vnet.SVlan, "CVlan": vnet.CVlan})
+ delflows := func(key interface{}, value interface{}) bool {
+ device := value.(*VoltDevice)
+ if device.SerialNum != deviceSerialNum {
+ return true
+ }
+ if vnetListIntf, ok := device.ConfiguredVlanForDeviceFlows.Get(VnetKey(vnet.SVlan, vnet.CVlan, 0)); ok {
+ vnetList := vnetListIntf.(*util.ConcurrentMap)
+ vnetList.Remove(vnet.Name)
+ device.ConfiguredVlanForDeviceFlows.Set(VnetKey(vnet.SVlan, vnet.CVlan, 0), vnetList)
+ if vnetList.Length() != 0 {
+ logger.Warnw(ctx, "Similar VNet associated to diff service. Not removing ICMPv6 flow", log.Fields{"SVlan": vnet.SVlan, "CVlan": vnet.CVlan, "vnetList-len": vnetList.Length()})
+ return true
+ }
+ } else if !vgcRebooted && len(vnet.DevicesList) != 0 {
+ // Return only in-case of non-reboot/delete scenario. Else, the flows need to be force removed
+ // DeviceList check is there to avoid dangling flow in-case of pod restart during service de-activation.
+ // The step will be as follow:
+ // 1. Deact Service
+ // 2. Pod Reboot
+ // 3. Pending Delete Service triggered
+ // 4. Del Service Ind followed by DelVnet req from NB
+ // 5. If Vlan status response is awaited, the ConfiguredVlanForDeviceFlows cache will not have flow info
+ // hence the flow will not be cleared
+ logger.Warnw(ctx, "Dev Flow map entry not found for Vnet", log.Fields{"PodReboot": vgcRebooted, "VnetDeleteInProgress": vnet.DeleteInProgress})
+ return true
+ }
+ if portID, err := va.GetPortID(device.NniPort); err == nil {
+ if state, _ := cntlr.GetController().GetPortState(device.Name, device.NniPort); state != cntlr.PortStateUp {
+ logger.Warnw(ctx, "Skipping ICMPv6 Flow Deletion - Port Down", log.Fields{"Device": device})
+ return false
+ }
+ flow := BuildICMPv6Flow(portID, vnet)
+ flow.ForceAction = true
+ if err := vnet.RemoveFlows(device, flow); err != nil {
+ logger.Warnw(ctx, "Delete Flow Failed", log.Fields{"Device": device, "Flow": flow, "Error": err})
+ }
+ logger.Infow(ctx, "ICMP Flow Delete Added to Queue", log.Fields{"flow": flow})
+
+ flow = BuildDSArpFlow(portID, vnet)
+ flow.ForceAction = true
+ if err := vnet.RemoveFlows(device, flow); err != nil {
+ logger.Warnw(ctx, "Delete Flow Failed", log.Fields{"Device": device, "Flow": flow, "Error": err})
+ }
+ logger.Infow(ctx, "ARP Flow Delete Added to Queue", log.Fields{"flow": flow})
+ device.ConfiguredVlanForDeviceFlows.Remove(VnetKey(vnet.SVlan, vnet.CVlan, 0))
+ }
+ return false
+ }
+ va.DevicesDisc.Range(delflows)
+}
+
+// BuildICMPv6Flow to Build DS flow for ICMPv6
+func BuildICMPv6Flow(inport uint32, vnet *VoltVnet) *of.VoltFlow {
+ logger.Info(ctx, "Building ICMPv6 MC Flow", log.Fields{"SVlan": vnet.SVlan, "CVlan": vnet.CVlan})
+ flow := &of.VoltFlow{}
+ flow.SubFlows = make(map[uint64]*of.VoltSubFlow)
+ subFlow := of.NewVoltSubFlow()
+
+ subFlow.SetICMPv6Match()
+ subFlow.SetMatchVlan(vnet.SVlan)
+ subFlow.SetInPort(inport)
+ subFlow.SetPopVlan()
+ subFlow.SetOutGroup(ICMPv6ArpGroupID)
+ subFlow.Cookie = uint64(vnet.CVlan)<<48 | uint64(vnet.SVlan)<<32 | of.IgmpFlowMask | of.DsFlowMask
+ subFlow.Priority = of.McFlowPriority
+ var metadata uint64
+ if vnet.VlanControl == None {
+ metadata = uint64(ONUCVlan)<<32 | uint64(vnet.CVlan)
+ } else {
+ metadata = uint64(vnet.VlanControl)<<32 | uint64(vnet.CVlan)
+ }
+ subFlow.SetTableMetadata(metadata)
+ metadata = uint64(vnet.setPbitRemarking())
+
+ logger.Infow(ctx, "ICMPv6 Pbit Remarking", log.Fields{"RemarkPbit": metadata})
+ subFlow.SetWriteMetadata(metadata)
+ flow.SubFlows[subFlow.Cookie] = subFlow
+ return flow
+}
+
+//BuildDSArpFlow Builds DS flow for ARP
+func BuildDSArpFlow(inport uint32, vnet *VoltVnet) *of.VoltFlow {
+ logger.Info(ctx, "Building ARP MC Flow", log.Fields{"SVlan": vnet.SVlan, "CVlan": vnet.CVlan})
+
+ flow := &of.VoltFlow{}
+ flow.SubFlows = make(map[uint64]*of.VoltSubFlow)
+ subFlow := of.NewVoltSubFlow()
+
+ BcastMAC, _ := net.ParseMAC("FF:FF:FF:FF:FF:FF")
+ subFlow.SetArpMatch()
+ subFlow.SetMatchDstMac(BcastMAC)
+ subFlow.SetMatchVlan(vnet.SVlan)
+ subFlow.SetInPort(inport)
+ subFlow.SetPopVlan()
+ subFlow.SetOutGroup(ICMPv6ArpGroupID)
+
+ subFlow.Cookie = uint64(vnet.CVlan)<<48 | uint64(vnet.SVlan)<<32 | of.DsArpFlowMask | of.DsFlowMask
+ subFlow.Priority = of.McFlowPriority
+
+ var metadata uint64
+ if vnet.VlanControl == None {
+ metadata = uint64(ONUCVlan)<<32 | uint64(vnet.CVlan)
+ } else {
+ metadata = uint64(vnet.VlanControl)<<32 | uint64(vnet.CVlan)
+ }
+ subFlow.SetTableMetadata(metadata)
+ metadata = uint64(vnet.setPbitRemarking())
+ subFlow.SetWriteMetadata(metadata)
+
+ flow.SubFlows[subFlow.Cookie] = subFlow
+ logger.Infow(ctx, "ARP Pbit Remarking", log.Fields{"RemarkPbit": metadata})
+ return flow
+}
+
+// setPbitRemarking to set Pbit remarking
+func (vv *VoltVnet) setPbitRemarking() uint32 {
+
+ // Remarkable
+ // Remarked Pbit Pbit
+ // |-----------------------------| |------|
+ // |7| |6| |5| |4| |3| |2| |1| |0| 76543210
+ // 000 000 000 000 000 000 000 000 00000000
+
+ // Eg:
+ // For 6:3 & 7:1
+ // 001 011 000 000 000 000 000 000 11000000
+
+ var remarkable uint8
+ var remarked uint32
+ for refPbit, remarkPbit := range vv.CtrlPktPbitRemark {
+ remarkable = remarkable | 1<<refPbit
+ remarked = remarked | uint32(remarkPbit)<<(refPbit*3)
+ }
+ return remarked<<8 | uint32(remarkable)
+}
+
+// ProcessIcmpv6McGroup to add icmpv6 multicast group
+func ProcessIcmpv6McGroup(device string, delete bool) error {
+
+ logger.Info(ctx, "Creating ICMPv6 MC Group")
+ va := GetApplication()
+ vd := va.GetDevice(device)
+ group := &of.Group{}
+ group.GroupID = ICMPv6ArpGroupID
+ group.Device = device
+ if delete {
+ if !vd.icmpv6GroupAdded {
+ logger.Info(ctx, "ICMPv6 MC Group is already deleted. Ignoring icmpv6 group Delete")
+ return nil //TODO
+ }
+ vd.icmpv6GroupAdded = false
+ group.Command = of.GroupCommandDel
+ group.ForceAction = true
+ } else {
+ if vd.icmpv6GroupAdded {
+ logger.Info(ctx, "ICMPv6 MC Group is already added. Ignoring icmpv6 group Add")
+ return nil //TODO
+ }
+ vd.icmpv6GroupAdded = true
+ group.Command = of.GroupCommandAdd
+ receivers := GetApplication().GetIcmpv6Receivers(device)
+ group.Buckets = append(group.Buckets, receivers...)
+ }
+ logger.Infow(ctx, "ICMPv6 MC Group Action", log.Fields{"Device": device, "Delete": delete})
+ port, _ := GetApplication().GetNniPort(device)
+ err := cntlr.GetController().GroupUpdate(port, device, group)
+ return err
+}
+
+//isVlanMatching - checks is vlans matches with vpv based on vlan control
+func (vpv *VoltPortVnet) isVlanMatching(cvlan of.VlanType, svlan of.VlanType) bool {
+
+ switch vpv.VlanControl {
+ case ONUCVlanOLTSVlan,
+ OLTCVlanOLTSVlan:
+ if vpv.SVlan == svlan && vpv.CVlan == cvlan {
+ return true
+ }
+ case ONUCVlan,
+ OLTSVlan,
+ None:
+ if vpv.SVlan == svlan {
+ return true
+ }
+ default:
+ logger.Errorw(ctx, "Invalid Vlan Control Option", log.Fields{"Value": vpv.VlanControl})
+ }
+ return false
+}
+
+//PushFlows - Triggers flow addition after registering for flow indication event
+func (vpv *VoltPortVnet) PushFlows(device *VoltDevice, flow *of.VoltFlow) error {
+
+ for cookie := range flow.SubFlows {
+ cookie := strconv.FormatUint(cookie, 10)
+ fe := &FlowEvent{
+ eType: EventTypeControlFlowAdded,
+ cookie: cookie,
+ eventData: vpv,
+ }
+ device.RegisterFlowAddEvent(cookie, fe)
+ }
+ return cntlr.GetController().AddFlows(vpv.Port, device.Name, flow)
+}
+
+//FlowInstallFailure - Process flow failure indication and triggers HSIA failure for all associated services
+func (vpv *VoltPortVnet) FlowInstallFailure(cookie string, errorCode uint32, errReason string) {
+
+ sendFlowFailureInd := func(key, value interface{}) bool {
+ //svc := value.(*VoltService)
+ //TODO-COMM: svc.triggerServiceFailureInd(errorCode, errReason)
+ return true
+ }
+ logger.Errorw(ctx, "Control Flow Add Failure Notification", log.Fields{"uniPort": vpv.Port, "Cookie": cookie, "ErrorCode": errorCode, "ErrorReason": errReason})
+ vpv.services.Range(sendFlowFailureInd)
+}
+
+//RemoveFlows - Triggers flow deletion after registering for flow indication event
+func (vpv *VoltPortVnet) RemoveFlows(device *VoltDevice, flow *of.VoltFlow) error {
+
+ vpv.PendingFlowLock.Lock()
+ defer vpv.PendingFlowLock.Unlock()
+
+ for cookie := range flow.SubFlows {
+ cookie := strconv.FormatUint(cookie, 10)
+ fe := &FlowEvent{
+ eType: EventTypeControlFlowRemoved,
+ device: device.Name,
+ cookie: cookie,
+ eventData: vpv,
+ }
+ device.RegisterFlowDelEvent(cookie, fe)
+ vpv.PendingDeleteFlow[cookie] = true
+ }
+ return cntlr.GetController().DelFlows(vpv.Port, device.Name, flow)
+}
+
+//CheckAndDeleteVpv - remove VPV from DB is there are no pending flows to be removed
+func (vpv *VoltPortVnet) CheckAndDeleteVpv() {
+ vpv.PendingFlowLock.RLock()
+ defer vpv.PendingFlowLock.RUnlock()
+ if !vpv.DeleteInProgress {
+ return
+ }
+ if len(vpv.PendingDeleteFlow) == 0 && !vpv.FlowsApplied {
+ logger.Infow(ctx, "All Flows removed for VPV. Triggering VPV Deletion from DB", log.Fields{"VPV Port": vpv.Port, "Device": vpv.Device, "Vnet": vpv.VnetName})
+ vpv.DelFromDb()
+ logger.Infow(ctx, "Deleted VPV from DB/Cache successfully", log.Fields{"VPV Port": vpv.Port, "Device": vpv.Device, "Vnet": vpv.VnetName})
+ }
+}
+
+//FlowRemoveSuccess - Process flow success indication
+func (vpv *VoltPortVnet) FlowRemoveSuccess(cookie string, device string) {
+ vpv.PendingFlowLock.Lock()
+ logger.Infow(ctx, "VPV Flow Remove Success Notification", log.Fields{"Port": vpv.Port, "Cookie": cookie, "Device": device})
+
+ delete(vpv.PendingDeleteFlow, cookie)
+ vpv.PendingFlowLock.Unlock()
+ vpv.CheckAndDeleteVpv()
+ vpv.WriteToDb()
+}
+
+//FlowRemoveFailure - Process flow failure indication and triggers Del HSIA failure for all associated services
+func (vpv *VoltPortVnet) FlowRemoveFailure(cookie string, device string, errorCode uint32, errReason string) {
+ vpv.PendingFlowLock.Lock()
+
+ logger.Errorw(ctx, "VPV Flow Remove Failure Notification", log.Fields{"Port": vpv.Port, "Cookie": cookie, "ErrorCode": errorCode, "ErrorReason": errReason, "Device": device})
+
+ sendFlowFailureInd := func(key, value interface{}) bool {
+ svc := value.(*VoltService)
+ svc.triggerServiceFailureInd(errorCode, errReason)
+ return true
+ }
+ logger.Errorw(ctx, "Control Flow Del Failure Notification", log.Fields{"uniPort": vpv.Port, "Cookie": cookie, "ErrorCode": errorCode, "ErrorReason": errReason})
+ vpv.services.Range(sendFlowFailureInd)
+
+ if vpv.DeleteInProgress {
+ delete(vpv.PendingDeleteFlow, cookie)
+ vpv.PendingFlowLock.Unlock()
+ vpv.CheckAndDeleteVpv()
+ } else {
+ vpv.PendingFlowLock.Unlock()
+ vpv.WriteToDb()
+ }
+}
+
+//RemoveFlows - Triggers flow deletion after registering for flow indication event
+func (vv *VoltVnet) RemoveFlows(device *VoltDevice, flow *of.VoltFlow) error {
+
+ vv.VnetLock.Lock()
+ defer vv.VnetLock.Unlock()
+
+ var flowMap map[string]bool
+ var ok bool
+
+ for cookie := range flow.SubFlows {
+ cookie := strconv.FormatUint(cookie, 10)
+ fe := &FlowEvent{
+ eType: EventTypeDeviceFlowRemoved,
+ device: device.Name,
+ cookie: cookie,
+ eventData: vv,
+ }
+ device.RegisterFlowDelEvent(cookie, fe)
+ if flowMap, ok = vv.PendingDeleteFlow[device.Name]; !ok {
+ flowMap = make(map[string]bool)
+ }
+ flowMap[cookie] = true
+ vv.PendingDeleteFlow[device.Name] = flowMap
+ }
+ vv.WriteToDb()
+ return cntlr.GetController().DelFlows(device.NniPort, device.Name, flow)
+}
+
+//CheckAndDeleteVnet - remove Vnet from DB is there are no pending flows to be removed
+func (vv *VoltVnet) CheckAndDeleteVnet(device string) {
+ if !vv.DeleteInProgress {
+ return
+ }
+ vv.VnetPortLock.RLock()
+ if len(vv.PendingDeleteFlow[device]) == 0 && !vv.isAssociatedPortsPresent() {
+ logger.Warnw(ctx, "Deleting Vnet : All flows removed", log.Fields{"Name": vv.Name, "AssociatedPorts": vv.AssociatedPorts, "Device": device})
+ GetApplication().deleteVnetConfig(vv)
+ _ = db.DelVnet(vv.Name)
+ logger.Infow(ctx, "Deleted Vnet from DB/Cache successfully", log.Fields{"Device": device, "Vnet": vv.Name})
+ } else {
+ logger.Warnw(ctx, "Skipping Del Vnet", log.Fields{"Name": vv.Name, "AssociatedPorts": vv.AssociatedPorts, "PendingDelFlows": vv.PendingDeleteFlow[device]})
+ }
+ vv.VnetPortLock.RUnlock()
+}
+
+//FlowRemoveSuccess - Process flow success indication
+func (vv *VoltVnet) FlowRemoveSuccess(cookie string, device string) {
+ vv.VnetLock.Lock()
+ defer vv.VnetLock.Unlock()
+
+ logger.Infow(ctx, "Vnet Flow Remove Success Notification", log.Fields{"VnetProfile": vv.Name, "Cookie": cookie, "Device": device})
+
+ if _, ok := vv.PendingDeleteFlow[device]; ok {
+ delete(vv.PendingDeleteFlow[device], cookie)
+ }
+
+ //Check and update success for pending disable request
+ if d := GetApplication().GetDevice(device); d != nil {
+ _, present := d.ConfiguredVlanForDeviceFlows.Get(VnetKey(vv.SVlan, vv.CVlan, 0))
+ if !present && len(vv.PendingDeleteFlow[device]) == 0 {
+ vv.CheckAndDeleteVnet(device)
+ }
+ }
+ vv.WriteToDb()
+}
+
+//FlowRemoveFailure - Process flow failure indication
+func (vv *VoltVnet) FlowRemoveFailure(cookie string, device string, errorCode uint32, errReason string) {
+
+ vv.VnetLock.Lock()
+ defer vv.VnetLock.Unlock()
+
+ if flowMap, ok := vv.PendingDeleteFlow[device]; ok {
+ if _, ok := flowMap[cookie]; ok {
+ logger.Errorw(ctx, "Device Flow Remove Failure Notification", log.Fields{"Vnet": vv.Name, "Cookie": cookie, "ErrorCode": errorCode, "ErrorReason": errReason, "Device": device})
+
+ if vv.DeleteInProgress {
+ delete(vv.PendingDeleteFlow[device], cookie)
+ vv.CheckAndDeleteVnet(device)
+ }
+ return
+ }
+ }
+ logger.Errorw(ctx, "Device Flow Remove Failure Notification for Unknown cookie", log.Fields{"Vnet": vv.Name, "Cookie": cookie, "ErrorCode": errorCode, "ErrorReason": errReason})
+}
+
+//IgmpFlowInstallFailure - Process flow failure indication and triggers HSIA failure for Igmp enabled services
+func (vpv *VoltPortVnet) IgmpFlowInstallFailure(cookie string, errorCode uint32, errReason string) {
+
+ //Note: Current implementation supports only for single service with Igmp Enabled for a subscriber
+ //When multiple Igmp-suported service enabled, comment "return false"
+
+ sendFlowFailureInd := func(key, value interface{}) bool {
+ svc := value.(*VoltService)
+ if svc.IgmpEnabled {
+ svc.triggerServiceFailureInd(errorCode, errReason)
+ return false
+ }
+ return true
+ }
+ logger.Errorw(ctx, "US IGMP Flow Failure Notification", log.Fields{"uniPort": vpv.Port, "Cookie": cookie, "ErrorCode": errorCode, "ErrorReason": errReason})
+ vpv.services.Range(sendFlowFailureInd)
+}
+
+// GetMatchingMcastService to get matching multicast service
+func (va *VoltApplication) GetMatchingMcastService(port string, device string, cvlan of.VlanType) *VoltService {
+
+ var service *VoltService
+ dIntf, ok := va.DevicesDisc.Load(device)
+ if !ok {
+ return nil
+ }
+ d := dIntf.(*VoltDevice)
+
+ // If the port is NNI port, the services dont exist on it. The svc then
+ // must be obtained from a different context and is not included here
+ if port == d.NniPort {
+ return nil
+ }
+
+ // This is an access port and the port should have all the associated
+ // services which can be uniquely identified by the VLANs in the packet
+ vnets, ok := va.VnetsByPort.Load(port)
+
+ if !ok {
+ logger.Debugw(ctx, "No Vnets for port", log.Fields{"Port": port})
+ return nil
+ }
+ logger.Debugw(ctx, "Matching for VLANs", log.Fields{"VLANs": cvlan})
+ getMcastService := func(key, value interface{}) bool {
+ srv := value.(*VoltService)
+ if srv.IgmpEnabled {
+ service = srv
+
+ //TODO: Current implementation supports only for single service with Igmp Enabled
+ //FIX-ME: When multiple service suports Igmp, update of logic required
+ return false
+ }
+ return true
+ }
+
+ for _, vpv := range vnets.([]*VoltPortVnet) {
+ if vpv.CVlan == cvlan {
+ vpv.services.Range(getMcastService)
+ if service != nil {
+ break
+ }
+ }
+ }
+ return service
+}
+
+//TriggerAssociatedFlowDelete - Re-trigger delete for pending delete flows
+func (vv *VoltVnet) TriggerAssociatedFlowDelete(device string) bool {
+ vv.VnetLock.Lock()
+ cookieList := []uint64{}
+ flowMap := vv.PendingDeleteFlow[device]
+
+ for cookie := range flowMap {
+ cookieList = append(cookieList, convertToUInt64(cookie))
+ }
+ vv.VnetLock.Unlock()
+
+ if len(cookieList) == 0 {
+ return false
+ }
+
+ for _, cookie := range cookieList {
+ if vd := GetApplication().GetDevice(device); vd != nil {
+ flow := &of.VoltFlow{}
+ flow.SubFlows = make(map[uint64]*of.VoltSubFlow)
+ subFlow := of.NewVoltSubFlow()
+ subFlow.Cookie = cookie
+ flow.SubFlows[cookie] = subFlow
+ logger.Infow(ctx, "Retriggering Vnet Delete Flow", log.Fields{"Device": device, "Vnet": vv.Name, "Cookie": cookie})
+ if err := vv.RemoveFlows(vd, flow); err != nil {
+ logger.Warnw(ctx, "Vnet Delete Flow Failed", log.Fields{"Device": device, "Vnet": vv.Name, "Cookie": cookie, "Error": err})
+ }
+ }
+ }
+ return true
+}