First Commit of Voltha-Go-Controller from Radisys

Change-Id: I8e2e908e7ab09a4fe3d86849da18b6d69dcf4ab0
diff --git a/internal/pkg/application/pppoeia.go b/internal/pkg/application/pppoeia.go
new file mode 100644
index 0000000..8b5a214
--- /dev/null
+++ b/internal/pkg/application/pppoeia.go
@@ -0,0 +1,618 @@
+/*
+* Copyright 2022-present Open Networking Foundation
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package application
+
+import (
+	"context"
+	"errors"
+	"net"
+	"time"
+
+	"github.com/google/gopacket"
+	"github.com/google/gopacket/layers"
+
+	cntlr "voltha-go-controller/internal/pkg/controller"
+	"voltha-go-controller/internal/pkg/of"
+	"voltha-go-controller/internal/pkg/util"
+	"github.com/opencord/voltha-lib-go/v7/pkg/log"
+)
+
+// PppoeIaState type
+type PppoeIaState uint8
+
+const (
+	// PppoeIaStateNone constant
+	PppoeIaStateNone PppoeIaState = iota
+	// PppoeIaStatePADI constant
+	PppoeIaStatePADI
+	// PppoeIaStatePADO constant
+	PppoeIaStatePADO
+	// PppoeIaStatePADR constant
+	PppoeIaStatePADR
+	// PppoeIaStatePADS constant
+	PppoeIaStatePADS
+	// PppoeIaStatePADT constant
+	PppoeIaStatePADT
+)
+
+const (
+	// PPPoEVendorID constant
+	PPPoEVendorID uint32 = 0x0DE9
+	// TYPECIRCUITID constant
+	TYPECIRCUITID byte = 0x01
+	// TYPEREMOTEID constant
+	TYPEREMOTEID byte = 0x02
+	// TYPEMINDATAUS constant
+	TYPEMINDATAUS byte = 0x83
+	// TYPEMINDATADS constant
+	TYPEMINDATADS byte = 0x84
+	// TYPEMAXDATAUS constant
+	TYPEMAXDATAUS byte = 0x87
+	// TYPEMAXDATADS constant
+	TYPEMAXDATADS byte = 0x88
+)
+
+var (
+	// DSLATTRVendorID is PPPoEVendorID in byte format
+	DSLATTRVendorID = util.Uint32ToByte(PPPoEVendorID)
+)
+
+// IPppoeIaSession interface
+type IPppoeIaSession interface {
+	GetCircuitID() []byte
+	GetRemoteID() []byte
+	GetNniVlans() (uint16, uint16)
+	GetPppoeIaState() PppoeIaState
+	SetPppoeIaState(PppoeIaState)
+	SetMacAddr(net.HardwareAddr)
+}
+
+// PppoeIaRelayVnet : The PppoeIa relay sessions are stored in a map to be retrieved from when
+// a response is received from the network. The map uses the VLANs and the
+// the MAC address as key to finding the service
+// PppoeIa Relay Virtual Network hosts a set of PppoeIa relay sessions that belong
+// to the network. It supports two VLANs as its identify. If a single VLAN or
+// no VLAN is to be used, those two should be passed as 4096 (VlanNone)
+type PppoeIaRelayVnet struct {
+	OuterVlan uint16
+	InnerVlan uint16
+	sessions  *util.ConcurrentMap //map[[6]byte]IPppoeIaSession
+}
+
+// PppoeIaNetworks : PppoeIa Networks hosts different PppoeIa networks that in turn hold the PppoeIa
+// sessions
+type PppoeIaNetworks struct {
+	Networks *util.ConcurrentMap //map[uint32]*PppoeIaRelayVnet
+}
+
+// NewPppoeIaRelayVnet is constructor for a PppoeIa Relay Virtual network
+func NewPppoeIaRelayVnet(outerVlan uint16, innerVlan uint16) *PppoeIaRelayVnet {
+	var drv PppoeIaRelayVnet
+
+	drv.OuterVlan = outerVlan
+	drv.InnerVlan = innerVlan
+	drv.sessions = util.NewConcurrentMap() //make(map[[6]byte]IPppoeIaSession)
+	return &drv
+}
+
+// AddPppoeIaRelayVnet add pppoeia relay vnet
+func (dn *PppoeIaNetworks) AddPppoeIaRelayVnet(outerVlan uint16, innerVlan uint16) *PppoeIaRelayVnet {
+	comboVlan := uint32(outerVlan)<<16 + uint32(innerVlan)
+	if drv, ok := dn.Networks.Get(comboVlan); ok {
+		return drv.(*PppoeIaRelayVnet)
+	}
+	drv := NewPppoeIaRelayVnet(outerVlan, innerVlan)
+	dn.Networks.Set(comboVlan, drv)
+	return drv
+}
+
+// NewPppoeIaNetworks is constructor for PppoeIa network
+func NewPppoeIaNetworks() *PppoeIaNetworks {
+	var dn PppoeIaNetworks
+	dn.Networks = util.NewConcurrentMap() //make(map[uint32]*PppoeIaRelayVnet)
+	return &dn
+}
+
+// AddPppoeIaSession to add pppoeia session
+func (dn *PppoeIaNetworks) AddPppoeIaSession(pkt gopacket.Packet, session IPppoeIaSession) {
+	var key [6]byte
+	ethl := pkt.Layer(layers.LayerTypeEthernet)
+	eth, _ := ethl.(*layers.Ethernet)
+	addr := eth.SrcMAC
+	copy(key[:], addr[0:6])
+	drv := dn.AddPppoeIaRelayVnet(session.GetNniVlans())
+	drv.sessions.Set(key, session)
+}
+
+// DelPppoeIaSession to delete pppoeia session
+func (dn *PppoeIaNetworks) DelPppoeIaSession(pkt gopacket.Packet, session IPppoeIaSession) {
+	var key [6]byte
+	ethl := pkt.Layer(layers.LayerTypeEthernet)
+	eth, _ := ethl.(*layers.Ethernet)
+	addr := eth.SrcMAC
+	if len(addr) != 6 {
+		logger.Errorw(ctx, "Invalid MAC address", log.Fields{"Addr": addr})
+		return
+	}
+	copy(key[:], addr[0:6])
+	drv := dn.AddPppoeIaRelayVnet(session.GetNniVlans())
+	drv.sessions.Remove(key)
+}
+
+// delPppoeIaSessions to delete pppoeia sessions
+func delPppoeIaSessions(addr net.HardwareAddr, outervlan of.VlanType, innervlan of.VlanType) {
+
+	var key [6]byte
+	if addr == nil || !NonZeroMacAddress(addr) {
+		logger.Warnw(ctx, "Invalid MAC address", log.Fields{"Addr": addr})
+		return
+	}
+	copy(key[:], addr[0:6])
+	drv := pppoeIaNws.AddPppoeIaRelayVnet(uint16(outervlan), uint16(innervlan))
+	drv.sessions.Remove(key)
+	logger.Infow(ctx, "PppoeIa Sessions deleted", log.Fields{"MAC": addr})
+}
+
+// GetPppoeIaSession to get pppoeia sessions
+func (dn *PppoeIaNetworks) GetPppoeIaSession(outerVlan uint16, innerVlan uint16, addr net.HardwareAddr) (IPppoeIaSession, error) {
+	var key [6]byte
+	if len(addr) != 6 {
+		logger.Errorw(ctx, "Invalid MAC address", log.Fields{"Addr": addr})
+		return nil, errors.New("Invalid MAC address")
+	}
+	copy(key[:], addr[0:6])
+	drv := dn.AddPppoeIaRelayVnet(outerVlan, innerVlan)
+	logger.Infow(ctx, "Key for PPPoE session", log.Fields{"Key": key})
+	if session, ok := drv.sessions.Get(key); ok {
+		return session.(IPppoeIaSession), nil
+	}
+	return nil, ErrSessionDoNotExist
+}
+
+// GetVnetForNni to get vnet for nni port
+func GetVnetForNni(addr net.HardwareAddr, cvlan of.VlanType, svlan of.VlanType, pbit uint8) (*VoltPortVnet, error) {
+
+	var err error
+	var session IPppoeIaSession
+	logger.Infow(ctx, "Mac Obtained MAC: ", log.Fields{"Addr": addr})
+	if session, err = pppoeIaNws.GetPppoeIaSession(uint16(svlan), uint16(cvlan), addr); err != nil {
+		logger.Errorw(ctx, "PPPoE Session retrieval failed", log.Fields{"Error": err})
+		if err == ErrSessionDoNotExist {
+			logger.Info(ctx, "Finding matching VPV from packet")
+			vpvs, err1 := GetApplication().GetVpvsForDsPkt(cvlan, svlan, addr, pbit)
+			if len(vpvs) == 1 {
+				return vpvs[0], nil
+			}
+			return nil, err1
+		}
+		return nil, err
+	}
+
+	if session != nil {
+		vpv, ok := session.(*VoltPortVnet)
+
+		if ok {
+			logger.Infow(ctx, "Session Exist: VPV found", log.Fields{"VPV": vpv})
+			return vpv, nil
+		}
+	}
+	logger.Error(ctx, "PPPoE Session retrieved of wrong type")
+	return nil, errors.New("The session retrieved of wrong type")
+}
+
+// AddIaOption : Addition of PppoeIa Option 82 which codes circuit-id and remote-id
+// into the packet. This happens as the request is relayed to the
+// PppoeIa servers on the NNI
+func AddIaOption(svc *VoltService, pppoe *layers.PPPoE) {
+
+	//NOTE : both cID and rID should not be empty if this function is called
+	var data []byte
+	cID := svc.GetCircuitID()
+	rID := svc.RemoteID
+
+	if len(cID) != 0 || len(rID) != 0 || svc.isDataRateAttrPresent() {
+		data = append(data, DSLATTRVendorID...)
+	}
+
+	logger.Debugw(ctx, "Vendor Info", log.Fields{"Data": data})
+
+	if len(cID) != 0 {
+		data = append(data, TYPECIRCUITID)
+		data = append(data, byte(len(cID)))
+		data = append(data, cID...)
+	}
+	if len(rID) != 0 {
+		data = append(data, TYPEREMOTEID)
+		data = append(data, byte(len(rID)))
+		data = append(data, rID...)
+	}
+
+	if svc.isDataRateAttrPresent() {
+		minDrUs := util.Uint32ToByte(svc.MinDataRateUs)
+		data = append(data, TYPEMINDATAUS)
+		data = append(data, byte(len(minDrUs)))
+		data = append(data, minDrUs...)
+
+		minDrDs := util.Uint32ToByte(svc.MinDataRateDs)
+		data = append(data, TYPEMINDATADS)
+		data = append(data, byte(len(minDrDs)))
+		data = append(data, minDrDs...)
+
+		maxDrUs := util.Uint32ToByte(svc.MaxDataRateUs)
+		data = append(data, TYPEMAXDATAUS)
+		data = append(data, byte(len(maxDrUs)))
+		data = append(data, maxDrUs...)
+
+		maxDrDs := util.Uint32ToByte(svc.MaxDataRateDs)
+		data = append(data, TYPEMAXDATADS)
+		data = append(data, byte(len(maxDrDs)))
+		data = append(data, maxDrDs...)
+	}
+	option := layers.NewPPPoEOption(layers.PPPoEOptVendorSpecific, data)
+	pppoe.Options = append(pppoe.Options, option)
+}
+
+// DelIaOption for deletion of IA option from the packet received on the NNI interface.
+func DelIaOption(pppoe *layers.PPPoE) {
+	for index, option := range pppoe.Options {
+		if option.Type == layers.PPPoEOptVendorSpecific {
+			pppoe.Options = append(pppoe.Options[0:index], pppoe.Options[index+1:]...)
+			return
+		}
+	}
+}
+
+// ProcessDsPppoeIaPacket : This function processes DS PppoeIa packet received on the NNI port.
+// The services are attached to the access ports. Thus, the PppoeIa
+// session is derived from the list of PppoeIa sessions stored in the
+// common map. The key for retrieval includes the VLAN tags in the
+// the packet and the MAC address of the client.
+func (va *VoltApplication) ProcessDsPppoeIaPacket(device string, port string, pkt gopacket.Packet) {
+
+	// Retrieve the layers to build the outgoing packet. It is not
+	// possible to add/remove layers to the existing packet and thus
+	// the lyayers are extracted to build the outgoing packet
+	eth := pkt.Layer(layers.LayerTypeEthernet).(*layers.Ethernet)
+	pppoe := pkt.Layer(layers.LayerTypePPPoE).(*layers.PPPoE)
+
+	logger.Infow(ctx, "Processing Southbound DS PppoeIa packet", log.Fields{"Port": port, "Type": pppoe.Code})
+
+	// Retrieve the priority and drop eligible flags from the
+	// packet received
+	var priority uint8
+	var dropEligible bool
+	dot1ql := pkt.Layer(layers.LayerTypeDot1Q)
+	if dot1ql != nil {
+		dot1q := dot1ql.(*layers.Dot1Q)
+		priority = dot1q.Priority
+		dropEligible = dot1q.DropEligible
+	}
+
+	pktInnerlan, pktOuterlan := GetVlansFromPacket(pkt)
+	vpv, err := GetVnetForNni(eth.DstMAC, pktInnerlan, pktOuterlan, priority)
+	if err != nil {
+		logger.Errorw(ctx, "VNET couldn't be found for NNI", log.Fields{"Error": err})
+		return
+	}
+
+	// Do not modify pppoe header if vnet's mac_learning type is not PPPoE-IA.
+	if vpv.PppoeIa {
+		// Delete the IA option that may be included in the response
+		DelIaOption(pppoe)
+		if pppoe.Code == layers.PPPoECodePADO {
+			vpv.SetPppoeIaState(PppoeIaStatePADO)
+		} else if pppoe.Code == layers.PPPoECodePADS {
+			vpv.SetPppoeIaState(PppoeIaStatePADS)
+		} else if pppoe.Code == layers.PPPoECodePADT {
+			vpv.SetPppoeIaState(PppoeIaStatePADT)
+		}
+		vpv.WriteToDb()
+	}
+	// Create the outgoing bufer and set the checksum in the packet
+	buff := gopacket.NewSerializeBuffer()
+	opts := gopacket.SerializeOptions{
+		FixLengths:       true,
+		ComputeChecksums: true,
+	}
+
+	cTagType := layers.EthernetTypePPPoEDiscovery
+	eth.EthernetType = layers.EthernetTypeDot1Q
+	priority = vpv.GetRemarkedPriority(priority)
+
+	var pktLayers []gopacket.SerializableLayer
+	pktLayers = append(pktLayers, eth)
+
+	var qVlans []of.VlanType
+	var qVlanLayers []gopacket.SerializableLayer
+
+	if vpv.AllowTransparent {
+		vlanThreshold := 2
+		// In case of ONU_CVLAN or OLT_SVLAN, the DS pkts have single configured vlan
+		// In case of ONU_CVLAN_OLT_SVLAN or OLT_CVLAN_OLT_SVLAN, the DS pkts have 2 configured vlan
+		// Based on that, the no. of vlans should be ignored to get only transparent vlans
+		if vpv.VlanControl == ONUCVlan || vpv.VlanControl == OLTSVlan || vpv.VlanControl == None {
+			vlanThreshold = 1
+		}
+		nxtLayer := layers.EthernetTypeDot1Q
+		if vlans := GetVlans(pkt); len(vlans) > vlanThreshold {
+			qVlans = vlans[vlanThreshold:]
+			cTagType = layers.EthernetTypeDot1Q
+		}
+		for i, qVlan := range qVlans {
+			vlan := uint16(qVlan)
+			if i == (len(qVlans) - 1) {
+				nxtLayer = layers.EthernetTypePPPoEDiscovery
+			}
+			qdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: vlan, DropEligible: dropEligible, Type: nxtLayer}
+			qVlanLayers = append(qVlanLayers, qdot1q)
+		}
+	}
+
+	switch vpv.VlanControl {
+	case ONUCVlanOLTSVlan:
+		cdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: uint16(vpv.CVlan), DropEligible: dropEligible, Type: cTagType}
+		pktLayers = append(pktLayers, cdot1q)
+	case ONUCVlan,
+		None:
+		sdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: uint16(vpv.SVlan), DropEligible: dropEligible, Type: cTagType}
+		pktLayers = append(pktLayers, sdot1q)
+	case OLTCVlanOLTSVlan,
+		OLTSVlan:
+		udot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: uint16(vpv.UniVlan), DropEligible: dropEligible, Type: cTagType}
+		pktLayers = append(pktLayers, udot1q)
+	default:
+		logger.Errorw(ctx, "Invalid Vlan Control Option", log.Fields{"Value": vpv.VlanControl})
+		return
+	}
+
+	pktLayers = append(pktLayers, qVlanLayers...)
+	pktLayers = append(pktLayers, pppoe)
+
+	logger.Debugw(ctx, "Layers Count", log.Fields{"Count": len(pktLayers)})
+	if err := gopacket.SerializeMultiLayers(buff, opts, pktLayers); err != nil {
+		logger.Errorw(ctx, "Packet Serialization Failed", log.Fields{"Reason": err.Error()})
+		return
+	}
+
+	if err := cntlr.GetController().PacketOutReq(device, vpv.Port, port, buff.Bytes(), false); err != nil {
+		logger.Warnw(ctx, "PacketOutReq Failed", log.Fields{"Device": device, "Error": err})
+	}
+}
+
+// ProcessUsPppoeIaPacket : The US PppoeIa packet is identified the PppoeIa OP in the packet. A request is considered upstream
+// and the service associated with the packet is located by the port and VLANs in the packet
+func (va *VoltApplication) ProcessUsPppoeIaPacket(device string, port string, pkt gopacket.Packet) {
+	// We received the packet on an access port and the service for the packet can be
+	// gotten from the port and the packet
+	vpv, svc := va.GetVnetFromPkt(device, port, pkt)
+	if vpv == nil {
+		logger.Errorw(ctx, "VNET couldn't be found from packet", log.Fields{"Device": device, "Port": port})
+		return
+	}
+
+	outport, _ := va.GetNniPort(device)
+	if outport == "" || outport == "0" {
+		logger.Errorw(ctx, "NNI Port not found for device. Dropping Packet", log.Fields{"NNI": outport})
+		return
+	}
+
+	//Add PPPoE session for reference so that the DS pkts can be processed and re-directed
+	pppoeIaNws.AddPppoeIaSession(pkt, vpv)
+
+	// Extract the layers in the packet to prepare the outgoing packet
+	// We use the layers to build the outgoing packet from scratch as
+	// the packet received can't be modified to add/remove layers
+	eth := pkt.Layer(layers.LayerTypeEthernet).(*layers.Ethernet)
+	pppoe := pkt.Layer(layers.LayerTypePPPoE).(*layers.PPPoE)
+	msgType := pppoe.Code
+	logger.Infow(ctx, "Processing Southbound US PppoeIa packet", log.Fields{"Device": device, "Port": port, "Type": pppoe.Code})
+
+	AddIaOption(svc, pppoe)
+
+	// Learn the 8021P values from the packet received
+	var priority uint8
+	dropEligible := false
+	dot1ql := pkt.Layer(layers.LayerTypeDot1Q)
+	if dot1ql != nil {
+		dot1q := dot1ql.(*layers.Dot1Q)
+		priority = dot1q.Priority
+		dropEligible = dot1q.DropEligible
+	}
+
+	if vpv.PppoeIa {
+		//Maintain the session MAC as learnt MAC, since MAC is required for deletion of PPPoE session
+		if msgType == layers.PPPoECodePADI || msgType == layers.PPPoECodePADR {
+			if !util.MacAddrsMatch(vpv.MacAddr, eth.SrcMAC) {
+				expectedPort := va.GetMacInPortMap(eth.SrcMAC)
+				if expectedPort != "" && expectedPort != vpv.Port {
+					logger.Errorw(ctx, "mac-learnt-from-different-port-ignoring-pppoe-message",
+						log.Fields{"MsgType": msgType, "ExpectedPort": expectedPort, "ReceivedPort": vpv.Port, "LearntMacAdrr": vpv.MacAddr, "NewMacAdrr": eth.SrcMAC.String()})
+					return
+				}
+			}
+			vpv.SetMacAddr(eth.SrcMAC)
+		}
+
+		if pppoe.Code == layers.PPPoECodePADI {
+			vpv.SetPppoeIaState(PppoeIaStatePADI)
+		} else if pppoe.Code == layers.PPPoECodePADR {
+			vpv.SetPppoeIaState(PppoeIaStatePADR)
+		}
+		vpv.WriteToDb()
+	}
+
+	buff := gopacket.NewSerializeBuffer()
+	opts := gopacket.SerializeOptions{
+		FixLengths:       true,
+		ComputeChecksums: true,
+	}
+
+	cTagType := layers.EthernetTypePPPoEDiscovery
+	outerVlan, innerVlan := vpv.GetNniVlans()
+	logger.Debugw(ctx, "Vnet Vlans", log.Fields{"Svlan": outerVlan, "Cvlan": innerVlan})
+	eth.EthernetType = vpv.SVlanTpid
+
+	var pktLayers []gopacket.SerializableLayer
+	pktLayers = append(pktLayers, eth)
+
+	var qVlans []of.VlanType
+	var qVlanLayers []gopacket.SerializableLayer
+
+	if vpv.AllowTransparent {
+		nxtLayer := layers.EthernetTypeDot1Q
+		if vlans := GetVlans(pkt); len(vlans) > 1 {
+			qVlans = vlans[1:]
+			logger.Debugw(ctx, "Q Vlans", log.Fields{"Vlan List": qVlans})
+			cTagType = layers.EthernetTypeDot1Q
+		}
+		for i, qVlan := range qVlans {
+			vlan := uint16(qVlan)
+			if i == (len(qVlans) - 1) {
+				nxtLayer = layers.EthernetTypePPPoEDiscovery
+			}
+			qdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: vlan, DropEligible: dropEligible, Type: nxtLayer}
+			qVlanLayers = append(qVlanLayers, qdot1q)
+		}
+	}
+
+	switch vpv.VlanControl {
+	case ONUCVlanOLTSVlan,
+		OLTCVlanOLTSVlan:
+		sdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: outerVlan, DropEligible: dropEligible, Type: layers.EthernetTypeDot1Q}
+		pktLayers = append(pktLayers, sdot1q)
+		cdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: innerVlan, DropEligible: dropEligible, Type: cTagType}
+		pktLayers = append(pktLayers, cdot1q)
+	case ONUCVlan,
+		OLTSVlan,
+		None:
+		cdot1q := &layers.Dot1Q{Priority: priority, VLANIdentifier: outerVlan, DropEligible: dropEligible, Type: cTagType}
+		pktLayers = append(pktLayers, cdot1q)
+	default:
+		logger.Errorw(ctx, "Invalid Vlan Control Option", log.Fields{"Value": vpv.VlanControl})
+		return
+	}
+
+	pktLayers = append(pktLayers, qVlanLayers...)
+	pktLayers = append(pktLayers, pppoe)
+	logger.Debugw(ctx, "Layers Count", log.Fields{"Count": len(pktLayers)})
+	if err := gopacket.SerializeMultiLayers(buff, opts, pktLayers); err != nil {
+		return
+	}
+
+	// Now the packet constructed is output towards the switch to be emitted on
+	// the NNI port
+	if err := cntlr.GetController().PacketOutReq(device, outport, port, buff.Bytes(), false); err != nil {
+		logger.Warnw(ctx, "PacketOutReq Failed", log.Fields{"Device": device, "Error": err})
+	}
+
+}
+
+// ProcessPPPoEIaPacket to process Pppoeia packet
+func (va *VoltApplication) ProcessPPPoEIaPacket(device string, port string, pkt gopacket.Packet) {
+	// Make some error checks before proceeding
+	pppoel := pkt.Layer(layers.LayerTypePPPoE)
+	if pppoel == nil {
+		return
+	}
+	_, ok := pppoel.(*layers.PPPoE)
+	if !ok {
+		return
+	}
+
+	// Let us assess the direction of the packet. We can do so by the port
+	// which is more reliable or do by the PPPoE code which is less reliable
+	isUs := true
+	if nni, _ := GetApplication().GetNniPort(device); nni == port {
+		isUs = false
+	}
+
+	// This is a valid PPPoE packet and can be processed
+	if isUs {
+		// This is treated as an upstream packet in the VOLT application
+		// as VOLT serves access subscribers who use DHCP to acquire IP
+		// address and these packets go upstream to the network
+		va.ProcessUsPppoeIaPacket(device, port, pkt)
+	} else {
+		// This is a downstream packet
+		va.ProcessDsPppoeIaPacket(device, port, pkt)
+	}
+}
+
+// ProcessPPPoEPacket to process Pppoe packet
+func (va *VoltApplication) ProcessPPPoEPacket(device string, port string, pkt gopacket.Packet) {
+	dpt := NewPppoeIaPacketTask(pkt, device, port)
+	va.pppoeTasks.AddTask(dpt)
+}
+
+// pppoeIaNws : The DHCP relay application is maintained within the structures below
+var pppoeIaNws *PppoeIaNetworks
+
+func init() {
+	pppoeIaNws = NewPppoeIaNetworks()
+	RegisterPacketHandler(PPPOE, ProcessPPPoEPacket)
+}
+
+// ProcessPPPoEPacket : CallBack function registered with application to handle PPPoE packetIn
+func ProcessPPPoEPacket(device string, port string, pkt gopacket.Packet) {
+	GetApplication().ProcessPPPoEPacket(device, port, pkt)
+}
+
+// PppoeIaPacketTask : Task to add or delete flows of a service
+type PppoeIaPacketTask struct {
+	taskID    uint8
+	ctx       context.Context
+	pkt       gopacket.Packet
+	device    string
+	port      string
+	timestamp string
+}
+
+// NewPppoeIaPacketTask constructor for PppoeIaPacketTask
+func NewPppoeIaPacketTask(pkt gopacket.Packet, dev string, port string) *PppoeIaPacketTask {
+	var dpt PppoeIaPacketTask
+	dpt.pkt = pkt
+	dpt.device = dev
+	dpt.port = port
+	dpt.timestamp = (time.Now()).Format(time.RFC3339Nano)
+	return &dpt
+}
+
+// Name to return name for PppoeIaPacketTask
+func (dpt *PppoeIaPacketTask) Name() string {
+	return "DHCP Packet Task"
+}
+
+// TaskID to return task id for PppoeIaPacketTask
+func (dpt *PppoeIaPacketTask) TaskID() uint8 {
+	return dpt.taskID
+}
+
+// Timestamp to return timestamp for PppoeIaPacketTask
+func (dpt *PppoeIaPacketTask) Timestamp() string {
+	return dpt.timestamp
+}
+
+// Stop to stop the PppoeIaPacketTask
+func (dpt *PppoeIaPacketTask) Stop() {
+}
+
+// Start to start PppoeIaPacketTask
+func (dpt *PppoeIaPacketTask) Start(ctx context.Context, taskID uint8) error {
+	dpt.taskID = taskID
+	dpt.ctx = ctx
+	GetApplication().ProcessPPPoEIaPacket(dpt.device, dpt.port, dpt.pkt)
+	return nil
+}