blob: 2d50033703e4edad87e96c8e0413d99ff9d7bb2f [file] [log] [blame]
/*
* 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"
"voltha-go-controller/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(context.Context, 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(cntx context.Context, 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(cntx)
}
// 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(cntx context.Context, 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(cntx, eth.SrcMAC)
}
if pppoe.Code == layers.PPPoECodePADI {
vpv.SetPppoeIaState(PppoeIaStatePADI)
} else if pppoe.Code == layers.PPPoECodePADR {
vpv.SetPppoeIaState(PppoeIaStatePADR)
}
vpv.WriteToDb(cntx)
}
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(cntx context.Context, 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(cntx, device, port, pkt)
} else {
// This is a downstream packet
va.ProcessDsPppoeIaPacket(cntx, 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(cntx context.Context, 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(ctx, dpt.device, dpt.port, dpt.pkt)
return nil
}