blob: 364c0c7c11f17dcae88c08a77a34334a908e4ebb [file] [log] [blame]
/*
* Copyright (c) 2022 Project CHIP Authors
* All rights reserved.
*
* 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.
*/
#pragma once
#include <atomic>
#include <inet/EndpointQueueFilter.h>
#include <inet/IPPacketInfo.h>
#include <lib/support/CodeUtils.h>
#include <system/SystemPacketBuffer.h>
namespace chip {
namespace Inet {
/**
* @brief Basic filter that counts how many pending (not yet dequeued) packets
* are accumulated that match a predicate function, and drops those that
* would cause crossing of the threshold.
*/
class DropIfTooManyQueuedPacketsFilter : public chip::Inet::EndpointQueueFilter
{
public:
typedef bool (*PacketMatchPredicateFunc)(void * context, const void * endpoint, const chip::Inet::IPPacketInfo & pktInfo,
const chip::System::PacketBufferHandle & pktPayload);
/**
* @brief Initialize the packet filter with a starting limit
*
* @param maxAllowedQueuedPackets - max number of pending-in-queue not yet processed predicate-matching packets
*/
DropIfTooManyQueuedPacketsFilter(size_t maxAllowedQueuedPackets) : mMaxAllowedQueuedPackets(maxAllowedQueuedPackets) {}
/**
* @brief Set the predicate to use for filtering
*
* @warning DO NOT modify at runtime while the filter is being called. If you do so, the queue accounting could
* get out of sync, and cause the filtering to fail to properly work.
*
* @param predicateFunc - Predicate function to apply. If nullptr, no filtering will take place
* @param context - Pointer to predicate-specific context that will be provided to predicate at every call. May be nullptr.
*/
void SetPredicate(PacketMatchPredicateFunc predicateFunc, void * context)
{
mPredicate = predicateFunc;
mContext = context;
}
/**
* @brief Set the ceiling for max allowed packets queued up that matched the predicate.
*
* @note Changing this at runtime while packets are coming only affects future dropping, and
* does not remove packets from the queue if the limit is lowered below the currently-in-queue
* count.
*
* @param maxAllowedQueuedPackets - number of packets currently pending allowed.
*/
void SetMaxQueuedPacketsLimit(int maxAllowedQueuedPackets) { mMaxAllowedQueuedPackets.store(maxAllowedQueuedPackets); }
/**
* @return the total number of packets dropped so far by the filter
*/
size_t GetNumDroppedPackets() const { return mNumDroppedPackets.load(); }
/**
* @brief Reset the counter of dropped packets.
*/
void ClearNumDroppedPackets() { mNumDroppedPackets.store(0); }
/**
* @brief Method called when a packet is dropped due to high watermark getting reached, based on predicate.
*
* Subclasses may use this to implement additional behavior or diagnostics.
*
* This is called once for every dropped packet. If there is no filter predicate, this is not called.
*
* @param endpoint - pointer to endpoint instance (platform-dependent, which is why it's void)
* @param pktInfo - info about source/dest of packet
* @param pktPayload - payload content of packet
*/
virtual void OnDropped(const void * endpoint, const chip::Inet::IPPacketInfo & pktInfo,
const chip::System::PacketBufferHandle & pktPayload)
{}
/**
* @brief Method called whenever queue of accumulated packets is now empty, based on predicate.
*
* Subclasses may use this to implement additional behavior or diagnostics.
*
* This is possibly called repeatedly in a row, if the queue actually never gets above one.
*
* This is only called for packets that had matched the filtering rule, where they had
* been explicitly allowed in the past. If there is no filter predicate, this is not called.
*
* @param endpoint - pointer to endpoint instance (platform-dependent, which is why it's void)
* @param pktInfo - info about source/dest of packet
* @param pktPayload - payload content of packet
*/
virtual void OnLastMatchDequeued(const void * endpoint, const chip::Inet::IPPacketInfo & pktInfo,
const chip::System::PacketBufferHandle & pktPayload)
{}
/**
* @brief Implementation of filtering before queueing that applies the predicate.
*
* See base class for arguments
*/
FilterOutcome FilterBeforeEnqueue(const void * endpoint, const chip::Inet::IPPacketInfo & pktInfo,
const chip::System::PacketBufferHandle & pktPayload) override
{
// WARNING: This is likely called in a different context than `FilterAfterDequeue`. We use an atomic for the counter.
// Non-matching is never accounted, always allowed. Lack of predicate is equivalent to non-matching.
if ((mPredicate == nullptr) || !mPredicate(mContext, endpoint, pktInfo, pktPayload))
{
return FilterOutcome::kAllowPacket;
}
if (mNumQueuedPackets.load() >= mMaxAllowedQueuedPackets)
{
++mNumDroppedPackets;
OnDropped(endpoint, pktInfo, pktPayload);
return FilterOutcome::kDropPacket;
}
++mNumQueuedPackets;
return FilterOutcome::kAllowPacket;
}
/**
* @brief Implementation of filtering after dequeueing that applies the predicate.
*
* See base class for arguments
*/
FilterOutcome FilterAfterDequeue(const void * endpoint, const chip::Inet::IPPacketInfo & pktInfo,
const chip::System::PacketBufferHandle & pktPayload) override
{
// WARNING: This is likely called in a different context than `FilterBeforeEnqueue`. We use an atomic for the counter.
// NOTE: This is always called from Matter platform event loop
// Non-matching is never accounted, always allowed. Lack of predicate is equivalent to non-matching.
if ((mPredicate == nullptr) || !mPredicate(mContext, endpoint, pktInfo, pktPayload))
{
return FilterOutcome::kAllowPacket;
}
--mNumQueuedPackets;
int numQueuedPackets = mNumQueuedPackets.load();
if (numQueuedPackets == 0)
{
OnLastMatchDequeued(endpoint, pktInfo, pktPayload);
}
// If we ever go negative, we have mismatch ingress/egress filter via predicate and
// device may eventually starve.
VerifyOrDie(numQueuedPackets >= 0);
// We always allow the packet and just do accounting, since all dropping is prior to queue entry.
return FilterOutcome::kAllowPacket;
}
protected:
PacketMatchPredicateFunc mPredicate = nullptr;
void * mContext = nullptr;
std::atomic_int mNumQueuedPackets{ 0 };
std::atomic_int mMaxAllowedQueuedPackets{ 0 };
std::atomic_size_t mNumDroppedPackets{ 0u };
};
} // namespace Inet
} // namespace chip