CORD-908 Remove VLAN MPLS workaround in OVS
* No longer pop VLAN before entering Unicast Routing and MPLS table.
* In MPLS label group, pop vlan first, push MPLS label and push back an arbitrary vlan.
The vlan will get overwritten in MPLS interface group.
* Deprecate OVS VLAN pipeline since this one will now support both scenario
* Introduce punt table
- Correctly determine whether vlan should be popped before sending to controller.
- The pop and punt will not affect deferred group since
it is done by group instead of apply action
- Prepare for upcoming trunk port support
Change-Id: I8a28821fdab28647f6871bc8ff2f006f6ac2b763
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/CpqdOfdpa2Pipeline.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/CpqdOfdpa2Pipeline.java
index 8e4ab53..58ba6d6 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/CpqdOfdpa2Pipeline.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/CpqdOfdpa2Pipeline.java
@@ -21,6 +21,7 @@
import org.onlab.packet.IpPrefix;
import org.onlab.packet.VlanId;
import org.onosproject.core.ApplicationId;
+import org.onosproject.core.DefaultGroupId;
import org.onosproject.net.Port;
import org.onosproject.net.PortNumber;
import org.onosproject.net.behaviour.NextGroup;
@@ -51,7 +52,13 @@
import org.onosproject.net.flowobjective.FilteringObjective;
import org.onosproject.net.flowobjective.ForwardingObjective;
import org.onosproject.net.flowobjective.ObjectiveError;
+import org.onosproject.net.group.DefaultGroupBucket;
+import org.onosproject.net.group.DefaultGroupDescription;
+import org.onosproject.net.group.DefaultGroupKey;
import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
import org.onosproject.net.group.GroupKey;
import org.onosproject.net.packet.PacketPriority;
import org.slf4j.Logger;
@@ -61,10 +68,12 @@
import java.util.Collections;
import java.util.Deque;
import java.util.List;
+import java.util.Objects;
import static org.onlab.packet.IPv6.PROTOCOL_ICMP6;
import static org.onlab.packet.MacAddress.BROADCAST;
import static org.onlab.packet.MacAddress.NONE;
+import static org.onosproject.driver.pipeline.Ofdpa2GroupHandler.FOUR_BIT_MASK;
import static org.slf4j.LoggerFactory.getLogger;
@@ -81,6 +90,24 @@
private final Logger log = getLogger(getClass());
/**
+ * Table that determines whether VLAN is popped before punting to controller.
+ * <p>
+ * This is a non-OFDPA table to emulate OFDPA packet in behavior.
+ * VLAN will be popped before punting if the VLAN is internally assigned.
+ * <p>
+ * Also note that 63 is the max table number in CpqD.
+ */
+ private static final int PUNT_TABLE = 63;
+
+ /**
+ * A static indirect group that pop vlan and punt to controller.
+ * <p>
+ * The purpose of using a group instead of immediate action is that this
+ * won't affect another copy on the data plane when write action exists.
+ */
+ private static final int POP_VLAN_PUNT_GROUP_ID = 0xc0000000;
+
+ /**
* Determines whether this pipeline support copy ttl instructions or not.
*
* @return true if copy ttl instructions are supported
@@ -89,6 +116,28 @@
return true;
}
+ /**
+ * Determines whether this pipeline support push mpls to vlan-tagged packets or not.
+ * <p>
+ * If not support, pop vlan before push entering unicast and mpls table.
+ * Side effect: HostService learns redundant hosts with same MAC but
+ * different VLAN. No known side effect on the network reachability.
+ *
+ * @return true if push mpls to vlan-tagged packets is supported
+ */
+ protected boolean supportTaggedMpls() {
+ return false;
+ }
+
+ /**
+ * Determines whether this pipeline support punt action in group bucket.
+ *
+ * @return true if punt action in group bucket is supported
+ */
+ protected boolean supportPuntGroup() {
+ return false;
+ }
+
@Override
protected void initDriverId() {
driverId = coreService.registerApplication(
@@ -258,11 +307,6 @@
if (vidCriterion.vlanId() == VlanId.NONE) {
// untagged packets are assigned vlans
treatment.pushVlan().setVlanId(assignedVlan);
-
- // Emulating OFDPA behavior by popping off internal assigned VLAN
- // before sending to controller
- rules.add(buildArpPunt(assignedVlan, applicationId));
- rules.add(buildIcmpV6Punt(assignedVlan, applicationId));
}
// ofdpa cannot match on ALL portnumber, so we need to use separate
@@ -279,6 +323,20 @@
}
for (PortNumber pnum : portnums) {
+ // NOTE: Emulating OFDPA behavior by popping off internal assigned
+ // VLAN before sending to controller
+ if (supportPuntGroup() && vidCriterion.vlanId() == VlanId.NONE) {
+ GroupKey groupKey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(
+ POP_VLAN_PUNT_GROUP_ID | (Objects.hash(deviceId) & FOUR_BIT_MASK)));
+ Group group = groupService.getGroup(deviceId, groupKey);
+ if (group != null) {
+ rules.add(buildPuntTableRule(pnum, assignedVlan));
+ } else {
+ log.info("popVlanPuntGroup not found in dev:{}", deviceId);
+ return Collections.emptyList();
+ }
+ }
+
// create rest of flowrule
selector.matchInPort(pnum);
FlowRule rule = DefaultFlowRule.builder()
@@ -296,7 +354,37 @@
}
/**
+ * Creates punt table entry that matches IN_PORT and VLAN_VID and points to
+ * a group that pop vlan and punt.
+ *
+ * @param portNumber port number
+ * @param assignedVlan internally assigned vlan id
+ * @return punt table flow rule
+ */
+ private FlowRule buildPuntTableRule(PortNumber portNumber, VlanId assignedVlan) {
+ TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder()
+ .matchInPort(portNumber)
+ .matchVlanId(assignedVlan);
+ TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder()
+ .group(new DefaultGroupId(POP_VLAN_PUNT_GROUP_ID));
+
+ return DefaultFlowRule.builder()
+ .forDevice(deviceId)
+ .withSelector(sbuilder.build())
+ .withTreatment(tbuilder.build())
+ .withPriority(PacketPriority.CONTROL.priorityValue())
+ .fromApp(driverId)
+ .makePermanent()
+ .forTable(PUNT_TABLE).build();
+ }
+
+ /**
* Builds a punt to the controller rule for the arp protocol.
+ * <p>
+ * NOTE: CpqD cannot punt correctly in group bucket. The current impl will
+ * pop VLAN before sending to controller disregarding whether
+ * it's an internally assigned VLAN or a natural VLAN.
+ * Therefore, trunk port is not supported in CpqD.
*
* @param assignedVlan the internal assigned vlan id
* @param applicationId the application id
@@ -322,6 +410,11 @@
/**
* Builds a punt to the controller rule for the icmp v6 messages.
+ * <p>
+ * NOTE: CpqD cannot punt correctly in group bucket. The current impl will
+ * pop VLAN before sending to controller disregarding whether
+ * it's an internally assigned VLAN or a natural VLAN.
+ * Therefore, trunk port is not supported in CpqD.
*
* @param assignedVlan the internal assigned vlan id
* @param applicationId the application id
@@ -388,20 +481,16 @@
List<FlowRule> rules = new ArrayList<FlowRule>();
for (PortNumber pnum : portnums) {
- // for unicast IP packets
+ // TMAC rules for unicast IP packets
TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
selector.matchInPort(pnum);
selector.matchVlanId(vidCriterion.vlanId());
selector.matchEthType(Ethernet.TYPE_IPV4);
selector.matchEthDst(ethCriterion.mac());
- /*
- * Note: CpqD switches do not handle MPLS-related operation properly
- * for a packet with VLAN tag. We pop VLAN here as a workaround.
- * Side effect: HostService learns redundant hosts with same MAC but
- * different VLAN. No known side effect on the network reachability.
- */
- treatment.popVlan();
+ if (!supportTaggedMpls()) {
+ treatment.popVlan();
+ }
treatment.transition(UNICAST_ROUTING_TABLE);
FlowRule rule = DefaultFlowRule.builder()
.forDevice(deviceId)
@@ -412,15 +501,17 @@
.makePermanent()
.forTable(TMAC_TABLE).build();
rules.add(rule);
- //for MPLS packets
+
+ // TMAC rules for MPLS packets
selector = DefaultTrafficSelector.builder();
treatment = DefaultTrafficTreatment.builder();
selector.matchInPort(pnum);
selector.matchVlanId(vidCriterion.vlanId());
selector.matchEthType(Ethernet.MPLS_UNICAST);
selector.matchEthDst(ethCriterion.mac());
- // workaround here again
- treatment.popVlan();
+ if (!supportTaggedMpls()) {
+ treatment.popVlan();
+ }
treatment.transition(MPLS_TABLE_0);
rule = DefaultFlowRule.builder()
.forDevice(deviceId)
@@ -431,21 +522,17 @@
.makePermanent()
.forTable(TMAC_TABLE).build();
rules.add(rule);
- /*
- * TMAC rules for IPv6 packets
- */
+
+ // TMAC rules for IPv6 packets
selector = DefaultTrafficSelector.builder();
treatment = DefaultTrafficTreatment.builder();
selector.matchInPort(pnum);
selector.matchVlanId(vidCriterion.vlanId());
selector.matchEthType(Ethernet.TYPE_IPV6);
selector.matchEthDst(ethCriterion.mac());
- /*
- * workaround here again, we are removing
- * the vlan tag before to go through the
- * rest of the pipeline
- */
- treatment.popVlan();
+ if (!supportTaggedMpls()) {
+ treatment.popVlan();
+ }
treatment.transition(UNICAST_ROUTING_TABLE);
rule = DefaultFlowRule.builder()
.forDevice(deviceId)
@@ -467,13 +554,9 @@
TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
selector.matchEthType(Ethernet.TYPE_IPV4);
selector.matchEthDst(ethCriterion.mac());
- /*
- * Note: CpqD switches do not handle MPLS-related operation properly
- * for a packet with VLAN tag. We pop VLAN here as a workaround.
- * Side effect: HostService learns redundant hosts with same MAC but
- * different VLAN. No known side effect on the network reachability.
- */
- treatment.popVlan();
+ if (!supportTaggedMpls()) {
+ treatment.popVlan();
+ }
treatment.transition(UNICAST_ROUTING_TABLE);
FlowRule rule = DefaultFlowRule.builder()
.forDevice(deviceId)
@@ -742,7 +825,7 @@
if (ins instanceof OutputInstruction) {
OutputInstruction o = (OutputInstruction) ins;
if (o.port() == PortNumber.CONTROLLER) {
- ttBuilder.add(o);
+ ttBuilder.transition(PUNT_TABLE);
} else {
log.warn("Only allowed treatments in versatile forwarding "
+ "objectives are punts to the controller");
@@ -799,6 +882,15 @@
initTableMiss(MPLS_TABLE_1, ACL_TABLE, null);
initTableMiss(BRIDGING_TABLE, ACL_TABLE, null);
initTableMiss(ACL_TABLE, -1, null);
+
+ if (supportPuntGroup()) {
+ initTableMiss(PUNT_TABLE, -1,
+ DefaultTrafficTreatment.builder().punt().build());
+ initPopVlanPuntGroup();
+ } else {
+ initTableMiss(PUNT_TABLE, -1,
+ DefaultTrafficTreatment.builder().popVlan().punt().build());
+ }
}
/**
@@ -845,4 +937,30 @@
}
}));
}
+
+ /**
+ * Builds a indirect group contains pop_vlan and punt actions.
+ * <p>
+ * Using group instead of immediate action to ensure that
+ * the copy of packet on the data plane is not affected by the pop vlan action.
+ */
+ private void initPopVlanPuntGroup() {
+ GroupKey groupKey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(
+ POP_VLAN_PUNT_GROUP_ID | (Objects.hash(deviceId) & FOUR_BIT_MASK)));
+ TrafficTreatment bucketTreatment = DefaultTrafficTreatment.builder()
+ .popVlan().punt().build();
+ GroupBucket bucket =
+ DefaultGroupBucket.createIndirectGroupBucket(bucketTreatment);
+ GroupDescription groupDesc =
+ new DefaultGroupDescription(
+ deviceId,
+ GroupDescription.Type.INDIRECT,
+ new GroupBuckets(Collections.singletonList(bucket)),
+ groupKey,
+ POP_VLAN_PUNT_GROUP_ID,
+ driverId);
+ groupService.addGroup(groupDesc);
+
+ log.info("Initialized pop vlan punt group on {}", deviceId);
+ }
}