blob: a1931f8e785507e4d05713cdafe084515d98b637 [file] [log] [blame]
/*
* Copyright 2017-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 core
import (
"context"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap"
"github.com/opencord/voltha/ponsim/v2/common"
"github.com/opencord/voltha/protos/go/openflow_13"
"github.com/sirupsen/logrus"
"net"
"sort"
)
// TODO: Pass-in the certificate information as a structure parameter
// TODO: Add certification information
type PonSimDevice struct {
Name string `json:name`
Port int32 `json:port`
Address string `json:address`
ExternalIf string `json:external_if`
InternalIf string `json:internal_if`
Promiscuous bool `json:promiscuous`
SnapshotLen int32 `json:snapshot_len`
AlarmsOn bool `json:alarm_on`
AlarmsFreq int `json:alarm_freq`
Counter *PonSimMetricCounter `json:counter`
//*grpc.GrpcSecurity
flows []*openflow_13.OfpFlowStats `json:-`
ingressHandler *pcap.Handle `json:-`
egressHandler *pcap.Handle `json:-`
links map[int]map[int]interface{} `json:-`
}
const (
UDP_DST = 1
UDP_SRC = 2
IPV4_DST = 4
VLAN_PCP = 8
VLAN_VID = 16
IP_PROTO = 32
ETH_TYPE = 64
IN_PORT = 128
)
/*
Start performs common setup operations for a ponsim device
*/
func (o *PonSimDevice) Start(ctx context.Context) {
}
/*
Stop performs common cleanup operations for a ponsim device
*/
func (o *PonSimDevice) Stop(ctx context.Context) {
}
/*
GetAddress returns the IP/FQDN for the device
*/
func (o *PonSimDevice) GetAddress() string {
return o.Address
}
/*
GetPort return the port assigned to the device
*/
func (o *PonSimDevice) GetPort() int32 {
return o.Port
}
/*
Forward is responsible of processing incoming data, filtering it and redirecting to the
intended destination
*/
func (o *PonSimDevice) Forward(
ctx context.Context,
port int,
frame gopacket.Packet,
) error {
common.Logger().WithFields(logrus.Fields{
"device": o,
"port": port,
"frame": frame,
}).Debug("Forwarding packet")
var err error
o.Counter.CountRxFrame(port, len(common.GetEthernetLayer(frame).Payload))
if egressPort, egressFrame := o.processFrame(ctx, port, frame); egressFrame != nil {
forwarded := 0
links := o.links[int(egressPort)]
o.Counter.CountTxFrame(int(egressPort), len(common.GetEthernetLayer(egressFrame).Payload))
for _, link := range links {
forwarded += 1
common.Logger().WithFields(logrus.Fields{
"device": o,
"egressPort": port,
"egressFrame": egressFrame,
}).Debug("Forwarding packet to link")
link.(func(int, gopacket.Packet))(int(egressPort), egressFrame)
}
if forwarded == 0 {
common.Logger().WithFields(logrus.Fields{
"device": o,
"port": port,
"frame": frame,
}).Warn("Nothing was forwarded")
}
} else {
common.Logger().WithFields(logrus.Fields{
"device": o,
"port": egressPort,
"frame": egressFrame,
}).Error("Failed to properly process frame")
}
return err
}
/*
connectNetworkInterfaces opens network interfaces for reading and/or writing packets
*/
func (o *PonSimDevice) connectNetworkInterfaces() {
common.Logger().WithFields(logrus.Fields{
"device": o,
}).Debug("Opening network interfaces")
var err error
if o.ingressHandler, err = pcap.OpenLive(
o.ExternalIf, o.SnapshotLen, o.Promiscuous, pcap.BlockForever,
); err != nil {
common.Logger().WithFields(logrus.Fields{
"device": o,
"interface": o.ExternalIf,
"error": err.Error(),
}).Fatal("Unable to open Ingress interface")
} else {
common.Logger().WithFields(logrus.Fields{
"device": o,
"interface": o.ExternalIf,
}).Info("Opened Ingress interface")
}
if o.egressHandler, err = pcap.OpenLive(
o.InternalIf, o.SnapshotLen, o.Promiscuous, pcap.BlockForever,
); err != nil {
common.Logger().WithFields(logrus.Fields{
"device": o,
"interface": o.InternalIf,
"error": err.Error(),
}).Fatal("Unable to open egress interface")
} else {
common.Logger().WithFields(logrus.Fields{
"device": o,
"interface": o.InternalIf,
}).Info("Opened egress interface")
}
}
/*
AddLink assigns a functional operation to a device endpoint
The functional operation is called whenever a packet has been processed
and the endpoint has been identified as the outgoing interface
*/
func (o *PonSimDevice) AddLink(
port int,
index int,
function interface{},
) error {
common.Logger().WithFields(logrus.Fields{
"device": o,
"port": port,
"index": index,
}).Debug("Linking port to functional operation")
if o.links == nil {
o.links = make(map[int]map[int]interface{})
}
if _, ok := o.links[port]; !ok {
o.links[port] = make(map[int]interface{})
}
o.links[port][index] = function
return nil
}
/*
RemoveLink will remove reference a functional operation for a given port and index
*/
func (o *PonSimDevice) RemoveLink(
port int,
index int,
) error {
if _, hasPort := o.links[port]; hasPort {
if _, hasIndex := o.links[port][index]; hasIndex {
common.Logger().WithFields(logrus.Fields{
"device": o,
"port": port,
"index": index,
}).Debug("Removing link functional operation")
delete(o.links[port], index)
} else {
common.Logger().WithFields(logrus.Fields{
"device": o,
"port": port,
"index": index,
}).Warn("No such index for link functional operation")
}
} else {
common.Logger().WithFields(logrus.Fields{
"device": o,
"port": port,
"index": index,
}).Warn("No such port for functional operation")
}
return nil
}
/*
InstallFlows assigns flows to the device in order of priority
*/
func (o *PonSimDevice) InstallFlows(
ctx context.Context,
flows []*openflow_13.OfpFlowStats,
) error {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flows": flows,
}).Debug("Installing flows")
o.flows = flows
sort.Sort(common.SortByPriority(o.flows))
common.Logger().WithFields(logrus.Fields{
"device": o,
}).Debug("Installed sorted flows")
return nil
}
/*
processFrame is responsible for matching or discarding a frame based on the configured flows
*/
func (o *PonSimDevice) processFrame(
ctx context.Context,
port int,
frame gopacket.Packet,
) (uint32, gopacket.Packet) {
common.Logger().WithFields(logrus.Fields{
"device": o,
"port": port,
"frame": frame,
}).Debug("Processing frame")
var err error
var matchedMask int = 0
var currentMask int
var highestPriority uint32 = 0
var matchedFlow *openflow_13.OfpFlowStats = nil
common.Logger().WithFields(logrus.Fields{
"device": o,
}).Debug("Looping through flows")
for _, flow := range o.flows {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
}).Debug("Checking flow")
if matchedFlow != nil && flow.Priority < highestPriority {
common.Logger().WithFields(logrus.Fields{
"device": o,
"matchedFlow": matchedFlow,
"priority": highestPriority,
}).Debug("Flow has already been matched")
break
} else {
common.Logger().WithFields(logrus.Fields{
"device": o,
"matchedFlow": matchedFlow,
"priority": flow.Priority,
"highestPriority": highestPriority,
}).Debug("Flow OR Priority requirements not met")
}
highestPriority = flow.Priority
if currentMask, err = o.isMatch(ctx, flow, port, frame); err != nil {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"port": port,
"frame": frame,
"error": err.Error(),
}).Error("Problem while matching flow")
} else if currentMask > matchedMask {
matchedMask = currentMask
matchedFlow = flow
common.Logger().WithFields(logrus.Fields{
"device": o,
"matchedFlow": flow,
"port": port,
"frame": frame,
"matchedMask": matchedMask,
}).Debug("Flow matches")
}
}
if matchedFlow != nil {
egressPort, egressFrame := o.processActions(ctx, matchedFlow, frame)
common.Logger().WithFields(logrus.Fields{
"device": o,
"port": port,
"egressPort": egressPort,
"egressFrame": egressFrame,
}).Debug("Processed actions to matched flow")
return egressPort, egressFrame
} else {
common.Logger().WithFields(logrus.Fields{
"device": o,
"port": port,
"frame": frame,
"matchedMask": matchedMask,
}).Warn("Flow was not successfully matched")
}
return 0, nil
}
/*
isMatch traverses the criteria of a flow and identify all matching elements of a frame (if any)
*/
func (o *PonSimDevice) isMatch(
ctx context.Context,
flow *openflow_13.OfpFlowStats,
port int,
frame gopacket.Packet,
) (int, error) {
matchedMask := 0
for _, ofbfield := range flow.Match.OxmFields {
if ofbfield.GetOxmClass() == openflow_13.OfpOxmClass_OFPXMC_OPENFLOW_BASIC {
switch ofbfield.GetOfbField().Type {
case openflow_13.OxmOfbFieldTypes_OFPXMT_OFB_IN_PORT:
if ofbfield.GetOfbField().GetPort() != uint32(port) {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"expected": ofbfield.GetOfbField().GetPort(),
"actual": port,
}).Warn("Port does not match")
return 0, nil
} else {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"expected": ofbfield.GetOfbField().GetPort(),
"actual": port,
}).Debug("Port matches")
}
matchedMask |= IN_PORT
case openflow_13.OxmOfbFieldTypes_OFPXMT_OFB_ETH_TYPE:
cmpType := uint32(common.GetEthernetLayer(frame).EthernetType)
if dot1q := common.GetDot1QLayer(frame); dot1q != nil {
cmpType = uint32(dot1q.Type)
}
if ofbfield.GetOfbField().GetEthType() != cmpType {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"expected": layers.EthernetType(ofbfield.GetOfbField().GetEthType()),
"actual": common.GetEthernetLayer(frame).EthernetType,
}).Warn("Frame type does not match")
return 0, nil
} else {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"expected": layers.EthernetType(ofbfield.GetOfbField().GetEthType()),
"actual": common.GetEthernetLayer(frame).EthernetType,
}).Debug("Frame type matches")
}
matchedMask |= ETH_TYPE
case openflow_13.OxmOfbFieldTypes_OFPXMT_OFB_IP_PROTO:
if ofbfield.GetOfbField().GetIpProto() != uint32(common.GetIpLayer(frame).Protocol) {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"expected": ofbfield.GetOfbField().GetIpProto(),
"actual": common.GetIpLayer(frame).Protocol,
}).Warn("IP protocol does not match")
return 0, nil
} else {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"expected": ofbfield.GetOfbField().GetIpProto(),
"actual": common.GetIpLayer(frame).Protocol,
}).Debug("IP protocol matches")
}
matchedMask |= IP_PROTO
case openflow_13.OxmOfbFieldTypes_OFPXMT_OFB_VLAN_VID:
expectedVlan := ofbfield.GetOfbField().GetVlanVid()
dot1q := common.GetDot1QLayer(frame)
if (expectedVlan&4096 == 0) != (dot1q == nil) {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"expectedVlan": expectedVlan,
"vlanBitwise": expectedVlan & 4096,
"dot1q": dot1q,
}).Warn("VLAN condition not met")
return 0, nil
}
if dot1q != nil {
if uint32(dot1q.VLANIdentifier) != (expectedVlan & 4095) {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"expected": expectedVlan,
"actual": uint32(dot1q.VLANIdentifier),
}).Warn("VLAN VID does not match")
return 0, nil
} else {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"expected": expectedVlan,
"actual": uint32(dot1q.VLANIdentifier),
}).Debug("VLAN VID matches")
}
} else {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
}).Warn("VLAN VID missing. Not dot1q encapsulation")
}
matchedMask |= VLAN_VID
case openflow_13.OxmOfbFieldTypes_OFPXMT_OFB_VLAN_PCP:
if ofbfield.GetOfbField().GetVlanPcp() != uint32(common.GetDot1QLayer(frame).Priority) {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"expected": ofbfield.GetOfbField().GetVlanPcp(),
"actual": uint32(common.GetDot1QLayer(frame).Priority),
}).Warn("VLAN priority does not match")
return 0, nil
} else {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"expected": ofbfield.GetOfbField().GetVlanPcp(),
"actual": uint32(common.GetDot1QLayer(frame).Priority),
}).Debug("VLAN priority matches")
}
matchedMask |= VLAN_PCP
case openflow_13.OxmOfbFieldTypes_OFPXMT_OFB_IPV4_DST:
dstIpRaw := ofbfield.GetOfbField().GetIpv4Dst()
dstIp := net.IPv4(
byte((dstIpRaw>>24)&0xFF),
byte((dstIpRaw>>16)&0xFF),
byte((dstIpRaw>>8)&0xFF),
byte(dstIpRaw&0xFF))
if !dstIp.Equal(common.GetIpLayer(frame).DstIP) {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"expected": dstIp,
"actual": common.GetIpLayer(frame).DstIP,
}).Warn("IPv4 destination does not match")
return 0, nil
} else {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"expected": dstIp,
"actual": common.GetIpLayer(frame).DstIP,
}).Debug("IPv4 destination matches")
}
matchedMask |= IPV4_DST
case openflow_13.OxmOfbFieldTypes_OFPXMT_OFB_UDP_SRC:
if ofbfield.GetOfbField().GetUdpSrc() != uint32(common.GetUdpLayer(frame).SrcPort) {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"expected": ofbfield.GetOfbField().GetUdpSrc(),
"actual": common.GetUdpLayer(frame).SrcPort,
}).Warn("UDP source port does not match")
return 0, nil
} else {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"expected": ofbfield.GetOfbField().GetUdpSrc(),
"actual": common.GetUdpLayer(frame).SrcPort,
}).Debug("UDP source port matches")
}
matchedMask |= UDP_SRC
case openflow_13.OxmOfbFieldTypes_OFPXMT_OFB_UDP_DST:
if ofbfield.GetOfbField().GetUdpDst() != uint32(common.GetUdpLayer(frame).DstPort) {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"expected": ofbfield.GetOfbField().GetUdpDst(),
"actual": common.GetUdpLayer(frame).DstPort,
}).Warn("UDP destination port does not match")
return 0, nil
} else {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"expected": ofbfield.GetOfbField().GetUdpDst(),
"actual": common.GetUdpLayer(frame).DstPort,
}).Debug("UDP destination port does matches")
}
matchedMask |= UDP_DST
case openflow_13.OxmOfbFieldTypes_OFPXMT_OFB_METADATA:
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
}).Warn("Skipping metadata")
continue
default:
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"type": ofbfield.GetOfbField().Type,
}).Warn("Field type not implemented")
}
}
}
return matchedMask, nil
}
/*
processActions applies transformation instructions to a frame that met all the flow criteria
*/
func (o *PonSimDevice) processActions(
ctx context.Context,
flow *openflow_13.OfpFlowStats,
frame gopacket.Packet,
) (uint32, gopacket.Packet) {
var egressPort uint32
var retFrame gopacket.Packet = frame
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"frame": retFrame,
}).Info("Processing actions")
for _, instruction := range flow.Instructions {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"frame": retFrame,
"instruction": instruction,
}).Debug("Processing actions - Instruction entry")
if instruction.Type == uint32(openflow_13.OfpInstructionType_OFPIT_APPLY_ACTIONS) {
for _, action := range instruction.GetActions().GetActions() {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"frame": retFrame,
"action": action,
"actionType": action.Type,
}).Debug("Processing actions - Action entry")
switch action.Type {
case openflow_13.OfpActionType_OFPAT_OUTPUT:
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"frame": retFrame,
}).Debug("Processing action OFPAT output")
egressPort = action.GetOutput().Port
case openflow_13.OfpActionType_OFPAT_POP_VLAN:
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"frame": retFrame,
}).Debug("Processing action OFPAT POP VLAN")
if shim := common.GetDot1QLayer(retFrame); shim != nil {
if eth := common.GetEthernetLayer(retFrame); eth != nil {
ethernetLayer := &layers.Ethernet{
SrcMAC: eth.SrcMAC,
DstMAC: eth.DstMAC,
EthernetType: shim.Type,
}
buffer := gopacket.NewSerializeBuffer()
gopacket.SerializeLayers(buffer, gopacket.SerializeOptions{},
ethernetLayer,
gopacket.Payload(shim.Payload),
)
retFrame = gopacket.NewPacket(
buffer.Bytes(),
layers.LayerTypeEthernet,
gopacket.Default,
)
} else {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"frame": retFrame,
}).Warn("No ETH found while processing POP VLAN action")
}
} else {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"frame": retFrame,
}).Warn("No DOT1Q found while processing POP VLAN action")
}
case openflow_13.OfpActionType_OFPAT_PUSH_VLAN:
if eth := common.GetEthernetLayer(retFrame); eth != nil {
ethernetLayer := &layers.Ethernet{
SrcMAC: eth.SrcMAC,
DstMAC: eth.DstMAC,
EthernetType: layers.EthernetType(action.GetPush().GetEthertype()),
}
dot1qLayer := &layers.Dot1Q{
Type: eth.EthernetType,
}
buffer := gopacket.NewSerializeBuffer()
gopacket.SerializeLayers(
buffer,
gopacket.SerializeOptions{
FixLengths: false,
},
ethernetLayer,
dot1qLayer,
gopacket.Payload(eth.Payload),
)
retFrame = gopacket.NewPacket(
buffer.Bytes(),
layers.LayerTypeEthernet,
gopacket.Default,
)
} else {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"frame": retFrame,
}).Warn("No ETH found while processing PUSH VLAN action")
}
case openflow_13.OfpActionType_OFPAT_SET_FIELD:
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"frame": retFrame,
}).Debug("Processing action OFPAT SET FIELD")
if action.GetSetField().GetField().GetOxmClass() ==
openflow_13.OfpOxmClass_OFPXMC_OPENFLOW_BASIC {
field := action.GetSetField().GetField().GetOfbField()
switch field.Type {
case openflow_13.OxmOfbFieldTypes_OFPXMT_OFB_VLAN_VID:
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"frame": retFrame,
}).Debug("Processing action OFPAT SET FIELD - VLAN VID")
if shim := common.GetDot1QLayer(retFrame); shim != nil {
eth := common.GetEthernetLayer(retFrame)
buffer := gopacket.NewSerializeBuffer()
var dot1qLayer *layers.Dot1Q
var ethernetLayer *layers.Ethernet
ethernetLayer = &layers.Ethernet{
SrcMAC: eth.SrcMAC,
DstMAC: eth.DstMAC,
EthernetType: eth.EthernetType,
}
dot1qLayer = &layers.Dot1Q{
Type: shim.Type,
VLANIdentifier: uint16(field.GetVlanVid() & 4095),
}
gopacket.SerializeLayers(
buffer,
gopacket.SerializeOptions{},
ethernetLayer,
dot1qLayer,
gopacket.Payload(shim.LayerPayload()),
)
retFrame = gopacket.NewPacket(
buffer.Bytes(),
layers.LayerTypeEthernet,
gopacket.Default,
)
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"frame": retFrame,
"frameDump": retFrame.Dump(),
"vlanVid": shim.VLANIdentifier,
}).Info("Setting DOT1Q VLAN VID")
} else {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"frame": retFrame,
}).Warn("No DOT1Q found while setting VLAN VID")
}
case openflow_13.OxmOfbFieldTypes_OFPXMT_OFB_VLAN_PCP:
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"frame": retFrame,
}).Debug("Processing action OFPAT SET FIELD - VLAN PCP")
if shim := common.GetDot1QLayer(retFrame); shim != nil {
shim.Priority = uint8(field.GetVlanPcp())
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"frame": retFrame,
"priority": shim.Priority,
}).Info("Setting DOT1Q VLAN PCP")
} else {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"frame": retFrame,
}).Warn("No DOT1Q found while setting VLAN PCP")
}
default:
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"frame": retFrame,
"type": field.Type,
}).Warn("Set field not implemented for this type")
}
} else {
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"frame": retFrame,
}).Warn("Field not of type OF-BASIC")
}
default:
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"frame": retFrame,
"type": action.Type,
}).Warn("Action type not implemented")
}
}
}
}
common.Logger().WithFields(logrus.Fields{
"device": o,
"flow": flow,
"egressPort": egressPort,
"retFrame": retFrame,
}).Debug("Processed actions")
return egressPort, retFrame
}