[SDFAB-189] UpfProgrammable implementation for fabric v1model
Change-Id: I4ea7980830d761a0da8a78943c08229c2da9410d
(cherry picked from commit 8d630f1091c63ff6e7b4ea31669344c5274773cc)
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfTranslator.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfTranslator.java
new file mode 100644
index 0000000..d270371
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfTranslator.java
@@ -0,0 +1,441 @@
+/*
+ * Copyright 2021-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.pipelines.fabric.impl.behaviour.upf;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.onlab.packet.Ip4Address;
+import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.behaviour.upf.ForwardingActionRule;
+import org.onosproject.net.behaviour.upf.GtpTunnel;
+import org.onosproject.net.behaviour.upf.PacketDetectionRule;
+import org.onosproject.net.behaviour.upf.UpfInterface;
+import org.onosproject.net.behaviour.upf.UpfProgrammableException;
+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.criteria.PiCriterion;
+import org.onosproject.net.pi.model.PiActionId;
+import org.onosproject.net.pi.model.PiTableId;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.net.pi.runtime.PiTableAction;
+
+import java.util.Arrays;
+
+import static org.onosproject.pipelines.fabric.FabricConstants.CTR_ID;
+import static org.onosproject.pipelines.fabric.FabricConstants.DROP;
+import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_DOWNLINK_PDRS;
+import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_FARS;
+import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_INTERFACES;
+import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_LOAD_DBUF_FAR;
+import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_LOAD_IFACE;
+import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_LOAD_NORMAL_FAR;
+import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_LOAD_PDR;
+import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_LOAD_PDR_QOS;
+import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_LOAD_TUNNEL_FAR;
+import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_UPLINK_PDRS;
+import static org.onosproject.pipelines.fabric.FabricConstants.FAR_ID;
+import static org.onosproject.pipelines.fabric.FabricConstants.HDR_FAR_ID;
+import static org.onosproject.pipelines.fabric.FabricConstants.HDR_GTPU_IS_VALID;
+import static org.onosproject.pipelines.fabric.FabricConstants.HDR_IPV4_DST_ADDR;
+import static org.onosproject.pipelines.fabric.FabricConstants.HDR_TEID;
+import static org.onosproject.pipelines.fabric.FabricConstants.HDR_TUNNEL_IPV4_DST;
+import static org.onosproject.pipelines.fabric.FabricConstants.HDR_UE_ADDR;
+import static org.onosproject.pipelines.fabric.FabricConstants.NEEDS_GTPU_DECAP;
+import static org.onosproject.pipelines.fabric.FabricConstants.NOTIFY_CP;
+import static org.onosproject.pipelines.fabric.FabricConstants.QID;
+import static org.onosproject.pipelines.fabric.FabricConstants.SRC_IFACE;
+import static org.onosproject.pipelines.fabric.FabricConstants.TEID;
+import static org.onosproject.pipelines.fabric.FabricConstants.TUNNEL_DST_ADDR;
+import static org.onosproject.pipelines.fabric.FabricConstants.TUNNEL_SRC_ADDR;
+import static org.onosproject.pipelines.fabric.FabricConstants.TUNNEL_SRC_PORT;
+
+/**
+ * Provides logic to translate UPF entities into pipeline-specific ones and vice-versa.
+ * Implementation should be stateless, with all state delegated to FabricUpfStore.
+ */
+public class FabricUpfTranslator {
+
+ // UPF related constants
+ public static final int INTERFACE_ACCESS = 1;
+ public static final int INTERFACE_CORE = 2;
+ public static final int INTERFACE_DBUF = 3;
+
+ private final FabricUpfStore fabricUpfStore;
+
+ public FabricUpfTranslator(FabricUpfStore fabricUpfStore) {
+ this.fabricUpfStore = fabricUpfStore;
+ }
+
+ /**
+ * Returns true if the given table entry is a Packet Detection Rule from the physical fabric pipeline, and
+ * false otherwise.
+ *
+ * @param entry the entry that may or may not be a fabric.p4 PDR
+ * @return true if the entry is a fabric.p4 PDR
+ */
+ public boolean isFabricPdr(FlowRule entry) {
+ return entry.table().equals(FABRIC_INGRESS_SPGW_UPLINK_PDRS)
+ || entry.table().equals(FABRIC_INGRESS_SPGW_DOWNLINK_PDRS);
+ }
+
+ /**
+ * Returns true if the given table entry is a Forwarding Action Rule from the physical fabric pipeline, and
+ * false otherwise.
+ *
+ * @param entry the entry that may or may not be a fabric.p4 FAR
+ * @return true if the entry is a fabric.p4 FAR
+ */
+ public boolean isFabricFar(FlowRule entry) {
+ return entry.table().equals(FABRIC_INGRESS_SPGW_FARS);
+ }
+
+ /**
+ * Returns true if the given table entry is an interface table entry from the fabric.p4 physical pipeline, and
+ * false otherwise.
+ *
+ * @param entry the entry that may or may not be a fabric.p4 UPF interface
+ * @return true if the entry is a fabric.p4 UPF interface
+ */
+ public boolean isFabricInterface(FlowRule entry) {
+ return entry.table().equals(FABRIC_INGRESS_SPGW_INTERFACES);
+ }
+
+
+ /**
+ * Translate a fabric.p4 PDR table entry to a PacketDetectionRule instance for easier handling.
+ *
+ * @param entry the fabric.p4 entry to translate
+ * @return the corresponding PacketDetectionRule
+ * @throws UpfProgrammableException if the entry cannot be translated
+ */
+ public PacketDetectionRule fabricEntryToPdr(FlowRule entry)
+ throws UpfProgrammableException {
+ var pdrBuilder = PacketDetectionRule.builder();
+ Pair<PiCriterion, PiTableAction> matchActionPair = FabricUpfTranslatorUtil.fabricEntryToPiPair(entry);
+ PiCriterion match = matchActionPair.getLeft();
+ PiAction action = (PiAction) matchActionPair.getRight();
+
+ // Grab keys and parameters that are present for all PDRs
+ int globalFarId = FabricUpfTranslatorUtil.getParamInt(action, FAR_ID);
+ UpfRuleIdentifier farId = fabricUpfStore.localFarIdOf(globalFarId);
+
+ PiActionId actionId = action.id();
+ if (actionId.equals(FABRIC_INGRESS_SPGW_LOAD_PDR)) {
+ int schedulingPriority = 0;
+ pdrBuilder.withSchedulingPriority(schedulingPriority);
+ } else if (actionId.equals(FABRIC_INGRESS_SPGW_LOAD_PDR_QOS)) {
+ int queueId = FabricUpfTranslatorUtil.getParamInt(action, QID);
+ String schedulingPriority = fabricUpfStore.schedulingPriorityOf(queueId);
+ if (schedulingPriority == null) {
+ throw new UpfProgrammableException("Undefined Scheduling Priority");
+ }
+ pdrBuilder.withSchedulingPriority(Integer.parseInt(schedulingPriority));
+ } else {
+ throw new UpfProgrammableException("Unknown action ID");
+ }
+ pdrBuilder.withCounterId(FabricUpfTranslatorUtil.getParamInt(action, CTR_ID))
+ .withLocalFarId(farId.getSessionLocalId())
+ .withSessionId(farId.getPfcpSessionId());
+
+ if (FabricUpfTranslatorUtil.fieldIsPresent(match, HDR_TEID)) {
+ // F-TEID is only present for GTP-matching PDRs
+ ImmutableByteSequence teid = FabricUpfTranslatorUtil.getFieldValue(match, HDR_TEID);
+ Ip4Address tunnelDst = FabricUpfTranslatorUtil.getFieldAddress(match, HDR_TUNNEL_IPV4_DST);
+ pdrBuilder.withTeid(teid)
+ .withTunnelDst(tunnelDst);
+ } else if (FabricUpfTranslatorUtil.fieldIsPresent(match, HDR_UE_ADDR)) {
+ // And UE address is only present for non-GTP-matching PDRs
+ pdrBuilder.withUeAddr(FabricUpfTranslatorUtil.getFieldAddress(match, HDR_UE_ADDR));
+ } else {
+ throw new UpfProgrammableException("Read malformed PDR from dataplane!:" + entry);
+ }
+ return pdrBuilder.build();
+ }
+
+ /**
+ * Translate a fabric.p4 FAR table entry to a ForwardActionRule instance for easier handling.
+ *
+ * @param entry the fabric.p4 entry to translate
+ * @return the corresponding ForwardingActionRule
+ * @throws UpfProgrammableException if the entry cannot be translated
+ */
+ public ForwardingActionRule fabricEntryToFar(FlowRule entry)
+ throws UpfProgrammableException {
+ var farBuilder = ForwardingActionRule.builder();
+ Pair<PiCriterion, PiTableAction> matchActionPair = FabricUpfTranslatorUtil.fabricEntryToPiPair(entry);
+ PiCriterion match = matchActionPair.getLeft();
+ PiAction action = (PiAction) matchActionPair.getRight();
+
+ int globalFarId = FabricUpfTranslatorUtil.getFieldInt(match, HDR_FAR_ID);
+ UpfRuleIdentifier farId = fabricUpfStore.localFarIdOf(globalFarId);
+
+ boolean dropFlag = FabricUpfTranslatorUtil.getParamInt(action, DROP) > 0;
+ boolean notifyFlag = FabricUpfTranslatorUtil.getParamInt(action, NOTIFY_CP) > 0;
+
+ // Match keys
+ farBuilder.withSessionId(farId.getPfcpSessionId())
+ .setFarId(farId.getSessionLocalId());
+
+ // Parameters common to all types of FARs
+ farBuilder.setDropFlag(dropFlag)
+ .setNotifyFlag(notifyFlag);
+
+ PiActionId actionId = action.id();
+
+ if (actionId.equals(FABRIC_INGRESS_SPGW_LOAD_TUNNEL_FAR)
+ || actionId.equals(FABRIC_INGRESS_SPGW_LOAD_DBUF_FAR)) {
+ // Grab parameters specific to encapsulating FARs if they're present
+ Ip4Address tunnelSrc = FabricUpfTranslatorUtil.getParamAddress(action, TUNNEL_SRC_ADDR);
+ Ip4Address tunnelDst = FabricUpfTranslatorUtil.getParamAddress(action, TUNNEL_DST_ADDR);
+ ImmutableByteSequence teid = FabricUpfTranslatorUtil.getParamValue(action, TEID);
+ short tunnelSrcPort = (short) FabricUpfTranslatorUtil.getParamInt(action, TUNNEL_SRC_PORT);
+
+ farBuilder.setBufferFlag(actionId.equals(FABRIC_INGRESS_SPGW_LOAD_DBUF_FAR));
+
+ farBuilder.setTunnel(
+ GtpTunnel.builder()
+ .setSrc(tunnelSrc)
+ .setDst(tunnelDst)
+ .setTeid(teid)
+ .setSrcPort(tunnelSrcPort)
+ .build());
+ }
+ return farBuilder.build();
+ }
+
+ /**
+ * Translate a fabric.p4 interface table entry to a UpfInterface instance for easier handling.
+ *
+ * @param entry the fabric.p4 entry to translate
+ * @return the corresponding UpfInterface
+ * @throws UpfProgrammableException if the entry cannot be translated
+ */
+ public UpfInterface fabricEntryToInterface(FlowRule entry)
+ throws UpfProgrammableException {
+ Pair<PiCriterion, PiTableAction> matchActionPair = FabricUpfTranslatorUtil.fabricEntryToPiPair(entry);
+ PiCriterion match = matchActionPair.getLeft();
+ PiAction action = (PiAction) matchActionPair.getRight();
+
+ var ifaceBuilder = UpfInterface.builder()
+ .setPrefix(FabricUpfTranslatorUtil.getFieldPrefix(match, HDR_IPV4_DST_ADDR));
+
+ int interfaceType = FabricUpfTranslatorUtil.getParamInt(action, SRC_IFACE);
+ if (interfaceType == INTERFACE_ACCESS) {
+ ifaceBuilder.setAccess();
+ } else if (interfaceType == INTERFACE_CORE) {
+ ifaceBuilder.setCore();
+ } else if (interfaceType == INTERFACE_DBUF) {
+ ifaceBuilder.setDbufReceiver();
+ }
+ return ifaceBuilder.build();
+ }
+
+ /**
+ * Translate a ForwardingActionRule to a FlowRule to be inserted into the fabric.p4 pipeline.
+ * A side effect of calling this method is the FAR object's globalFarId is assigned if it was not already.
+ *
+ * @param far The FAR to be translated
+ * @param deviceId the ID of the device the FlowRule should be installed on
+ * @param appId the ID of the application that will insert the FlowRule
+ * @param priority the FlowRule's priority
+ * @return the FAR translated to a FlowRule
+ * @throws UpfProgrammableException if the FAR to be translated is malformed
+ */
+ public FlowRule farToFabricEntry(ForwardingActionRule far, DeviceId deviceId, ApplicationId appId, int priority)
+ throws UpfProgrammableException {
+ PiAction action;
+ if (!far.encaps()) {
+ action = PiAction.builder()
+ .withId(FABRIC_INGRESS_SPGW_LOAD_NORMAL_FAR)
+ .withParameters(Arrays.asList(
+ new PiActionParam(DROP, far.drops() ? 1 : 0),
+ new PiActionParam(NOTIFY_CP, far.notifies() ? 1 : 0)
+ ))
+ .build();
+
+ } else {
+ if (far.tunnelSrc() == null || far.tunnelDst() == null
+ || far.teid() == null || far.tunnel().srcPort() == null) {
+ throw new UpfProgrammableException(
+ "Not all action parameters present when translating " +
+ "intermediate encapsulating/buffering FAR to physical FAR!");
+ }
+ // TODO: copy tunnel destination port from logical switch write requests, instead of hardcoding 2152
+ PiActionId actionId = far.buffers() ? FABRIC_INGRESS_SPGW_LOAD_DBUF_FAR :
+ FABRIC_INGRESS_SPGW_LOAD_TUNNEL_FAR;
+ action = PiAction.builder()
+ .withId(actionId)
+ .withParameters(Arrays.asList(
+ new PiActionParam(DROP, far.drops() ? 1 : 0),
+ new PiActionParam(NOTIFY_CP, far.notifies() ? 1 : 0),
+ new PiActionParam(TEID, far.teid()),
+ new PiActionParam(TUNNEL_SRC_ADDR, far.tunnelSrc().toInt()),
+ new PiActionParam(TUNNEL_DST_ADDR, far.tunnelDst().toInt()),
+ new PiActionParam(TUNNEL_SRC_PORT, far.tunnel().srcPort())
+ ))
+ .build();
+ }
+ PiCriterion match = PiCriterion.builder()
+ .matchExact(HDR_FAR_ID, fabricUpfStore.globalFarIdOf(far.sessionId(), far.farId()))
+ .build();
+ return DefaultFlowRule.builder()
+ .forDevice(deviceId).fromApp(appId).makePermanent()
+ .forTable(FABRIC_INGRESS_SPGW_FARS)
+ .withSelector(DefaultTrafficSelector.builder().matchPi(match).build())
+ .withTreatment(DefaultTrafficTreatment.builder().piTableAction(action).build())
+ .withPriority(priority)
+ .build();
+ }
+
+ /**
+ * Translate a PacketDetectionRule to a FlowRule to be inserted into the fabric.p4 pipeline.
+ * A side effect of calling this method is the PDR object's globalFarId is assigned if it was not already.
+ *
+ * @param pdr The PDR to be translated
+ * @param deviceId the ID of the device the FlowRule should be installed on
+ * @param appId the ID of the application that will insert the FlowRule
+ * @param priority the FlowRule's priority
+ * @return the FAR translated to a FlowRule
+ * @throws UpfProgrammableException if the PDR to be translated is malformed
+ */
+ public FlowRule pdrToFabricEntry(PacketDetectionRule pdr, DeviceId deviceId, ApplicationId appId, int priority)
+ throws UpfProgrammableException {
+ PiCriterion match;
+ PiTableId tableId;
+ PiAction action;
+
+ if (pdr.matchesEncapped()) {
+ match = PiCriterion.builder()
+ .matchExact(HDR_TEID, pdr.teid().asArray())
+ .matchExact(HDR_TUNNEL_IPV4_DST, pdr.tunnelDest().toInt())
+ .build();
+ tableId = FABRIC_INGRESS_SPGW_UPLINK_PDRS;
+ } else if (pdr.matchesUnencapped()) {
+ match = PiCriterion.builder()
+ .matchExact(HDR_UE_ADDR, pdr.ueAddress().toInt())
+ .build();
+ tableId = FABRIC_INGRESS_SPGW_DOWNLINK_PDRS;
+ } else {
+ throw new UpfProgrammableException("Flexible PDRs not yet supported! Cannot translate " + pdr.toString());
+ }
+
+ PiAction.Builder builder = PiAction.builder()
+ .withParameters(Arrays.asList(
+ new PiActionParam(CTR_ID, pdr.counterId()),
+ new PiActionParam(FAR_ID, fabricUpfStore.globalFarIdOf(pdr.sessionId(), pdr.farId())),
+ new PiActionParam(NEEDS_GTPU_DECAP, pdr.matchesEncapped() ? 1 : 0)
+ ));
+ if (pdr.hasSchedulingPriority()) {
+ String queueId = fabricUpfStore.queueIdOf(pdr.schedulingPriority());
+ if (queueId == null) {
+ throw new UpfProgrammableException("Udefined Scheduling Priority");
+ }
+ action = builder
+ .withId(FABRIC_INGRESS_SPGW_LOAD_PDR_QOS)
+ .withParameter(new PiActionParam(QID, Integer.parseInt(queueId)))
+ .build();
+ } else {
+ action = builder
+ .withId(FABRIC_INGRESS_SPGW_LOAD_PDR)
+ .build();
+ }
+ return DefaultFlowRule.builder()
+ .forDevice(deviceId).fromApp(appId).makePermanent()
+ .forTable(tableId)
+ .withSelector(DefaultTrafficSelector.builder().matchPi(match).build())
+ .withTreatment(DefaultTrafficTreatment.builder().piTableAction(action).build())
+ .withPriority(priority)
+ .build();
+ }
+
+ /**
+ * Translate a UpfInterface to a FlowRule to be inserted into the fabric.p4 pipeline.
+ *
+ * @param upfInterface The interface to be translated
+ * @param deviceId the ID of the device the FlowRule should be installed on
+ * @param appId the ID of the application that will insert the FlowRule
+ * @param priority the FlowRule's priority
+ * @return the UPF interface translated to a FlowRule
+ * @throws UpfProgrammableException if the interface cannot be translated
+ */
+ public FlowRule interfaceToFabricEntry(UpfInterface upfInterface, DeviceId deviceId,
+ ApplicationId appId, int priority)
+ throws UpfProgrammableException {
+ int interfaceTypeInt;
+ int gtpuValidity;
+ if (upfInterface.isDbufReceiver()) {
+ interfaceTypeInt = INTERFACE_DBUF;
+ gtpuValidity = 1;
+ } else if (upfInterface.isAccess()) {
+ interfaceTypeInt = INTERFACE_ACCESS;
+ gtpuValidity = 1;
+ } else {
+ interfaceTypeInt = INTERFACE_CORE;
+ gtpuValidity = 0;
+ }
+
+ PiCriterion match = PiCriterion.builder()
+ .matchLpm(HDR_IPV4_DST_ADDR,
+ upfInterface.prefix().address().toInt(),
+ upfInterface.prefix().prefixLength())
+ .matchExact(HDR_GTPU_IS_VALID, gtpuValidity)
+ .build();
+ PiAction action = PiAction.builder()
+ .withId(FABRIC_INGRESS_SPGW_LOAD_IFACE)
+ .withParameter(new PiActionParam(SRC_IFACE, interfaceTypeInt))
+ .build();
+ return DefaultFlowRule.builder()
+ .forDevice(deviceId).fromApp(appId).makePermanent()
+ .forTable(FABRIC_INGRESS_SPGW_INTERFACES)
+ .withSelector(DefaultTrafficSelector.builder().matchPi(match).build())
+ .withTreatment(DefaultTrafficTreatment.builder().piTableAction(action).build())
+ .withPriority(priority)
+ .build();
+ }
+
+// public FlowRule buildGtpuWithPscEncapRule(DeviceId deviceId, ApplicationId appId, int qfi) {
+// PiAction action = PiAction.builder()
+// .withId(FABRIC_EGRESS_SPGW_GTPU_WITH_PSC)
+// .withParameter(new PiActionParam(QFI, qfi))
+// .build();
+// // Default entry, no selector.
+// return DefaultFlowRule.builder()
+// .forDevice(deviceId).fromApp(appId).makePermanent()
+// .forTable(FABRIC_EGRESS_SPGW_GTPU_ENCAP)
+// .withTreatment(DefaultTrafficTreatment.builder().piTableAction(action).build())
+// .withPriority(0)
+// .build();
+// }
+//
+// public FlowRule buildGtpuOnlyEncapRule(DeviceId deviceId, ApplicationId appId) {
+// PiAction action = PiAction.builder()
+// .withId(FABRIC_EGRESS_SPGW_GTPU_ONLY)
+// .build();
+// // Default entry, no selector.
+// return DefaultFlowRule.builder()
+// .forDevice(deviceId).fromApp(appId).makePermanent()
+// .forTable(FABRIC_EGRESS_SPGW_GTPU_ENCAP)
+// .withTreatment(DefaultTrafficTreatment.builder().piTableAction(action).build())
+// .withPriority(0)
+// .build();
+// }
+
+}