[ONOS-7624] Implement egress pipeline programming
- Implemented logic to handle double-tagged host in segmentrouting application.
- Added 'DummyVlanId' to segmentrouting application to keep track of dummy vlan for L3L2Unfiltered group chain and egress tables.
- Implemented L2Unfiltered group and Egress pipeline programming support in OFDPA pipeline.
- Added EGRESS flag to the forwardingObjective to program Egress tables.
- Fixed bugs when handling double-tagged ARP request, to get correct vlan id and reply with double-tagged packet.
- Fixed bugs in BasicHostConfig, to set the value of 'outerTpid' to 0x8100 if it is not specified.
- Fixed build(ARP/ICMP/ICMP6)reply to build double-tagged reply if corresponding request is double-tagged.
Change-Id: I1fdc30b55827c3f73fad9e854bcaa5fb23f7bcd0
(cherry picked from commit 800d9d0770bb97c5d89f65bc88310c7b13f11055)
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
index 31936e2..eb0af67 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
@@ -23,6 +23,7 @@
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
+import org.onlab.packet.EthType;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.Ip6Address;
import org.onlab.packet.IpPrefix;
@@ -38,6 +39,7 @@
import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
import org.onosproject.segmentrouting.config.DeviceConfiguration;
import org.onosproject.segmentrouting.grouphandler.DefaultGroupHandler;
+import org.onosproject.segmentrouting.storekey.DummyVlanIdStoreKey;
import org.onosproject.store.serializers.KryoNamespaces;
import org.onosproject.store.service.Serializer;
import org.slf4j.Logger;
@@ -1179,6 +1181,81 @@
}
/**
+ * Populates IP rules for a route when the next hop is double-tagged.
+ *
+ * @param deviceId device ID that next hop attaches to
+ * @param prefix IP prefix of the route
+ * @param hostMac MAC address of the next hop
+ * @param innerVlan Inner Vlan ID of the next hop
+ * @param outerVlan Outer Vlan ID of the next hop
+ * @param outerTpid Outer TPID of the next hop
+ * @param outPort port that the next hop attaches to
+ */
+ void populateDoubleTaggedRoute(DeviceId deviceId, IpPrefix prefix, MacAddress hostMac, VlanId innerVlan,
+ VlanId outerVlan, EthType outerTpid, PortNumber outPort) {
+ if (srManager.mastershipService.isLocalMaster(deviceId)) {
+ VlanId dummyVlan = srManager.allocateDummyVlanId(
+ new ConnectPoint(deviceId, outPort), prefix.address());
+ if (!dummyVlan.equals(VlanId.NONE)) {
+ srManager.routingRulePopulator.populateDoubleTaggedRoute(
+ deviceId, prefix, hostMac, dummyVlan, innerVlan, outerVlan, outerTpid, outPort);
+ srManager.routingRulePopulator.processDoubleTaggedFilter(
+ deviceId, outPort, outerVlan, innerVlan, true);
+ } else {
+ log.error("Failed to allocate dummy VLAN ID for host {} at {}/{}",
+ prefix.address(), deviceId, outPort);
+ }
+ }
+ }
+
+ /**
+ * Revokes IP rules for a route when the next hop is double-tagged.
+ *
+ * @param deviceId device ID that next hop attaches to
+ * @param prefix IP prefix of the route
+ * @param hostMac MAC address of the next hop
+ * @param innerVlan Inner Vlan ID of the next hop
+ * @param outerVlan Outer Vlan ID of the next hop
+ * @param outerTpid Outer TPID of the next hop
+ * @param outPort port that the next hop attaches to
+ */
+ void revokeDoubleTaggedRoute(DeviceId deviceId, IpPrefix prefix, MacAddress hostMac, VlanId innerVlan,
+ VlanId outerVlan, EthType outerTpid, PortNumber outPort) {
+ // Revoke route either if this node have the mastership (when device is available) or
+ // if this node is the leader (even when device is unavailable)
+ if (!srManager.mastershipService.isLocalMaster(deviceId)) {
+ if (srManager.deviceService.isAvailable(deviceId)) {
+ // Master node will revoke specified rule.
+ log.debug("This node is not a master for {}, stop revoking route.", deviceId);
+ return;
+ }
+
+ // isLocalMaster will return false when the device is unavailable.
+ // Verify if this node is the leader in that case.
+ NodeId leader = srManager.leadershipService.runForLeadership(
+ deviceId.toString()).leaderNodeId();
+ if (!srManager.clusterService.getLocalNode().id().equals(leader)) {
+ // Leader node will revoke specified rule.
+ log.debug("This node is not a master for {}, stop revoking route.", deviceId);
+ return;
+ }
+ }
+
+ VlanId dummyVlan = srManager.dummyVlanIdStore().get(new DummyVlanIdStoreKey(
+ new ConnectPoint(deviceId, outPort), prefix.address()));
+ if (dummyVlan == null) {
+ log.error("Failed to get dummyVlanId for host {} at {}/{}.",
+ prefix.address(), deviceId, outPort);
+ } else {
+ srManager.routingRulePopulator.revokeDoubleTaggedRoute(
+ deviceId, prefix, hostMac, dummyVlan, innerVlan, outerVlan, outerTpid, outPort);
+ srManager.routingRulePopulator.processDoubleTaggedFilter(
+ deviceId, outPort, outerVlan, innerVlan, false);
+ }
+ }
+
+
+ /**
* Remove ECMP graph entry for the given device. Typically called when
* device is no longer available.
*
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/HostHandler.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/HostHandler.java
index 5fdbf69..e14fdc2 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/HostHandler.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/HostHandler.java
@@ -16,6 +16,7 @@
package org.onosproject.segmentrouting;
+import org.onlab.packet.EthType;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
@@ -94,10 +95,17 @@
Set<IpAddress> ips = host.ipAddresses();
log.info("Host {}/{} is added at {}", hostMac, hostVlanId, locations);
- processBridgingRule(location.deviceId(), location.port(), hostMac, hostVlanId, false);
- ips.forEach(ip ->
+ if (isDoubleTaggedHost(host)) {
+ ips.forEach(ip ->
+ processDoubleTaggedRoutingRule(location.deviceId(), location.port(), hostMac,
+ host.innerVlan(), hostVlanId, host.tpid(), ip, false)
+ );
+ } else {
+ processBridgingRule(location.deviceId(), location.port(), hostMac, hostVlanId, false);
+ ips.forEach(ip ->
processRoutingRule(location.deviceId(), location.port(), hostMac, hostVlanId, ip, false)
- );
+ );
+ }
// Use the pair link temporarily before the second location of a dual-homed host shows up.
// This do not affect single-homed hosts since the flow will be blocked in
@@ -133,10 +141,17 @@
log.info("Host {}/{} is removed from {}", hostMac, hostVlanId, locations);
locations.forEach(location -> {
- processBridgingRule(location.deviceId(), location.port(), hostMac, hostVlanId, true);
- ips.forEach(ip ->
+ if (isDoubleTaggedHost(host)) {
+ ips.forEach(ip ->
+ processDoubleTaggedRoutingRule(location.deviceId(), location.port(), hostMac,
+ host.innerVlan(), hostVlanId, host.tpid(), ip, true)
+ );
+ } else {
+ processBridgingRule(location.deviceId(), location.port(), hostMac, hostVlanId, true);
+ ips.forEach(ip ->
processRoutingRule(location.deviceId(), location.port(), hostMac, hostVlanId, ip, true)
- );
+ );
+ }
// Also remove redirection flows on the pair device if exists.
Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(location.deviceId());
@@ -155,18 +170,20 @@
}
void processHostMovedEvent(HostEvent event) {
- MacAddress hostMac = event.subject().mac();
- VlanId hostVlanId = event.subject().vlan();
Set<HostLocation> prevLocations = event.prevSubject().locations();
Set<IpAddress> prevIps = event.prevSubject().ipAddresses();
Set<HostLocation> newLocations = event.subject().locations();
Set<IpAddress> newIps = event.subject().ipAddresses();
- processHostMoved(hostMac, hostVlanId, prevLocations, prevIps, newLocations, newIps);
+ processHostMoved(event.subject(), prevLocations, prevIps, newLocations, newIps);
}
- private void processHostMoved(MacAddress hostMac, VlanId hostVlanId, Set<HostLocation> prevLocations,
- Set<IpAddress> prevIps, Set<HostLocation> newLocations, Set<IpAddress> newIps) {
+ private void processHostMoved(Host host, Set<HostLocation> prevLocations, Set<IpAddress> prevIps,
+ Set<HostLocation> newLocations, Set<IpAddress> newIps) {
+ MacAddress hostMac = host.mac();
+ VlanId hostVlanId = host.vlan();
+ EthType hostTpid = host.tpid();
+ boolean doubleTaggedHost = isDoubleTaggedHost(host);
log.info("Host {}/{} is moved from {} to {}", hostMac, hostVlanId, prevLocations, newLocations);
Set<DeviceId> newDeviceIds = newLocations.stream().map(HostLocation::deviceId)
.collect(Collectors.toSet());
@@ -174,10 +191,15 @@
// For each old location
Sets.difference(prevLocations, newLocations).forEach(prevLocation -> {
// Remove routing rules for old IPs
- Sets.difference(prevIps, newIps).forEach(ip ->
+ Sets.difference(prevIps, newIps).forEach(ip -> {
+ if (doubleTaggedHost) {
+ processDoubleTaggedRoutingRule(prevLocation.deviceId(), prevLocation.port(),
+ hostMac, host.innerVlan(), hostVlanId, hostTpid, ip, true);
+ } else {
processRoutingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId,
- ip, true)
- );
+ ip, true);
+ }
+ });
// Redirect the flows to pair link if configured
// Note: Do not continue removing any rule
@@ -200,10 +222,15 @@
// Otherwise, do not remove and let the adding part update the old flow
if (!newDeviceIds.contains(prevLocation.deviceId())) {
processBridgingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId, true);
- Sets.intersection(prevIps, newIps).forEach(ip ->
- processRoutingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId,
- ip, true)
- );
+ Sets.intersection(prevIps, newIps).forEach(ip -> {
+ if (doubleTaggedHost) {
+ processDoubleTaggedRoutingRule(prevLocation.deviceId(), prevLocation.port(),
+ hostMac, host.innerVlan(), hostVlanId, hostTpid, ip, true);
+ } else {
+ processRoutingRule(prevLocation.deviceId(), prevLocation.port(),
+ hostMac, hostVlanId, ip, true);
+ }
+ });
}
// Remove bridging rules if new interface vlan is different from old interface vlan
@@ -225,8 +252,13 @@
Sets.intersection(prevIps, newIps).forEach(ip -> {
if (newLocations.stream().noneMatch(newLocation ->
srManager.deviceConfiguration.inSameSubnet(newLocation, ip))) {
- processRoutingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId,
- ip, true);
+ if (doubleTaggedHost) {
+ processDoubleTaggedRoutingRule(prevLocation.deviceId(), prevLocation.port(),
+ hostMac, host.innerVlan(), hostVlanId, hostTpid, ip, true);
+ } else {
+ processRoutingRule(prevLocation.deviceId(), prevLocation.port(),
+ hostMac, hostVlanId, ip, true);
+ }
}
});
});
@@ -234,23 +266,38 @@
// For each new location, add all new IPs.
Sets.difference(newLocations, prevLocations).forEach(newLocation -> {
processBridgingRule(newLocation.deviceId(), newLocation.port(), hostMac, hostVlanId, false);
- newIps.forEach(ip ->
+ newIps.forEach(ip -> {
+ if (doubleTaggedHost) {
+ processDoubleTaggedRoutingRule(newLocation.deviceId(), newLocation.port(),
+ hostMac, host.innerVlan(), hostVlanId, hostTpid, ip, false);
+ } else {
processRoutingRule(newLocation.deviceId(), newLocation.port(), hostMac, hostVlanId,
- ip, false)
- );
+ ip, false);
+ }
+ });
});
// For each unchanged location, add new IPs and remove old IPs.
Sets.intersection(newLocations, prevLocations).forEach(unchangedLocation -> {
- Sets.difference(prevIps, newIps).forEach(ip ->
- processRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(), hostMac,
- hostVlanId, ip, true)
- );
+ Sets.difference(prevIps, newIps).forEach(ip -> {
+ if (doubleTaggedHost) {
+ processDoubleTaggedRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(),
+ hostMac, host.innerVlan(), hostVlanId, hostTpid, ip, true);
+ } else {
+ processRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(),
+ hostMac, hostVlanId, ip, true);
+ }
+ });
- Sets.difference(newIps, prevIps).forEach(ip ->
- processRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(), hostMac,
- hostVlanId, ip, false)
- );
+ Sets.difference(newIps, prevIps).forEach(ip -> {
+ if (doubleTaggedHost) {
+ processDoubleTaggedRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(),
+ hostMac, host.innerVlan(), hostVlanId, hostTpid, ip, false);
+ } else {
+ processRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(),
+ hostMac, hostVlanId, ip, false);
+ }
+ });
});
// ensure dual-homed host locations have viable uplinks
@@ -267,18 +314,31 @@
Host host = event.subject();
MacAddress hostMac = host.mac();
VlanId hostVlanId = host.vlan();
+ EthType hostTpid = host.tpid();
Set<HostLocation> locations = host.locations();
Set<IpAddress> prevIps = event.prevSubject().ipAddresses();
Set<IpAddress> newIps = host.ipAddresses();
log.info("Host {}/{} is updated", hostMac, hostVlanId);
locations.forEach(location -> {
- Sets.difference(prevIps, newIps).forEach(ip ->
- processRoutingRule(location.deviceId(), location.port(), hostMac, hostVlanId, ip, true)
- );
- Sets.difference(newIps, prevIps).forEach(ip ->
- processRoutingRule(location.deviceId(), location.port(), hostMac, hostVlanId, ip, false)
- );
+ Sets.difference(prevIps, newIps).forEach(ip -> {
+ if (isDoubleTaggedHost(host)) {
+ processDoubleTaggedRoutingRule(location.deviceId(), location.port(), hostMac,
+ host.innerVlan(), hostVlanId, hostTpid, ip, true);
+ } else {
+ processRoutingRule(location.deviceId(), location.port(), hostMac,
+ hostVlanId, ip, true);
+ }
+ });
+ Sets.difference(newIps, prevIps).forEach(ip -> {
+ if (isDoubleTaggedHost(host)) {
+ processDoubleTaggedRoutingRule(location.deviceId(), location.port(), hostMac,
+ host.innerVlan(), hostVlanId, hostTpid, ip, false);
+ } else {
+ processRoutingRule(location.deviceId(), location.port(), hostMac,
+ hostVlanId, ip, false);
+ }
+ });
});
// Use the pair link temporarily before the second location of a dual-homed host shows up.
@@ -374,8 +434,8 @@
}
/**
- * Populate or revoke a bridging rule on given deviceId that matches given mac, given vlan and
- * output to given port.
+ * Populates or revokes a bridging rule on given deviceId that matches given mac,
+ * given vlan and output to given port.
*
* @param deviceId device ID
* @param port port
@@ -422,7 +482,39 @@
}
}
-
+ /**
+ * Populate or revoke a routing rule and egress rules on given deviceId that matches given IP,
+ * to set destination mac to given mac, set inner vlan and outer vlan to given vlans,
+ * set outer TPID, and output to given port.
+ *
+ * @param deviceId device ID
+ * @param port port
+ * @param mac mac address
+ * @param innerVlan inner VLAN ID
+ * @param outerVlan outer VLAN ID
+ * @param outerTpid outer TPID
+ * @param ip IP address
+ * @param revoke true to revoke the rule; false to populate
+ */
+ private void processDoubleTaggedRoutingRule(DeviceId deviceId, PortNumber port,
+ MacAddress mac, VlanId innerVlan,
+ VlanId outerVlan, EthType outerTpid,
+ IpAddress ip, boolean revoke) {
+ ConnectPoint location = new ConnectPoint(deviceId, port);
+ if (!srManager.deviceConfiguration.inSameSubnet(location, ip)) {
+ log.info("{} is not included in the subnet config of {}/{}. Ignored.", ip, deviceId, port);
+ return;
+ }
+ log.info("{} routing rule for double-tagged host {} at {}",
+ revoke ? "Revoking" : "Populating", ip, location);
+ if (revoke) {
+ srManager.defaultRoutingHandler.revokeDoubleTaggedRoute(
+ deviceId, ip.toIpPrefix(), mac, innerVlan, outerVlan, outerTpid, port);
+ } else {
+ srManager.defaultRoutingHandler.populateDoubleTaggedRoute(
+ deviceId, ip.toIpPrefix(), mac, innerVlan, outerVlan, outerTpid, port);
+ }
+ }
/**
* Update forwarding objective for unicast bridging and unicast routing.
@@ -510,4 +602,15 @@
.forEach(loc -> dualHomedLocations.add(loc.port())));
return dualHomedLocations;
}
+
+ /**
+ * Checks if the given host is double-tagged or not.
+ *
+ * @param host host to check
+ * @return true if it is double-tagged, false otherwise
+ */
+ private boolean isDoubleTaggedHost(Host host) {
+ return !host.innerVlan().equals(VlanId.NONE);
+ }
+
}
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
index 06b650d..d4a5b65 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
@@ -52,6 +52,7 @@
import org.onosproject.net.flowobjective.ForwardingObjective;
import org.onosproject.net.flowobjective.ForwardingObjective.Builder;
import org.onosproject.net.flowobjective.ForwardingObjective.Flag;
+import org.onosproject.segmentrouting.storekey.DummyVlanIdStoreKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -76,6 +77,7 @@
import static org.onlab.packet.ICMP6.ROUTER_SOLICITATION;
import static org.onlab.packet.IPv6.PROTOCOL_ICMP6;
import static org.onosproject.segmentrouting.SegmentRoutingManager.INTERNAL_VLAN;
+import static org.onosproject.segmentrouting.SegmentRoutingService.DEFAULT_PRIORITY;
/**
* Populator of segment routing flow rules.
@@ -418,9 +420,18 @@
return null;
}
} else {
- log.warn("Tagged nexthop {}/{} is not allowed on {} without VLAN listed"
- + " in tagged vlan", hostMac, hostVlanId, connectPoint);
- return null;
+ // Internally-assigned dummy VLAN id will be given as hostVlanId
+ // when destination is double-tagged.
+ VlanId vlanId = srManager.dummyVlanIdStore().get(
+ new DummyVlanIdStoreKey(connectPoint, prefix.address()));
+ if (vlanId != null && vlanId.equals(hostVlanId)) {
+ tbuilder.setVlanId(hostVlanId);
+ mbuilder.matchVlanId(VlanId.ANY);
+ } else {
+ log.warn("Tagged nexthop {}/{} is not allowed on {} without VLAN listed"
+ + " in tagged vlan", hostMac, hostVlanId, connectPoint);
+ return null;
+ }
}
// if the objective is to revoke an existing rule, and for some reason
// the next-objective does not exist, then a new one should not be created
@@ -940,6 +951,7 @@
Set<VlanId> taggedVlans = srManager.getTaggedVlanId(connectPoint);
VlanId nativeVlan = srManager.getNativeVlanId(connectPoint);
+ // Do not configure filter for edge ports where double-tagged hosts are connected.
if (taggedVlans.size() != 0) {
// Filter for tagged vlans
if (!srManager.getTaggedVlanId(connectPoint).stream().allMatch(taggedVlanId ->
@@ -957,8 +969,8 @@
if (!processSinglePortFiltersInternal(deviceId, portnum, true, untaggedVlan, install)) {
return false;
}
- } else {
- // Unconfigured port, use INTERNAL_VLAN
+ } else if (srManager.linkService.getLinks(connectPoint).size() == 0) {
+ // Filter for unconfigured upstream port, using INTERNAL_VLAN
if (!processSinglePortFiltersInternal(deviceId, portnum, true, INTERNAL_VLAN, install)) {
return false;
}
@@ -1042,6 +1054,70 @@
}
/**
+ * Creates or removes filtering objectives for a double-tagged host on a port.
+ *
+ * @param deviceId device identifier
+ * @param portNum port identifier for port to be filtered
+ * @param outerVlan outer VLAN ID
+ * @param innerVlan inner VLAN ID
+ * @param install true to install the filtering objective, false to remove
+ */
+ void processDoubleTaggedFilter(DeviceId deviceId, PortNumber portNum, VlanId outerVlan,
+ VlanId innerVlan, boolean install) {
+ FilteringObjective.Builder fob = buildDoubleTaggedFilteringObj(deviceId, portNum, outerVlan, innerVlan);
+ if (fob == null) {
+ // error encountered during build
+ return;
+ }
+ log.debug("{} double-tagged filtering objectives for dev/port: {}/{}",
+ install ? "Installing" : "Removing", deviceId, portNum);
+ ObjectiveContext context = new DefaultObjectiveContext(
+ (objective) -> log.debug("Filter for {}/{} {}", deviceId, portNum,
+ install ? "installed" : "removed"),
+ (objective, error) -> log.warn("Failed to {} filter for {}/{}: {}",
+ install ? "install" : "remove", deviceId, portNum, error));
+ if (install) {
+ srManager.flowObjectiveService.filter(deviceId, fob.add(context));
+ } else {
+ srManager.flowObjectiveService.filter(deviceId, fob.remove(context));
+ }
+ }
+
+ private FilteringObjective.Builder buildDoubleTaggedFilteringObj(DeviceId deviceId, PortNumber portNum,
+ VlanId outerVlan, VlanId innerVlan) {
+ MacAddress deviceMac;
+ try {
+ deviceMac = config.getDeviceMac(deviceId);
+ } catch (DeviceConfigNotFoundException e) {
+ log.warn(e.getMessage() + " Processing DoubleTaggedFilters aborted");
+ return null;
+ }
+ FilteringObjective.Builder fob = DefaultFilteringObjective.builder();
+ // Outer vlan id match should be appeared before inner vlan id match.
+ fob.withKey(Criteria.matchInPort(portNum))
+ .addCondition(Criteria.matchEthDst(deviceMac))
+ .addCondition(Criteria.matchVlanId(outerVlan))
+ .addCondition(Criteria.matchVlanId(innerVlan))
+ .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
+
+ TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+ // Pop outer vlan
+ tBuilder.popVlan();
+
+ // NOTE: Some switch hardware share the same filtering flow among different ports.
+ // We use this metadata to let the driver know that there is no more enabled port
+ // within the same VLAN on this device.
+ if (noMoreEnabledPort(deviceId, outerVlan)) {
+ tBuilder.wipeDeferred();
+ }
+
+ fob.withMeta(tBuilder.build());
+
+ fob.permit().fromApp(srManager.appId);
+ return fob;
+ }
+
+ /**
* Creates a forwarding objective to punt all IP packets, destined to the
* router's port IP addresses, to the controller. Note that the input
* port should not be matched on, as these packets can come from any input.
@@ -1447,4 +1523,130 @@
(srManager.getInternalVlanId(cp) != null && srManager.getInternalVlanId(cp).equals(vlanId))
);
}
+
+ /**
+ * Returns a forwarding objective builder for egress forwarding rules.
+ * <p>
+ * The forwarding objective installs flow rules to egress pipeline to push
+ * two vlan headers with given inner, outer vlan ids and outer tpid.
+ *
+ * @param portNumber port where the next hop attaches to
+ * @param dummyVlanId vlan ID of the packet to match
+ * @param innerVlan inner vlan ID of the next hop
+ * @param outerVlan outer vlan ID of the next hop
+ * @param outerTpid outer TPID of the next hop
+ * @return forwarding objective builder
+ */
+ private ForwardingObjective.Builder egressFwdObjBuilder(PortNumber portNumber, VlanId dummyVlanId,
+ VlanId innerVlan, VlanId outerVlan, EthType outerTpid) {
+ TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
+ sbuilder.matchVlanId(dummyVlanId);
+ TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
+ tbuilder.setOutput(portNumber).setVlanId(innerVlan);
+
+ if (outerTpid.equals(EthType.EtherType.QINQ.ethType())) {
+ tbuilder.pushVlan(outerTpid);
+ } else {
+ tbuilder.pushVlan();
+ }
+
+ tbuilder.setVlanId(outerVlan);
+ return DefaultForwardingObjective.builder()
+ .withSelector(sbuilder.build())
+ .withTreatment(tbuilder.build())
+ .fromApp(srManager.appId)
+ .makePermanent()
+ .withPriority(DEFAULT_PRIORITY)
+ .withFlag(ForwardingObjective.Flag.EGRESS);
+ }
+
+ /**
+ * Populates IP rules for a route that has double-tagged next hop.
+ *
+ * @param deviceId device ID of the device that next hop attaches to
+ * @param prefix IP prefix of the route
+ * @param hostMac MAC address of the next hop
+ * @param dummyVlan Dummy Vlan ID allocated for this route
+ * @param innerVlan inner Vlan ID of the next hop
+ * @param outerVlan outer Vlan ID of the next hop
+ * @param outerTpid outer TPID of the next hop
+ * @param outPort port where the next hop attaches to
+ */
+ void populateDoubleTaggedRoute(DeviceId deviceId, IpPrefix prefix, MacAddress hostMac, VlanId dummyVlan,
+ VlanId innerVlan, VlanId outerVlan, EthType outerTpid, PortNumber outPort) {
+ ForwardingObjective.Builder fwdBuilder;
+ log.debug("Populate direct routing entry for double-tagged host route {} at {}:{}",
+ prefix, deviceId, outPort);
+
+ ForwardingObjective.Builder egressFwdBuilder = egressFwdObjBuilder(
+ outPort, dummyVlan, innerVlan, outerVlan, outerTpid);
+ DefaultObjectiveContext egressFwdContext = new DefaultObjectiveContext(
+ objective -> log.debug("Egress rule for IP {} is populated", prefix.address()),
+ (objective, error) -> {
+ log.warn("Failed to populate egress rule for IP {}: {}", prefix.address(), error);
+ srManager.dummyVlanIdStore().remove(new DummyVlanIdStoreKey(
+ new ConnectPoint(deviceId, outPort), prefix.address()
+ ));
+ });
+ try {
+ fwdBuilder = routingFwdObjBuilder(deviceId, prefix, hostMac, dummyVlan, outPort, false);
+ } catch (DeviceConfigNotFoundException e) {
+ log.error(e.getMessage() + " Aborting populateDoubleTaggedRoute");
+ return;
+ }
+ if (fwdBuilder == null) {
+ log.error("Aborting double-tagged host routing table entry due to error for dev:{} route:{}",
+ deviceId, prefix);
+ return;
+ }
+
+ // Egress forwarding objective should be installed after the nextObjective for the output port is installed.
+ // Installation of routingFwdObj will ensure the installation of the nextObjective.
+ int nextId = fwdBuilder.add().nextId();
+ DefaultObjectiveContext context = new DefaultObjectiveContext(objective -> {
+ log.debug("Direct routing rule for double-tagged host route {} populated. nextId={}", prefix, nextId);
+ srManager.flowObjectiveService.forward(deviceId, egressFwdBuilder.add(egressFwdContext));
+ }, (objective, error) ->
+ log.warn("Failed to populate direct routing rule for double-tagged host route {}: {}", prefix, error)
+ );
+ srManager.flowObjectiveService.forward(deviceId, fwdBuilder.add(context));
+ rulePopulationCounter.incrementAndGet();
+ }
+
+ /**
+ * Revokes IP rules for a route that has double-tagged next hop.
+ *
+ * @param deviceId device ID of the device that next hop attaches to
+ * @param prefix IP prefix of the route
+ * @param hostMac MAC address of the next hop
+ * @param hostVlan Vlan ID of the next hop
+ * @param innerVlan inner Vlan ID of the next hop
+ * @param outerVlan outer Vlan ID of the next hop
+ * @param outerTpid outer TPID of the next hop
+ * @param outPort port where the next hop attaches to
+ */
+ void revokeDoubleTaggedRoute(DeviceId deviceId, IpPrefix prefix, MacAddress hostMac,
+ VlanId hostVlan, VlanId innerVlan, VlanId outerVlan,
+ EthType outerTpid, PortNumber outPort) {
+ revokeRoute(deviceId, prefix, hostMac, hostVlan, outPort);
+
+ DummyVlanIdStoreKey key = new DummyVlanIdStoreKey(
+ new ConnectPoint(deviceId, outPort), prefix.address());
+ VlanId dummyVlanId = srManager.dummyVlanIdStore().get(key);
+ if (dummyVlanId == null) {
+ log.warn("Failed to retrieve dummy VLAN ID for {}/{} and {}",
+ deviceId, outPort, prefix.address());
+ return;
+ }
+ ForwardingObjective.Builder fob = egressFwdObjBuilder(
+ outPort, dummyVlanId, innerVlan, outerVlan, outerTpid);
+ DefaultObjectiveContext context = new DefaultObjectiveContext(objective -> {
+ log.debug("Egress rule for IP {} revoked", prefix.address());
+ srManager.dummyVlanIdStore().remove(key);
+ }, (objective, error) -> {
+ log.warn("Failed to revoke egress rule for IP {}: {}", prefix.address(), error);
+ });
+ srManager.flowObjectiveService.forward(deviceId, fob.remove(context));
+ }
+
}
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
index 0322201..388d213 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
@@ -41,6 +41,7 @@
import org.onosproject.cluster.ClusterEvent;
import org.onosproject.cluster.ClusterEventListener;
import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.LeadershipService;
import org.onosproject.cluster.NodeId;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
@@ -118,6 +119,7 @@
import org.onosproject.segmentrouting.pwaas.L2TunnelDescription;
import org.onosproject.segmentrouting.storekey.DestinationSetNextObjectiveStoreKey;
+import org.onosproject.segmentrouting.storekey.DummyVlanIdStoreKey;
import org.onosproject.segmentrouting.storekey.McastStoreKey;
import org.onosproject.segmentrouting.storekey.PortNextObjectiveStoreKey;
import org.onosproject.segmentrouting.storekey.VlanNextObjectiveStoreKey;
@@ -148,6 +150,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
+import java.util.stream.IntStream;
import static com.google.common.base.Preconditions.checkState;
import static org.onlab.packet.Ethernet.TYPE_ARP;
@@ -222,6 +225,9 @@
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
public WorkPartitionService workPartitionService;
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ public LeadershipService leadershipService;
+
@Property(name = "activeProbing", boolValue = true,
label = "Enable active probing to discover dual-homed hosts.")
boolean activeProbing = true;
@@ -287,6 +293,13 @@
private EventuallyConsistentMap<PortNextObjectiveStoreKey, Integer>
portNextObjStore = null;
+ /**
+ * Per port dummy VLAN ID store with (connect point + ip address) as key.
+ * Used to keep track on dummy VLAN ID allocation.
+ */
+ private EventuallyConsistentMap<DummyVlanIdStoreKey, VlanId>
+ dummyVlanIdStore = null;
+
private EventuallyConsistentMap<String, Tunnel> tunnelStore = null;
private EventuallyConsistentMap<String, Policy> policyStore = null;
@@ -342,6 +355,12 @@
*/
public static final VlanId INTERNAL_VLAN = VlanId.vlanId((short) 4094);
+ /**
+ * Minumum and maximum value of dummy VLAN ID to be allocated.
+ */
+ public static final int MIN_DUMMY_VLAN_ID = 2;
+ public static final int MAX_DUMMY_VLAN_ID = 4093;
+
@Activate
protected void activate(ComponentContext context) {
appId = coreService.registerApplication(APP_NAME);
@@ -379,6 +398,14 @@
.withTimestampProvider((k, v) -> new WallClockTimestamp())
.build();
+ EventuallyConsistentMapBuilder<DummyVlanIdStoreKey, VlanId>
+ dummyVlanIdMapBuilder = storageService.eventuallyConsistentMapBuilder();
+ dummyVlanIdStore = dummyVlanIdMapBuilder
+ .withName("dummyvlanidstore")
+ .withSerializer(createSerializer())
+ .withTimestampProvider((k, v) -> new WallClockTimestamp())
+ .build();
+
EventuallyConsistentMapBuilder<String, Tunnel> tunnelMapBuilder =
storageService.eventuallyConsistentMapBuilder();
tunnelStore = tunnelMapBuilder
@@ -484,7 +511,8 @@
L2Tunnel.class,
L2TunnelPolicy.class,
DefaultL2Tunnel.class,
- DefaultL2TunnelPolicy.class
+ DefaultL2TunnelPolicy.class,
+ DummyVlanIdStoreKey.class
);
}
@@ -524,6 +552,7 @@
dsNextObjStore.destroy();
vlanNextObjStore.destroy();
portNextObjStore.destroy();
+ dummyVlanIdStore.destroy();
tunnelStore.destroy();
policyStore.destroy();
@@ -774,6 +803,16 @@
}
/**
+ * Per port dummy VLAN ID store with (connect point + ip address) as key.
+ * Used to keep track on dummy VLAN ID allocation.
+ *
+ * @return dummy vlan id store.
+ */
+ public EventuallyConsistentMap<DummyVlanIdStoreKey, VlanId> dummyVlanIdStore() {
+ return dummyVlanIdStore;
+ }
+
+ /**
* Returns the MPLS-ECMP configuration which indicates whether ECMP on
* labeled packets should be programmed or not.
*
@@ -986,6 +1025,35 @@
return defaultRoutingHandler;
}
+ /**
+ * Returns new dummy VLAN ID.
+ * Dummy VLAN ID should be unique in each connect point.
+ *
+ * @param cp connect point
+ * @param ipAddress IP address
+ * @return new dummy VLAN ID. Returns VlanId.NONE if no VLAN ID is available.
+ */
+ public synchronized VlanId allocateDummyVlanId(ConnectPoint cp, IpAddress ipAddress) {
+ Set<VlanId> usedVlanId = Sets.union(getVlanPortMap(cp.deviceId()).keySet(),
+ dummyVlanIdStore.entrySet().stream().filter(entry ->
+ (entry.getKey()).connectPoint().equals(cp))
+ .map(Map.Entry::getValue)
+ .collect(Collectors.toSet()));
+
+ VlanId dummyVlanId = IntStream.range(MIN_DUMMY_VLAN_ID, MAX_DUMMY_VLAN_ID).mapToObj(
+ i -> VlanId.vlanId((short) (i & 0xFFFF))
+ ).filter(vlanId -> !usedVlanId.contains(vlanId)).findFirst().orElse(VlanId.NONE);
+
+ if (!dummyVlanId.equals(VlanId.NONE)) {
+ this.dummyVlanIdStore.put(new DummyVlanIdStoreKey(cp, ipAddress), dummyVlanId);
+ log.debug("Dummy VLAN ID {} is allocated to {}, {}", dummyVlanId, cp, ipAddress);
+ } else {
+ log.error("Failed to allocate dummy VLAN ID for {}, {}", cp, ipAddress);
+ }
+ return dummyVlanId;
+ }
+
+
private class InternalPacketProcessor implements PacketProcessor {
@Override
public void process(PacketContext context) {
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingNeighbourHandler.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingNeighbourHandler.java
index f03e0be..5752bfa 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingNeighbourHandler.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingNeighbourHandler.java
@@ -19,6 +19,7 @@
import org.onlab.packet.Ethernet;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
import org.onosproject.net.neighbour.NeighbourMessageContext;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
@@ -113,7 +114,9 @@
* @param hostService the host service
*/
protected void sendResponse(NeighbourMessageContext pkt, MacAddress targetMac, HostService hostService) {
- HostId dstId = HostId.hostId(pkt.srcMac(), pkt.vlan());
+ short vlanId = pkt.packet().getQinQVID();
+ HostId dstId = HostId.hostId(pkt.srcMac(), vlanId == Ethernet.VLAN_UNTAGGED
+ ? pkt.vlan() : VlanId.vlanId(vlanId));
Host dst = hostService.getHost(dstId);
if (dst == null) {
log.warn("Cannot send {} response to host {} - does not exist in the store",
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/storekey/DummyVlanIdStoreKey.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/storekey/DummyVlanIdStoreKey.java
new file mode 100644
index 0000000..65e7ca3
--- /dev/null
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/storekey/DummyVlanIdStoreKey.java
@@ -0,0 +1,86 @@
+/*
+ * 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.segmentrouting.storekey;
+
+import org.onlab.packet.IpAddress;
+import org.onosproject.net.ConnectPoint;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Key of VLAN ID to DummyVlanIdStore.
+ */
+public class DummyVlanIdStoreKey {
+ private final ConnectPoint connectPoint;
+ private final IpAddress ipAddress;
+
+ /**
+ * Construct the key of dummy vlan id key store.
+ *
+ * @param connectPoint connect point that this vlan id is associated
+ * @param ipAddress IP address that this vlan id is associated
+ */
+ public DummyVlanIdStoreKey(ConnectPoint connectPoint, IpAddress ipAddress) {
+ this.connectPoint = connectPoint;
+ this.ipAddress = ipAddress;
+ }
+
+ /**
+ * Returns the connect point in the key.
+ *
+ * @return connect point
+ */
+ public ConnectPoint connectPoint() {
+ return connectPoint;
+ }
+
+ /**
+ * Returns the IP address in the key.
+ *
+ * @return IP address
+ */
+ public IpAddress ipAddress() {
+ return ipAddress;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof DummyVlanIdStoreKey)) {
+ return false;
+ }
+ DummyVlanIdStoreKey that = (DummyVlanIdStoreKey) o;
+ return (Objects.equals(this.connectPoint, that.connectPoint) &&
+ Objects.equals(this.ipAddress, that.ipAddress));
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(connectPoint, ipAddress);
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(getClass())
+ .add("connectPoint", connectPoint)
+ .add("ipAddress", ipAddress)
+ .toString();
+ }
+}
diff --git a/core/api/src/main/java/org/onosproject/net/config/basics/BasicHostConfig.java b/core/api/src/main/java/org/onosproject/net/config/basics/BasicHostConfig.java
index bba095b..3d19003 100644
--- a/core/api/src/main/java/org/onosproject/net/config/basics/BasicHostConfig.java
+++ b/core/api/src/main/java/org/onosproject/net/config/basics/BasicHostConfig.java
@@ -147,7 +147,7 @@
}
public EthType outerTpid() {
- short tpid = (short) (Integer.decode(get(OUTER_TPID, "0")) & 0xFFFF);
+ short tpid = (short) (Integer.decode(get(OUTER_TPID, "0x8100")) & 0xFFFF);
if (!(tpid == EthType.EtherType.VLAN.ethType().toShort() ||
tpid == EthType.EtherType.QINQ.ethType().toShort())) {
return EthType.EtherType.UNKNOWN.ethType();
diff --git a/core/api/src/main/java/org/onosproject/net/flowobjective/ForwardingObjective.java b/core/api/src/main/java/org/onosproject/net/flowobjective/ForwardingObjective.java
index 1d62a23..a0c81af 100644
--- a/core/api/src/main/java/org/onosproject/net/flowobjective/ForwardingObjective.java
+++ b/core/api/src/main/java/org/onosproject/net/flowobjective/ForwardingObjective.java
@@ -37,6 +37,14 @@
* A specific forwarding objective represents a specific rule matching one or
* more header fields. The installation of this rule may result in several rules
* at the device. For example, one per table type.
+ *
+ * There is one additional type of forwarding objective:
+ *
+ * - Egress
+ *
+ * An egress forwarding objecrive represents a flow rule that is inserted into
+ * egress tables, only if they exist in the device.
+ *
*/
@Beta
public interface ForwardingObjective extends Objective {
@@ -54,7 +62,12 @@
/**
* A monolithic objective.
*/
- VERSATILE
+ VERSATILE,
+
+ /**
+ * An objective to program egress pipeline.
+ */
+ EGRESS
}
/**
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/ForwardingObjectiveCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/ForwardingObjectiveCodec.java
index 281d63c..8ec51fa 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/ForwardingObjectiveCodec.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/ForwardingObjectiveCodec.java
@@ -125,9 +125,12 @@
case "VERSATILE":
builder.withFlag(ForwardingObjective.Flag.VERSATILE);
break;
+ case "EGRESS":
+ builder.withFlag(ForwardingObjective.Flag.EGRESS);
+ break;
default:
throw new IllegalArgumentException("The requested flag " + flagStr +
- " is not defined for FilteringObjective.");
+ " is not defined for ForwardingObjective.");
}
// decode selector
diff --git a/drivers/default/src/main/java/org/onosproject/driver/extensions/Ofdpa3CopyField.java b/drivers/default/src/main/java/org/onosproject/driver/extensions/Ofdpa3CopyField.java
index 8ec8869..aba38a6 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/extensions/Ofdpa3CopyField.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/extensions/Ofdpa3CopyField.java
@@ -28,6 +28,11 @@
* OFDPA copy field extension instruction.
*/
public class Ofdpa3CopyField extends AbstractExtension implements ExtensionTreatment {
+ // OXM ID of VLAN_VID field from OF-DPA spec.
+ public static final int OXM_ID_VLAN_VID = 0x80000c02;
+ // OXM ID of PACKET_REG(1) field from OF-DPA spec.
+ public static final int OXM_ID_PACKET_REG_1 = 0x80010200;
+
private int nBits;
private int srcOffset;
private int dstOffset;
@@ -42,10 +47,8 @@
nBits = 0;
srcOffset = 0;
dstOffset = 0;
- // this default value corresponds to the vlan-id from ofdpa spec.
- src = 0x80000c02;
- // this default value corresponds to register 1 from ofdpa spec.
- dst = 0x80010200;
+ src = OXM_ID_VLAN_VID;
+ dst = OXM_ID_PACKET_REG_1;
}
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2Pipeline.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2Pipeline.java
index 6fd0450..04d581a 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2Pipeline.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2Pipeline.java
@@ -18,6 +18,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import org.onlab.osgi.ServiceDirectory;
+import org.onlab.packet.EthType;
import org.onlab.packet.Ethernet;
import org.onlab.packet.EthType.EtherType;
import org.onlab.packet.IpAddress;
@@ -27,8 +28,11 @@
import org.onlab.util.KryoNamespace;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
+import org.onosproject.driver.extensions.Ofdpa3CopyField;
import org.onosproject.driver.extensions.Ofdpa3MplsType;
import org.onosproject.driver.extensions.Ofdpa3SetMplsType;
+import org.onosproject.driver.extensions.OfdpaMatchActsetOutput;
+import org.onosproject.driver.extensions.OfdpaMatchAllowVlanTranslation;
import org.onosproject.driver.extensions.OfdpaMatchVlanVid;
import org.onosproject.driver.extensions.OfdpaSetVlanVid;
import org.onosproject.net.DeviceId;
@@ -90,6 +94,7 @@
import java.util.Deque;
import java.util.List;
import java.util.Objects;
+import java.util.Optional;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -99,6 +104,8 @@
import static org.onlab.packet.MacAddress.IPV6_MULTICAST;
import static org.onlab.packet.MacAddress.NONE;
import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.driver.extensions.Ofdpa3CopyField.OXM_ID_PACKET_REG_1;
+import static org.onosproject.driver.extensions.Ofdpa3CopyField.OXM_ID_VLAN_VID;
import static org.onosproject.driver.pipeline.ofdpa.OfdpaGroupHandlerUtility.*;
import static org.onosproject.net.flow.criteria.Criterion.Type.ETH_TYPE;
import static org.onosproject.net.group.GroupDescription.Type.SELECT;
@@ -123,6 +130,9 @@
protected static final int MPLS_TYPE_TABLE = 29;
protected static final int BRIDGING_TABLE = 50;
protected static final int ACL_TABLE = 60;
+ protected static final int EGRESS_VLAN_FLOW_TABLE = 210;
+ protected static final int EGRESS_DSCP_PCP_REMARK_FLOW_TABLE = 230;
+ protected static final int EGRESS_TPID_FLOW_TABLE = 235;
protected static final int MAC_LEARNING_TABLE = 254;
protected static final long OFPP_MAX = 0xffffff00L;
@@ -138,6 +148,10 @@
protected static final int MPLS_NNI_PORT_BASE = 0x00020000;
protected static final int MPLS_NNI_PORT_MAX = 0x0002FFFF;
+ protected static final short ALLOW_VLAN_TRANSLATION = 1;
+ protected static final int COPY_FIELD_NBITS = 12;
+ protected static final int COPY_FIELD_OFFSET = 0;
+
private final Logger log = getLogger(getClass());
protected ServiceDirectory serviceDirectory;
protected FlowRuleService flowRuleService;
@@ -1016,6 +1030,8 @@
return processSpecific(fwd);
case VERSATILE:
return processVersatile(fwd);
+ case EGRESS:
+ return processEgress(fwd);
default:
fail(fwd, ObjectiveError.UNKNOWN);
log.warn("Unknown forwarding flag {}", fwd.flag());
@@ -1024,6 +1040,103 @@
}
/**
+ * In the OF-DPA 2.0 pipeline, egress forwarding objectives go to the
+ * egress tables.
+ * @param fwd the forwarding objective of type 'egress'
+ * @return a collection of flow rules to be sent to the switch. An empty
+ * collection may be returned if there is a problem in processing
+ * the flow rule
+ */
+ protected Collection<FlowRule> processEgress(ForwardingObjective fwd) {
+ log.debug("Processing egress forwarding objective:{} in dev:{}",
+ fwd, deviceId);
+
+ List<FlowRule> rules = new ArrayList<>();
+
+ // Build selector
+ TrafficSelector.Builder sb = DefaultTrafficSelector.builder();
+ VlanIdCriterion vlanIdCriterion = (VlanIdCriterion) fwd.selector().getCriterion(Criterion.Type.VLAN_VID);
+ if (vlanIdCriterion == null) {
+ log.error("Egress forwarding objective:{} must include vlanId", fwd.id());
+ fail(fwd, ObjectiveError.BADPARAMS);
+ return rules;
+ }
+
+ Optional<Instruction> outInstr = fwd.treatment().allInstructions().stream()
+ .filter(instruction -> instruction instanceof OutputInstruction).findFirst();
+ if (!outInstr.isPresent()) {
+ log.error("Egress forwarding objective:{} must include output port", fwd.id());
+ fail(fwd, ObjectiveError.BADPARAMS);
+ return rules;
+ }
+
+ PortNumber portNumber = ((OutputInstruction) outInstr.get()).port();
+
+ sb.matchVlanId(vlanIdCriterion.vlanId());
+ OfdpaMatchActsetOutput actsetOutput = new OfdpaMatchActsetOutput(portNumber);
+ sb.extension(actsetOutput, deviceId);
+
+ sb.extension(new OfdpaMatchAllowVlanTranslation(ALLOW_VLAN_TRANSLATION), deviceId);
+
+ // Build a flow rule for Egress VLAN Flow table
+ TrafficTreatment.Builder tb = DefaultTrafficTreatment.builder();
+ tb.transition(EGRESS_DSCP_PCP_REMARK_FLOW_TABLE);
+ if (fwd.treatment() != null) {
+ for (Instruction instr : fwd.treatment().allInstructions()) {
+ if (instr instanceof L2ModificationInstruction &&
+ ((L2ModificationInstruction) instr).subtype() == L2SubType.VLAN_ID) {
+ tb.immediate().add(instr);
+ }
+ if (instr instanceof L2ModificationInstruction &&
+ ((L2ModificationInstruction) instr).subtype() == L2SubType.VLAN_PUSH) {
+ tb.immediate().pushVlan();
+ EthType ethType = ((L2ModificationInstruction.ModVlanHeaderInstruction) instr).ethernetType();
+ if (ethType.equals(EtherType.QINQ.ethType())) {
+ // Build a flow rule for Egress TPID Flow table
+ TrafficSelector tpidSelector = DefaultTrafficSelector.builder()
+ .extension(actsetOutput, deviceId)
+ .matchVlanId(VlanId.ANY).build();
+
+ TrafficTreatment tpidTreatment = DefaultTrafficTreatment.builder()
+ .extension(new Ofdpa3CopyField(COPY_FIELD_NBITS, COPY_FIELD_OFFSET,
+ COPY_FIELD_OFFSET, OXM_ID_VLAN_VID,
+ OXM_ID_PACKET_REG_1),
+ deviceId)
+ .popVlan()
+ .pushVlan(EtherType.QINQ.ethType())
+ .extension(new Ofdpa3CopyField(COPY_FIELD_NBITS, COPY_FIELD_OFFSET,
+ COPY_FIELD_OFFSET, OXM_ID_PACKET_REG_1,
+ OXM_ID_VLAN_VID),
+ deviceId)
+ .build();
+
+ FlowRule.Builder tpidRuleBuilder = DefaultFlowRule.builder()
+ .fromApp(fwd.appId())
+ .withPriority(fwd.priority())
+ .forDevice(deviceId)
+ .withSelector(tpidSelector)
+ .withTreatment(tpidTreatment)
+ .makePermanent()
+ .forTable(EGRESS_TPID_FLOW_TABLE);
+ rules.add(tpidRuleBuilder.build());
+ }
+ }
+ }
+ }
+
+ FlowRule.Builder ruleBuilder = DefaultFlowRule.builder()
+ .fromApp(fwd.appId())
+ .withPriority(fwd.priority())
+ .forDevice(deviceId)
+ .withSelector(sb.build())
+ .withTreatment(tb.build())
+ .makePermanent()
+ .forTable(EGRESS_VLAN_FLOW_TABLE);
+ rules.add(ruleBuilder.build());
+ return rules;
+ }
+
+ /**
* In the OF-DPA 2.0 pipeline, versatile forwarding objectives go to the
* ACL table.
* @param fwd the forwarding objective of type 'versatile'
@@ -1745,7 +1858,7 @@
return null;
}
- private static MacAddress readEthDstFromTreatment(TrafficTreatment treatment) {
+ protected static MacAddress readEthDstFromTreatment(TrafficTreatment treatment) {
if (treatment == null) {
return null;
}
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3GroupHandler.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3GroupHandler.java
index e0e5099..6d28a11 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3GroupHandler.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3GroupHandler.java
@@ -17,14 +17,22 @@
package org.onosproject.driver.pipeline.ofdpa;
import com.google.common.collect.Lists;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.GroupId;
+import org.onosproject.driver.extensions.Ofdpa3AllowVlanTranslationType;
import org.onosproject.driver.extensions.Ofdpa3PushCw;
import org.onosproject.driver.extensions.Ofdpa3PushL2Header;
+import org.onosproject.driver.extensions.OfdpaSetAllowVlanTranslation;
+import org.onosproject.driver.extensions.OfdpaSetVlanVid;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.VlanIdCriterion;
import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
import org.onosproject.net.flow.instructions.L2ModificationInstruction;
import org.onosproject.net.flow.instructions.L3ModificationInstruction;
import org.onosproject.net.flowobjective.NextObjective;
@@ -62,7 +70,9 @@
protected GroupInfo createL2L3Chain(TrafficTreatment treatment, int nextId,
ApplicationId appId, boolean mpls,
TrafficSelector meta) {
- return createL2L3ChainInternal(treatment, nextId, appId, mpls, meta, false);
+ return isUnfiltered(treatment, meta) ?
+ createUnfilteredL2L3Chain(treatment, nextId, appId, false) :
+ createL2L3ChainInternal(treatment, nextId, appId, mpls, meta, false);
}
@Override
@@ -369,4 +379,136 @@
}
}
// TODO Introduce in the future an inner class to return two treatments
+
+ /**
+ * Internal implementation of createL2L3Chain for L2 unfiltered interface group.
+ *
+ * @param treatment that needs to be broken up to create the group chain
+ * @param nextId of the next objective that needs this group chain
+ * @param appId of the application that sent this next objective
+ * @param useSetVlanExtension use the setVlanVid extension that has is_present bit set to 0.
+ * @return GroupInfo containing the GroupDescription of the
+ * L2 Unfiltered Interface group(inner) and the GroupDescription of the (outer)
+ * L3Unicast group. May return null if there is an error in processing the chain.
+ */
+ private GroupInfo createUnfilteredL2L3Chain(TrafficTreatment treatment, int nextId,
+ ApplicationId appId, boolean useSetVlanExtension) {
+ // for the l2 unfiltered interface group, get port info
+ // for the l3 unicast group, get the src/dst mac, and vlan info
+ TrafficTreatment.Builder outerTtb = DefaultTrafficTreatment.builder();
+ TrafficTreatment.Builder innerTtb = DefaultTrafficTreatment.builder();
+ VlanId vlanId;
+ long portNum = 0;
+ MacAddress srcMac;
+ MacAddress dstMac;
+ for (Instruction ins : treatment.allInstructions()) {
+ if (ins.type() == Instruction.Type.L2MODIFICATION) {
+ L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
+ switch (l2ins.subtype()) {
+ case ETH_DST:
+ dstMac = ((L2ModificationInstruction.ModEtherInstruction) l2ins).mac();
+ outerTtb.setEthDst(dstMac);
+ break;
+ case ETH_SRC:
+ srcMac = ((L2ModificationInstruction.ModEtherInstruction) l2ins).mac();
+ outerTtb.setEthSrc(srcMac);
+ break;
+ case VLAN_ID:
+ vlanId = ((L2ModificationInstruction.ModVlanIdInstruction) l2ins).vlanId();
+ if (useSetVlanExtension) {
+ OfdpaSetVlanVid ofdpaSetVlanVid = new OfdpaSetVlanVid(vlanId);
+ outerTtb.extension(ofdpaSetVlanVid, deviceId);
+ } else {
+ outerTtb.setVlanId(vlanId);
+ }
+ break;
+ default:
+ break;
+ }
+ } else if (ins.type() == Instruction.Type.OUTPUT) {
+ portNum = ((Instructions.OutputInstruction) ins).port().toLong();
+ innerTtb.add(ins);
+ } else {
+ log.debug("Driver does not handle this type of TrafficTreatment"
+ + " instruction in l2l3chain: {} - {}", ins.type(),
+ ins);
+ }
+ }
+
+ innerTtb.extension(new OfdpaSetAllowVlanTranslation(
+ Ofdpa3AllowVlanTranslationType.ALLOW), deviceId);
+
+ // assemble information for ofdpa l2 unfiltered interface group
+ int l2groupId = l2UnfilteredGroupId(portNum);
+ // a globally unique groupkey that is different for ports in the same device,
+ // but different for the same portnumber on different devices. Also different
+ // for the various group-types created out of the same next objective.
+ int l2gk = l2UnfilteredGroupKey(deviceId, portNum);
+ final GroupKey l2groupkey = new DefaultGroupKey(Ofdpa3Pipeline.appKryo.serialize(l2gk));
+
+ // assemble information for outer group (L3Unicast)
+ GroupDescription outerGrpDesc;
+ int l3unicastIndex = getNextAvailableIndex();
+ int l3groupId = L3_UNICAST_TYPE | (TYPE_MASK & l3unicastIndex);
+ final GroupKey l3groupkey = new DefaultGroupKey(
+ Ofdpa3Pipeline.appKryo.serialize(l3unicastIndex));
+ outerTtb.group(new GroupId(l2groupId));
+ // create the l3unicast group description to wait for the
+ // l2 unfiltered interface group to be processed
+ GroupBucket l3unicastGroupBucket =
+ DefaultGroupBucket.createIndirectGroupBucket(outerTtb.build());
+ outerGrpDesc = new DefaultGroupDescription(
+ deviceId,
+ GroupDescription.Type.INDIRECT,
+ new GroupBuckets(Collections.singletonList(l3unicastGroupBucket)),
+ l3groupkey,
+ l3groupId,
+ appId);
+ log.debug("Trying L3Unicast: device:{} gid:{} gkey:{} nextid:{}",
+ deviceId, Integer.toHexString(l3groupId),
+ l3groupkey, nextId);
+
+ // store l2groupkey with the groupChainElem for the outer-group that depends on it
+ GroupChainElem gce = new GroupChainElem(outerGrpDesc, 1, false, deviceId);
+ updatePendingGroups(l2groupkey, gce);
+
+ // create group description for the inner l2 unfiltered interface group
+ GroupBucket l2InterfaceGroupBucket =
+ DefaultGroupBucket.createIndirectGroupBucket(innerTtb.build());
+ GroupDescription l2groupDescription =
+ new DefaultGroupDescription(deviceId,
+ GroupDescription.Type.INDIRECT,
+ new GroupBuckets(Collections.singletonList(l2InterfaceGroupBucket)),
+ l2groupkey,
+ l2groupId,
+ appId);
+ log.debug("Trying L2Unfiltered: device:{} gid:{} gkey:{} nextId:{}",
+ deviceId, Integer.toHexString(l2groupId), l2groupkey, nextId);
+ return new GroupInfo(l2groupDescription, outerGrpDesc);
+ }
+
+ /**
+ * Helper method to decide whether L2 Interface group or L2 Unfiltered group needs to be created.
+ * L2 Unfiltered group will be created if meta has VlanIdCriterion with VlanId.ANY, and
+ * treatment has set Vlan ID action.
+ *
+ * @param treatment treatment passed in by the application as part of the nextObjective
+ * @param meta metadata passed in by the application as part of the nextObjective
+ * @return true if L2 Unfiltered group needs to be created, false otherwise.
+ */
+ private boolean isUnfiltered(TrafficTreatment treatment, TrafficSelector meta) {
+ if (meta == null || treatment == null) {
+ return false;
+ }
+ VlanIdCriterion vlanIdCriterion = (VlanIdCriterion) meta.getCriterion(Criterion.Type.VLAN_VID);
+ if (vlanIdCriterion == null || !vlanIdCriterion.vlanId().equals(VlanId.ANY)) {
+ return false;
+ }
+
+ return treatment.allInstructions().stream()
+ .filter(i -> (i.type() == Instruction.Type.L2MODIFICATION
+ && ((L2ModificationInstruction) i).subtype() == L2ModificationInstruction.L2SubType.VLAN_ID))
+ .count() == 1;
+ }
+
}
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3Pipeline.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3Pipeline.java
index 7ea79bd..7377a20 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3Pipeline.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3Pipeline.java
@@ -17,6 +17,7 @@
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;
@@ -27,6 +28,8 @@
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;
@@ -37,7 +40,9 @@
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;
@@ -58,6 +63,7 @@
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;
@@ -218,12 +224,230 @@
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;
}
- // If it is not a pseudo wire flow we fall back
- // to the OFDPA 2.0 pipeline.
- super.processFilter(filteringObjective, install, applicationId);
+
+ 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;
}
/**
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/OfdpaGroupHandlerUtility.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/OfdpaGroupHandlerUtility.java
index 04ed65a..002df22 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/OfdpaGroupHandlerUtility.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/OfdpaGroupHandlerUtility.java
@@ -66,6 +66,7 @@
* L3 VPN Groups have <4bits-9><4bits-2><24bits-index>
*/
static final int L2_INTERFACE_TYPE = 0x00000000;
+ static final int L2_UNFILTERED_TYPE = 0xb0000000;
static final int L3_INTERFACE_TYPE = 0x50000000;
static final int L3_UNICAST_TYPE = 0x20000000;
static final int L3_MULTICAST_TYPE = 0x60000000;
@@ -452,6 +453,10 @@
return L2_INTERFACE_TYPE | (vlanId.toShort() << 16) | (int) portNum;
}
+ public static int l2UnfilteredGroupId(long portNum) {
+ return L2_UNFILTERED_TYPE | (int) portNum;
+ }
+
/**
* Returns a hash as the L2 Interface Group Key.
*
@@ -471,6 +476,23 @@
}
/**
+ * Returns a hash as the L2 Unfiltered Interface Group Key.
+ *
+ * Keep the lower 6-bit for port since port number usually smaller than 64.
+ * Hash other information into remaining 28 bits.
+ *
+ * @param deviceId Device ID
+ * @param portNumber Port number
+ * @return L2 unfiltered interface group key
+ */
+ public static int l2UnfilteredGroupKey(DeviceId deviceId, long portNumber) {
+ int portLowerBits = (int) portNumber & PORT_LOWER_BITS_MASK;
+ long portHigherBits = portNumber & PORT_HIGHER_BITS_MASK;
+ int hash = Objects.hash(deviceId, portHigherBits);
+ return L2_UNFILTERED_TYPE | (TYPE_MASK & hash << 6) | portLowerBits;
+ }
+
+ /**
* Utility class for moving group information around.
*
* Example: Suppose we are trying to create a group-chain A-B-C-D, where
diff --git a/utils/misc/src/main/java/org/onlab/packet/ARP.java b/utils/misc/src/main/java/org/onlab/packet/ARP.java
index b76ff61..998062c 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ARP.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ARP.java
@@ -399,6 +399,8 @@
eth.setDestinationMACAddress(request.getSourceMAC());
eth.setSourceMACAddress(srcMac);
eth.setEtherType(Ethernet.TYPE_ARP);
+ eth.setQinQVID(request.getQinQVID());
+ eth.setQinQTPID(request.getQinQTPID());
eth.setVlanID(request.getVlanID());
ARP arp = new ARP();
diff --git a/utils/misc/src/main/java/org/onlab/packet/ICMP.java b/utils/misc/src/main/java/org/onlab/packet/ICMP.java
index 92b61ba..68fddcb 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ICMP.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ICMP.java
@@ -250,6 +250,8 @@
ipReply.setPayload(icmpReply);
ethReply.setEtherType(Ethernet.TYPE_IPV4);
+ ethReply.setQinQVID(ethRequest.getQinQVID());
+ ethReply.setQinQTPID(ethRequest.getQinQTPID());
ethReply.setVlanID(ethRequest.getVlanID());
ethReply.setDestinationMACAddress(ethRequest.getSourceMACAddress());
ethReply.setSourceMACAddress(ethRequest.getDestinationMACAddress());
diff --git a/utils/misc/src/main/java/org/onlab/packet/ICMP6.java b/utils/misc/src/main/java/org/onlab/packet/ICMP6.java
index 0bcf129..31aabfc 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ICMP6.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ICMP6.java
@@ -385,6 +385,8 @@
ipv6Reply.setPayload(icmpv6Reply);
ethReply.setEtherType(Ethernet.TYPE_IPV6);
+ ethReply.setQinQVID(ethRequest.getQinQVID());
+ ethReply.setQinQTPID(ethRequest.getQinQTPID());
ethReply.setVlanID(ethRequest.getVlanID());
ethReply.setDestinationMACAddress(ethRequest.getSourceMACAddress());
ethReply.setSourceMACAddress(ethRequest.getDestinationMACAddress());