First Commit of Voltha-Go-Controller from Radisys

Change-Id: I8e2e908e7ab09a4fe3d86849da18b6d69dcf4ab0
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
+}