blob: f607d8704e347bcc7a6209a86c20f92a0ab5880f [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.net.behaviour.upf;
import org.onlab.packet.Ip4Address;
import org.onlab.util.ImmutableByteSequence;
import java.util.Objects;
import static com.google.common.base.Preconditions.checkArgument;
/**
* A single Packet Detection Rule (PDR), an entity described in the 3GPP
* specifications (although that does not mean that this class is 3GPP
* compliant). An instance of this class will be generated by a logical switch
* write request to the database-style PDR P4 table, and the resulting instance
* should contain all the information needed to reproduce that logical switch
* PDR in the event of a client read request. The instance should also contain
* sufficient information (or expose the means to retrieve such information) to
* generate the corresponding dataplane forwarding state that implements the PDR.
*/
public final class PacketDetectionRule {
// Match keys
private final Ip4Address ueAddr; // The UE IP address that this PDR matches on
private final ImmutableByteSequence teid; // The Tunnel Endpoint ID that this PDR matches on
private final Ip4Address tunnelDst; // The tunnel destination address that this PDR matches on
// Action parameters
private final ImmutableByteSequence sessionId; // The ID of the PFCP session that created this PDR
private final Integer ctrId; // Counter ID unique to this PDR
private final Integer farId; // The PFCP session-local ID of the FAR that should apply after this PDR hits
private final Type type;
private final Byte qfi; // QoS Flow Identifier of this PDR
private final boolean qfiPush; // True when QFI should be pushed to the packet
private final boolean qfiMatch; // True when QFI should be matched
private static final int SESSION_ID_BITWIDTH = 96;
private PacketDetectionRule(ImmutableByteSequence sessionId, Integer ctrId,
Integer farId, Ip4Address ueAddr, Byte qfi,
ImmutableByteSequence teid, Ip4Address tunnelDst,
Type type, boolean qfiPush, boolean qfiMatch) {
this.ueAddr = ueAddr;
this.teid = teid;
this.tunnelDst = tunnelDst;
this.sessionId = sessionId;
this.ctrId = ctrId;
this.farId = farId;
this.qfi = qfi;
this.type = type;
this.qfiPush = qfiPush;
this.qfiMatch = qfiMatch;
}
public static Builder builder() {
return new Builder();
}
/**
* Return a string representing the match conditions of this PDR.
*
* @return a string representing the PDR match conditions
*/
public String matchString() {
if (matchesEncapped()) {
return matchQfi() ?
String.format("Match(Dst=%s, TEID=%s, QFI=%s)", tunnelDest(), teid(), qfi()) :
String.format("Match(Dst=%s, TEID=%s)", tunnelDest(), teid());
} else {
return String.format("Match(Dst=%s, !GTP)", ueAddress());
}
}
@Override
public String toString() {
String actionParams = "";
if (hasActionParameters()) {
actionParams = hasQfi() && !matchQfi() ?
String.format("SEID=%s, FAR=%d, CtrIdx=%d, QFI=%s",
sessionId.toString(), farId, ctrId, qfi) :
String.format("SEID=%s, FAR=%d, CtrIdx=%d",
sessionId.toString(), farId, ctrId);
actionParams = pushQfi() ? String.format("%s, QFI_PUSH", actionParams) : actionParams;
}
return String.format("PDR{%s -> LoadParams(%s)}",
matchString(), actionParams);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
PacketDetectionRule that = (PacketDetectionRule) obj;
// Safe comparisons between potentially null objects
return (this.type.equals(that.type) &&
Objects.equals(this.teid, that.teid) &&
Objects.equals(this.tunnelDst, that.tunnelDst) &&
Objects.equals(this.ueAddr, that.ueAddr) &&
Objects.equals(this.ctrId, that.ctrId) &&
Objects.equals(this.sessionId, that.sessionId) &&
Objects.equals(this.farId, that.farId) &&
Objects.equals(this.qfi, that.qfi) &&
Objects.equals(this.qfiPush, that.qfiPush) &&
Objects.equals(this.qfiMatch, that.qfiMatch));
}
@Override
public int hashCode() {
return Objects.hash(ueAddr, teid, tunnelDst, sessionId, ctrId, farId,
qfi, type);
}
/**
* Instances created as a result of DELETE write requests will not have
* action parameters, only match keys. This method should be used to avoid
* null pointer exceptions in those instances.
*
* @return true if this instance has PDR action parameters, false otherwise.
*/
public boolean hasActionParameters() {
return type == Type.MATCH_ENCAPPED || type == Type.MATCH_UNENCAPPED;
}
/**
* Return a new instance of this PDR with the action parameters stripped,
* leaving only the match keys.
*
* @return a new PDR with only match keys
*/
public PacketDetectionRule withoutActionParams() {
if (matchesEncapped()) {
PacketDetectionRule.Builder pdrBuilder = PacketDetectionRule.builder()
.withTeid(teid)
.withTunnelDst(tunnelDst);
if (this.hasQfi() && this.matchQfi()) {
pdrBuilder.withQfiMatch();
pdrBuilder.withQfi(qfi);
}
return pdrBuilder.build();
} else {
return PacketDetectionRule.builder()
.withUeAddr(ueAddr).build();
}
}
/**
* True if this PDR matches on packets received with a GTP header, and false
* otherwise.
*
* @return true if the PDR matches only encapsulated packets
*/
public boolean matchesEncapped() {
return type == Type.MATCH_ENCAPPED ||
type == Type.MATCH_ENCAPPED_NO_ACTION;
}
/**
* True if this PDR matches on packets received without a GTP header, and
* false otherwise.
*
* @return true if the PDR matches only un-encapsulated packets
*/
public boolean matchesUnencapped() {
return type == Type.MATCH_UNENCAPPED ||
type == Type.MATCH_UNENCAPPED_NO_ACTION;
}
/**
* Get the ID of the PFCP session that produced this PDR.
*
* @return PFCP session ID
*/
public ImmutableByteSequence sessionId() {
return sessionId;
}
/**
* Get the UE IP address that this PDR matches on.
*
* @return UE IP address
*/
public Ip4Address ueAddress() {
return ueAddr;
}
/**
* Get the identifier of the GTP tunnel that this PDR matches on.
*
* @return GTP tunnel ID
*/
public ImmutableByteSequence teid() {
return teid;
}
/**
* Get the destination IP of the GTP tunnel that this PDR matches on.
*
* @return GTP tunnel destination IP
*/
public Ip4Address tunnelDest() {
return tunnelDst;
}
/**
* Get the dataplane PDR counter cell ID that this PDR is assigned.
*
* @return PDR counter cell ID
*/
public int counterId() {
return ctrId;
}
/**
* Get the PFCP session-local ID of the far that should apply to packets
* that this PDR matches.
*
* @return PFCP session-local FAR ID
*/
public int farId() {
return farId;
}
/**
* Get the QoS Flow Identifier for this PDR.
*
* @return QoS Flow Identifier
*/
public byte qfi() {
return qfi;
}
/**
* Returns whether QFi should be pushed to the packet.
*
* @return True if the QFI should be pushed to the packet, false otherwise
*/
public boolean pushQfi() {
return qfiPush;
}
/**
* Returns whether QFI should be matched on the packet or not.
*
* @return True if QFI should be matched on the packet, false otherwise
*/
public boolean matchQfi() {
return qfiMatch;
}
/**
* Checks if the PDR has a QFI to match upon or to push on the packet.
*
* @return True if the PDR has a QFI, false otherwise
*/
public boolean hasQfi() {
return qfi != null;
}
private enum Type {
/**
* Match on packets that are encapsulated in a GTP tunnel.
*/
MATCH_ENCAPPED,
/**
* Match on packets that are not encapsulated in a GTP tunnel.
*/
MATCH_UNENCAPPED,
/**
* For PDRs that match on encapsulated packets but do not yet have any
* action parameters set.
* These are usually built in the context of P4Runtime DELETE write requests.
*/
MATCH_ENCAPPED_NO_ACTION,
/**
* For PDRs that match on unencapsulated packets but do not yet have any
* action parameters set.
* These are usually built in the context of P4Runtime DELETE write requests.
*/
MATCH_UNENCAPPED_NO_ACTION
}
public static class Builder {
private ImmutableByteSequence sessionId = null;
private Integer ctrId = null;
private Integer localFarId = null;
private Integer schedulingPriority = null;
private Ip4Address ueAddr = null;
private ImmutableByteSequence teid = null;
private Ip4Address tunnelDst = null;
private Byte qfi = null;
private boolean qfiPush = false;
private boolean qfiMatch = false;
public Builder() {
}
/**
* Set the ID of the PFCP session that produced this PDR.
*
* @param sessionId PFCP session ID
* @return This builder object
*/
public Builder withSessionId(ImmutableByteSequence sessionId) {
this.sessionId = sessionId;
return this;
}
/**
* Set the ID of the PFCP session that produced this PDR.
*
* @param sessionId PFCP session ID
* @return This builder object
*/
public Builder withSessionId(long sessionId) {
try {
this.sessionId = ImmutableByteSequence.copyFrom(sessionId)
.fit(SESSION_ID_BITWIDTH);
} catch (ImmutableByteSequence.ByteSequenceTrimException e) {
// This error is literally impossible
}
return this;
}
/**
* Set the UE IP address that this PDR matches on.
*
* @param ueAddr UE IP address
* @return This builder object
*/
public Builder withUeAddr(Ip4Address ueAddr) {
this.ueAddr = ueAddr;
return this;
}
/**
* Set the dataplane PDR counter cell ID that this PDR is assigned.
*
* @param ctrId PDR counter cell ID
* @return This builder object
*/
public Builder withCounterId(int ctrId) {
this.ctrId = ctrId;
return this;
}
/**
* Set the PFCP session-local ID of the far that should apply to packets that this PDR matches.
*
* @param localFarId PFCP session-local FAR ID
* @return This builder object
*/
public Builder withLocalFarId(int localFarId) {
this.localFarId = localFarId;
return this;
}
/**
* Set the QoS Flow Identifier for this PDR.
*
* @param qfi GTP Tunnel QFI
* @return This builder object
*/
public Builder withQfi(byte qfi) {
this.qfi = qfi;
return this;
}
/**
* The QoS Flow Identifier should be pushed to the packet.
* This is valid for downstream packets and for 5G traffic only.
*
* @return This builder object
*/
public Builder withQfiPush() {
this.qfiPush = true;
return this;
}
/**
* The QoS Flow Identifier should be matched to the packet.
* This is valid for upstream packets and for 5G traffic only.
*
* @return This builder object
*/
public Builder withQfiMatch() {
this.qfiMatch = true;
return this;
}
/**
* Set the identifier of the GTP tunnel that this PDR matches on.
*
* @param teid GTP tunnel ID
* @return This builder object
*/
public Builder withTeid(int teid) {
this.teid = ImmutableByteSequence.copyFrom(teid);
return this;
}
/**
* Set the identifier of the GTP tunnel that this PDR matches on.
*
* @param teid GTP tunnel ID
* @return This builder object
*/
public Builder withTeid(ImmutableByteSequence teid) {
this.teid = teid;
return this;
}
/**
* Set the destination IP of the GTP tunnel that this PDR matches on.
*
* @param tunnelDst GTP tunnel destination IP
* @return This builder object
*/
public Builder withTunnelDst(Ip4Address tunnelDst) {
this.tunnelDst = tunnelDst;
return this;
}
/**
* Set the tunnel ID and destination IP of the GTP tunnel that this PDR matches on.
*
* @param teid GTP tunnel ID
* @param tunnelDst GTP tunnel destination IP
* @return This builder object
*/
public Builder withTunnel(ImmutableByteSequence teid, Ip4Address tunnelDst) {
this.teid = teid;
this.tunnelDst = tunnelDst;
return this;
}
/**
* Set the tunnel ID, destination IP and QFI of the GTP tunnel that this PDR matches on.
*
* @param teid GTP tunnel ID
* @param tunnelDst GTP tunnel destination IP
* @param qfi GTP QoS Flow Identifier
* @return This builder object
*/
public Builder withTunnel(ImmutableByteSequence teid, Ip4Address tunnelDst, byte qfi) {
this.teid = teid;
this.tunnelDst = tunnelDst;
this.qfi = qfi;
return this;
}
public PacketDetectionRule build() {
// Some match keys are required.
checkArgument(
(ueAddr != null && teid == null && tunnelDst == null) ||
(ueAddr == null && teid != null && tunnelDst != null),
"Either a UE address or a TEID and Tunnel destination must be provided, but not both.");
// Action parameters are optional but must be all provided together if they are provided
checkArgument(
(sessionId != null && ctrId != null && localFarId != null) ||
(sessionId == null && ctrId == null && localFarId == null),
"PDR action parameters must be provided together or not at all.");
checkArgument(!qfiPush || !qfiMatch,
"Either match of push QFI can be true, not both.");
checkArgument(!qfiPush || qfi != null,
"A QFI must be provided when pushing QFI to the packet.");
checkArgument(!qfiMatch || qfi != null,
"A QFI must be provided when matching QFI on the packet.");
Type type;
if (teid != null) {
if (sessionId != null) {
type = Type.MATCH_ENCAPPED;
} else {
type = Type.MATCH_ENCAPPED_NO_ACTION;
}
} else {
if (sessionId != null) {
type = Type.MATCH_UNENCAPPED;
} else {
type = Type.MATCH_UNENCAPPED_NO_ACTION;
}
}
return new PacketDetectionRule(sessionId, ctrId, localFarId, ueAddr,
qfi, teid, tunnelDst, type,
qfiPush, qfiMatch);
}
}
}