First Commit of Voltha-Go-Controller from Radisys

Change-Id: I8e2e908e7ab09a4fe3d86849da18b6d69dcf4ab0
diff --git a/internal/pkg/application/dhcprelay.go b/internal/pkg/application/dhcprelay.go
new file mode 100644
index 0000000..06aa3db
--- /dev/null
+++ b/internal/pkg/application/dhcprelay.go
@@ -0,0 +1,1343 @@
+/*
+* Copyright 2022-present Open Networking Foundation
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+ */
+
+package application
+
+import (
+	"encoding/hex"
+	"errors"
+	"net"
+	"sync"
+
+	"github.com/google/gopacket"
+	"github.com/google/gopacket/layers"
+
+	cntlr "voltha-go-controller/internal/pkg/controller"
+	"voltha-go-controller/internal/pkg/of"
+	"voltha-go-controller/internal/pkg/util"
+	"github.com/opencord/voltha-lib-go/v7/pkg/log"
+)
+
+// DhcpRelayState type
+type DhcpRelayState uint8
+
+const (
+	// DhcpRelayStateNone constant
+	DhcpRelayStateNone DhcpRelayState = iota
+	// DhcpRelayStateDiscover constant
+	DhcpRelayStateDiscover
+	// DhcpRelayStateOffer constant
+	DhcpRelayStateOffer
+	// DhcpRelayStateRequest constant
+	DhcpRelayStateRequest
+	// DhcpRelayStateAck constant
+	DhcpRelayStateAck
+	// DhcpRelayStateNAK constant
+	DhcpRelayStateNAK
+	// DhcpRelayStateRelease constant
+	DhcpRelayStateRelease
+)
+
+// RemoteIDType represents data type for various RemoteID types
+type RemoteIDType string
+
+// List of RemoteID types supported
+const (
+        MACAddress      RemoteIDType = "MAC_ADDRESS"
+        CustomRemotedID RemoteIDType = "Custom"
+)
+
+// MaxLenDhcpv6DUID constant
+const MaxLenDhcpv6DUID = 130 // 2: DUID-Type, 128: MaxLen of DUID value
+
+// opt82 constant
+const opt82 = 82
+
+// Dhcpv6RelayState type
+type Dhcpv6RelayState uint8
+
+const (
+	// Dhcpv6RelayStateNone constant
+	Dhcpv6RelayStateNone Dhcpv6RelayState = iota
+	// Dhcpv6RelayStateSolicit constant
+	Dhcpv6RelayStateSolicit
+	// Dhcpv6RelayStateReply constant
+	Dhcpv6RelayStateReply
+	// Dhcpv6RelayStateRelease constant
+	Dhcpv6RelayStateRelease
+)
+
+var (
+	// ErrSessionDoNotExist error type
+	ErrSessionDoNotExist = errors.New("Session Doesn't Exist")
+)
+
+// IDhcpRelaySession to get dhcp session field value
+type IDhcpRelaySession interface {
+	GetCircuitID() []byte
+	GetRemoteID() []byte
+	GetNniVlans() (uint16, uint16)
+	GetDhcpState() DhcpRelayState
+	GetDhcpv6State() Dhcpv6RelayState
+	SetDhcpState(DhcpRelayState)
+	SetDhcpv6State(Dhcpv6RelayState)
+	SetMacAddr(net.HardwareAddr)
+	DhcpResultInd(*layers.DHCPv4)
+	Dhcpv6ResultInd(ipv6Addr net.IP, leaseTime uint32)
+}
+
+// DhcpRelayVnet : The DHCP relay sessions are stored in a map to be retrieved from when
+// a response is received from the network. The map uses the VLANs and the
+// the MAC address as key to finding the service
+// DHCP Relay Virtual Network hosts a set of DHCP relay sessions that belong
+// to the network. It supports two VLANs as its identify. If a single VLAN or
+// no VLAN is to be used, those two should be passed as 4096 (VlanNone)
+type DhcpRelayVnet struct {
+	OuterVlan   uint16
+	InnerVlan   uint16
+	sessions    map[[6]byte]IDhcpRelaySession
+	sessionsv6  map[[MaxLenDhcpv6DUID]byte]IDhcpRelaySession
+	sessionLock sync.RWMutex
+}
+
+// DhcpNetworks hosts different DHCP networks that in turn hold the DHCP
+// sessions
+type DhcpNetworks struct {
+	Networks map[uint32]*DhcpRelayVnet
+}
+
+func init() {
+	RegisterPacketHandler(DHCPv4, ProcessUDP4Packet)
+	RegisterPacketHandler(DHCPv6, ProcessUDP6Packet)
+}
+
+// NewDhcpRelayVnet is constructor for a DHCP Relay Virtual network
+func NewDhcpRelayVnet(outerVlan uint16, innerVlan uint16) *DhcpRelayVnet {
+	var drv DhcpRelayVnet
+
+	drv.OuterVlan = outerVlan
+	drv.InnerVlan = innerVlan
+	drv.sessions = make(map[[6]byte]IDhcpRelaySession)
+	drv.sessionsv6 = make(map[[MaxLenDhcpv6DUID]byte]IDhcpRelaySession)
+	return &drv
+}
+
+// GetDhcpVnet to add dhcp vnet
+func (dn *DhcpNetworks) GetDhcpVnet(outerVlan uint16, innerVlan uint16) *DhcpRelayVnet {
+	comboVlan := uint32(outerVlan)<<16 + uint32(innerVlan)
+	drv, ok := dn.Networks[comboVlan]
+	if ok {
+		return drv
+	}
+	return nil
+}
+
+// AddDhcpVnet to add dhcp vnet
+func (dn *DhcpNetworks) AddDhcpVnet(outerVlan uint16, innerVlan uint16) *DhcpRelayVnet {
+	comboVlan := uint32(outerVlan)<<16 + uint32(innerVlan)
+	if drv, ok := dn.Networks[comboVlan]; ok {
+		return drv
+	}
+	drv := NewDhcpRelayVnet(outerVlan, innerVlan)
+	dn.Networks[comboVlan] = drv
+	return drv
+}
+
+// NewDhcpNetworks to get new dhcp network
+func NewDhcpNetworks() *DhcpNetworks {
+	var dn DhcpNetworks
+	dn.Networks = make(map[uint32]*DhcpRelayVnet)
+	return &dn
+}
+
+// AddDhcpSession to add dhcp session
+func (dn *DhcpNetworks) AddDhcpSession(pkt gopacket.Packet, session IDhcpRelaySession) error {
+	var key [6]byte
+	ethl := pkt.Layer(layers.LayerTypeEthernet)
+	eth, _ := ethl.(*layers.Ethernet)
+	addr := eth.SrcMAC
+	if len(addr) != 6 {
+		logger.Errorw(ctx, "Invalid MAC address", log.Fields{"Addr": addr})
+		return errors.New("Invalid MAC address")
+	}
+	copy(key[:], addr[0:6])
+
+	drv := dn.AddDhcpVnet(session.GetNniVlans())
+
+	drv.sessionLock.Lock()
+	drv.sessions[key] = session
+	drv.sessionLock.Unlock()
+	return nil
+}
+
+// DelDhcpSession to delete dhcp session
+func (dn *DhcpNetworks) DelDhcpSession(pkt gopacket.Packet, session IDhcpRelaySession) {
+	var key [6]byte
+	ethl := pkt.Layer(layers.LayerTypeEthernet)
+	eth, _ := ethl.(*layers.Ethernet)
+	addr := eth.SrcMAC
+	if len(addr) != 6 {
+		logger.Errorw(ctx, "Invalid MAC address", log.Fields{"Addr": addr})
+		return
+	}
+	copy(key[:], addr[0:6])
+	drv := dn.AddDhcpVnet(session.GetNniVlans())
+	drv.sessionLock.Lock()
+	delete(drv.sessions, key)
+	drv.sessionLock.Unlock()
+}
+
+// delDhcpSessions to delete dhcp sessions
+func delDhcpSessions(addr net.HardwareAddr, outervlan of.VlanType, innervlan of.VlanType, sessionKey [MaxLenDhcpv6DUID]byte) {
+	var key [6]byte
+	if addr == nil || !NonZeroMacAddress(addr) {
+		logger.Warnw(ctx, "Invalid MAC address", log.Fields{"Addr": addr})
+		return
+	}
+	copy(key[:], addr[0:6])
+	drv := dhcpNws.AddDhcpVnet(uint16(outervlan), uint16(innervlan))
+	drv.sessionLock.Lock()
+	delete(drv.sessions, key)
+	delete(drv.sessionsv6, sessionKey)
+	drv.sessionLock.Unlock()
+	logger.Infow(ctx, "DHCP Sessions deleted", log.Fields{"MAC": addr})
+}
+
+// AddDhcp6Session to add dhcpv6 session
+func (dn *DhcpNetworks) AddDhcp6Session(key [MaxLenDhcpv6DUID]byte, session IDhcpRelaySession) error {
+	outerVlan, innerVlan := session.GetNniVlans()
+	logger.Infow(ctx, "Adding Session", log.Fields{"outerVlan": outerVlan, "innerVlan": innerVlan, "Addr": key})
+	drv := dn.AddDhcpVnet(outerVlan, innerVlan)
+	drv.sessionLock.Lock()
+	drv.sessionsv6[key] = session
+	drv.sessionLock.Unlock()
+	return nil
+}
+
+// DelDhcp6Session to delete dhcpv6 session
+func (dn *DhcpNetworks) DelDhcp6Session(key [MaxLenDhcpv6DUID]byte, session IDhcpRelaySession) {
+	outerVlan, innerVlan := session.GetNniVlans()
+	logger.Infow(ctx, "Get Session", log.Fields{"OuterVLAN": outerVlan, "InnerVLAN": innerVlan, "Addr": key})
+	drv := dn.GetDhcpVnet(outerVlan, innerVlan)
+	drv.sessionLock.Lock()
+	delete(drv.sessionsv6, key)
+	drv.sessionLock.Unlock()
+}
+
+// GetDhcpSession to get dhcp session info
+func (dn *DhcpNetworks) GetDhcpSession(outerVlan uint16, innerVlan uint16, addr net.HardwareAddr) (IDhcpRelaySession, error) {
+	var key [6]byte
+	if len(addr) != 6 {
+		logger.Errorw(ctx, "Invalid MAC address", log.Fields{"Addr": addr})
+		return nil, errors.New("Invalid MAC address")
+	}
+	copy(key[:], addr[0:6])
+	drv := dn.AddDhcpVnet(outerVlan, innerVlan)
+	drv.sessionLock.RLock()
+	defer drv.sessionLock.RUnlock()
+	if session, ok := drv.sessions[key]; ok {
+		return session, nil
+	}
+	return nil, ErrSessionDoNotExist
+}
+
+// GetDhcp6Session to get Dhcp6Session
+func (dn *DhcpNetworks) GetDhcp6Session(outerVlan uint16, innerVlan uint16, key [MaxLenDhcpv6DUID]byte) (IDhcpRelaySession, error) {
+	logger.Infow(ctx, "Locating Session", log.Fields{"OuterVlan": outerVlan, "InnerVlan": innerVlan, "key": key})
+
+	drv := dn.AddDhcpVnet(outerVlan, innerVlan)
+	drv.sessionLock.RLock()
+	defer drv.sessionLock.RUnlock()
+	if session, ok := drv.sessionsv6[key]; ok {
+		return session, nil
+	}
+	return nil, ErrSessionDoNotExist
+}
+
+// GetVlansFromPacket to get vlans from the packet
+func GetVlansFromPacket(pkt gopacket.Packet) (innerVlan of.VlanType, outerVlan of.VlanType) {
+
+	vlans := GetVlans(pkt)
+	if len(vlans) == 1 {
+		outerVlan = vlans[0]
+		innerVlan = of.VlanNone
+	} else if len(vlans) == 0 {
+		innerVlan = of.VlanNone
+		outerVlan = of.VlanNone
+	} else {
+		innerVlan = vlans[1]
+		outerVlan = vlans[0]
+	}
+	return
+}
+
+// GetVnetForV4Nni to get vnet for v4 Nni
+func GetVnetForV4Nni(dhcp *layers.DHCPv4, cvlan of.VlanType, svlan of.VlanType, pbit uint8) ([]*VoltPortVnet, error) {
+	var err error
+	var session IDhcpRelaySession
+	var vpvList []*VoltPortVnet
+	logger.Infow(ctx, "Mac Obtained MAC: ", log.Fields{"Addr": dhcp.ClientHWAddr})
+	session, err = dhcpNws.GetDhcpSession(uint16(svlan), uint16(cvlan), dhcp.ClientHWAddr)
+
+	if session != nil {
+		vpv, ok := session.(*VoltPortVnet)
+		logger.Infow(ctx, "Session Exist: VPV found", log.Fields{"VPV": vpv})
+		if ok {
+			vpvList = append(vpvList, vpv)
+			return vpvList, nil
+		}
+	}
+
+	if err == ErrSessionDoNotExist {
+		//No DHCP Session found, find matching VPV to send the packet out
+		logger.Info(ctx, "Session Doesnt Exist: Finding matching VPV")
+		return GetApplication().GetVpvsForDsPkt(cvlan, svlan, dhcp.ClientHWAddr, pbit)
+	}
+	return nil, errors.New("The session retrieved of wrong type")
+}
+
+// GetVnetForV6Nni to get vnet for v6 Nni
+func GetVnetForV6Nni(dhcp *layers.DHCPv6, cvlan of.VlanType, svlan of.VlanType,
+	pbit uint8, clientMAC net.HardwareAddr) ([]*VoltPortVnet, net.HardwareAddr, error) {
+	var err error
+	var session IDhcpRelaySession
+	var vpvList []*VoltPortVnet
+
+	var sessionKey [MaxLenDhcpv6DUID]byte
+
+	clientDuid, decodedDuid := getDhcpv6ClientDUID(dhcp)
+	if clientDuid == nil || decodedDuid == nil {
+		copy(sessionKey[:], clientMAC)
+	} else {
+		copy(sessionKey[:], clientDuid[0:])
+		if decodedDuid.Type == layers.DHCPv6DUIDTypeLLT || decodedDuid.Type == layers.DHCPv6DUIDTypeLL {
+			clientMAC = decodedDuid.LinkLayerAddress
+		}
+	}
+	session, err = dhcpNws.GetDhcp6Session(uint16(svlan), uint16(cvlan), sessionKey)
+	if session != nil {
+		vpv, ok := session.(*VoltPortVnet)
+		logger.Infow(ctx, "Session Exist: VPV found", log.Fields{"VPV": vpv})
+		if ok {
+			vpvList = append(vpvList, vpv)
+			return vpvList, clientMAC, nil
+		}
+	}
+
+	if err == ErrSessionDoNotExist {
+		//No DHCP Session found, find matching VPV to send the packet out
+		logger.Info(ctx, "Session Doesnt Exist: Finding matching VPV")
+		vpvList, err := GetApplication().GetVpvsForDsPkt(cvlan, svlan, clientMAC, pbit)
+		return vpvList, clientMAC, err
+	}
+	return nil, clientMAC, errors.New("The session retrieved of wrong type")
+}
+
+/*
+// getDhcpv4ClientMacAddr to get mac address for dhcpv4 client
+func getDhcpv4ClientMacAddr(pkt gopacket.Packet) net.HardwareAddr {
+	dhcp := pkt.Layer(layers.LayerTypeDHCPv4).(*layers.DHCPv4)
+	logger.Infow(ctx, "Mac Obtained v4: ", log.Fields{"Addr": dhcp.ClientHWAddr})
+	return dhcp.ClientHWAddr
+}
+
+// getDhcpv6ClientMacAddr to get mac address for dhcpv6 client
+func getDhcpv6ClientMacAddr(dhcpv6 *layers.DHCPv6) net.HardwareAddr {
+	var cID layers.DHCPv6Option
+	for _, option := range dhcpv6.Options {
+		if option.Code == layers.DHCPv6OptClientID {
+			cID = option
+		}
+	}
+	duid := &layers.DHCPv6DUID{}
+
+	//If cID is not found, DecodeFromBytes() returns error on empty cID
+	if err := duid.DecodeFromBytes(cID.Data); err == nil {
+		logger.Infow(ctx, "Mac Obtained v6: ", log.Fields{"Addr": duid.LinkLayerAddress, "Option": cID.String()})
+		return duid.LinkLayerAddress
+	}
+	return nil
+}*/
+
+// getDhcpv6ClientDUID to get Dhcpv6 client DUID
+func getDhcpv6ClientDUID(dhcpv6 *layers.DHCPv6) ([]byte, *layers.DHCPv6DUID) {
+
+	for _, option := range dhcpv6.Options {
+		logger.Debugw(ctx, "DHCPv6 Options", log.Fields{"option": option.Code})
+		if option.Code == layers.DHCPv6OptClientID {
+			duid := &layers.DHCPv6DUID{}
+			err := duid.DecodeFromBytes(option.Data)
+			if err == nil {
+				logger.Infow(ctx, "ClientIdentifier", log.Fields{"DUID": duid, "Option": option.String()})
+				duidLen := len(option.Data)
+				if duidLen > 130 {
+					duidLen = 130
+				}
+				return option.Data[0:duidLen], duid
+			}
+			logger.Errorw(ctx, "Client DUID decode failed", log.Fields{"error": err})
+			break
+		}
+	}
+	logger.Error(ctx, "Client DUID is not present in the packet")
+	return nil, nil
+}
+
+// AddDhcpv4Option82 : DHCPv4 packet operations
+// Addition of DHCP Option 82 which codes circuit-id and remote-id
+// into the packet. This happens as the request is relayed to the
+// DHCP servers on the NNI
+func AddDhcpv4Option82(svc *VoltService, rID []byte, dhcpv4 *layers.DHCPv4) {
+	//NOTE : both cID and rID should not be empty if this function is called
+	cID := svc.GetCircuitID()
+	var data []byte
+	if len(cID) != 0 {
+		data = append(data, 0x01)
+		data = append(data, byte(len(cID)))
+		data = append(data, cID...)
+	}
+	if len(rID) != 0 {
+		data = append(data, 0x02)
+		data = append(data, byte(len(rID)))
+		data = append(data, rID...)
+	}
+
+	if svc.isDataRateAttrPresent() {
+		minDrUs := util.Uint32ToByte(svc.MinDataRateUs)
+		data = append(data, TYPEMINDATAUS)
+		data = append(data, byte(len(minDrUs)))
+		data = append(data, minDrUs...)
+
+		minDrDs := util.Uint32ToByte(svc.MinDataRateDs)
+		data = append(data, TYPEMINDATADS)
+		data = append(data, byte(len(minDrDs)))
+		data = append(data, minDrDs...)
+
+		maxDrUs := util.Uint32ToByte(svc.MaxDataRateUs)
+		data = append(data, TYPEMAXDATAUS)
+		data = append(data, byte(len(maxDrUs)))
+		data = append(data, maxDrUs...)
+
+		maxDrDs := util.Uint32ToByte(svc.MaxDataRateDs)
+		data = append(data, TYPEMAXDATADS)
+		data = append(data, byte(len(maxDrDs)))
+		data = append(data, maxDrDs...)
+	}
+
+	option := layers.NewDHCPOption(82, data)
+	dhcpv4.Options = append(dhcpv4.Options, option)
+}
+
+// DelOption82 : Deletion of option 82 from the packet received on the NNI interface.
+// Once the packet is received, the option 82 is stripped off and the
+// packet is forwarded towards access
+func DelOption82(dhcpv4 *layers.DHCPv4) {
+	for index, option := range dhcpv4.Options {
+		if option.Type == opt82 {
+			dhcpv4.Options = append(dhcpv4.Options[0:index], dhcpv4.Options[index+1:]...)
+			return
+		}
+	}
+}
+
+// DhcpMsgType returns the DHCP message type from the packet
+func DhcpMsgType(dhcp *layers.DHCPv4) layers.DHCPMsgType {
+	for _, option := range dhcp.Options {
+		if option.Type == layers.DHCPOptMessageType {
+			return layers.DHCPMsgType(option.Data[0])
+		}
+	}
+	return layers.DHCPMsgTypeUnspecified
+}
+
+// GetIpv4Addr returns the IP address in the DHCP reply
+func GetIpv4Addr(dhcp *layers.DHCPv4) (net.IP, int64) {
+	var leaseTime uint32
+	for _, opt := range dhcp.Options {
+		if opt.Type == layers.DHCPOptLeaseTime {
+			leaseTime = GetIPv4LeaseTime(opt)
+		}
+	}
+	return dhcp.YourClientIP, int64(leaseTime)
+}
+
+//GetIPv4LeaseTime get ip lease time
+func GetIPv4LeaseTime(opt layers.DHCPOption) uint32 {
+	return uint32(opt.Data[0])<<24 | uint32(opt.Data[1])<<16 | uint32(opt.Data[2])<<8 | uint32(opt.Data[3])
+}
+
+// GetIpv6Addr returns the IPv6 address in the DHCPv6 reply
+func GetIpv6Addr(dhcp6 *layers.DHCPv6) (net.IP, uint32) {
+	var ipv6Addr net.IP
+	var leaseTime uint32
+
+	//Check for IANA allocation, if not present, then look for IAPD allocation
+	if dhcp6.MsgType == layers.DHCPv6MsgTypeReply {
+		ipv6Addr, leaseTime = GetIANAAddress(dhcp6)
+		if ipv6Addr == nil {
+			ipv6Addr, leaseTime = GetIAPDAddress(dhcp6)
+		}
+	}
+	return ipv6Addr, leaseTime
+}
+
+// GetIANAAddress returns the IPv6 address in the DHCPv6 reply
+func GetIANAAddress(dhcp6 *layers.DHCPv6) (net.IP, uint32) {
+	var ipv6Addr net.IP
+	var leaseTime uint32
+	if dhcp6.MsgType == layers.DHCPv6MsgTypeReply {
+		for _, o := range dhcp6.Options {
+			if o.Code == layers.DHCPv6OptIANA {
+
+				iana := &layers.DHCPv6IANA{}
+				err := iana.DecodeFromBytes(o.Data)
+				if err == nil {
+					ipv6Addr = iana.IA.IPv6Addr
+					leaseTime = iana.IA.ValidLifeTime
+					logger.Debugw(ctx, "IPv6 Allocated", log.Fields{"IANA IPv6": ipv6Addr})
+					return ipv6Addr, leaseTime
+				}
+				logger.Warn(ctx, "Decode of IANA Failed", log.Fields{"Reason": err.Error()})
+				break
+			}
+		}
+	}
+	return nil, 0
+}
+
+// GetIAPDAddress returns the IPv6 address in the DHCPv6 reply
+func GetIAPDAddress(dhcp6 *layers.DHCPv6) (net.IP, uint32) {
+	var ipv6Addr net.IP
+	var leaseTime uint32
+	if dhcp6.MsgType == layers.DHCPv6MsgTypeReply {
+		for _, o := range dhcp6.Options {
+			if o.Code == layers.DHCPv6OptIAPD {
+
+				iapd := &layers.DHCPv6IAPD{}
+				if err := iapd.DecodeFromBytes(o.Data); err == nil {
+					ipv6Addr = iapd.PD.Prefix
+					leaseTime = iapd.PD.ValidLifeTime
+					logger.Debugw(ctx, "IPv6 Allocated", log.Fields{"IAPD IPv6": ipv6Addr})
+					break
+				} else {
+					logger.Warn(ctx, "Decode of IAPD Failed", log.Fields{"Reason": err.Error()})
+					break
+				}
+			}
+		}
+	}
+	return ipv6Addr, leaseTime
+}
+
+// ProcessDsDhcpv4Packet : DHCPv4 packet processor functions
+// This function processes DS DHCP packet received on the NNI port.
+// The services are attached to the access ports. Thus, the DHCP
+// session is derived from the list of DHCP sessions stored in the
+// common map. The key for retrieval includes the VLAN tags in the
+// the packet and the MAC address of the client.
+func (va *VoltApplication) ProcessDsDhcpv4Packet(device string, port string, pkt gopacket.Packet) {
+
+	// Retrieve the layers to build the outgoing packet. It is not
+	// possible to add/remove layers to the existing packet and thus
+	// the lyayers are extracted to build the outgoing packet
+	eth := pkt.Layer(layers.LayerTypeEthernet).(*layers.Ethernet)
+	ip := pkt.Layer(layers.LayerTypeIPv4).(*layers.IPv4)
+	udp := pkt.Layer(layers.LayerTypeUDP).(*layers.UDP)
+	dhcp4 := pkt.Layer(layers.LayerTypeDHCPv4).(*layers.DHCPv4)
+	msgType := DhcpMsgType(dhcp4)
+
+	// Need to locate the service from the packet alone as the services
+	// are not attached to NNI port. The service is stored on DHCP relay
+	// application
+	logger.Infow(ctx, "Processing Southbound DS DHCPv4 packet", log.Fields{"Port": port, "Type": msgType})
+
+	// Retrieve the priority and drop eligible flags from the
+	// packet received
+	var priority uint8
+	var dsPbit uint8
+	var dropEligible bool
+	dot1ql := pkt.Layer(layers.LayerTypeDot1Q)
+	if dot1ql != nil {
+		dot1q := dot1ql.(*layers.Dot1Q)
+		priority = dot1q.Priority
+		dropEligible = dot1q.DropEligible
+	}
+
+	pktInnerlan, pktOuterlan := GetVlansFromPacket(pkt)
+	vpvList, _ := GetVnetForV4Nni(dhcp4, pktInnerlan, pktOuterlan, priority)
+	if len(vpvList) == 0 {
+		logger.Warn(ctx, "VNET couldn't be found for NNI")
+		return
+	}
+
+	// The DHCP option 82, if it exists is removed from the packet
+	DelOption82(dhcp4)
+	ipAddr, leaseTime := GetIpv4Addr(dhcp4)
+
+	for _, vpv := range vpvList {
+		dsPbit = vpv.GetRemarkedPriority(priority)
+		// Raise DHCP ACK/NCK indication
+		if vpv.DhcpRelay {
+			// Inform dhcp response information to dhcp server handler
+			dhcpResponseReceived(uint16(vpv.CVlan), uint16(vpv.SVlan))
+			// Process the Ack/Nack to track to state of the IP layer of the connection
+			if msgType == layers.DHCPMsgTypeAck || msgType == layers.DHCPMsgTypeNak {
+				// Install DS HSIA flows after DHCP ACK.
+				if msgType == layers.DHCPMsgTypeAck {
+					// Voltha will push US and DS HSIA flow on receivng the DS HSIA
+					// flow installation request, VGC to update US HSIA flow with leanrt MAC.
+					// separate go rotuine is spawned to avoid drop of ACK packet
+					// as HSIA flows will be deleted if new MAC is learnt.
+					go vpv.SetMacAddr(dhcp4.ClientHWAddr)
+				}
+				vpv.DhcpResultInd(dhcp4)
+
+			}
+			raiseDHCPv4Indication(msgType, vpv, dhcp4.ClientHWAddr, ipAddr, dsPbit, device, leaseTime)
+		}
+
+		// Create the outgoing bufer and set the checksum in the packet
+		buff := gopacket.NewSerializeBuffer()
+		if err := udp.SetNetworkLayerForChecksum(ip); err != nil {
+			logger.Error(ctx, "Error in setting checksum")
+			return
+		}
+		opts := gopacket.SerializeOptions{
+			FixLengths:       true,
+			ComputeChecksums: true,
+		}
+
+		cTagType := layers.EthernetTypeIPv4
+		eth.EthernetType = layers.EthernetTypeDot1Q
+
+		var pktLayers []gopacket.SerializableLayer
+		pktLayers = append(pktLayers, eth)
+
+		var qVlans []of.VlanType
+		var qVlanLayers []gopacket.SerializableLayer
+
+		if vpv.AllowTransparent {
+			vlanThreshold := 2
+			// In case of ONU_CVLAN or OLT_SVLAN, the DS pkts have single configured vlan
+			// In case of ONU_CVLAN_OLT_SVLAN or OLT_CVLAN_OLT_SVLAN, the DS pkts have 2 configured vlan
+			// Based on that, the no. of vlans should be ignored to get only transparent vlans
+			if vpv.VlanControl == ONUCVlan || vpv.VlanControl == OLTSVlan || vpv.VlanControl == None {
+				vlanThreshold = 1
+			}
+			nxtLayer := layers.EthernetTypeDot1Q
+			if vlans := GetVlans(pkt); len(vlans) > vlanThreshold {
+				qVlans = vlans[vlanThreshold:]
+				cTagType = layers.EthernetTypeDot1Q
+			}
+			for i, qVlan := range qVlans {
+				vlan := uint16(qVlan)
+				if i == (len(qVlans) - 1) {
+					nxtLayer = layers.EthernetTypeIPv4
+				}
+				qdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: vlan, DropEligible: dropEligible, Type: nxtLayer}
+				qVlanLayers = append(qVlanLayers, qdot1q)
+			}
+		}
+		switch vpv.VlanControl {
+		case ONUCVlanOLTSVlan:
+			cdot1q := &layers.Dot1Q{Priority: dsPbit, VLANIdentifier: uint16(vpv.CVlan), DropEligible: dropEligible, Type: cTagType}
+			pktLayers = append(pktLayers, cdot1q)
+		case ONUCVlan,
+			None:
+			sdot1q := &layers.Dot1Q{Priority: dsPbit, VLANIdentifier: uint16(vpv.SVlan), DropEligible: dropEligible, Type: cTagType}
+			pktLayers = append(pktLayers, sdot1q)
+		case OLTCVlanOLTSVlan,
+			OLTSVlan:
+			udot1q := &layers.Dot1Q{Priority: dsPbit, VLANIdentifier: uint16(vpv.UniVlan), DropEligible: dropEligible, Type: cTagType}
+			pktLayers = append(pktLayers, udot1q)
+		default:
+			logger.Errorw(ctx, "Invalid Vlan Control Option", log.Fields{"Value": vpv.VlanControl})
+		}
+
+		pktLayers = append(pktLayers, qVlanLayers...)
+		pktLayers = append(pktLayers, ip)
+		pktLayers = append(pktLayers, udp)
+		pktLayers = append(pktLayers, dhcp4)
+		logger.Debugw(ctx, "Layers Count", log.Fields{"Count": len(pktLayers)})
+		if err := gopacket.SerializeMultiLayers(buff, opts, pktLayers); err != nil {
+			logger.Errorw(ctx, "Packet Serialization Failed", log.Fields{"Reason": err.Error()})
+			return
+		}
+
+		if err := cntlr.GetController().PacketOutReq(device, vpv.Port, port, buff.Bytes(), false); err != nil {
+			logger.Errorw(ctx, "PacketOutReq Failed",  log.Fields{"Error" : err})
+		}
+	}
+}
+
+// raiseDHCPv4Indication process DHCPv4 packet and raise indication
+func raiseDHCPv4Indication(msgType layers.DHCPMsgType, vpv *VoltPortVnet, smac net.HardwareAddr,
+	ip net.IP, pktPbit uint8, device string, leaseTime int64) {
+
+	logger.Debugw(ctx, "Processing Dhcpv4 packet", log.Fields{"ethsrcMac": smac.String(),
+		"MacLearningInVPV": vpv.MacLearning, "MacConfigured": vpv.MacAddr, "dhcpType": msgType,
+		"vlanPriority": pktPbit, "VPVLearntMac": vpv.LearntMacAddr})
+
+	matchServiceAndRaiseInd := func(key, value interface{}) bool {
+		// walk through all svcs under vpv and match pbit with packet.
+		svc := value.(*VoltService)
+
+		if svc.IsPbitExist(of.PbitType(pktPbit)) {
+			logger.Debugw(ctx, "Matching Pbit found in service config", log.Fields{"ServiceName": svc.Name, "Pbit": pktPbit})
+			return false
+		}
+		return true
+	}
+
+	switch msgType {
+	case layers.DHCPMsgTypeDiscover, layers.DHCPMsgTypeRequest:
+		if msgType == layers.DHCPMsgTypeDiscover {
+			vpv.SetDhcpState(DhcpRelayStateDiscover)
+		} else if msgType == layers.DHCPMsgTypeRequest {
+			vpv.SetDhcpState(DhcpRelayStateRequest)
+		}
+	// Reset learnt mac address in case of DHCPv4 release
+	case layers.DHCPMsgTypeRelease:
+		vpv.LearntMacAddr, _ = net.ParseMAC("00:00:00:00:00:00")
+		vpv.services.Range(matchServiceAndRaiseInd)
+		vpv.SetDhcpState(DhcpRelayStateRelease)
+
+	case layers.DHCPMsgTypeAck, layers.DHCPMsgTypeNak:
+		vpv.services.Range(matchServiceAndRaiseInd)
+		if msgType == layers.DHCPMsgTypeAck {
+			vpv.SetDhcpState(DhcpRelayStateAck)
+		} else if msgType == layers.DHCPMsgTypeNak {
+			vpv.SetDhcpState(DhcpRelayStateNAK)
+		}
+	case layers.DHCPMsgTypeOffer:
+		vpv.SetDhcpState(DhcpRelayStateOffer)
+	}
+}
+
+// raiseDHCPv6Indication process DHCPv6 packet and raise indication
+func raiseDHCPv6Indication(msgType layers.DHCPv6MsgType, vpv *VoltPortVnet,
+	smac net.HardwareAddr, ip net.IP, pktPbit uint8, device string, leaseTime uint32) {
+
+	logger.Debugw(ctx, "Processing DHCPv6 packet", log.Fields{"dhcpType": msgType,
+		"vlanPriority": pktPbit, "dhcpClientMac": smac.String(),
+		"MacLearningInVPV": vpv.MacLearning, "MacConfigured": vpv.MacAddr,
+		"VPVLearntMac": vpv.LearntMacAddr})
+
+	matchServiceAndRaiseInd := func(key, value interface{}) bool {
+		svc := value.(*VoltService)
+		if svc.IsPbitExist(of.PbitType(pktPbit)) {
+			logger.Debugw(ctx, "Matching Pbit found in service config", log.Fields{"ServiceName": svc.Name, "Pbit": pktPbit})
+			return false
+		}
+		return true
+	}
+
+	switch msgType {
+	case layers.DHCPv6MsgTypeSolicit:
+		vpv.SetDhcpv6State(Dhcpv6RelayStateSolicit)
+	// Reset learnt mac address in case of DHCPv6 release
+	case layers.DHCPv6MsgTypeRelease:
+		vpv.LearntMacAddr, _ = net.ParseMAC("00:00:00:00:00:00")
+		vpv.services.Range(matchServiceAndRaiseInd)
+		vpv.SetDhcpv6State(Dhcpv6RelayStateRelease)
+
+	case layers.DHCPv6MsgTypeReply:
+		vpv.services.Range(matchServiceAndRaiseInd)
+		vpv.SetDhcpv6State(Dhcpv6RelayStateReply)
+	}
+}
+
+// ProcessUsDhcpv4Packet : The US DHCPv4 packet is identified the DHCP OP in the packet. A request is considered upstream
+// and the service associated with the packet is located by the port and VLANs in the packet
+func (va *VoltApplication) ProcessUsDhcpv4Packet(device string, port string, pkt gopacket.Packet) {
+	// We received the packet on an access port and the service for the packet can be
+	// gotten from the port and the packet
+	vpv, svc := va.GetVnetFromPkt(device, port, pkt)
+	if vpv == nil {
+		logger.Warn(ctx, "VNET couldn't be found from packet")
+		return
+	}
+
+	outport, _ := va.GetNniPort(device)
+	if outport == "" || outport == "0" {
+		logger.Errorw(ctx, "NNI Port not found for device. Dropping Packet", log.Fields{"NNI": outport})
+		return
+	}
+
+	// Extract the layers in the packet to prepare the outgoing packet
+	// We use the layers to build the outgoing packet from scratch as
+	// the packet received can't be modified to add/remove layers
+	eth := pkt.Layer(layers.LayerTypeEthernet).(*layers.Ethernet)
+	ip := pkt.Layer(layers.LayerTypeIPv4).(*layers.IPv4)
+	udp := pkt.Layer(layers.LayerTypeUDP).(*layers.UDP)
+	dhcp4 := pkt.Layer(layers.LayerTypeDHCPv4).(*layers.DHCPv4)
+	msgType := DhcpMsgType(dhcp4)
+	logger.Infow(ctx, "Processing Southbound US DHCPv4 packet", log.Fields{"Device": device, "Port": port, "Type": msgType})
+
+	// Learn the 8021P values from the packet received
+	var priority uint8
+	var dropEligible bool
+	dot1ql := pkt.Layer(layers.LayerTypeDot1Q)
+	if dot1ql != nil {
+		dot1q := dot1ql.(*layers.Dot1Q)
+		priority = dot1q.Priority
+		dropEligible = dot1q.DropEligible
+	}
+	// If this is the first message in the DHCP sequence, the service
+	// is added to the DHCP relay application. The reply packets locate
+	// the associated service/session from the relay application.
+	if msgType == layers.DHCPMsgTypeDiscover || msgType == layers.DHCPMsgTypeRequest {
+		if err := dhcpNws.AddDhcpSession(pkt, vpv); err != nil {
+			logger.Errorw(ctx, "Adding dhcp session failed", log.Fields{"Error": err})
+		}
+	}
+
+	// Raise mac-learnt(DHCP Discover) indication when mac learning is enabled and learnt mac
+	// is not same as received mac address. If mac learning disabled, we have mac address in the
+	// service configuration. Hence mac learnt indication is not raised
+	// Reset learnt mac address in case of DHCP release and raise the indication
+	if vpv.DhcpRelay {
+		// If this is the first message in the DHCP sequence, the service
+		// is added to the DHCP relay application. The reply packets locate
+		// the associated service/session from the relay application.
+		// DS HSIA flows will be added after DHCP ACK .
+		if msgType == layers.DHCPMsgTypeDiscover || msgType == layers.DHCPMsgTypeRequest {
+			if !util.MacAddrsMatch(vpv.MacAddr, dhcp4.ClientHWAddr) {
+				// MAC is different and relearning is disabled.
+				if NonZeroMacAddress(vpv.MacAddr) && vpv.MacLearning == Learn {
+					// update learnt mac for debug purpose
+					vpv.LearntMacAddr = dhcp4.ClientHWAddr
+					vpv.WriteToDb()
+					logger.Warnw(ctx, "Dropping the packet Mac relearn is disabled",
+						log.Fields{"vpv.MacAddr": vpv.MacAddr, "LearntMac": dhcp4.ClientHWAddr})
+					return
+				}
+				expectedPort := va.GetMacInPortMap(dhcp4.ClientHWAddr)
+				if expectedPort != "" && expectedPort != vpv.Port {
+					logger.Errorw(ctx, "mac-learnt-from-different-port-ignoring-dhcp-message", log.Fields{"MsgType": msgType, "ExpectedPort": expectedPort, "ReceivedPort": vpv.Port, "LearntMacAdrr": vpv.MacAddr, "NewMacAdrr": dhcp4.ClientHWAddr.String()})
+					return
+				}
+			}
+		}
+		raiseDHCPv4Indication(msgType, vpv, dhcp4.ClientHWAddr, vpv.Ipv4Addr, priority, device, 0)
+
+		// Check IsOption82Disabled flag in configuration. if true(disabled), do not add option82 into dhcpv4 header.
+		// Remote id can be custom or mac address.
+		// If remote id is custom, then add service will carry the remote id
+		// If remote id is mac address, and if mac is configured, then add service will carry the remote id
+		// If remote id is mac address, in mac learning case, then mac has to be taken from dhcp packet
+		if !svc.IsOption82Disabled {
+			var remoteID []byte
+			if svc.RemoteIDType == string(MACAddress) {
+				remoteID = []byte((dhcp4.ClientHWAddr).String())
+			} else if svc.RemoteID != nil {
+				remoteID = svc.RemoteID
+			}
+			AddDhcpv4Option82(svc, remoteID, dhcp4)
+		}
+	}
+
+	buff := gopacket.NewSerializeBuffer()
+	if err := udp.SetNetworkLayerForChecksum(ip); err != nil {
+		logger.Error(ctx, "Error in setting checksum")
+		return
+	}
+	opts := gopacket.SerializeOptions{
+		FixLengths:       true,
+		ComputeChecksums: true,
+	}
+
+	cTagType := layers.EthernetTypeIPv4
+	outerVlan, innerVlan := vpv.GetNniVlans()
+	logger.Debugw(ctx, "Vnet Vlans", log.Fields{"Svlan": outerVlan, "Cvlan": innerVlan})
+	eth.EthernetType = vpv.SVlanTpid
+
+	var pktLayers []gopacket.SerializableLayer
+	pktLayers = append(pktLayers, eth)
+
+	var qVlans []of.VlanType
+	var qVlanLayers []gopacket.SerializableLayer
+
+	if vpv.AllowTransparent {
+		nxtLayer := layers.EthernetTypeDot1Q
+		if vlans := GetVlans(pkt); len(vlans) > 1 {
+			qVlans = vlans[1:]
+			logger.Debugw(ctx, "Q Vlans", log.Fields{"Vlan List": qVlans})
+			cTagType = layers.EthernetTypeDot1Q
+		}
+		for i, qVlan := range qVlans {
+			vlan := uint16(qVlan)
+			if i == (len(qVlans) - 1) {
+				nxtLayer = layers.EthernetTypeIPv4
+			}
+			qdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: vlan, DropEligible: dropEligible, Type: nxtLayer}
+			qVlanLayers = append(qVlanLayers, qdot1q)
+		}
+	}
+	switch vpv.VlanControl {
+	case ONUCVlanOLTSVlan,
+		OLTCVlanOLTSVlan:
+		sdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: outerVlan, DropEligible: dropEligible, Type: layers.EthernetTypeDot1Q}
+		pktLayers = append(pktLayers, sdot1q)
+		cdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: innerVlan, DropEligible: dropEligible, Type: cTagType}
+		pktLayers = append(pktLayers, cdot1q)
+	case ONUCVlan,
+		OLTSVlan,
+		None:
+		cdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: outerVlan, DropEligible: dropEligible, Type: cTagType}
+		pktLayers = append(pktLayers, cdot1q)
+	default:
+		logger.Errorw(ctx, "Invalid Vlan Control Option", log.Fields{"Value": vpv.VlanControl})
+	}
+
+	pktLayers = append(pktLayers, qVlanLayers...)
+	pktLayers = append(pktLayers, ip)
+	pktLayers = append(pktLayers, udp)
+	pktLayers = append(pktLayers, dhcp4)
+	logger.Debugw(ctx, "Layers Count", log.Fields{"Count": len(pktLayers)})
+	if err := gopacket.SerializeMultiLayers(buff, opts, pktLayers); err != nil {
+		return
+	}
+
+	// Now the packet constructed is output towards the switch to be emitted on
+	// the NNI port
+	if err := cntlr.GetController().PacketOutReq(device, outport, port, buff.Bytes(), false); err != nil {
+		logger.Errorw(ctx, "PacketOutReq Failed",  log.Fields{"Error" : err})
+	}
+	if vpv.DhcpRelay {
+		// Inform dhcp request information to dhcp server handler
+		dhcpRequestReceived(uint16(vpv.CVlan), uint16(vpv.SVlan), eth.SrcMAC.String())
+	}
+}
+
+// ProcessUDP4Packet : CallBack function registered with application to handle DHCP packetIn
+func ProcessUDP4Packet(device string, port string, pkt gopacket.Packet) {
+	GetApplication().ProcessUDP4Packet(device, port, pkt)
+}
+
+// ProcessUDP4Packet : The packet is a UDP packet and currently only DHCP relay application is supported
+// We determine the packet direction and process it based on the direction
+func (va *VoltApplication) ProcessUDP4Packet(device string, port string, pkt gopacket.Packet) {
+	// Currently DHCP is the only application supported by the application
+	// We check for DHCP before proceeding futher. In future, this could be
+	// based on registration and the callbacks
+	dhcpl := pkt.Layer(layers.LayerTypeDHCPv4)
+	if dhcpl == nil {
+		return
+	}
+	//logger.Debugw(ctx, "Received Packet In", log.Fields{"Pkt": hex.EncodeToString(pkt.Data())})
+	dhcp4 := pkt.Layer(layers.LayerTypeDHCPv4).(*layers.DHCPv4)
+	if dhcp4.Operation == layers.DHCPOpRequest {
+		// This is treated as an upstream packet in the VOLT application
+		// as VOLT serves access subscribers who use DHCP to acquire IP
+		// address and these packets go upstream to the network
+		va.ProcessUsDhcpv4Packet(device, port, pkt)
+	} else {
+		// This is a downstream packet
+		va.ProcessDsDhcpv4Packet(device, port, pkt)
+	}
+
+}
+
+// ProcessUDP6Packet : CallBack function registered with application to handle DHCPv6 packetIn
+func ProcessUDP6Packet(device string, port string, pkt gopacket.Packet) {
+	GetApplication().ProcessUDP6Packet(device, port, pkt)
+}
+
+// ProcessUDP6Packet : As a LDRA node, we expect to see only RelayReply from the DHCP server and we always
+// pack the received request and send it to the server as a RelayForward message
+// We expect to see Solicit, Request in the most normal cases. Before the lease expires
+// we should also see Renew. However, we should always pack the US message by adding
+// additional option that identifies to the server that the DHCP packet is forwarded
+// by an LDRA node.
+func (va *VoltApplication) ProcessUDP6Packet(device string, port string, pkt gopacket.Packet) []byte {
+	dhcpl := pkt.Layer(layers.LayerTypeDHCPv6)
+	if dhcpl == nil {
+		return nil
+	}
+	logger.Infow(ctx, "Processing DHCPv6 packet", log.Fields{"Port": port})
+	dhcpv6 := dhcpl.(*layers.DHCPv6)
+	switch dhcpv6.MsgType {
+	case layers.DHCPv6MsgTypeSolicit, layers.DHCPv6MsgTypeRequest, layers.DHCPv6MsgTypeRenew,
+		layers.DHCPv6MsgTypeRelease, layers.DHCPv6MsgTypeRebind, layers.DHCPv6MsgTypeInformationRequest,
+		layers.DHCPv6MsgTypeDecline:
+		va.ProcessUsDhcpv6Packet(device, port, pkt)
+	case layers.DHCPv6MsgTypeAdvertise, layers.DHCPv6MsgTypeConfirm, layers.DHCPv6MsgTypeReconfigure:
+		logger.Warnw(ctx, "SouthBound DHCPv6 DS Messages Expected For a Relay Agent", log.Fields{"Type": dhcpv6.MsgType})
+	case layers.DHCPv6MsgTypeRelayForward:
+		logger.Warn(ctx, "As the first DHCPv6 Relay Agent, Unexpected Relay Forward")
+	case layers.DHCPv6MsgTypeRelayReply:
+		// We received a response from the server
+		va.ProcessDsDhcpv6Packet(device, port, pkt)
+	}
+	return nil
+}
+
+// GetRelayReplyBytes to get relay reply bytes
+func GetRelayReplyBytes(dhcp6 *layers.DHCPv6) []byte {
+	for _, o := range dhcp6.Options {
+		logger.Infow(ctx, "Received Option", log.Fields{"Code": o.Code})
+		if o.Code == layers.DHCPv6OptRelayMessage {
+			return o.Data
+		}
+	}
+	return nil
+}
+
+// BuildRelayFwd to build forward relay
+func BuildRelayFwd(paddr net.IP, intfID []byte, remoteID []byte, payload []byte, isOption82Disabled bool, dhcpRelay bool) *layers.DHCPv6 {
+	dhcp6 := &layers.DHCPv6{MsgType: layers.DHCPv6MsgTypeRelayForward, LinkAddr: net.ParseIP("::"), PeerAddr: []byte(paddr)}
+	dhcp6.Options = append(dhcp6.Options, layers.NewDHCPv6Option(layers.DHCPv6OptRelayMessage, payload))
+	// Check IsOption82Disabled flag in configuration. if true(disabled), do not add remoteID and circuitID into dhcpv6 header.
+	if dhcpRelay {
+		if !isOption82Disabled {
+			remote := &layers.DHCPv6RemoteId{RemoteId: remoteID}
+			if len(remoteID) != 0 {
+				dhcp6.Options = append(dhcp6.Options, layers.NewDHCPv6Option(layers.DHCPv6OptRemoteID, remote.Encode()))
+			}
+			if len(intfID) != 0 {
+				intf := &layers.DHCPv6IntfId{Data: intfID}
+				dhcp6.Options = append(dhcp6.Options, layers.NewDHCPv6Option(layers.DHCPv6OptInterfaceID, intf.Encode()))
+			}
+		}
+	}
+	return dhcp6
+}
+
+// ProcessUsDhcpv6Packet to rpocess upstream DHCPv6 packet
+func (va *VoltApplication) ProcessUsDhcpv6Packet(device string, port string, pkt gopacket.Packet) {
+	// We received the packet on an access port and the service for the packet can be
+	// gotten from the port and the packet
+	logger.Infow(ctx, "Processing Southbound US DHCPv6 packet", log.Fields{"Port": port})
+	logger.Debugw(ctx, "Packet IN", log.Fields{"Pkt": hex.EncodeToString(pkt.Data())})
+	vpv, svc := va.GetVnetFromPkt(device, port, pkt)
+	if vpv == nil {
+		logger.Warn(ctx, "VNET couldn't be found from packet")
+		return
+	}
+
+	outport, _ := va.GetNniPort(device)
+	if outport == "" || outport == "0" {
+		logger.Errorw(ctx, "NNI Port not found for device. Dropping Packet", log.Fields{"NNI": outport})
+		return
+	}
+
+	// Extract the layers in the packet to prepare the outgoing packet
+	// We use the layers to build the outgoing packet from scratch as
+	// the packet received can't be modified to add/remove layers
+	eth := pkt.Layer(layers.LayerTypeEthernet).(*layers.Ethernet)
+	ip := pkt.Layer(layers.LayerTypeIPv6).(*layers.IPv6)
+	udp := pkt.Layer(layers.LayerTypeUDP).(*layers.UDP)
+	idhcp6 := pkt.Layer(layers.LayerTypeDHCPv6).(*layers.DHCPv6)
+
+	// Remote id can be custom or mac address.
+	// If remote id is custom, then add service will carry the remote id
+	// If remote id is mac address, and if mac is configured, then add service will carry the remote id
+	// If remote id is mac address, in mac learning case, then mac has to be taken from dhcp packet
+	var remoteID []byte
+	if svc.RemoteIDType == string(MACAddress) {
+		remoteID = []byte((eth.SrcMAC).String())
+	} else if svc.RemoteID != nil {
+		remoteID = svc.RemoteID
+	}
+	dhcp6 := BuildRelayFwd(ip.SrcIP, svc.GetCircuitID(), remoteID, udp.Payload, svc.IsOption82Disabled, vpv.DhcpRelay)
+
+	var sourceMac = eth.SrcMAC
+	var sessionKey [MaxLenDhcpv6DUID]byte
+
+	clientDuid, decodedDuid := getDhcpv6ClientDUID(idhcp6)
+	if clientDuid == nil || decodedDuid == nil {
+		copy(sessionKey[:], eth.SrcMAC)
+	} else {
+		copy(sessionKey[:], clientDuid[0:])
+		if decodedDuid.Type == layers.DHCPv6DUIDTypeLLT || decodedDuid.Type == layers.DHCPv6DUIDTypeLL {
+			sourceMac = decodedDuid.LinkLayerAddress
+		}
+	}
+	// Learn the 8021P values from the packet received
+	var priority uint8
+	var dropEligible bool
+	dot1ql := pkt.Layer(layers.LayerTypeDot1Q)
+	if dot1ql != nil {
+		dot1q := dot1ql.(*layers.Dot1Q)
+		priority = dot1q.Priority
+		dropEligible = dot1q.DropEligible
+	}
+	if idhcp6.MsgType == layers.DHCPv6MsgTypeSolicit {
+		if err := dhcpNws.AddDhcp6Session(sessionKey, vpv); err != nil {
+			logger.Errorw(ctx, "Adding dhcpv6 session failed", log.Fields{"Error": err})
+		}
+		vpv.DHCPv6DUID = sessionKey
+	}
+
+	// Raise mac-learnt(DHCPv6MsgTypeSolicit) indication when mac learning is enabled and learnt mac
+	// is not same as received mac address. If mac learning disabled, we have mac address in the
+	// service configuration. Hence mac learnt indication is not raised
+	if vpv.DhcpRelay {
+		if idhcp6.MsgType == layers.DHCPv6MsgTypeSolicit {
+			if !util.MacAddrsMatch(vpv.MacAddr, sourceMac) {
+				// MAC is different and relearning is disabled.
+				if NonZeroMacAddress(vpv.MacAddr) && vpv.MacLearning == Learn {
+					// update learnt mac for debug purpose
+					vpv.LearntMacAddr = sourceMac
+					vpv.WriteToDb()
+					logger.Warnw(ctx, "Dropping the packet Mac relearn is disabled",
+						log.Fields{"vpv.MacAddr": vpv.MacAddr, "LearntMac": sourceMac})
+					return
+				}
+				expectedPort := va.GetMacInPortMap(sourceMac)
+				if expectedPort != "" && expectedPort != vpv.Port {
+					logger.Errorw(ctx, "mac-learnt-from-different-port-ignoring-dhcp-message", log.Fields{"MsgType": idhcp6.MsgType, "ExpectedPort": expectedPort, "ReceivedPort": vpv.Port, "LearntMacAdrr": vpv.MacAddr, "NewMacAdrr": sourceMac.String()})
+					return
+				}
+			}
+		}
+		raiseDHCPv6Indication(idhcp6.MsgType, vpv, sourceMac, vpv.Ipv6Addr, priority, device, 0)
+	}
+
+	// Create the buffer and the encode options for the outgoing packet
+	buff := gopacket.NewSerializeBuffer()
+	if err := udp.SetNetworkLayerForChecksum(ip); err != nil {
+		logger.Error(ctx, "Error in setting checksum")
+		return
+	}
+	opts := gopacket.SerializeOptions{
+		FixLengths:       true,
+		ComputeChecksums: true,
+	}
+
+	cTagType := layers.EthernetTypeIPv6
+	outerVlan, innerVlan := vpv.GetNniVlans()
+	eth.EthernetType = vpv.SVlanTpid
+
+	var pktLayers []gopacket.SerializableLayer
+	pktLayers = append(pktLayers, eth)
+
+	var qVlans []of.VlanType
+	var qVlanLayers []gopacket.SerializableLayer
+
+	if vpv.AllowTransparent {
+		nxtLayer := layers.EthernetTypeDot1Q
+		if vlans := GetVlans(pkt); len(vlans) > 1 {
+			qVlans = vlans[1:]
+			cTagType = layers.EthernetTypeDot1Q
+		}
+		for i, qVlan := range qVlans {
+			vlan := uint16(qVlan)
+			if i == (len(qVlans) - 1) {
+				nxtLayer = layers.EthernetTypeIPv6
+			}
+			qdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: vlan, DropEligible: dropEligible, Type: nxtLayer}
+			qVlanLayers = append(qVlanLayers, qdot1q)
+		}
+
+	}
+	switch vpv.VlanControl {
+	case ONUCVlanOLTSVlan,
+		OLTCVlanOLTSVlan:
+		sdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: outerVlan, DropEligible: dropEligible, Type: layers.EthernetTypeDot1Q}
+		pktLayers = append(pktLayers, sdot1q)
+		cdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: innerVlan, DropEligible: dropEligible, Type: cTagType}
+		pktLayers = append(pktLayers, cdot1q)
+	case ONUCVlan,
+		OLTSVlan,
+		None:
+		cdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: outerVlan, DropEligible: dropEligible, Type: cTagType}
+		pktLayers = append(pktLayers, cdot1q)
+	default:
+		logger.Errorw(ctx, "Invalid Vlan Control Option", log.Fields{"Value": vpv.VlanControl})
+	}
+
+	pktLayers = append(pktLayers, qVlanLayers...)
+	pktLayers = append(pktLayers, ip)
+	pktLayers = append(pktLayers, udp)
+	pktLayers = append(pktLayers, dhcp6)
+	logger.Debugw(ctx, "Layers Count", log.Fields{"Count": len(pktLayers)})
+	if err := gopacket.SerializeMultiLayers(buff, opts, pktLayers); err != nil {
+		return
+	}
+	// Now the packet constructed is output towards the switch to be emitted on
+	// the NNI port
+	if err := cntlr.GetController().PacketOutReq(device, outport, port, buff.Bytes(), false); err != nil {
+		logger.Errorw(ctx, "PacketOutReq Failed",  log.Fields{"Error" : err})
+	}
+	if vpv.DhcpRelay {
+		// Inform dhcp request information to dhcp server handler
+		dhcpRequestReceived(uint16(vpv.CVlan), uint16(vpv.SVlan), eth.SrcMAC.String())
+	}
+}
+
+// GetDhcpv6 to get dhcpv6 info
+func GetDhcpv6(payload []byte) (*layers.DHCPv6, error) {
+	pkt := gopacket.NewPacket(payload, layers.LayerTypeDHCPv6, gopacket.Default)
+	if dl := pkt.Layer(layers.LayerTypeDHCPv6); dl != nil {
+		if dhcp6, ok := dl.(*layers.DHCPv6); ok {
+			return dhcp6, nil
+		}
+	}
+	return nil, errors.New("Failed to decode DHCPv6")
+}
+
+// ProcessDsDhcpv6Packet to process downstream dhcpv6 packet
+func (va *VoltApplication) ProcessDsDhcpv6Packet(device string, port string, pkt gopacket.Packet) {
+	logger.Infow(ctx, "Processing Southbound DS DHCPv6 packet", log.Fields{"Port": port})
+	logger.Debugw(ctx, "Packet IN", log.Fields{"Pkt": hex.EncodeToString(pkt.Data())})
+
+	// Retrieve the layers to build the outgoing packet. It is not
+	// possible to add/remove layers to the existing packet and thus
+	// the lyayers are extracted to build the outgoing packet
+	// The DHCP layer is handled differently. The Relay-Reply option
+	// of DHCP is extracted and is made the UDP payload.
+	eth := pkt.Layer(layers.LayerTypeEthernet).(*layers.Ethernet)
+	ip := pkt.Layer(layers.LayerTypeIPv6).(*layers.IPv6)
+	udp := pkt.Layer(layers.LayerTypeUDP).(*layers.UDP)
+	idhcp6 := pkt.Layer(layers.LayerTypeDHCPv6).(*layers.DHCPv6)
+	//var dhcp6 *layers.DHCPv6
+	var payload []byte
+	if payload = GetRelayReplyBytes(idhcp6); payload == nil {
+		logger.Warn(ctx, "Didn't Receive RelayMessage IE")
+		return
+	}
+
+	dhcp6, err := GetDhcpv6(payload)
+	if err != nil {
+		logger.Warnw(ctx, "DHCPv6 Decode Failed", log.Fields{"Reason": err.Error()})
+		return
+	}
+
+	// Learn the 8021P values from the packet received
+	var priority uint8
+	var dsPbit uint8
+	var dropEligible bool
+	dot1ql := pkt.Layer(layers.LayerTypeDot1Q)
+	if dot1ql != nil {
+		dot1q := dot1ql.(*layers.Dot1Q)
+		priority = dot1q.Priority
+		dropEligible = dot1q.DropEligible
+	}
+
+	pktInnerlan, pktOuterlan := GetVlansFromPacket(pkt)
+	vpvList, clientMac, err := GetVnetForV6Nni(dhcp6, pktInnerlan, pktOuterlan, priority, eth.DstMAC)
+	if len(vpvList) == 0 {
+		logger.Warnw(ctx, "VNET couldn't be found for NNI", log.Fields{"Reason": err})
+		return
+	}
+
+	ipv6Addr, leaseTime := GetIpv6Addr(dhcp6)
+
+	for _, vpv := range vpvList {
+
+		dsPbit = vpv.GetRemarkedPriority(priority)
+		// Raise DHCPv6 Reply indication
+		if vpv.DhcpRelay {
+			// Inform dhcp response information to dhcp server handler
+			dhcpResponseReceived(uint16(vpv.CVlan), uint16(vpv.SVlan))
+
+			if dhcp6.MsgType == layers.DHCPv6MsgTypeReply && ipv6Addr != nil {
+				// separate go rotuine is spawned to avoid drop of ACK packet
+				// as HSIA flows will be deleted if new MAC is learnt.
+				if len(vpvList) == 1 {
+					go vpv.SetMacAddr(clientMac)
+				}
+				vpv.Dhcpv6ResultInd(ipv6Addr, leaseTime)
+			}
+			raiseDHCPv6Indication(dhcp6.MsgType, vpv, clientMac, ipv6Addr, dsPbit, device, leaseTime)
+		}
+
+		//Replace dst Port value to 546
+		udp.DstPort = 546
+		logger.Infow(ctx, "Packet Out UDP Port..", log.Fields{"UDP": udp, "Port": udp.DstPort})
+
+		// Create the buffer and the encode options for the outgoing packet
+		buff := gopacket.NewSerializeBuffer()
+		if err := udp.SetNetworkLayerForChecksum(ip); err != nil {
+			logger.Error(ctx, "Error in setting checksum")
+			return
+		}
+		opts := gopacket.SerializeOptions{
+			FixLengths:       true,
+			ComputeChecksums: true,
+		}
+
+		cTagType := layers.EthernetTypeIPv6
+		eth.EthernetType = layers.EthernetTypeDot1Q
+
+		var pktLayers []gopacket.SerializableLayer
+		pktLayers = append(pktLayers, eth)
+
+		var qVlans []of.VlanType
+		var qVlanLayers []gopacket.SerializableLayer
+
+		if vpv.AllowTransparent {
+			vlanThreshold := 2
+			// In case of ONU_CVLAN or OLT_SVLAN, the DS pkts have single configured vlan
+			// In case of ONU_CVLAN_OLT_SVLAN or OLT_CVLAN_OLT_SVLAN, the DS pkts have 2 configured vlan
+			// Based on that, the no. of vlans should be ignored to get only transparent vlans
+			if vpv.VlanControl == ONUCVlan || vpv.VlanControl == OLTSVlan || vpv.VlanControl == None {
+				vlanThreshold = 1
+			}
+			nxtLayer := layers.EthernetTypeDot1Q
+			if vlans := GetVlans(pkt); len(vlans) > vlanThreshold {
+				qVlans = vlans[vlanThreshold:]
+				cTagType = layers.EthernetTypeDot1Q
+			}
+			for i, qVlan := range qVlans {
+				vlan := uint16(qVlan)
+				if i == (len(qVlans) - 1) {
+					nxtLayer = layers.EthernetTypeIPv6
+				}
+				qdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: vlan, DropEligible: dropEligible, Type: nxtLayer}
+				qVlanLayers = append(qVlanLayers, qdot1q)
+			}
+
+		}
+		switch vpv.VlanControl {
+		case ONUCVlanOLTSVlan:
+			cdot1q := &layers.Dot1Q{Priority: dsPbit, VLANIdentifier: uint16(vpv.CVlan), DropEligible: dropEligible, Type: cTagType}
+			pktLayers = append(pktLayers, cdot1q)
+		case ONUCVlan,
+			None:
+			sdot1q := &layers.Dot1Q{Priority: dsPbit, VLANIdentifier: uint16(vpv.SVlan), DropEligible: dropEligible, Type: cTagType}
+			pktLayers = append(pktLayers, sdot1q)
+		case OLTCVlanOLTSVlan,
+			OLTSVlan:
+			udot1q := &layers.Dot1Q{Priority: dsPbit, VLANIdentifier: uint16(vpv.UniVlan), DropEligible: dropEligible, Type: cTagType}
+			pktLayers = append(pktLayers, udot1q)
+		default:
+			logger.Errorw(ctx, "Invalid Vlan Control Option", log.Fields{"Value": vpv.VlanControl})
+		}
+
+		pktLayers = append(pktLayers, qVlanLayers...)
+		pktLayers = append(pktLayers, ip)
+		pktLayers = append(pktLayers, udp)
+		pktLayers = append(pktLayers, dhcp6)
+		logger.Debugw(ctx, "Layers Count", log.Fields{"Count": len(pktLayers)})
+		if err := gopacket.SerializeMultiLayers(buff, opts, pktLayers); err != nil {
+			logger.Errorw(ctx, "Packet Serialization Failed", log.Fields{"Reason": err.Error()})
+			return
+		}
+
+		if err := cntlr.GetController().PacketOutReq(device, vpv.Port, port, buff.Bytes(), false); err != nil {
+			logger.Errorw(ctx, "PacketOutReq Failed", log.Fields{"Reason": err.Error()})
+		}
+	}
+}
+
+// The DHCP relay application is maintained within the structures below
+var dhcpNws *DhcpNetworks
+
+func init() {
+	dhcpNws = NewDhcpNetworks()
+}