| /* |
| * 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); |
| } |
| } |
| } |