blob: 7377a20647419c74315154fb68abab1b096f36f2 [file] [log] [blame]
/*
* Copyright 2016-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 org.onosproject.driver.pipeline.ofdpa;
import com.google.common.collect.ImmutableList;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import org.onosproject.core.ApplicationId;
import org.onosproject.driver.extensions.Ofdpa3MatchMplsL2Port;
import org.onosproject.driver.extensions.Ofdpa3MatchOvid;
import org.onosproject.driver.extensions.Ofdpa3PopCw;
import org.onosproject.driver.extensions.Ofdpa3PopL2Header;
import org.onosproject.driver.extensions.Ofdpa3SetMplsL2Port;
import org.onosproject.driver.extensions.Ofdpa3SetMplsType;
import org.onosproject.driver.extensions.Ofdpa3SetOvid;
import org.onosproject.driver.extensions.Ofdpa3SetQosIndex;
import org.onosproject.driver.extensions.OfdpaMatchVlanVid;
import org.onosproject.net.PortNumber;
import org.onosproject.net.behaviour.NextGroup;
import org.onosproject.net.behaviour.PipelinerContext;
import org.onosproject.net.flow.DefaultFlowRule;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.FlowRuleOperations;
import org.onosproject.net.flow.FlowRuleOperationsContext;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.criteria.Criteria;
import org.onosproject.net.flow.criteria.Criterion;
import org.onosproject.net.flow.criteria.EthCriterion;
import org.onosproject.net.flow.criteria.PortCriterion;
import org.onosproject.net.flow.criteria.TunnelIdCriterion;
import org.onosproject.net.flow.criteria.VlanIdCriterion;
import org.onosproject.net.flow.instructions.Instruction;
import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
import org.onosproject.net.flow.instructions.L2ModificationInstruction;
import org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType;
import org.onosproject.net.flow.instructions.L3ModificationInstruction;
import org.onosproject.net.flowobjective.FilteringObjective;
import org.onosproject.net.flowobjective.ForwardingObjective;
import org.onosproject.net.flowobjective.ObjectiveError;
import org.onosproject.net.group.Group;
import org.onosproject.net.group.GroupKey;
import org.slf4j.Logger;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import static org.onlab.packet.MacAddress.NONE;
import static org.onosproject.driver.extensions.Ofdpa3MplsType.VPWS;
import static org.onosproject.net.flow.criteria.Criterion.Type.*;
import static org.onosproject.net.flow.instructions.Instruction.Type.L2MODIFICATION;
import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModTunnelIdInstruction;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Pipeliner for Broadcom OF-DPA 3.0 TTP.
*/
public class Ofdpa3Pipeline extends Ofdpa2Pipeline {
private final Logger log = getLogger(getClass());
@Override
protected void initDriverId() {
driverId = coreService.registerApplication(
"org.onosproject.driver.Ofdpa3Pipeline");
}
@Override
protected void initGroupHander(PipelinerContext context) {
groupHandler = new Ofdpa3GroupHandler();
groupHandler.init(deviceId, context);
}
@Override
protected boolean requireVlanExtensions() {
return false;
}
@Override
protected boolean shouldRetry() {
return false;
}
@Override
protected void processFilter(FilteringObjective filteringObjective,
boolean install,
ApplicationId applicationId) {
// Check if filter is intended for pseudowire
boolean isPw = isPseudowire(filteringObjective);
if (isPw) {
FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
PortCriterion portCriterion;
VlanIdCriterion innerVlanIdCriterion = null;
for (Criterion criterion : filteringObjective.conditions()) {
if (criterion.type() == INNER_VLAN_VID) {
innerVlanIdCriterion = (VlanIdCriterion) criterion;
break;
}
}
VlanIdCriterion outerVlanIdCriterion = null;
// We extract the expected port criterion in the key.
portCriterion = (PortCriterion) filteringObjective.key();
// We extract the outer vlan id criterion.
for (Criterion criterion : filteringObjective.conditions()) {
if (criterion.type() == VLAN_VID) {
outerVlanIdCriterion = (VlanIdCriterion) criterion;
break;
}
}
// We extract the tunnel id.
long tunnelId;
VlanId egressVlan;
if (filteringObjective.meta() != null &&
filteringObjective.meta().allInstructions().size() != 2) {
log.warn("Bad filtering objective from app: {}. Not"
+ "processing filtering objective", applicationId);
fail(filteringObjective, ObjectiveError.BADPARAMS);
return;
} else if (filteringObjective.meta() != null &&
filteringObjective.meta().allInstructions().size() == 2 &&
filteringObjective.meta().allInstructions().get(0).type() == L2MODIFICATION &&
filteringObjective.meta().allInstructions().get(1).type() == L2MODIFICATION) {
L2ModificationInstruction l2instruction = (L2ModificationInstruction)
filteringObjective.meta().allInstructions().get(0);
if (l2instruction.subtype() != L2SubType.TUNNEL_ID) {
log.warn("Bad filtering objective from app: {}. Not"
+ "processing filtering objective", applicationId);
fail(filteringObjective, ObjectiveError.BADPARAMS);
return;
} else {
tunnelId = ((ModTunnelIdInstruction) l2instruction).tunnelId();
}
L2ModificationInstruction vlanInstruction = (L2ModificationInstruction)
filteringObjective.meta().allInstructions().get(1);
if (vlanInstruction.subtype() != L2SubType.VLAN_ID) {
log.warn("Bad filtering objective from app: {}. Not"
+ "processing filtering objective", applicationId);
fail(filteringObjective, ObjectiveError.BADPARAMS);
return;
} else {
egressVlan = ((L2ModificationInstruction.ModVlanIdInstruction) vlanInstruction).vlanId();
}
} else {
log.warn("Bad filtering objective from app: {}. Not"
+ "processing filtering objective", applicationId);
fail(filteringObjective, ObjectiveError.BADPARAMS);
return;
}
// Mpls tunnel ids according to the OFDPA manual have to be
// in the range [2^17-1, 2^16].
tunnelId = MPLS_TUNNEL_ID_BASE | tunnelId;
// Sanity check for the filtering objective.
if (portCriterion == null ||
outerVlanIdCriterion == null ||
tunnelId > MPLS_TUNNEL_ID_MAX) {
log.warn("Bad filtering objective from app: {}. Not"
+ "processing filtering objective", applicationId);
fail(filteringObjective, ObjectiveError.BADPARAMS);
return;
}
// 0x0000XXXX is UNI interface.
if (portCriterion.port().toLong() > MPLS_UNI_PORT_MAX) {
log.error("Filtering Objective invalid logical port {}",
portCriterion.port().toLong());
fail(filteringObjective, ObjectiveError.BADPARAMS);
return;
}
// We create the flows.
List<FlowRule> pwRules = processPwFilter(portCriterion,
innerVlanIdCriterion,
outerVlanIdCriterion,
tunnelId,
applicationId,
egressVlan
);
// We tag the flow for adding or for removing.
for (FlowRule pwRule : pwRules) {
log.debug("adding filtering rule in VLAN tables: {} for dev: {}",
pwRule, deviceId);
ops = install ? ops.add(pwRule) : ops.remove(pwRule);
}
// We push the filtering rules for the pw.
flowRuleService.apply(ops.build(new FlowRuleOperationsContext() {
@Override
public void onSuccess(FlowRuleOperations ops) {
log.info("Applied {} filtering rules in device {}",
ops.stages().get(0).size(), deviceId);
pass(filteringObjective);
}
@Override
public void onError(FlowRuleOperations ops) {
log.info("Failed to apply all filtering rules in dev {}", deviceId);
fail(filteringObjective, ObjectiveError.FLOWINSTALLATIONFAILED);
}
}));
} else if (isDoubleTagged(filteringObjective)) {
processDoubleTaggedFilter(filteringObjective, install, applicationId);
} else {
// If it is not a pseudo wire flow or double-tagged filter, we fall back
// to the OFDPA 2.0 pipeline.
super.processFilter(filteringObjective, install, applicationId);
}
}
/**
* Configure filtering rules of outer and inner VLAN IDs, and a MAC address.
* Filtering happens in three tables (VLAN_TABLE, VLAN_1_TABLE, TMAC_TABLE).
*
* @param filteringObjective the filtering objective
* @param install true to add, false to remove
* @param applicationId for application programming this filter
*/
private void processDoubleTaggedFilter(FilteringObjective filteringObjective,
boolean install,
ApplicationId applicationId) {
PortCriterion portCriterion = null;
EthCriterion ethCriterion = null;
VlanIdCriterion innervidCriterion = null;
VlanIdCriterion outerVidCriterion = null;
boolean popVlan = false;
TrafficTreatment meta = filteringObjective.meta();
if (!filteringObjective.key().equals(Criteria.dummy()) &&
filteringObjective.key().type() == Criterion.Type.IN_PORT) {
portCriterion = (PortCriterion) filteringObjective.key();
}
if (portCriterion == null) {
log.warn("No IN_PORT defined in filtering objective from app: {}" +
"Failed to program VLAN tables.", applicationId);
return;
} else {
log.debug("Received filtering objective for dev/port: {}/{}", deviceId,
portCriterion.port());
}
// meta should have only one instruction, popVlan.
if (meta != null && meta.allInstructions().size() == 1) {
L2ModificationInstruction l2Inst = (L2ModificationInstruction) meta.allInstructions().get(0);
if (l2Inst.subtype().equals(L2SubType.VLAN_POP)) {
popVlan = true;
} else {
log.warn("Filtering objective can have only VLAN_POP instruction.");
return;
}
} else {
log.warn("Filtering objective should have one instruction.");
return;
}
FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
for (Criterion criterion : filteringObjective.conditions()) {
switch (criterion.type()) {
case ETH_DST:
case ETH_DST_MASKED:
ethCriterion = (EthCriterion) criterion;
break;
case VLAN_VID:
if (innervidCriterion == null) {
innervidCriterion = (VlanIdCriterion) criterion;
} else {
outerVidCriterion = innervidCriterion;
innervidCriterion = (VlanIdCriterion) criterion;
}
break;
default:
log.warn("Unsupported filter {}", criterion);
fail(filteringObjective, ObjectiveError.UNSUPPORTED);
return;
}
}
if (innervidCriterion == null || outerVidCriterion == null) {
log.warn("filtering objective should have two vidCriterion.");
return;
}
if (ethCriterion == null || ethCriterion.mac().equals(NONE)) {
// NOTE: it is possible that a filtering objective only has vidCriterion
log.warn("filtering objective missing dstMac, cannot program TMAC table");
return;
} else {
MacAddress unicastMac = readEthDstFromTreatment(filteringObjective.meta());
List<List<FlowRule>> allStages = processEthDstFilter(portCriterion, ethCriterion, innervidCriterion,
innervidCriterion.vlanId(), unicastMac,
applicationId);
for (List<FlowRule> flowRules : allStages) {
log.trace("Starting a new flow rule stage for TMAC table flow");
ops.newStage();
for (FlowRule flowRule : flowRules) {
log.trace("{} flow rules in TMAC table: {} for dev: {}",
(install) ? "adding" : "removing", flowRules, deviceId);
if (install) {
ops = ops.add(flowRule);
} else {
// NOTE: Only remove TMAC flow when there is no more enabled port within the
// same VLAN on this device if TMAC doesn't support matching on in_port.
if (matchInPortTmacTable()
|| (filteringObjective.meta() != null
&& filteringObjective.meta().clearedDeferred())) {
ops = ops.remove(flowRule);
} else {
log.debug("Abort TMAC flow removal on {}. Some other ports still share this TMAC flow");
}
}
}
}
}
List<FlowRule> rules;
rules = processDoubleVlanIdFilter(portCriterion, innervidCriterion,
outerVidCriterion, popVlan, applicationId);
for (FlowRule flowRule : rules) {
log.trace("{} flow rule in VLAN table: {} for dev: {}",
(install) ? "adding" : "removing", flowRule, deviceId);
ops = install ? ops.add(flowRule) : ops.remove(flowRule);
}
// apply filtering flow rules
flowRuleService.apply(ops.build(new FlowRuleOperationsContext() {
@Override
public void onSuccess(FlowRuleOperations ops) {
log.debug("Applied {} filtering rules in device {}",
ops.stages().get(0).size(), deviceId);
pass(filteringObjective);
}
@Override
public void onError(FlowRuleOperations ops) {
log.info("Failed to apply all filtering rules in dev {}", deviceId);
fail(filteringObjective, ObjectiveError.FLOWINSTALLATIONFAILED);
}
}));
}
/**
* Internal implementation of processDoubleVlanIdFilter.
*
* @param portCriterion port on device for which this filter is programmed
* @param innerVidCriterion inner vlan
* @param outerVidCriterion outer vlan
* @param popVlan true if outer vlan header needs to be removed
* @param applicationId for application programming this filter
* @return flow rules for port-vlan filters
*/
private List<FlowRule> processDoubleVlanIdFilter(PortCriterion portCriterion,
VlanIdCriterion innerVidCriterion,
VlanIdCriterion outerVidCriterion,
boolean popVlan,
ApplicationId applicationId) {
TrafficSelector.Builder outerSelector = DefaultTrafficSelector.builder();
TrafficTreatment.Builder outerTreatment = DefaultTrafficTreatment.builder();
TrafficSelector.Builder innerSelector = DefaultTrafficSelector.builder();
TrafficTreatment.Builder innerTreatment = DefaultTrafficTreatment.builder();
VlanId outerVlanId = outerVidCriterion.vlanId();
VlanId innerVlanId = innerVidCriterion.vlanId();
PortNumber portNumber = portCriterion.port();
// Check arguments
if (PortNumber.ALL.equals(portNumber)
|| outerVlanId.equals(VlanId.NONE)
|| innerVlanId.equals(VlanId.NONE)) {
log.warn("Incomplete Filtering Objective. " +
"VLAN Table cannot be programmed for {}", deviceId);
return ImmutableList.of();
} else {
outerSelector.matchInPort(portNumber);
innerSelector.matchInPort(portNumber);
outerTreatment.transition(VLAN_1_TABLE);
innerTreatment.transition(TMAC_TABLE);
if (requireVlanExtensions()) {
OfdpaMatchVlanVid ofdpaOuterMatchVlanVid = new OfdpaMatchVlanVid(outerVlanId);
outerSelector.extension(ofdpaOuterMatchVlanVid, deviceId);
OfdpaMatchVlanVid ofdpaInnerMatchVlanVid = new OfdpaMatchVlanVid(innerVlanId);
innerSelector.extension(ofdpaInnerMatchVlanVid, deviceId);
} else {
outerSelector.matchVlanId(outerVlanId);
innerSelector.matchVlanId(innerVlanId);
}
innerSelector.extension(new Ofdpa3MatchOvid(outerVlanId), deviceId);
outerTreatment.extension(new Ofdpa3SetOvid(outerVlanId), deviceId);
if (popVlan) {
outerTreatment.popVlan();
}
}
FlowRule outerRule = DefaultFlowRule.builder()
.forDevice(deviceId)
.withSelector(outerSelector.build())
.withTreatment(outerTreatment.build())
.withPriority(DEFAULT_PRIORITY)
.fromApp(applicationId)
.makePermanent()
.forTable(VLAN_TABLE)
.build();
FlowRule innerRule = DefaultFlowRule.builder()
.forDevice(deviceId)
.withSelector(innerSelector.build())
.withTreatment(innerTreatment.build())
.withPriority(DEFAULT_PRIORITY)
.fromApp(applicationId)
.makePermanent()
.forTable(VLAN_1_TABLE)
.build();
return ImmutableList.of(outerRule, innerRule);
}
/**
* Determines if the filtering objective will be used for double-tagged packets.
*
* @param fob Filtering objective
* @return True if the objective was created for double-tagged packets, false otherwise.
*/
private boolean isDoubleTagged(FilteringObjective fob) {
return fob.meta() != null &&
fob.meta().allInstructions().stream().anyMatch(inst -> inst.type() == L2MODIFICATION
&& ((L2ModificationInstruction) inst).subtype() == L2SubType.VLAN_POP) &&
fob.conditions().stream().filter(criterion -> criterion.type() == VLAN_VID).count() == 2;
}
/**
* Determines if the filtering objective will be used for a pseudowire.
*
* @param filteringObjective
* @return True if objective was created for a pseudowire, false otherwise.
*/
private boolean isPseudowire(FilteringObjective filteringObjective) {
if (filteringObjective.meta() != null) {
TrafficTreatment treatment = filteringObjective.meta();
for (Instruction instr : treatment.immediate()) {
if (instr.type().equals(Instruction.Type.L2MODIFICATION)) {
L2ModificationInstruction l2Instr = (L2ModificationInstruction) instr;
if (l2Instr.subtype().equals(L2SubType.TUNNEL_ID)) {
return true;
}
}
}
}
return false;
}
/**
* Method to process the pw related filtering objectives.
*
* @param portCriterion the in port match
* @param innerVlanIdCriterion the inner vlan match
* @param outerVlanIdCriterion the outer vlan match
* @param tunnelId the tunnel id
* @param applicationId the application id
* @param egressVlan the vlan to modify, was passed in metadata
* @return a list of flow rules to install
*/
private List<FlowRule> processPwFilter(PortCriterion portCriterion,
VlanIdCriterion innerVlanIdCriterion,
VlanIdCriterion outerVlanIdCriterion,
long tunnelId,
ApplicationId applicationId,
VlanId egressVlan) {
FlowRule vlan1FlowRule;
int mplsLogicalPort = ((int) portCriterion.port().toLong());
// We have to match on the inner vlan and outer vlan at the same time.
// Ofdpa supports this through the OVID meta-data type.
ImmutableList<FlowRule> toReturn;
// pseudowire configured with double tagged vlans
if (!(innerVlanIdCriterion.vlanId().equals(VlanId.NONE))
&& !(outerVlanIdCriterion.vlanId().equals(VlanId.NONE))) {
log.info("Installing filter objective for double tagged CE for tunnel {}", tunnelId);
TrafficSelector.Builder vlan1Selector = DefaultTrafficSelector.builder()
.matchInPort(portCriterion.port())
.matchVlanId(innerVlanIdCriterion.vlanId())
.extension(new Ofdpa3MatchOvid(outerVlanIdCriterion.vlanId()), deviceId);
// TODO understand for the future how to manage the vlan rewrite.
TrafficTreatment.Builder vlan1Treatment = DefaultTrafficTreatment.builder()
.pushVlan()
.setVlanId(egressVlan)
.extension(new Ofdpa3SetMplsType(VPWS), deviceId)
.extension(new Ofdpa3SetMplsL2Port(mplsLogicalPort), deviceId)
.setTunnelId(tunnelId)
.transition(MPLS_L2_PORT_FLOW_TABLE);
vlan1FlowRule = DefaultFlowRule.builder()
.forDevice(deviceId)
.withSelector(vlan1Selector.build())
.withTreatment(vlan1Treatment.build())
.withPriority(DEFAULT_PRIORITY)
.fromApp(applicationId)
.makePermanent()
.forTable(VLAN_1_TABLE)
.build();
// Finally we create the flow rule for the vlan table.
FlowRule vlanFlowRule;
// We have to match on the outer vlan.
TrafficSelector.Builder vlanSelector = DefaultTrafficSelector.builder()
.matchInPort(portCriterion.port())
.matchVlanId(outerVlanIdCriterion.vlanId());
// TODO understand for the future how to manage the vlan rewrite.
TrafficTreatment.Builder vlanTreatment = DefaultTrafficTreatment.builder()
.popVlan()
.extension(new Ofdpa3SetOvid(outerVlanIdCriterion.vlanId()), deviceId)
.transition(VLAN_1_TABLE);
vlanFlowRule = DefaultFlowRule.builder()
.forDevice(deviceId)
.withSelector(vlanSelector.build())
.withTreatment(vlanTreatment.build())
.withPriority(DEFAULT_PRIORITY)
.fromApp(applicationId)
.makePermanent()
.forTable(VLAN_TABLE)
.build();
return ImmutableList.of(vlan1FlowRule, vlanFlowRule);
} else if (!(innerVlanIdCriterion.vlanId().equals(VlanId.NONE))
&& (outerVlanIdCriterion.vlanId().equals(VlanId.NONE))) {
log.info("Installing filter objective for single tagged CE for tunnel {}", tunnelId);
TrafficSelector.Builder singleVlanSelector = DefaultTrafficSelector.builder()
.matchInPort(portCriterion.port())
.matchVlanId(innerVlanIdCriterion.vlanId());
TrafficTreatment.Builder singleVlanTreatment = DefaultTrafficTreatment.builder()
// .pushVlan()
.setVlanId(egressVlan)
.extension(new Ofdpa3SetMplsType(VPWS), deviceId)
.extension(new Ofdpa3SetMplsL2Port(mplsLogicalPort), deviceId)
.setTunnelId(tunnelId)
.transition(MPLS_L2_PORT_FLOW_TABLE);
vlan1FlowRule = DefaultFlowRule.builder()
.forDevice(deviceId)
.withSelector(singleVlanSelector.build())
.withTreatment(singleVlanTreatment.build())
.withPriority(DEFAULT_PRIORITY)
.fromApp(applicationId)
.makePermanent()
.forTable(VLAN_TABLE)
.build();
return ImmutableList.of(vlan1FlowRule);
} else if ((innerVlanIdCriterion.vlanId().equals(VlanId.NONE))
&& (outerVlanIdCriterion.vlanId().equals(VlanId.NONE))) {
TrafficSelector.Builder singleVlanSelector = DefaultTrafficSelector.builder()
.matchInPort(portCriterion.port())
.matchVlanId(innerVlanIdCriterion.vlanId());
TrafficTreatment.Builder singleVlanTreatment = DefaultTrafficTreatment.builder()
.extension(new Ofdpa3SetMplsType(VPWS), deviceId)
.extension(new Ofdpa3SetMplsL2Port(mplsLogicalPort), deviceId)
.setTunnelId(tunnelId)
.transition(MPLS_L2_PORT_FLOW_TABLE);
vlan1FlowRule = DefaultFlowRule.builder()
.forDevice(deviceId)
.withSelector(singleVlanSelector.build())
.withTreatment(singleVlanTreatment.build())
.withPriority(DEFAULT_PRIORITY)
.fromApp(applicationId)
.makePermanent()
.forTable(VLAN_TABLE)
.build();
return ImmutableList.of(vlan1FlowRule);
} else {
// failure...
return Collections.emptyList();
}
}
@Override
protected Collection<FlowRule> processEthTypeSpecific(ForwardingObjective fwd) {
// if its not-bos, we go to MPLS_TYPE_TABLE regardless of whether we pop or swap
// if it is bos, we go to MPLS_TYPE_TABLE only if we swap
if (isNotMplsBos(fwd.selector())
|| (isMplsBos(fwd.selector()) && !isMplsPop(fwd))) {
return processEthTypeSpecificInternal(fwd, true, MPLS_TYPE_TABLE);
}
// if it is bos, and we pop, we go to MPLS_L3_TYPE_TABLE
return processEthTypeSpecificInternal(fwd, true, MPLS_L3_TYPE_TABLE);
}
@Override
protected Collection<FlowRule> processVersatile(ForwardingObjective fwd) {
// We use the tunnel id to identify pw related flows.
// Looking for the fwd objective of the initiation.
TunnelIdCriterion tunnelIdCriterion = (TunnelIdCriterion) fwd.selector()
.getCriterion(TUNNEL_ID);
if (tunnelIdCriterion != null) {
return processInitPwVersatile(fwd);
}
// Looking for the fwd objective of the termination.
ModTunnelIdInstruction modTunnelIdInstruction = getModTunnelIdInstruction(fwd.treatment());
OutputInstruction outputInstruction = getOutputInstruction(fwd.treatment());
if (modTunnelIdInstruction != null && outputInstruction != null) {
return processTermPwVersatile(fwd, modTunnelIdInstruction, outputInstruction);
}
// If it is not a pseudo wire flow we fall back
// to the OFDPA 2.0 pipeline.
return super.processVersatile(fwd);
}
private Collection<FlowRule> processTermPwVersatile(ForwardingObjective forwardingObjective,
ModTunnelIdInstruction modTunnelIdInstruction,
OutputInstruction outputInstruction) {
TrafficTreatment.Builder flowTreatment;
TrafficSelector.Builder flowSelector;
// We divide the mpls actions from the tunnel actions. We need
// this to order the actions in the final treatment.
TrafficTreatment.Builder mplsTreatment = DefaultTrafficTreatment.builder();
createMplsTreatment(forwardingObjective.treatment(), mplsTreatment);
// The match of the forwarding objective is ready to go.
flowSelector = DefaultTrafficSelector.builder(forwardingObjective.selector());
// We verify the tunnel id and mpls port are correct.
long tunnelId = MPLS_TUNNEL_ID_BASE | modTunnelIdInstruction.tunnelId();
if (tunnelId > MPLS_TUNNEL_ID_MAX) {
log.error("Pw Versatile Forwarding Objective must include tunnel id < {}",
MPLS_TUNNEL_ID_MAX);
fail(forwardingObjective, ObjectiveError.BADPARAMS);
return Collections.emptySet();
}
// 0x0002XXXX is NNI interface.
int mplsLogicalPort = ((int) outputInstruction.port().toLong()) | MPLS_NNI_PORT_BASE;
if (mplsLogicalPort > MPLS_NNI_PORT_MAX) {
log.error("Pw Versatile Forwarding Objective invalid logical port {}",
mplsLogicalPort);
fail(forwardingObjective, ObjectiveError.BADPARAMS);
return Collections.emptySet();
}
// Next id cannot be null.
if (forwardingObjective.nextId() == null) {
log.error("Pw Versatile Forwarding Objective must contain nextId ",
forwardingObjective.nextId());
fail(forwardingObjective, ObjectiveError.BADPARAMS);
return Collections.emptySet();
}
// We retrieve the l2 interface group and point the mpls
// flow to this.
NextGroup next = getGroupForNextObjective(forwardingObjective.nextId());
if (next == null) {
log.warn("next-id:{} not found in dev:{}", forwardingObjective.nextId(), deviceId);
fail(forwardingObjective, ObjectiveError.GROUPMISSING);
return Collections.emptySet();
}
List<Deque<GroupKey>> gkeys = appKryo.deserialize(next.data());
Group group = groupService.getGroup(deviceId, gkeys.get(0).peekFirst());
if (group == null) {
log.warn("Group with key:{} for next-id:{} not found in dev:{}",
gkeys.get(0).peekFirst(), forwardingObjective.nextId(), deviceId);
fail(forwardingObjective, ObjectiveError.GROUPMISSING);
return Collections.emptySet();
}
// We prepare the treatment for the mpls flow table.
// The order of the actions has to be strictly this
// according to the OFDPA 2.0 specification.
flowTreatment = DefaultTrafficTreatment.builder(mplsTreatment.build());
flowTreatment.extension(new Ofdpa3PopCw(), deviceId);
// Even though the specification and the xml/json files
// specify is allowed, the switch rejects the flow. In the
// OFDPA 3.0 EA0 version was necessary
//flowTreatment.popVlan();
flowTreatment.extension(new Ofdpa3PopL2Header(), deviceId);
flowTreatment.setTunnelId(tunnelId);
flowTreatment.extension(new Ofdpa3SetMplsL2Port(mplsLogicalPort), deviceId);
flowTreatment.extension(new Ofdpa3SetMplsType(VPWS), deviceId);
flowTreatment.transition(MPLS_TYPE_TABLE);
flowTreatment.deferred().group(group.id());
// We prepare the flow rule for the mpls table.
FlowRule.Builder ruleBuilder = DefaultFlowRule.builder()
.fromApp(forwardingObjective.appId())
.withPriority(forwardingObjective.priority())
.forDevice(deviceId)
.withSelector(flowSelector.build())
.withTreatment(flowTreatment.build())
.makePermanent()
.forTable(MPLS_TABLE_1);
return Collections.singletonList(ruleBuilder.build());
}
/**
* Helper method to process the pw forwarding objectives.
*
* @param forwardingObjective the fw objective to process
* @return a singleton list of flow rule
*/
private Collection<FlowRule> processInitPwVersatile(ForwardingObjective forwardingObjective) {
// We retrieve the matching criteria for mpls l2 port.
TunnelIdCriterion tunnelIdCriterion = (TunnelIdCriterion) forwardingObjective.selector()
.getCriterion(TUNNEL_ID);
PortCriterion portCriterion = (PortCriterion) forwardingObjective.selector()
.getCriterion(IN_PORT);
TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
int mplsLogicalPort;
long tunnelId;
// Mpls tunnel ids according to the OFDPA manual have to be
// in the range [2^17-1, 2^16].
tunnelId = MPLS_TUNNEL_ID_BASE | tunnelIdCriterion.tunnelId();
if (tunnelId > MPLS_TUNNEL_ID_MAX) {
log.error("Pw Versatile Forwarding Objective must include tunnel id < {}",
MPLS_TUNNEL_ID_MAX);
fail(forwardingObjective, ObjectiveError.BADPARAMS);
return Collections.emptySet();
}
// Port has not been null.
if (portCriterion == null) {
log.error("Pw Versatile Forwarding Objective must include port");
fail(forwardingObjective, ObjectiveError.BADPARAMS);
return Collections.emptySet();
}
// 0x0000XXXX is UNI interface.
if (portCriterion.port().toLong() > MPLS_UNI_PORT_MAX) {
log.error("Pw Versatile Forwarding Objective invalid logical port {}",
portCriterion.port().toLong());
fail(forwardingObjective, ObjectiveError.BADPARAMS);
return Collections.emptySet();
}
mplsLogicalPort = ((int) portCriterion.port().toLong());
if (forwardingObjective.nextId() == null) {
log.error("Pw Versatile Forwarding Objective must contain nextId ",
forwardingObjective.nextId());
fail(forwardingObjective, ObjectiveError.BADPARAMS);
return Collections.emptySet();
}
// We don't expect a treatment.
if (forwardingObjective.treatment() != null &&
!forwardingObjective.treatment().equals(DefaultTrafficTreatment.emptyTreatment())) {
log.error("Pw Versatile Forwarding Objective cannot contain a treatment ",
forwardingObjective.nextId());
fail(forwardingObjective, ObjectiveError.BADPARAMS);
return Collections.emptySet();
}
// We retrieve the l2 vpn group and point the mpls
// l2 port to this.
NextGroup next = getGroupForNextObjective(forwardingObjective.nextId());
if (next == null) {
log.warn("next-id:{} not found in dev:{}", forwardingObjective.nextId(), deviceId);
fail(forwardingObjective, ObjectiveError.GROUPMISSING);
return Collections.emptySet();
}
List<Deque<GroupKey>> gkeys = appKryo.deserialize(next.data());
Group group = groupService.getGroup(deviceId, gkeys.get(0).peekFirst());
if (group == null) {
log.warn("Group with key:{} for next-id:{} not found in dev:{}",
gkeys.get(0).peekFirst(), forwardingObjective.nextId(), deviceId);
fail(forwardingObjective, ObjectiveError.GROUPMISSING);
return Collections.emptySet();
}
// We prepare the flow rule for the mpls l2 port table.
selector.matchTunnelId(tunnelId);
selector.extension(new Ofdpa3MatchMplsL2Port(mplsLogicalPort), deviceId);
// This should not be necessary but without we receive an error
treatment.extension(new Ofdpa3SetQosIndex(0), deviceId);
treatment.transition(MPLS_L2_PORT_PCP_TRUST_FLOW_TABLE);
treatment.deferred().group(group.id());
FlowRule.Builder ruleBuilder = DefaultFlowRule.builder()
.fromApp(forwardingObjective.appId())
.withPriority(MPLS_L2_PORT_PRIORITY)
.forDevice(deviceId)
.withSelector(selector.build())
.withTreatment(treatment.build())
.makePermanent()
.forTable(MPLS_L2_PORT_FLOW_TABLE);
return Collections.singletonList(ruleBuilder.build());
}
/**
* Utility function to get the mod tunnel id instruction
* if present.
*
* @param treatment the treatment to analyze
* @return the mod tunnel id instruction if present,
* otherwise null
*/
private ModTunnelIdInstruction getModTunnelIdInstruction(TrafficTreatment treatment) {
if (treatment == null) {
return null;
}
L2ModificationInstruction l2ModificationInstruction;
for (Instruction instruction : treatment.allInstructions()) {
if (instruction.type() == L2MODIFICATION) {
l2ModificationInstruction = (L2ModificationInstruction) instruction;
if (l2ModificationInstruction.subtype() == L2SubType.TUNNEL_ID) {
return (ModTunnelIdInstruction) l2ModificationInstruction;
}
}
}
return null;
}
/**
* Utility function to get the output instruction
* if present.
*
* @param treatment the treatment to analyze
* @return the output instruction if present,
* otherwise null
*/
private OutputInstruction getOutputInstruction(TrafficTreatment treatment) {
if (treatment == null) {
return null;
}
for (Instruction instruction : treatment.allInstructions()) {
if (instruction.type() == Instruction.Type.OUTPUT) {
return (OutputInstruction) instruction;
}
}
return null;
}
/**
* Helper method for dividing the tunnel instructions from the mpls
* instructions.
*
* @param treatment the treatment to analyze
* @param mplsTreatment the mpls treatment builder
*/
private void createMplsTreatment(TrafficTreatment treatment,
TrafficTreatment.Builder mplsTreatment) {
for (Instruction ins : treatment.allInstructions()) {
if (ins.type() == Instruction.Type.L2MODIFICATION) {
L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
switch (l2ins.subtype()) {
// These instructions have to go in the mpls
// treatment.
case TUNNEL_ID:
break;
case DEC_MPLS_TTL:
case MPLS_POP:
mplsTreatment.add(ins);
break;
default:
log.warn("Driver does not handle this type of TrafficTreatment"
+ " instruction in nextObjectives: {} - {}",
ins.type(), ins);
break;
}
} else if (ins.type() == Instruction.Type.OUTPUT) {
break;
} else if (ins.type() == Instruction.Type.L3MODIFICATION) {
// We support partially the l3 instructions.
L3ModificationInstruction l3ins = (L3ModificationInstruction) ins;
switch (l3ins.subtype()) {
case TTL_IN:
mplsTreatment.add(ins);
break;
default:
log.warn("Driver does not handle this type of TrafficTreatment"
+ " instruction in nextObjectives: {} - {}",
ins.type(), ins);
}
} else {
log.warn("Driver does not handle this type of TrafficTreatment"
+ " instruction in nextObjectives: {} - {}",
ins.type(), ins);
}
}
}
}