[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/FabricPipeconfManager.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/FabricPipeconfManager.java
index d8af395..7d1ee4e 100644
--- a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/FabricPipeconfManager.java
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/FabricPipeconfManager.java
@@ -19,6 +19,8 @@
import org.onosproject.net.behaviour.inbandtelemetry.IntProgrammable;
import org.onosproject.net.behaviour.BngProgrammable;
+import org.onosproject.net.behaviour.upf.UpfProgrammable;
+import org.onosproject.pipelines.fabric.impl.behaviour.upf.FabricUpfProgrammable;
import org.onosproject.net.behaviour.Pipeliner;
import org.onosproject.net.pi.model.DefaultPiPipeconf;
import org.onosproject.net.pi.model.PiPipeconf;
@@ -51,6 +53,7 @@
private static final String INT_PROFILE_SUFFIX = "-int";
private static final String FULL_PROFILE_SUFFIX = "-full";
private static final String BNG_PROFILE_SUFFIX = "-bng";
+ private static final String UPF_PROFILE_SUFFIX = "-spgw";
private static Logger log = getLogger(FabricPipeconfLoader.class);
@@ -98,6 +101,11 @@
if (profileName.endsWith(BNG_PROFILE_SUFFIX)) {
pipeconfBuilder.addBehaviour(BngProgrammable.class, FabricBngProgrammable.class);
}
+ // Add UpfProgrammable behavior for UPF-enabled pipelines.
+ if (profileName.contains(UPF_PROFILE_SUFFIX) ||
+ profileName.endsWith(FULL_PROFILE_SUFFIX)) {
+ pipeconfBuilder.addBehaviour(UpfProgrammable.class, FabricUpfProgrammable.class);
+ }
return pipeconfBuilder.build();
}
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricCapabilities.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricCapabilities.java
index 2eb947d..fbb709a 100644
--- a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricCapabilities.java
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricCapabilities.java
@@ -28,6 +28,7 @@
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.CPU_PORT_TXT;
+import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_DOWNLINK_PDRS;
import static org.slf4j.LoggerFactory.getLogger;
/**
@@ -89,6 +90,17 @@
}
/**
+ * Returns true if the pipeconf supports UPF capabilities, false otherwise.
+ *
+ * @return boolean
+ */
+ public boolean supportUpf() {
+ return pipeconf.pipelineModel()
+ .table(FABRIC_INGRESS_SPGW_DOWNLINK_PDRS)
+ .isPresent();
+ }
+
+ /**
* Returns true if the pipeconf supports BNG user plane capabilities, false
* otherwise.
*
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/DistributedFabricUpfStore.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/DistributedFabricUpfStore.java
new file mode 100644
index 0000000..1f40f3b
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/DistributedFabricUpfStore.java
@@ -0,0 +1,248 @@
+/*
+ * 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 com.google.common.collect.BiMap;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.Maps;
+import org.onlab.packet.Ip4Address;
+import org.onlab.util.ImmutableByteSequence;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.net.behaviour.upf.PacketDetectionRule;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.DistributedSet;
+import org.onosproject.store.service.MapEvent;
+import org.onosproject.store.service.MapEventListener;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Distributed implementation of FabricUpfStore.
+ */
+// FIXME: this store is generic and not tied to a single device, should we have a store based on deviceId?
+@Component(immediate = true, service = FabricUpfStore.class)
+public final class DistributedFabricUpfStore implements FabricUpfStore {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected StorageService storageService;
+
+ protected static final String FAR_ID_MAP_NAME = "fabric-upf-far-id";
+ protected static final String BUFFER_FAR_ID_SET_NAME = "fabric-upf-buffer-far-id";
+ protected static final String FAR_ID_UE_MAP_NAME = "fabric-upf-far-id-ue";
+ protected static final KryoNamespace.Builder SERIALIZER = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(UpfRuleIdentifier.class);
+
+ // TODO: check queue IDs for BMv2, is priority inverted?
+ // Mapping between scheduling priority ranges with BMv2 priority queues
+ private static final BiMap<Integer, Integer> SCHEDULING_PRIORITY_MAP
+ = new ImmutableBiMap.Builder<Integer, Integer>()
+ // Highest scheduling priority for 3GPP is 1 and highest BMv2 queue priority is 7
+ .put(1, 5)
+ .put(6, 4)
+ .put(7, 3)
+ .put(8, 2)
+ .put(9, 1)
+ .build();
+
+ // Distributed local FAR ID to global FAR ID mapping
+ protected ConsistentMap<UpfRuleIdentifier, Integer> farIdMap;
+ private MapEventListener<UpfRuleIdentifier, Integer> farIdMapListener;
+ // Local, reversed copy of farIdMapper for better reverse lookup performance
+ protected Map<Integer, UpfRuleIdentifier> reverseFarIdMap;
+ private int nextGlobalFarId = 1;
+
+ protected DistributedSet<UpfRuleIdentifier> bufferFarIds;
+ protected ConsistentMap<UpfRuleIdentifier, Set<Ip4Address>> farIdToUeAddrs;
+
+ @Activate
+ protected void activate() {
+ // Allow unit test to inject farIdMap here.
+ if (storageService != null) {
+ this.farIdMap = storageService.<UpfRuleIdentifier, Integer>consistentMapBuilder()
+ .withName(FAR_ID_MAP_NAME)
+ .withRelaxedReadConsistency()
+ .withSerializer(Serializer.using(SERIALIZER.build()))
+ .build();
+ this.bufferFarIds = storageService.<UpfRuleIdentifier>setBuilder()
+ .withName(BUFFER_FAR_ID_SET_NAME)
+ .withRelaxedReadConsistency()
+ .withSerializer(Serializer.using(SERIALIZER.build()))
+ .build().asDistributedSet();
+ this.farIdToUeAddrs = storageService.<UpfRuleIdentifier, Set<Ip4Address>>consistentMapBuilder()
+ .withName(FAR_ID_UE_MAP_NAME)
+ .withRelaxedReadConsistency()
+ .withSerializer(Serializer.using(SERIALIZER.build()))
+ .build();
+
+ }
+ farIdMapListener = new FarIdMapListener();
+ farIdMap.addListener(farIdMapListener);
+
+ reverseFarIdMap = Maps.newHashMap();
+ farIdMap.entrySet().forEach(entry -> reverseFarIdMap.put(entry.getValue().value(), entry.getKey()));
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ farIdMap.removeListener(farIdMapListener);
+ farIdMap.destroy();
+ reverseFarIdMap.clear();
+
+ log.info("Stopped");
+ }
+
+ @Override
+ public void reset() {
+ farIdMap.clear();
+ reverseFarIdMap.clear();
+ bufferFarIds.clear();
+ farIdToUeAddrs.clear();
+ nextGlobalFarId = 0;
+ }
+
+ @Override
+ public Map<UpfRuleIdentifier, Integer> getFarIdMap() {
+ return Map.copyOf(farIdMap.asJavaMap());
+ }
+
+ @Override
+ public int globalFarIdOf(UpfRuleIdentifier farIdPair) {
+ int globalFarId = farIdMap.compute(farIdPair,
+ (k, existingId) -> {
+ return Objects.requireNonNullElseGet(existingId, () -> nextGlobalFarId++);
+ }).value();
+ log.info("{} translated to GlobalFarId={}", farIdPair, globalFarId);
+ return globalFarId;
+ }
+
+ @Override
+ public int globalFarIdOf(ImmutableByteSequence pfcpSessionId, int sessionLocalFarId) {
+ UpfRuleIdentifier farId = new UpfRuleIdentifier(pfcpSessionId, sessionLocalFarId);
+ return globalFarIdOf(farId);
+
+ }
+
+ @Override
+ public String queueIdOf(int schedulingPriority) {
+ return (SCHEDULING_PRIORITY_MAP.get(schedulingPriority)).toString();
+ }
+
+ @Override
+ public String schedulingPriorityOf(int queueId) {
+ return (SCHEDULING_PRIORITY_MAP.inverse().get(queueId)).toString();
+ }
+
+ @Override
+ public UpfRuleIdentifier localFarIdOf(int globalFarId) {
+ return reverseFarIdMap.get(globalFarId);
+ }
+
+ public void learnFarIdToUeAddrs(PacketDetectionRule pdr) {
+ UpfRuleIdentifier ruleId = UpfRuleIdentifier.of(pdr.sessionId(), pdr.farId());
+ farIdToUeAddrs.compute(ruleId, (k, set) -> {
+ if (set == null) {
+ set = new HashSet<>();
+ }
+ set.add(pdr.ueAddress());
+ return set;
+ });
+ }
+
+ @Override
+ public boolean isFarIdBuffering(UpfRuleIdentifier farId) {
+ checkNotNull(farId);
+ return bufferFarIds.contains(farId);
+ }
+
+ @Override
+ public void learBufferingFarId(UpfRuleIdentifier farId) {
+ checkNotNull(farId);
+ bufferFarIds.add(farId);
+ }
+
+ @Override
+ public void forgetBufferingFarId(UpfRuleIdentifier farId) {
+ checkNotNull(farId);
+ bufferFarIds.remove(farId);
+ }
+
+ @Override
+ public void forgetUeAddr(Ip4Address ueAddr) {
+ farIdToUeAddrs.keySet().forEach(
+ farId -> farIdToUeAddrs.computeIfPresent(farId, (farIdz, ueAddrs) -> {
+ ueAddrs.remove(ueAddr);
+ return ueAddrs;
+ }));
+ }
+
+ @Override
+ public Set<Ip4Address> ueAddrsOfFarId(UpfRuleIdentifier farId) {
+ return farIdToUeAddrs.getOrDefault(farId, Set.of()).value();
+ }
+
+ @Override
+ public Set<UpfRuleIdentifier> getBufferFarIds() {
+ return Set.copyOf(bufferFarIds);
+ }
+
+ @Override
+ public Map<UpfRuleIdentifier, Set<Ip4Address>> getFarIdToUeAddrs() {
+ return Map.copyOf(farIdToUeAddrs.asJavaMap());
+ }
+
+ // NOTE: FarIdMapListener is run on the same thread intentionally in order to ensure that
+ // reverseFarIdMap update always finishes right after farIdMap is updated
+ private class FarIdMapListener implements MapEventListener<UpfRuleIdentifier, Integer> {
+ @Override
+ public void event(MapEvent<UpfRuleIdentifier, Integer> event) {
+ switch (event.type()) {
+ case INSERT:
+ reverseFarIdMap.put(event.newValue().value(), event.key());
+ break;
+ case UPDATE:
+ reverseFarIdMap.remove(event.oldValue().value());
+ reverseFarIdMap.put(event.newValue().value(), event.key());
+ break;
+ case REMOVE:
+ reverseFarIdMap.remove(event.oldValue().value());
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfProgrammable.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfProgrammable.java
new file mode 100644
index 0000000..77ad732
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfProgrammable.java
@@ -0,0 +1,668 @@
+/*
+ * 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 com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.drivers.p4runtime.AbstractP4RuntimeHandlerBehaviour;
+import org.onosproject.net.PortNumber;
+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.PdrStats;
+import org.onosproject.net.behaviour.upf.UpfInterface;
+import org.onosproject.net.behaviour.upf.UpfProgrammable;
+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.FlowEntry;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.criteria.PiCriterion;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.net.pi.model.PiCounterId;
+import org.onosproject.net.pi.model.PiCounterModel;
+import org.onosproject.net.pi.model.PiTableId;
+import org.onosproject.net.pi.model.PiTableModel;
+import org.onosproject.net.pi.runtime.PiCounterCell;
+import org.onosproject.net.pi.runtime.PiCounterCellHandle;
+import org.onosproject.net.pi.runtime.PiCounterCellId;
+import org.onosproject.pipelines.fabric.impl.FabricPipeconfLoader;
+import org.onosproject.pipelines.fabric.impl.behaviour.FabricCapabilities;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.onosproject.net.behaviour.upf.UpfProgrammableException.Type.UNSUPPORTED_OPERATION;
+import static org.onosproject.net.pi.model.PiCounterType.INDIRECT;
+import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_EGRESS_SPGW_PDR_COUNTER;
+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_PDR_COUNTER;
+import static org.onosproject.pipelines.fabric.FabricConstants.FABRIC_INGRESS_SPGW_UPLINK_PDRS;
+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;
+
+
+/**
+ * Implementation of a UPF programmable device behavior.
+ */
+public class FabricUpfProgrammable extends AbstractP4RuntimeHandlerBehaviour
+ implements UpfProgrammable {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+ private static final int DEFAULT_PRIORITY = 128;
+ private static final long DEFAULT_P4_DEVICE_ID = 1;
+
+ protected FlowRuleService flowRuleService;
+ protected PacketService packetService;
+ protected FabricUpfStore fabricUpfStore;
+ protected FabricUpfTranslator upfTranslator;
+
+ private long farTableSize;
+ private long encappedPdrTableSize;
+ private long unencappedPdrTableSize;
+ private long pdrCounterSize;
+
+ private ApplicationId appId;
+
+ // FIXME: remove, buffer drain should be triggered by Up4Service
+ private BufferDrainer bufferDrainer;
+
+ // FIXME: dbuf tunnel should be managed by Up4Service
+ // Up4Service should be responsible of setting up such tunnel, then transforming FARs for this
+ // device accordingly. When the tunnel endpoint change, it should be up to Up4Service to update
+ // the FAR on the device.
+ private GtpTunnel dbufTunnel;
+
+ @Override
+ protected boolean setupBehaviour(String opName) {
+ if (!super.setupBehaviour(opName)) {
+ return false;
+ }
+ flowRuleService = handler().get(FlowRuleService.class);
+ packetService = handler().get(PacketService.class);
+ fabricUpfStore = handler().get(FabricUpfStore.class);
+ upfTranslator = new FabricUpfTranslator(fabricUpfStore);
+ final CoreService coreService = handler().get(CoreService.class);
+ appId = coreService.getAppId(FabricPipeconfLoader.PIPELINE_APP_NAME);
+ if (appId == null) {
+ log.warn("Application ID is null. Cannot initialize behaviour.");
+ return false;
+ }
+
+ var capabilities = new FabricCapabilities(pipeconf);
+ if (!capabilities.supportUpf()) {
+ log.warn("Pipeconf {} on {} does not support UPF capabilities, " +
+ "cannot perform {}",
+ pipeconf.id(), deviceId, opName);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean init() {
+ if (setupBehaviour("init()")) {
+ if (!computeHardwareResourceSizes()) {
+ // error message will be printed by computeHardwareResourceSizes()
+ return false;
+ }
+ log.info("UpfProgrammable initialized for appId {} and deviceId {}", appId, deviceId);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Grab the capacities for the PDR and FAR tables from the pipeconf. Runs only once, on initialization.
+ *
+ * @return true if resource is fetched successfully, false otherwise.
+ * @throws IllegalStateException when FAR or PDR table can't be found in the pipeline model.
+ */
+ private boolean computeHardwareResourceSizes() {
+ long farTableSize = 0;
+ long encappedPdrTableSize = 0;
+ long unencappedPdrTableSize = 0;
+
+ // Get table sizes of interest
+ for (PiTableModel piTable : pipeconf.pipelineModel().tables()) {
+ if (piTable.id().equals(FABRIC_INGRESS_SPGW_UPLINK_PDRS)) {
+ encappedPdrTableSize = piTable.maxSize();
+ } else if (piTable.id().equals(FABRIC_INGRESS_SPGW_DOWNLINK_PDRS)) {
+ unencappedPdrTableSize = piTable.maxSize();
+ } else if (piTable.id().equals(FABRIC_INGRESS_SPGW_FARS)) {
+ farTableSize = piTable.maxSize();
+ }
+ }
+ if (encappedPdrTableSize == 0) {
+ throw new IllegalStateException("Unable to find uplink PDR table in pipeline model.");
+ }
+ if (unencappedPdrTableSize == 0) {
+ throw new IllegalStateException("Unable to find downlink PDR table in pipeline model.");
+ }
+ if (encappedPdrTableSize != unencappedPdrTableSize) {
+ log.warn("The uplink and downlink PDR tables don't have equal sizes! Using the minimum of the two.");
+ }
+ if (farTableSize == 0) {
+ throw new IllegalStateException("Unable to find FAR table in pipeline model.");
+ }
+ // Get counter sizes of interest
+ long ingressCounterSize = 0;
+ long egressCounterSize = 0;
+ for (PiCounterModel piCounter : pipeconf.pipelineModel().counters()) {
+ if (piCounter.id().equals(FABRIC_INGRESS_SPGW_PDR_COUNTER)) {
+ ingressCounterSize = piCounter.size();
+ } else if (piCounter.id().equals(FABRIC_EGRESS_SPGW_PDR_COUNTER)) {
+ egressCounterSize = piCounter.size();
+ }
+ }
+ if (ingressCounterSize != egressCounterSize) {
+ log.warn("PDR ingress and egress counter sizes are not equal! Using the minimum of the two.");
+ }
+ this.farTableSize = farTableSize;
+ this.encappedPdrTableSize = encappedPdrTableSize;
+ this.unencappedPdrTableSize = unencappedPdrTableSize;
+ this.pdrCounterSize = Math.min(ingressCounterSize, egressCounterSize);
+ return true;
+ }
+
+ @Override
+ public void setBufferDrainer(BufferDrainer drainer) {
+ if (!setupBehaviour("setBufferDrainer()")) {
+ return;
+ }
+ this.bufferDrainer = drainer;
+ }
+
+ @Override
+ public void unsetBufferDrainer() {
+ if (!setupBehaviour("unsetBufferDrainer()")) {
+ return;
+ }
+ this.bufferDrainer = null;
+ }
+
+ @Override
+ public void enablePscEncap(int defaultQfi) throws UpfProgrammableException {
+ throw new UpfProgrammableException("PSC encap is not supported in fabric-v1model",
+ UNSUPPORTED_OPERATION);
+ }
+
+ @Override
+ public void disablePscEncap() throws UpfProgrammableException {
+ throw new UpfProgrammableException("PSC encap is not supported in fabric-v1model",
+ UNSUPPORTED_OPERATION);
+ }
+
+ @Override
+ public void sendPacketOut(ByteBuffer data) {
+ if (!setupBehaviour("sendPacketOut()")) {
+ return;
+ }
+ final OutboundPacket pkt = new DefaultOutboundPacket(
+ deviceId,
+ // Use TABLE logical port to have pkt routed via pipeline tables.
+ DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.TABLE)
+ .build(),
+ data);
+ packetService.emit(pkt);
+ }
+
+ @Override
+ public void setDbufTunnel(Ip4Address switchAddr, Ip4Address dbufAddr) {
+ if (!setupBehaviour("setDbufTunnel()")) {
+ return;
+ }
+ this.dbufTunnel = GtpTunnel.builder()
+ .setSrc(switchAddr)
+ .setDst(dbufAddr)
+ .setSrcPort((short) 2152)
+ .setTeid(0)
+ .build();
+ }
+
+ @Override
+ public void unsetDbufTunnel() {
+ if (!setupBehaviour("unsetDbufTunnel()")) {
+ return;
+ }
+ this.dbufTunnel = null;
+ }
+
+ /**
+ * Convert the given buffering FAR to a FAR that tunnels the packet to dbuf.
+ *
+ * @param far the FAR to convert
+ * @return the converted FAR
+ */
+ private ForwardingActionRule convertToDbufFar(ForwardingActionRule far) {
+ if (!far.buffers()) {
+ throw new IllegalArgumentException("Converting a non-buffering FAR to a dbuf FAR! This shouldn't happen.");
+ }
+ return ForwardingActionRule.builder()
+ .setFarId(far.farId())
+ .withSessionId(far.sessionId())
+ .setNotifyFlag(far.notifies())
+ .setBufferFlag(true)
+ .setTunnel(dbufTunnel)
+ .build();
+ }
+
+ @Override
+ public void cleanUp() {
+ if (!setupBehaviour("cleanUp()")) {
+ return;
+ }
+ log.info("Clearing all UPF-related table entries.");
+ flowRuleService.removeFlowRulesById(appId);
+ fabricUpfStore.reset();
+ }
+
+ @Override
+ public void clearInterfaces() {
+ if (!setupBehaviour("clearInterfaces()")) {
+ return;
+ }
+ log.info("Clearing all UPF interfaces.");
+ for (FlowRule entry : flowRuleService.getFlowEntriesById(appId)) {
+ if (upfTranslator.isFabricInterface(entry)) {
+ flowRuleService.removeFlowRules(entry);
+ }
+ }
+ }
+
+ @Override
+ public void clearFlows() {
+ if (!setupBehaviour("clearFlows()")) {
+ return;
+ }
+ log.info("Clearing all UE sessions.");
+ int pdrsCleared = 0;
+ int farsCleared = 0;
+ for (FlowRule entry : flowRuleService.getFlowEntriesById(appId)) {
+ if (upfTranslator.isFabricPdr(entry)) {
+ pdrsCleared++;
+ flowRuleService.removeFlowRules(entry);
+ } else if (upfTranslator.isFabricFar(entry)) {
+ farsCleared++;
+ flowRuleService.removeFlowRules(entry);
+ }
+ }
+ log.info("Cleared {} PDRs and {} FARS.", pdrsCleared, farsCleared);
+ }
+
+
+ @Override
+ public Collection<PdrStats> readAllCounters(long maxCounterId) {
+ if (!setupBehaviour("readAllCounters()")) {
+ return null;
+ }
+
+ long counterSize = pdrCounterSize();
+ if (maxCounterId != -1) {
+ counterSize = Math.min(maxCounterId, counterSize);
+ }
+
+ // Prepare PdrStats object builders, one for each counter ID currently in use
+ Map<Integer, PdrStats.Builder> pdrStatBuilders = Maps.newHashMap();
+ for (int cellId = 0; cellId < counterSize; cellId++) {
+ pdrStatBuilders.put(cellId, PdrStats.builder().withCellId(cellId));
+ }
+
+ // Generate the counter cell IDs.
+ Set<PiCounterId> counterIds = ImmutableSet.of(
+ FABRIC_INGRESS_SPGW_PDR_COUNTER,
+ FABRIC_EGRESS_SPGW_PDR_COUNTER
+ );
+
+ // Query the device.
+ Collection<PiCounterCell> counterEntryResponse = client.read(
+ DEFAULT_P4_DEVICE_ID, pipeconf)
+ .counterCells(counterIds)
+ .submitSync()
+ .all(PiCounterCell.class);
+
+ // Process response.
+ counterEntryResponse.forEach(counterCell -> {
+ if (counterCell.cellId().counterType() != INDIRECT) {
+ log.warn("Invalid counter data type {}, skipping", counterCell.cellId().counterType());
+ return;
+ }
+ if (!pdrStatBuilders.containsKey((int) counterCell.cellId().index())) {
+ // Most likely Up4config.maxUes() is set to a value smaller than what the switch
+ // pipeline can hold.
+ log.debug("Unrecognized index {} when reading all counters, " +
+ "that's expected if we are manually limiting maxUes", counterCell);
+ return;
+ }
+ PdrStats.Builder statsBuilder = pdrStatBuilders.get((int) counterCell.cellId().index());
+ if (counterCell.cellId().counterId().equals(FABRIC_INGRESS_SPGW_PDR_COUNTER)) {
+ statsBuilder.setIngress(counterCell.data().packets(),
+ counterCell.data().bytes());
+ } else if (counterCell.cellId().counterId().equals(FABRIC_EGRESS_SPGW_PDR_COUNTER)) {
+ statsBuilder.setEgress(counterCell.data().packets(),
+ counterCell.data().bytes());
+ } else {
+ log.warn("Unrecognized counter ID {}, skipping", counterCell);
+ }
+ });
+
+ return pdrStatBuilders
+ .values()
+ .stream()
+ .map(PdrStats.Builder::build)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public long pdrCounterSize() {
+ if (!setupBehaviour("pdrCounterSize()")) {
+ return -1;
+ }
+ computeHardwareResourceSizes();
+ return pdrCounterSize;
+ }
+
+ @Override
+ public long farTableSize() {
+ if (!setupBehaviour("farTableSize()")) {
+ return -1;
+ }
+ computeHardwareResourceSizes();
+ return farTableSize;
+ }
+
+ @Override
+ public long pdrTableSize() {
+ if (!setupBehaviour("pdrTableSize()")) {
+ return -1;
+ }
+ computeHardwareResourceSizes();
+ return Math.min(encappedPdrTableSize, unencappedPdrTableSize) * 2;
+ }
+
+ @Override
+ public PdrStats readCounter(int cellId) throws UpfProgrammableException {
+ if (!setupBehaviour("readCounter()")) {
+ return null;
+ }
+ if (cellId >= pdrCounterSize() || cellId < 0) {
+ throw new UpfProgrammableException("Requested PDR counter cell index is out of bounds.",
+ UpfProgrammableException.Type.COUNTER_INDEX_OUT_OF_RANGE);
+ }
+ PdrStats.Builder stats = PdrStats.builder().withCellId(cellId);
+
+ // Make list of cell handles we want to read.
+ List<PiCounterCellHandle> counterCellHandles = List.of(
+ PiCounterCellHandle.of(deviceId,
+ PiCounterCellId.ofIndirect(FABRIC_INGRESS_SPGW_PDR_COUNTER, cellId)),
+ PiCounterCellHandle.of(deviceId,
+ PiCounterCellId.ofIndirect(FABRIC_EGRESS_SPGW_PDR_COUNTER, cellId)));
+
+ // Query the device.
+ Collection<PiCounterCell> counterEntryResponse = client.read(
+ DEFAULT_P4_DEVICE_ID, pipeconf)
+ .handles(counterCellHandles).submitSync()
+ .all(PiCounterCell.class);
+
+ // Process response.
+ counterEntryResponse.forEach(counterCell -> {
+ if (counterCell.cellId().counterType() != INDIRECT) {
+ log.warn("Invalid counter data type {}, skipping", counterCell.cellId().counterType());
+ return;
+ }
+ if (cellId != counterCell.cellId().index()) {
+ log.warn("Unrecognized counter index {}, skipping", counterCell);
+ return;
+ }
+ if (counterCell.cellId().counterId().equals(FABRIC_INGRESS_SPGW_PDR_COUNTER)) {
+ stats.setIngress(counterCell.data().packets(), counterCell.data().bytes());
+ } else if (counterCell.cellId().counterId().equals(FABRIC_EGRESS_SPGW_PDR_COUNTER)) {
+ stats.setEgress(counterCell.data().packets(), counterCell.data().bytes());
+ } else {
+ log.warn("Unrecognized counter ID {}, skipping", counterCell);
+ }
+ });
+ return stats.build();
+ }
+
+
+ @Override
+ public void addPdr(PacketDetectionRule pdr) throws UpfProgrammableException {
+ if (!setupBehaviour("addPdr()")) {
+ return;
+ }
+ if (pdr.counterId() >= pdrCounterSize() || pdr.counterId() < 0) {
+ throw new UpfProgrammableException("Counter cell index referenced by PDR is out of bounds.",
+ UpfProgrammableException.Type.COUNTER_INDEX_OUT_OF_RANGE);
+ }
+ FlowRule fabricPdr = upfTranslator.pdrToFabricEntry(pdr, deviceId, appId, DEFAULT_PRIORITY);
+ log.info("Installing {}", pdr.toString());
+ flowRuleService.applyFlowRules(fabricPdr);
+ log.debug("PDR added with flowID {}", fabricPdr.id().value());
+
+ // If the flow rule was applied and the PDR is downlink, add the PDR to the farID->PDR mapping
+ if (pdr.matchesUnencapped()) {
+ fabricUpfStore.learnFarIdToUeAddrs(pdr);
+ }
+ }
+
+
+ @Override
+ public void addFar(ForwardingActionRule far) throws UpfProgrammableException {
+ if (!setupBehaviour("addFar()")) {
+ return;
+ }
+ UpfRuleIdentifier ruleId = UpfRuleIdentifier.of(far.sessionId(), far.farId());
+ if (far.buffers()) {
+ // If the far has the buffer flag, modify its tunnel so it directs to dbuf
+ far = convertToDbufFar(far);
+ fabricUpfStore.learBufferingFarId(ruleId);
+ }
+ FlowRule fabricFar = upfTranslator.farToFabricEntry(far, deviceId, appId, DEFAULT_PRIORITY);
+ log.info("Installing {}", far.toString());
+ flowRuleService.applyFlowRules(fabricFar);
+ log.debug("FAR added with flowID {}", fabricFar.id().value());
+ if (!far.buffers() && fabricUpfStore.isFarIdBuffering(ruleId)) {
+ // If this FAR does not buffer but used to, then drain the buffer for every UE address
+ // that hits this FAR.
+ fabricUpfStore.forgetBufferingFarId(ruleId);
+ for (var ueAddr : fabricUpfStore.ueAddrsOfFarId(ruleId)) {
+ if (bufferDrainer == null) {
+ log.warn("Unable to drain downlink buffer for UE {}, bufferDrainer is null", ueAddr);
+ } else {
+ bufferDrainer.drain(ueAddr);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void addInterface(UpfInterface upfInterface) throws UpfProgrammableException {
+ if (!setupBehaviour("addInterface()")) {
+ return;
+ }
+ FlowRule flowRule = upfTranslator.interfaceToFabricEntry(upfInterface, deviceId, appId, DEFAULT_PRIORITY);
+ log.info("Installing {}", upfInterface);
+ flowRuleService.applyFlowRules(flowRule);
+ log.debug("Interface added with flowID {}", flowRule.id().value());
+ // By default we enable UE-to-UE communication on the UE subnet identified by the CORE interface.
+ // TODO: allow enabling/disabling UE-to-UE via netcfg or other API.
+ log.warn("UE-to-UE traffic is not supported in fabric-v1model");
+ }
+
+ private boolean removeEntry(PiCriterion match, PiTableId tableId, boolean failSilent)
+ throws UpfProgrammableException {
+ if (!setupBehaviour("removeEntry()")) {
+ return false;
+ }
+ FlowRule entry = DefaultFlowRule.builder()
+ .forDevice(deviceId).fromApp(appId).makePermanent()
+ .forTable(tableId)
+ .withSelector(DefaultTrafficSelector.builder().matchPi(match).build())
+ .withPriority(DEFAULT_PRIORITY)
+ .build();
+
+ /*
+ * FIXME: Stupid stupid slow hack, needed because removeFlowRules expects FlowRule objects
+ * with correct and complete actions and parameters, but P4Runtime deletion requests
+ * will not have those.
+ */
+ for (FlowEntry installedEntry : flowRuleService.getFlowEntriesById(appId)) {
+ if (installedEntry.selector().equals(entry.selector())) {
+ log.info("Found matching entry to remove, it has FlowID {}", installedEntry.id());
+ flowRuleService.removeFlowRules(installedEntry);
+ return true;
+ }
+ }
+ if (!failSilent) {
+ throw new UpfProgrammableException("Match criterion " + match.toString() +
+ " not found in table " + tableId.toString());
+ }
+ return false;
+ }
+
+ @Override
+ public Collection<PacketDetectionRule> getPdrs() throws UpfProgrammableException {
+ if (!setupBehaviour("getPdrs()")) {
+ return null;
+ }
+ ArrayList<PacketDetectionRule> pdrs = new ArrayList<>();
+ for (FlowRule flowRule : flowRuleService.getFlowEntriesById(appId)) {
+ if (upfTranslator.isFabricPdr(flowRule)) {
+ pdrs.add(upfTranslator.fabricEntryToPdr(flowRule));
+ }
+ }
+ return pdrs;
+ }
+
+ @Override
+ public Collection<ForwardingActionRule> getFars() throws UpfProgrammableException {
+ if (!setupBehaviour("getFars()")) {
+ return null;
+ }
+ ArrayList<ForwardingActionRule> fars = new ArrayList<>();
+ for (FlowRule flowRule : flowRuleService.getFlowEntriesById(appId)) {
+ if (upfTranslator.isFabricFar(flowRule)) {
+ fars.add(upfTranslator.fabricEntryToFar(flowRule));
+ }
+ }
+ return fars;
+ }
+
+ @Override
+ public Collection<UpfInterface> getInterfaces() throws UpfProgrammableException {
+ if (!setupBehaviour("getInterfaces()")) {
+ return null;
+ }
+ ArrayList<UpfInterface> ifaces = new ArrayList<>();
+ for (FlowRule flowRule : flowRuleService.getFlowEntriesById(appId)) {
+ if (upfTranslator.isFabricInterface(flowRule)) {
+ ifaces.add(upfTranslator.fabricEntryToInterface(flowRule));
+ }
+ }
+ return ifaces;
+ }
+
+ @Override
+ public void removePdr(PacketDetectionRule pdr) throws UpfProgrammableException {
+ if (!setupBehaviour("removePdr()")) {
+ return;
+ }
+ final PiCriterion match;
+ final PiTableId tableId;
+ 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 {
+ match = PiCriterion.builder()
+ .matchExact(HDR_UE_ADDR, pdr.ueAddress().toInt())
+ .build();
+ tableId = FABRIC_INGRESS_SPGW_DOWNLINK_PDRS;
+ }
+ log.info("Removing {}", pdr.toString());
+ removeEntry(match, tableId, false);
+
+ // Remove the PDR from the farID->PDR mapping
+ // This is an inefficient hotfix FIXME: remove UE addrs from the mapping in sublinear time
+ if (pdr.matchesUnencapped()) {
+ // Should we remove just from the map entry with key == far ID?
+ fabricUpfStore.forgetUeAddr(pdr.ueAddress());
+ }
+ }
+
+ @Override
+ public void removeFar(ForwardingActionRule far) throws UpfProgrammableException {
+ log.info("Removing {}", far.toString());
+
+ PiCriterion match = PiCriterion.builder()
+ .matchExact(HDR_FAR_ID, fabricUpfStore.globalFarIdOf(far.sessionId(), far.farId()))
+ .build();
+
+ removeEntry(match, FABRIC_INGRESS_SPGW_FARS, false);
+ }
+
+ @Override
+ public void removeInterface(UpfInterface upfInterface) throws UpfProgrammableException {
+ if (!setupBehaviour("removeInterface()")) {
+ return;
+ }
+ Ip4Prefix ifacePrefix = upfInterface.getPrefix();
+ // If it isn't a core interface (so it is either access or unknown), try removing core
+ if (!upfInterface.isCore()) {
+ PiCriterion match1 = PiCriterion.builder()
+ .matchLpm(HDR_IPV4_DST_ADDR, ifacePrefix.address().toInt(),
+ ifacePrefix.prefixLength())
+ .matchExact(HDR_GTPU_IS_VALID, 1)
+ .build();
+ if (removeEntry(match1, FABRIC_INGRESS_SPGW_INTERFACES, true)) {
+ return;
+ }
+ }
+ // If that didn't work or didn't execute, try removing access
+ PiCriterion match2 = PiCriterion.builder()
+ .matchLpm(HDR_IPV4_DST_ADDR, ifacePrefix.address().toInt(),
+ ifacePrefix.prefixLength())
+ .matchExact(HDR_GTPU_IS_VALID, 0)
+ .build();
+ removeEntry(match2, FABRIC_INGRESS_SPGW_INTERFACES, false);
+ }
+}
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfStore.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfStore.java
new file mode 100644
index 0000000..786ef3c
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfStore.java
@@ -0,0 +1,141 @@
+/*
+ * 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.onlab.packet.Ip4Address;
+import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.behaviour.upf.PacketDetectionRule;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Stores state required for translation of UPF entities to pipeline-specific ones.
+ */
+public interface FabricUpfStore {
+ /**
+ * Clear all state associated with translation.
+ */
+ void reset();
+
+ /**
+ * Returns the farIdMap.
+ *
+ * @return the farIdMap.
+ */
+ Map<UpfRuleIdentifier, Integer> getFarIdMap();
+
+ /**
+ * Get a globally unique integer identifier for the FAR identified by the given (Session ID, Far
+ * ID) pair.
+ *
+ * @param farIdPair a RuleIdentifier instance uniquely identifying the FAR
+ * @return A globally unique integer identifier
+ */
+ int globalFarIdOf(UpfRuleIdentifier farIdPair);
+
+ /**
+ * Get a globally unique integer identifier for the FAR identified by the given (Session ID, Far
+ * ID) pair.
+ *
+ * @param pfcpSessionId The ID of the PFCP session that produced the FAR ID.
+ * @param sessionLocalFarId The FAR ID.
+ * @return A globally unique integer identifier
+ */
+ int globalFarIdOf(ImmutableByteSequence pfcpSessionId, int sessionLocalFarId);
+
+ /**
+ * Get the corresponding PFCP session ID and session-local FAR ID from a globally unique FAR ID,
+ * or return null if no such mapping is found.
+ *
+ * @param globalFarId globally unique FAR ID
+ * @return the corresponding PFCP session ID and session-local FAR ID, as a RuleIdentifier
+ */
+ UpfRuleIdentifier localFarIdOf(int globalFarId);
+
+ /**
+ * Get the corresponding queue Id from scheduling priority.
+ *
+ * @param schedulingPriority QCI scheduling priority
+ * @return the corresponding queue ID
+ */
+ String queueIdOf(int schedulingPriority);
+
+ /**
+ * Get the corresponding queue Id from scheduling priority.
+ *
+ * @param queueId Tofino queue Id
+ * @return the corresponding scheduling priroity
+ */
+ String schedulingPriorityOf(int queueId);
+
+ /**
+ * Stores the mapping between FAR ID and UE address as defined by the given PDR.
+ *
+ * @param pdr PDR
+ */
+ void learnFarIdToUeAddrs(PacketDetectionRule pdr);
+
+ /**
+ * Returns true if the given FAR IDs is known to be a buffering one.
+ *
+ * @param farId FAR ID
+ * @return boolean
+ */
+ boolean isFarIdBuffering(UpfRuleIdentifier farId);
+
+ /**
+ * Learns the given FAR ID as being a buffering one.
+ *
+ * @param farId FAR ID
+ */
+ void learBufferingFarId(UpfRuleIdentifier farId);
+
+ /**
+ * Forgets the given FAR ID as being a buffering one.
+ *
+ * @param farId FAR ID
+ */
+ void forgetBufferingFarId(UpfRuleIdentifier farId);
+
+ /**
+ * Returns the set of UE addresses associated with the given FAR ID.
+ *
+ * @param farId FAR ID
+ * @return Set of Ip4Address
+ */
+ Set<Ip4Address> ueAddrsOfFarId(UpfRuleIdentifier farId);
+
+ /**
+ * Removes the given UE address from the FAR ID to UE address map.
+ * @param ueAddr UE address
+ */
+ void forgetUeAddr(Ip4Address ueAddr);
+
+ /**
+ * Returns the set of known buffering FAR IDs.
+ * @return set
+ */
+ Set<UpfRuleIdentifier> getBufferFarIds();
+
+ /**
+ * Returns the FAR ID to UE addresses map.
+ *
+ * @return map
+ */
+ Map<UpfRuleIdentifier, Set<Ip4Address>> getFarIdToUeAddrs();
+}
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();
+// }
+
+}
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfTranslatorUtil.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfTranslatorUtil.java
new file mode 100644
index 0000000..a931f1f
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/FabricUpfTranslatorUtil.java
@@ -0,0 +1,142 @@
+/*
+ * 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.packet.Ip4Prefix;
+import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.behaviour.upf.UpfProgrammableException;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.PiCriterion;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.PiInstruction;
+import org.onosproject.net.pi.model.PiActionParamId;
+import org.onosproject.net.pi.model.PiMatchFieldId;
+import org.onosproject.net.pi.model.PiMatchType;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.net.pi.runtime.PiExactFieldMatch;
+import org.onosproject.net.pi.runtime.PiFieldMatch;
+import org.onosproject.net.pi.runtime.PiLpmFieldMatch;
+import org.onosproject.net.pi.runtime.PiRangeFieldMatch;
+import org.onosproject.net.pi.runtime.PiTableAction;
+import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
+
+import java.util.Optional;
+
+/**
+ * Utility class for manipulation of FlowRules and PiTableEntry objects specific to fabric-tna.
+ */
+final class FabricUpfTranslatorUtil {
+
+ private FabricUpfTranslatorUtil() {
+ }
+
+ static ImmutableByteSequence getFieldValue(PiFieldMatch field, PiMatchFieldId fieldId)
+ throws UpfProgrammableException {
+ if (field == null) {
+ throw new UpfProgrammableException(
+ String.format("Unable to find field %s where expected!", fieldId.toString()));
+ }
+ if (field.type() == PiMatchType.EXACT) {
+ return ((PiExactFieldMatch) field).value();
+ } else if (field.type() == PiMatchType.LPM) {
+ return ((PiLpmFieldMatch) field).value();
+ } else if (field.type() == PiMatchType.TERNARY) {
+ return ((PiTernaryFieldMatch) field).value();
+ } else if (field.type() == PiMatchType.RANGE) {
+ return ((PiRangeFieldMatch) field).lowValue();
+ } else {
+ throw new UpfProgrammableException(
+ String.format("Field %s has unknown match type: %s", fieldId.toString(), field.type().toString()));
+ }
+ }
+
+ static ImmutableByteSequence getFieldValue(PiCriterion criterion, PiMatchFieldId fieldId)
+ throws UpfProgrammableException {
+ return getFieldValue(criterion.fieldMatch(fieldId).orElse(null), fieldId);
+ }
+
+ static boolean fieldIsPresent(PiCriterion criterion, PiMatchFieldId fieldId) {
+ return criterion.fieldMatch(fieldId).isPresent();
+ }
+
+ static ImmutableByteSequence getParamValue(PiAction action, PiActionParamId paramId)
+ throws UpfProgrammableException {
+
+ for (PiActionParam param : action.parameters()) {
+ if (param.id().equals(paramId)) {
+ return param.value();
+ }
+ }
+ throw new UpfProgrammableException(
+ String.format("Unable to find parameter %s where expected!", paramId.toString()));
+ }
+
+ static int getFieldInt(PiCriterion criterion, PiMatchFieldId fieldId)
+ throws UpfProgrammableException {
+ return byteSeqToInt(getFieldValue(criterion, fieldId));
+ }
+
+ static int getParamInt(PiAction action, PiActionParamId paramId)
+ throws UpfProgrammableException {
+ return byteSeqToInt(getParamValue(action, paramId));
+ }
+
+ static Ip4Address getParamAddress(PiAction action, PiActionParamId paramId)
+ throws UpfProgrammableException {
+ return Ip4Address.valueOf(getParamValue(action, paramId).asArray());
+ }
+
+ static Ip4Prefix getFieldPrefix(PiCriterion criterion, PiMatchFieldId fieldId) {
+ Optional<PiFieldMatch> optField = criterion.fieldMatch(fieldId);
+ if (optField.isEmpty()) {
+ return null;
+ }
+ PiLpmFieldMatch field = (PiLpmFieldMatch) optField.get();
+ Ip4Address address = Ip4Address.valueOf(field.value().asArray());
+ return Ip4Prefix.valueOf(address, field.prefixLength());
+ }
+
+ static Ip4Address getFieldAddress(PiCriterion criterion, PiMatchFieldId fieldId)
+ throws UpfProgrammableException {
+ return Ip4Address.valueOf(getFieldValue(criterion, fieldId).asArray());
+ }
+
+ static int byteSeqToInt(ImmutableByteSequence sequence) {
+ try {
+ return sequence.fit(32).asReadOnlyBuffer().getInt();
+ } catch (ImmutableByteSequence.ByteSequenceTrimException e) {
+ throw new IllegalArgumentException("Attempted to convert a >4 byte wide sequence to an integer!");
+ }
+ }
+
+ static Pair<PiCriterion, PiTableAction> fabricEntryToPiPair(FlowRule entry) {
+ PiCriterion match = (PiCriterion) entry.selector().getCriterion(Criterion.Type.PROTOCOL_INDEPENDENT);
+ PiTableAction action = null;
+ for (Instruction instruction : entry.treatment().allInstructions()) {
+ if (instruction.type() == Instruction.Type.PROTOCOL_INDEPENDENT) {
+ PiInstruction piInstruction = (PiInstruction) instruction;
+ action = piInstruction.action();
+ break;
+ }
+ }
+ return Pair.of(match, action);
+ }
+}
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/UpfRuleIdentifier.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/UpfRuleIdentifier.java
new file mode 100644
index 0000000..5b6c6b6
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/UpfRuleIdentifier.java
@@ -0,0 +1,98 @@
+/*
+ * 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.onlab.util.ImmutableByteSequence;
+
+import java.util.Objects;
+
+/**
+ * Wrapper for identifying information of FARs and PDRs.
+ */
+public final class UpfRuleIdentifier {
+ private final int sessionlocalId;
+ private final ImmutableByteSequence pfcpSessionId;
+
+ /**
+ * A PDR or FAR can be globally uniquely identified by the combination of the ID of the PFCP session that
+ * produced it, and the ID that the rule was assigned in that PFCP session.
+ *
+ * @param pfcpSessionId The PFCP session that produced the rule ID
+ * @param sessionlocalId The rule ID
+ */
+ public UpfRuleIdentifier(ImmutableByteSequence pfcpSessionId, int sessionlocalId) {
+ this.pfcpSessionId = pfcpSessionId;
+ this.sessionlocalId = sessionlocalId;
+ }
+
+ /**
+ * Create an instance of this class from the given PFCP session ID and the session-local Rule ID.
+ *
+ * @param pfcpSessionId PFCP session ID of the rule to identify
+ * @param sessionlocalId session-local Rule ID of the rule to identify
+ * @return a new rule identifier
+ */
+ public static UpfRuleIdentifier of(ImmutableByteSequence pfcpSessionId, int sessionlocalId) {
+ return new UpfRuleIdentifier(pfcpSessionId, sessionlocalId);
+ }
+
+ /**
+ * Get the PFCP session-local rule ID.
+ *
+ * @return session-local rule ID
+ */
+ public int getSessionLocalId() {
+ return sessionlocalId;
+ }
+
+ /**
+ * Get the PFCP session ID.
+ *
+ * @return PFCP session ID
+ */
+ public ImmutableByteSequence getPfcpSessionId() {
+ return pfcpSessionId;
+ }
+
+ @Override
+ public String toString() {
+ return "RuleIdentifier{" +
+ "sessionlocalId=" + sessionlocalId +
+ ", pfcpSessionId=" + pfcpSessionId +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ UpfRuleIdentifier that = (UpfRuleIdentifier) obj;
+ return (this.sessionlocalId == that.sessionlocalId) && (this.pfcpSessionId.equals(that.pfcpSessionId));
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.sessionlocalId, this.pfcpSessionId);
+ }
+}
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/package-info.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/package-info.java
new file mode 100644
index 0000000..42591786
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/upf/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * UPF programmable behaviour implementation for fabric-v1model.
+ */
+package org.onosproject.pipelines.fabric.impl.behaviour.upf;