blob: 179e7931c420dc9f65dfe2ed0710324181e57fdf [file] [log] [blame]
/*
* 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_HAS_QFI;
import static org.onosproject.pipelines.fabric.FabricConstants.HDR_IPV4_DST_ADDR;
import static org.onosproject.pipelines.fabric.FabricConstants.HDR_QFI;
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.NEEDS_QFI_PUSH;
import static org.onosproject.pipelines.fabric.FabricConstants.NOTIFY_CP;
import static org.onosproject.pipelines.fabric.FabricConstants.QFI;
import static org.onosproject.pipelines.fabric.FabricConstants.SLICE_ID;
import static org.onosproject.pipelines.fabric.FabricConstants.SRC_IFACE;
import static org.onosproject.pipelines.fabric.FabricConstants.TC;
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;
import static org.onosproject.pipelines.fabric.impl.behaviour.Constants.DEFAULT_QFI;
import static org.onosproject.pipelines.fabric.impl.behaviour.Constants.DEFAULT_SLICE_ID;
import static org.onosproject.pipelines.fabric.impl.behaviour.Constants.DEFAULT_TC;
import static org.onosproject.pipelines.fabric.impl.behaviour.Constants.FALSE;
import static org.onosproject.pipelines.fabric.impl.behaviour.Constants.TRUE;
/**
* 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);
if (farId == null) {
throw new UpfProgrammableException(String.format("Unable to find local far id of %s", globalFarId));
}
pdrBuilder.withCounterId(FabricUpfTranslatorUtil.getParamInt(action, CTR_ID))
.withLocalFarId(farId.getSessionLocalId())
.withSessionId(farId.getPfcpSessionId());
PiActionId actionId = action.id();
if (actionId.equals(FABRIC_INGRESS_SPGW_LOAD_PDR_QOS)) {
pdrBuilder.withQfi(FabricUpfTranslatorUtil.getParamByte(action, QFI));
if (FabricUpfTranslatorUtil.getParamByte(action, NEEDS_QFI_PUSH) == TRUE) {
pdrBuilder.withQfiPush();
}
}
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);
if (FabricUpfTranslatorUtil.fieldIsPresent(match, HDR_HAS_QFI) &&
FabricUpfTranslatorUtil.getFieldByte(match, HDR_HAS_QFI) == TRUE) {
pdrBuilder.withQfi(FabricUpfTranslatorUtil.getFieldByte(match, HDR_QFI));
pdrBuilder.withQfiMatch();
}
} 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);
if (farId == null) {
throw new UpfProgrammableException(String.format("Unable to find local far id of %s", 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 {
final PiCriterion match;
final PiTableId tableId;
final PiAction action;
final PiCriterion.Builder matchBuilder = PiCriterion.builder();
PiAction.Builder actionBuilder = 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() ?
TRUE : FALSE),
new PiActionParam(TC, DEFAULT_TC)
));
PiActionId actionId = FABRIC_INGRESS_SPGW_LOAD_PDR;
if (pdr.matchesEncapped()) {
tableId = FABRIC_INGRESS_SPGW_UPLINK_PDRS;
matchBuilder.matchExact(HDR_TEID, pdr.teid().asArray())
.matchExact(HDR_TUNNEL_IPV4_DST, pdr.tunnelDest().toInt());
if (pdr.matchQfi()) {
matchBuilder.matchExact(HDR_HAS_QFI, TRUE)
.matchExact(HDR_QFI, pdr.qfi());
} else {
matchBuilder.matchExact(HDR_HAS_QFI, FALSE)
.matchExact(HDR_QFI, DEFAULT_QFI);
if (pdr.hasQfi()) {
actionId = FABRIC_INGRESS_SPGW_LOAD_PDR_QOS;
actionBuilder.withParameter(new PiActionParam(QFI, pdr.qfi()))
.withParameter(new PiActionParam(NEEDS_QFI_PUSH, FALSE));
}
}
} else if (pdr.matchesUnencapped()) {
tableId = FABRIC_INGRESS_SPGW_DOWNLINK_PDRS;
matchBuilder.matchExact(HDR_UE_ADDR, pdr.ueAddress().toInt());
if (pdr.hasQfi()) {
actionBuilder.withParameter(new PiActionParam(QFI, pdr.qfi()));
actionId = FABRIC_INGRESS_SPGW_LOAD_PDR_QOS;
}
actionBuilder.withParameter(
new PiActionParam(NEEDS_QFI_PUSH, pdr.pushQfi() ? TRUE : FALSE));
} else {
throw new UpfProgrammableException("Flexible PDRs not yet supported! Cannot translate " + pdr);
}
match = matchBuilder.build();
action = actionBuilder.withId(actionId)
.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))
.withParameter(new PiActionParam(SLICE_ID, DEFAULT_SLICE_ID))
.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();
}
}