blob: 354db3cca45db9902633fc453b646c92cee63a1b [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"
"encoding/hex"
"errors"
"net"
"sync"
"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"
)
// 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(context.Context, net.HardwareAddr)
DhcpResultInd(context.Context, *layers.DHCPv4)
Dhcpv6ResultInd(cntx context.Context, 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 {
sessions map[[6]byte]IDhcpRelaySession
sessionsv6 map[[MaxLenDhcpv6DUID]byte]IDhcpRelaySession
sessionLock sync.RWMutex
OuterVlan uint16
InnerVlan uint16
}
// 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 {
logger.Debugw(ctx, "Get Dhcp Vnet", log.Fields{"OuterVlan": outerVlan, "InnerVlan": innerVlan})
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 {
logger.Debugw(ctx, "Add Dhcp Vnet", log.Fields{"OuterVlan": outerVlan, "InnerVlan": innerVlan})
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 {
logger.Info(ctx, "Add Dhcp Session")
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) {
logger.Info(ctx, "Delete Dhcp Session")
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) {
logger.Debugw(ctx, "Delete Dhcp Sessions", log.Fields{"Addr": addr, "OuterVlan": outervlan, "InnerVlan": innervlan})
var key [6]byte
if addr == nil || !NonZeroMacAddress(addr) {
logger.Warnw(ctx, "Invalid MAC address", log.Fields{"Addr": addr, "OuterVlan": outervlan, "InnerVlan": innervlan})
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.Debugw(ctx, "Adding Dhcp6 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.Debugw(ctx, "Delete Dhcp6 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) {
logger.Debugw(ctx, "Get Dhcp Session", log.Fields{"OuterVLAN": outerVlan, "InnerVLAN": innerVlan, "Addr": addr})
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.Debugw(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) {
logger.Debugw(ctx, "Get Vlans From Packet", log.Fields{"OuterVlan": outerVlan, "InnerVlan": innerVlan})
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.Debugw(ctx, "Get Vnet For V4 Nni: ", 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.Warn(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
logger.Info(ctx, "Get Vnet For V6 Nni")
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) {
logger.Info(ctx, "Get Dhcp v6 Client DUID")
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.Warnw(ctx, "Client DUID decode failed", log.Fields{"error": err})
break
}
}
logger.Warn(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) {
logger.Debugw(ctx, "Add Dhcp v4 Option82", log.Fields{"Addr": dhcpv4.ClientHWAddr})
//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) {
logger.Debugw(ctx, "Delete Dhcp v4 Option82", log.Fields{"Addr": dhcpv4.ClientHWAddr})
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 {
logger.Debugw(ctx, "Dhcp msg type", log.Fields{"Addr": dhcp.ClientHWAddr})
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) {
logger.Debugw(ctx, "Get Dhcp ipv4 addr", log.Fields{"Addr": dhcp.ClientHWAddr})
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) {
logger.Debugw(ctx, "Get Dhcp ipv6 addr", log.Fields{"Addr": dhcp6.MsgType})
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) {
logger.Debugw(ctx, "Get Dhcp IANA addr", log.Fields{"Addr": dhcp6.MsgType})
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.Warnw(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) {
logger.Debugw(ctx, "Get Dhcp IAPD addr", log.Fields{"Addr": dhcp6.MsgType})
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.Warnw(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(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)
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(cntx, dhcp4.ClientHWAddr)
}
vpv.DhcpResultInd(cntx, 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.Errorw(ctx, "Error in setting checksum", log.Fields{"Reason": err.Error()})
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.Warnw(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(cntx context.Context, device string, port string, pkt gopacket.Packet) {
logger.Infow(ctx, "Processing Southbound US DHCPv4 packet", log.Fields{"Device": device, "Port": port})
// 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)
// 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(cntx)
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 IsOption82Enabled flag in configuration. if true(enabled), 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.IsOption82Enabled {
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(cntx context.Context, device string, port string, pkt gopacket.Packet) {
GetApplication().ProcessUDP4Packet(cntx, 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(cntx context.Context, device string, port string, pkt gopacket.Packet) {
// Currently DHCP is the only application supported by the application
// We check for DHCP before proceeding further. In future, this could be
// based on registration and the callbacks
logger.Debugw(ctx, "Process UDP4 Packet", log.Fields{"Device": device, "Port": port})
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(cntx, device, port, pkt)
} else {
// This is a downstream packet
va.ProcessDsDhcpv4Packet(cntx, device, port, pkt)
}
}
// ProcessUDP6Packet : CallBack function registered with application to handle DHCPv6 packetIn
func ProcessUDP6Packet(cntx context.Context, device string, port string, pkt gopacket.Packet) {
GetApplication().ProcessUDP6Packet(cntx, 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(cntx context.Context, device string, port string, pkt gopacket.Packet) []byte {
logger.Debugw(ctx, "Processing DHCPv6 packet", log.Fields{"Device": device, "Port": port})
dhcpl := pkt.Layer(layers.LayerTypeDHCPv6)
if dhcpl == nil {
return nil
}
dhcpv6 := dhcpl.(*layers.DHCPv6)
switch dhcpv6.MsgType {
case layers.DHCPv6MsgTypeSolicit, layers.DHCPv6MsgTypeRequest, layers.DHCPv6MsgTypeRenew,
layers.DHCPv6MsgTypeRelease, layers.DHCPv6MsgTypeRebind, layers.DHCPv6MsgTypeInformationRequest,
layers.DHCPv6MsgTypeDecline:
va.ProcessUsDhcpv6Packet(cntx, 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(cntx, device, port, pkt)
}
return nil
}
// GetRelayReplyBytes to get relay reply bytes
func GetRelayReplyBytes(dhcp6 *layers.DHCPv6) []byte {
for _, o := range dhcp6.Options {
logger.Debugw(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, isOption82Enabled bool, dhcpRelay bool) *layers.DHCPv6 {
logger.Debugw(ctx, "Build Relay Fwd", log.Fields{"Paddr": paddr, "isOption82Enabled": isOption82Enabled, "dhcpRelay": dhcpRelay})
dhcp6 := &layers.DHCPv6{MsgType: layers.DHCPv6MsgTypeRelayForward, LinkAddr: net.ParseIP("::"), PeerAddr: []byte(paddr)}
dhcp6.Options = append(dhcp6.Options, layers.NewDHCPv6Option(layers.DHCPv6OptRelayMessage, payload))
// Check IsOption82Enabled flag in configuration. if true(enabled), add remoteID and circuitID into dhcpv6 header.
if dhcpRelay {
if isOption82Enabled {
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
}
// nolint: gocyclo
// ProcessUsDhcpv6Packet to rpocess upstream DHCPv6 packet
func (va *VoltApplication) ProcessUsDhcpv6Packet(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
logger.Infow(ctx, "Processing Southbound US DHCPv6 packet", log.Fields{"Device": device, "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.IsOption82Enabled, 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(cntx)
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(cntx context.Context, 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(cntx, clientMac)
}
vpv.Dhcpv6ResultInd(cntx, 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()
}
type DhcpAllocation struct {
SubscriberID string `json:"subscriberId"`
ConnectPoint string `json:"connectPoint"`
AllocationTimeStamp time.Time `json:"allocationTimestamp"`
MacAddress net.HardwareAddr `json:"macAddress"`
CircuitID []byte `json:"circuitId"`
IPAllocated net.IP `json:"ipAllocated"`
State int `json:"state"`
VlanID int `json:"vlanId"`
}
// GetAllocations returns DhcpAllocation info for all devices or for a device ID
func (va *VoltApplication) GetAllocations(cntx context.Context, deviceID string) ([]DhcpAllocation, error) {
logger.Debugw(ctx, "GetAllocations", log.Fields{"DeviceID": deviceID})
allocations := []DhcpAllocation{}
for _, drv := range dhcpNws.Networks {
drv.sessionLock.RLock()
for _, session := range drv.sessions {
vpv, ok := session.(*VoltPortVnet)
if ok {
var subscriber string
// return Name of first service
vpv.services.Range(func(key, value interface{}) bool {
svc := value.(*VoltService)
subscriber = svc.Name
return false
})
// If deviceID is not provided, return all allocations
// If deviceID exists then filter on deviceID
if len(deviceID) == 0 || deviceID == vpv.Device {
allocation := DhcpAllocation{
SubscriberID: subscriber,
ConnectPoint: vpv.Device,
MacAddress: vpv.MacAddr,
State: int(vpv.RelayState),
VlanID: int(vpv.SVlan),
CircuitID: vpv.CircuitID,
IPAllocated: vpv.Ipv4Addr,
AllocationTimeStamp: vpv.DhcpExpiryTime,
}
logger.Debugw(ctx, "DHCP Allocation found", log.Fields{"DhcpAlloc": allocation})
allocations = append(allocations, allocation)
}
}
}
drv.sessionLock.RUnlock()
}
return allocations, nil
}
type MacLearnerInfo struct {
DeviceID string `json:"deviceId"`
PortNumber string `json:"portNumber"`
VlanID string `json:"vlanId"`
MacAddress string `json:"macAddress"`
}
func (va *VoltApplication) GetAllMacLearnerInfo() ([]MacLearnerInfo, error) {
logger.Info(ctx, "GetMacLearnerInfo")
macLearner := []MacLearnerInfo{}
for _, drv := range dhcpNws.Networks {
logger.Debugw(ctx, "drv found", log.Fields{"drv": drv})
drv.sessionLock.RLock()
for _, session := range drv.sessions {
vpv, ok := session.(*VoltPortVnet)
if ok {
macLearn := MacLearnerInfo{
DeviceID: vpv.Device,
PortNumber: vpv.Port,
VlanID: vpv.SVlan.String(),
MacAddress: vpv.MacAddr.String(),
}
logger.Debugw(ctx, "MacLerner found", log.Fields{"MacLearn": macLearn})
macLearner = append(macLearner, macLearn)
}
}
drv.sessionLock.RUnlock()
}
return macLearner, nil
}
func (va *VoltApplication) GetMacLearnerInfo(cntx context.Context, deviceID, portNumber, vlanID string) (MacLearnerInfo, error) {
logger.Debugw(ctx, "GetMecLearnerInfo", log.Fields{"DeviceID": deviceID, "PortNumber": portNumber, "VlanID": vlanID})
macLearn := MacLearnerInfo{}
for _, drv := range dhcpNws.Networks {
logger.Debugw(ctx, "drv found", log.Fields{"drv": drv})
drv.sessionLock.RLock()
for _, session := range drv.sessions {
vpv, ok := session.(*VoltPortVnet)
if ok {
if deviceID == vpv.Device && portNumber == vpv.Port && vlanID == vpv.SVlan.String() {
macLearn = MacLearnerInfo{
DeviceID: vpv.Device,
PortNumber: vpv.Port,
VlanID: vpv.SVlan.String(),
MacAddress: vpv.MacAddr.String(),
}
logger.Debugw(ctx, "MacLerner found", log.Fields{"MacLearn": macLearn})
} else if deviceID == vpv.Device && portNumber == vpv.Port && vlanID == "" {
macLearn = MacLearnerInfo{
DeviceID: vpv.Device,
PortNumber: vpv.Port,
VlanID: vpv.SVlan.String(),
MacAddress: vpv.MacAddr.String(),
}
logger.Debugw(ctx, "MacLerner found", log.Fields{"MacLearn": macLearn})
}
}
}
drv.sessionLock.RUnlock()
}
return macLearn, nil
}
func (va *VoltApplication) GetIgnoredPorts() (map[string][]string, error) {
logger.Info(ctx, "GetIgnoredPorts")
IgnoredPorts := make(map[string][]string)
portIgnored := func(key, value interface{}) bool {
voltDevice := value.(*VoltDevice)
logger.Debugw(ctx, "Inside GetIgnoredPorts method", log.Fields{"deviceName": voltDevice.Name})
voltDevice.Ports.Range(func(key, value interface{}) bool {
port := key.(string)
logger.Debugw(ctx, "Inside GetIgnoredPorts method", log.Fields{"port": port})
//Obtain all VPVs associated with the port
vnets, ok := GetApplication().VnetsByPort.Load(port)
if !ok {
return true
}
for _, vpv := range vnets.([]*VoltPortVnet) {
if vpv.MacLearning == MacLearningNone {
IgnoredPorts[vpv.Device] = append(IgnoredPorts[vpv.Device], vpv.Port)
}
}
logger.Warnw(ctx, "Ignored Port", log.Fields{"Ignored Port": IgnoredPorts})
return true
})
return true
}
va.DevicesDisc.Range(portIgnored)
logger.Debug(ctx, "GetIgnoredPorts completed")
return IgnoredPorts, nil
}