Line data Source code
1 : /*
2 : * Copyright (c) 2022 Project CHIP Authors
3 : * All rights reserved.
4 : *
5 : * Licensed under the Apache License, Version 2.0 (the "License");
6 : * you may not use this file except in compliance with the License.
7 : * You may obtain a copy of the License at
8 : *
9 : * http://www.apache.org/licenses/LICENSE-2.0
10 : *
11 : * Unless required by applicable law or agreed to in writing, software
12 : * distributed under the License is distributed on an "AS IS" BASIS,
13 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 : * See the License for the specific language governing permissions and
15 : * limitations under the License.
16 : */
17 :
18 : #pragma once
19 :
20 : #include <atomic>
21 : #include <inet/EndpointQueueFilter.h>
22 : #include <inet/IPPacketInfo.h>
23 : #include <lib/support/CodeUtils.h>
24 : #include <system/SystemPacketBuffer.h>
25 :
26 : namespace chip {
27 : namespace Inet {
28 :
29 : /**
30 : * @brief Basic filter that counts how many pending (not yet dequeued) packets
31 : * are accumulated that match a predicate function, and drops those that
32 : * would cause crossing of the threshold.
33 : */
34 : class DropIfTooManyQueuedPacketsFilter : public chip::Inet::EndpointQueueFilter
35 : {
36 : public:
37 : typedef bool (*PacketMatchPredicateFunc)(void * context, const void * endpoint, const chip::Inet::IPPacketInfo & pktInfo,
38 : const chip::System::PacketBufferHandle & pktPayload);
39 :
40 : /**
41 : * @brief Initialize the packet filter with a starting limit
42 : *
43 : * @param maxAllowedQueuedPackets - max number of pending-in-queue not yet processed predicate-matching packets
44 : */
45 1 : DropIfTooManyQueuedPacketsFilter(size_t maxAllowedQueuedPackets) : mMaxAllowedQueuedPackets(maxAllowedQueuedPackets) {}
46 :
47 : /**
48 : * @brief Set the predicate to use for filtering
49 : *
50 : * @warning DO NOT modify at runtime while the filter is being called. If you do so, the queue accounting could
51 : * get out of sync, and cause the filtering to fail to properly work.
52 : *
53 : * @param predicateFunc - Predicate function to apply. If nullptr, no filtering will take place
54 : * @param context - Pointer to predicate-specific context that will be provided to predicate at every call. May be nullptr.
55 : */
56 3 : void SetPredicate(PacketMatchPredicateFunc predicateFunc, void * context)
57 : {
58 3 : mPredicate = predicateFunc;
59 3 : mContext = context;
60 3 : }
61 :
62 : /**
63 : * @brief Set the ceiling for max allowed packets queued up that matched the predicate.
64 : *
65 : * @note Changing this at runtime while packets are coming only affects future dropping, and
66 : * does not remove packets from the queue if the limit is lowered below the currently-in-queue
67 : * count.
68 : *
69 : * @param maxAllowedQueuedPackets - number of packets currently pending allowed.
70 : */
71 3 : void SetMaxQueuedPacketsLimit(size_t maxAllowedQueuedPackets) { mMaxAllowedQueuedPackets.store(maxAllowedQueuedPackets); }
72 :
73 : /**
74 : * @return the total number of packets dropped so far by the filter
75 : */
76 32 : size_t GetNumDroppedPackets() const { return mNumDroppedPackets.load(); }
77 :
78 : /**
79 : * @brief Reset the counter of dropped packets.
80 : */
81 1 : void ClearNumDroppedPackets() { mNumDroppedPackets.store(0); }
82 :
83 : /**
84 : * @brief Method called when a packet is dropped due to high watermark getting reached, based on predicate.
85 : *
86 : * Subclasses may use this to implement additional behavior or diagnostics.
87 : *
88 : * This is called once for every dropped packet. If there is no filter predicate, this is not called.
89 : *
90 : * @param endpoint - pointer to endpoint instance (platform-dependent, which is why it's void)
91 : * @param pktInfo - info about source/dest of packet
92 : * @param pktPayload - payload content of packet
93 : */
94 0 : virtual void OnDropped(const void * endpoint, const chip::Inet::IPPacketInfo & pktInfo,
95 : const chip::System::PacketBufferHandle & pktPayload)
96 0 : {}
97 :
98 : /**
99 : * @brief Method called whenever queue of accumulated packets is now empty, based on predicate.
100 : *
101 : * Subclasses may use this to implement additional behavior or diagnostics.
102 : *
103 : * This is possibly called repeatedly in a row, if the queue actually never gets above one.
104 : *
105 : * This is only called for packets that had matched the filtering rule, where they had
106 : * been explicitly allowed in the past. If there is no filter predicate, this is not called.
107 : *
108 : * @param endpoint - pointer to endpoint instance (platform-dependent, which is why it's void)
109 : * @param pktInfo - info about source/dest of packet
110 : * @param pktPayload - payload content of packet
111 : */
112 0 : virtual void OnLastMatchDequeued(const void * endpoint, const chip::Inet::IPPacketInfo & pktInfo,
113 : const chip::System::PacketBufferHandle & pktPayload)
114 0 : {}
115 :
116 : /**
117 : * @brief Implementation of filtering before queueing that applies the predicate.
118 : *
119 : * See base class for arguments
120 : */
121 31 : FilterOutcome FilterBeforeEnqueue(const void * endpoint, const chip::Inet::IPPacketInfo & pktInfo,
122 : const chip::System::PacketBufferHandle & pktPayload) override
123 : {
124 : // WARNING: This is likely called in a different context than `FilterAfterDequeue`. We use an atomic for the counter.
125 :
126 : // Non-matching is never accounted, always allowed. Lack of predicate is equivalent to non-matching.
127 31 : if ((mPredicate == nullptr) || !mPredicate(mContext, endpoint, pktInfo, pktPayload))
128 : {
129 19 : return FilterOutcome::kAllowPacket;
130 : }
131 :
132 24 : if (mNumQueuedPackets.load() >= mMaxAllowedQueuedPackets)
133 : {
134 7 : ++mNumDroppedPackets;
135 7 : OnDropped(endpoint, pktInfo, pktPayload);
136 7 : return FilterOutcome::kDropPacket;
137 : }
138 :
139 5 : ++mNumQueuedPackets;
140 :
141 5 : return FilterOutcome::kAllowPacket;
142 : }
143 :
144 : /**
145 : * @brief Implementation of filtering after dequeueing that applies the predicate.
146 : *
147 : * See base class for arguments
148 : */
149 15 : FilterOutcome FilterAfterDequeue(const void * endpoint, const chip::Inet::IPPacketInfo & pktInfo,
150 : const chip::System::PacketBufferHandle & pktPayload) override
151 : {
152 : // WARNING: This is likely called in a different context than `FilterBeforeEnqueue`. We use an atomic for the counter.
153 : // NOTE: This is always called from Matter platform event loop
154 :
155 : // Non-matching is never accounted, always allowed. Lack of predicate is equivalent to non-matching.
156 15 : if ((mPredicate == nullptr) || !mPredicate(mContext, endpoint, pktInfo, pktPayload))
157 : {
158 10 : return FilterOutcome::kAllowPacket;
159 : }
160 :
161 : // If we ever go negative, we have mismatch ingress/egress filter via predicate and
162 : // device may eventually starve.
163 5 : VerifyOrDie(mNumQueuedPackets != 0);
164 :
165 5 : --mNumQueuedPackets;
166 5 : size_t numQueuedPackets = mNumQueuedPackets.load();
167 5 : if (numQueuedPackets == 0)
168 : {
169 1 : OnLastMatchDequeued(endpoint, pktInfo, pktPayload);
170 : }
171 :
172 : // We always allow the packet and just do accounting, since all dropping is prior to queue entry.
173 5 : return FilterOutcome::kAllowPacket;
174 : }
175 :
176 : protected:
177 : PacketMatchPredicateFunc mPredicate = nullptr;
178 : void * mContext = nullptr;
179 : std::atomic_size_t mNumQueuedPackets{ 0 };
180 : std::atomic_size_t mMaxAllowedQueuedPackets{ 0 };
181 : std::atomic_size_t mNumDroppedPackets{ 0u };
182 : };
183 :
184 : } // namespace Inet
185 : } // namespace chip
|