blob: ef07cab057eb898882856e27baad760baa20226e [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.
*/
#include <stdio.h>
#include <string.h>
#include <inet/BasicPacketFilters.h>
#include <inet/IPPacketInfo.h>
#include <inet/InetInterface.h>
#include <lib/support/CHIPMem.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/Span.h>
#include <lib/support/UnitTestRegistration.h>
#include <lib/support/logging/CHIPLogging.h>
#include <system/SystemPacketBuffer.h>
#include <nlunit-test.h>
namespace {
using namespace chip;
using namespace chip::Inet;
class DropIfTooManyQueuedPacketsHarness : public DropIfTooManyQueuedPacketsFilter
{
public:
DropIfTooManyQueuedPacketsHarness(size_t maxAllowedQueuedPackets) : DropIfTooManyQueuedPacketsFilter(maxAllowedQueuedPackets) {}
void OnDropped(const void * endpoint, const IPPacketInfo & pktInfo,
const chip::System::PacketBufferHandle & pktPayload) override
{
++mNumOnDroppedCalled;
// Log a hysteretic event
if (!mHitCeilingWantFloor)
{
mHitCeilingWantFloor = true;
ChipLogError(Inet, "Hit waterwark, will log as resolved once we get back to empty");
}
}
void OnLastMatchDequeued(const void * endpoint, const IPPacketInfo & pktInfo,
const chip::System::PacketBufferHandle & pktPayload) override
{
++mNumOnLastMatchDequeuedCalled;
// Log a hysteretic event
if (mHitCeilingWantFloor)
{
mHitCeilingWantFloor = false;
ChipLogError(Inet, "Resolved burst, got back to fully empty.");
}
}
// Public bits to make testing easier
nlTestSuite * mTestSuite = nullptr;
int mNumOnDroppedCalled = 0;
int mNumOnLastMatchDequeuedCalled = 0;
bool mHitCeilingWantFloor;
};
class FilterDriver
{
public:
FilterDriver(EndpointQueueFilter * filter, const void * endpoint) : mFilter(filter), mEndpoint(endpoint) {}
EndpointQueueFilter::FilterOutcome ProcessEnqueue(const IPAddress & srcAddr, uint16_t srcPort, const IPAddress & dstAddr,
uint16_t dstPort, ByteSpan payload)
{
VerifyOrDie(mFilter != nullptr);
chip::Inet::IPPacketInfo pktInfo;
pktInfo.SrcAddress = srcAddr;
pktInfo.DestAddress = dstAddr;
pktInfo.SrcPort = srcPort;
pktInfo.DestPort = dstPort;
auto pktPayload = chip::System::PacketBufferHandle::NewWithData(payload.data(), payload.size());
return mFilter->FilterBeforeEnqueue(mEndpoint, pktInfo, pktPayload);
}
EndpointQueueFilter::FilterOutcome ProcessDequeue(const IPAddress & srcAddr, uint16_t srcPort, const IPAddress & dstAddr,
uint16_t dstPort, ByteSpan payload)
{
VerifyOrDie(mFilter != nullptr);
chip::Inet::IPPacketInfo pktInfo;
pktInfo.SrcAddress = srcAddr;
pktInfo.DestAddress = dstAddr;
pktInfo.SrcPort = srcPort;
pktInfo.DestPort = dstPort;
auto pktPayload = chip::System::PacketBufferHandle::NewWithData(payload.data(), payload.size());
return mFilter->FilterAfterDequeue(mEndpoint, pktInfo, pktPayload);
}
protected:
EndpointQueueFilter * mFilter = nullptr;
const void * mEndpoint = nullptr;
};
DropIfTooManyQueuedPacketsHarness gFilter(0);
int gFakeEndpointForPointer = 0;
void TestBasicPacketFilter(nlTestSuite * inSuite, void * inContext)
{
constexpr uint16_t kMdnsPort = 5353u;
gFilter.mTestSuite = inSuite;
// Predicate for test is filter that destination port is 5353 (mDNS).
// NOTE: A non-capturing lambda is used, but a plain function could have been used as well...
auto predicate = [](void * context, const void * endpoint, const chip::Inet::IPPacketInfo & pktInfo,
const chip::System::PacketBufferHandle & pktPayload) -> bool {
auto filter = reinterpret_cast<DropIfTooManyQueuedPacketsHarness *>(context);
auto testSuite = filter->mTestSuite;
auto expectedEndpoint = &gFakeEndpointForPointer;
// Ensure we get called with context and expected endpoint pointer
NL_TEST_ASSERT(testSuite, context == &gFilter);
NL_TEST_ASSERT(testSuite, endpoint == expectedEndpoint);
// Predicate filters destination port being 5353
return (pktInfo.DestPort == kMdnsPort);
};
gFilter.SetPredicate(predicate, &gFilter);
FilterDriver fakeUdpEndpoint(&gFilter, &gFakeEndpointForPointer);
IPAddress fakeSrc;
IPAddress fakeDest;
IPAddress fakeMdnsDest;
constexpr uint16_t kOtherPort = 43210u;
const uint8_t kFakePayloadData[] = { 1, 2, 3 };
const ByteSpan kFakePayload{ kFakePayloadData };
NL_TEST_ASSERT(inSuite, IPAddress::FromString("fe80::aaaa:bbbb:cccc:dddd", fakeSrc));
NL_TEST_ASSERT(inSuite, IPAddress::FromString("fe80::0000:1111:2222:3333", fakeDest));
NL_TEST_ASSERT(inSuite, IPAddress::FromString("ff02::fb", fakeMdnsDest));
// Shorthands for simplifying asserts
constexpr EndpointQueueFilter::FilterOutcome kAllowPacket = EndpointQueueFilter::FilterOutcome::kAllowPacket;
constexpr EndpointQueueFilter::FilterOutcome kDropPacket = EndpointQueueFilter::FilterOutcome::kDropPacket;
constexpr int kMaxQueuedPacketsLimit = 3;
gFilter.SetMaxQueuedPacketsLimit(kMaxQueuedPacketsLimit);
{
// Enqueue some packets that don't match filter, all allowed, never hit the drop
for (int numPkt = 0; numPkt < (kMaxQueuedPacketsLimit + 1); ++numPkt)
{
NL_TEST_ASSERT(inSuite,
kAllowPacket == fakeUdpEndpoint.ProcessEnqueue(fakeSrc, kOtherPort, fakeDest, kOtherPort, kFakePayload));
}
NL_TEST_ASSERT(inSuite, gFilter.GetNumDroppedPackets() == 0);
// Dequeue all packets
for (int numPkt = 0; numPkt < (kMaxQueuedPacketsLimit + 1); ++numPkt)
{
NL_TEST_ASSERT(inSuite,
kAllowPacket == fakeUdpEndpoint.ProcessDequeue(fakeSrc, kOtherPort, fakeDest, kOtherPort, kFakePayload));
}
NL_TEST_ASSERT(inSuite, gFilter.GetNumDroppedPackets() == 0);
// OnDroped/OnLastMatchDequeued only ever called for matching packets, never for non-matching
NL_TEST_ASSERT(inSuite, gFilter.mNumOnDroppedCalled == 0);
NL_TEST_ASSERT(inSuite, gFilter.mNumOnLastMatchDequeuedCalled == 0);
}
{
// Enqueue packets that match filter, up to watermark. None dropped
for (int numPkt = 0; numPkt < kMaxQueuedPacketsLimit; ++numPkt)
{
NL_TEST_ASSERT(inSuite,
kAllowPacket ==
fakeUdpEndpoint.ProcessEnqueue(fakeSrc, kOtherPort, fakeMdnsDest, kMdnsPort, kFakePayload));
}
NL_TEST_ASSERT(inSuite, gFilter.GetNumDroppedPackets() == 0);
NL_TEST_ASSERT(inSuite, gFilter.mNumOnDroppedCalled == 0);
NL_TEST_ASSERT(inSuite, gFilter.mNumOnLastMatchDequeuedCalled == 0);
// Enqueue packets that match filter, beyond watermark: all dropped.
for (int numPkt = 0; numPkt < 2; ++numPkt)
{
NL_TEST_ASSERT(
inSuite, kDropPacket == fakeUdpEndpoint.ProcessEnqueue(fakeSrc, kOtherPort, fakeMdnsDest, kMdnsPort, kFakePayload));
}
NL_TEST_ASSERT(inSuite, gFilter.GetNumDroppedPackets() == 2);
NL_TEST_ASSERT(inSuite, gFilter.mNumOnDroppedCalled == 2);
NL_TEST_ASSERT(inSuite, gFilter.mNumOnLastMatchDequeuedCalled == 0);
// Dequeue 2 packets that were enqueued, matching filter
for (int numPkt = 0; numPkt < 2; ++numPkt)
{
NL_TEST_ASSERT(inSuite,
kAllowPacket ==
fakeUdpEndpoint.ProcessDequeue(fakeSrc, kOtherPort, fakeMdnsDest, kMdnsPort, kFakePayload));
}
// Number of dropped packets didn't change
NL_TEST_ASSERT(inSuite, gFilter.GetNumDroppedPackets() == 2);
NL_TEST_ASSERT(inSuite, gFilter.mNumOnDroppedCalled == 2);
NL_TEST_ASSERT(inSuite, gFilter.mNumOnLastMatchDequeuedCalled == 0);
// Enqueue packets that match filter, up to watermark again. None dropped.
for (int numPkt = 0; numPkt < 2; ++numPkt)
{
NL_TEST_ASSERT(inSuite,
kAllowPacket ==
fakeUdpEndpoint.ProcessEnqueue(fakeSrc, kOtherPort, fakeMdnsDest, kMdnsPort, kFakePayload));
}
// No change from prior state
NL_TEST_ASSERT(inSuite, gFilter.GetNumDroppedPackets() == 2);
NL_TEST_ASSERT(inSuite, gFilter.mNumOnDroppedCalled == 2);
NL_TEST_ASSERT(inSuite, gFilter.mNumOnLastMatchDequeuedCalled == 0);
// Enqueue two more packets, expect drop
for (int numPkt = 0; numPkt < 2; ++numPkt)
{
NL_TEST_ASSERT(
inSuite, kDropPacket == fakeUdpEndpoint.ProcessEnqueue(fakeSrc, kOtherPort, fakeMdnsDest, kMdnsPort, kFakePayload));
}
// Expect two more dropped total
NL_TEST_ASSERT(inSuite, gFilter.GetNumDroppedPackets() == 4);
NL_TEST_ASSERT(inSuite, gFilter.mNumOnDroppedCalled == 4);
NL_TEST_ASSERT(inSuite, gFilter.mNumOnLastMatchDequeuedCalled == 0);
// Enqueue non-matching packet, expect allowed.
for (int numPkt = 0; numPkt < kMaxQueuedPacketsLimit; ++numPkt)
{
NL_TEST_ASSERT(inSuite,
kAllowPacket == fakeUdpEndpoint.ProcessEnqueue(fakeSrc, kOtherPort, fakeDest, kOtherPort, kFakePayload));
}
// Expect no more dropepd
NL_TEST_ASSERT(inSuite, gFilter.GetNumDroppedPackets() == 4);
NL_TEST_ASSERT(inSuite, gFilter.mNumOnDroppedCalled == 4);
NL_TEST_ASSERT(inSuite, gFilter.mNumOnLastMatchDequeuedCalled == 0);
// Dequeue non-matching packet, expect allowed.
for (int numPkt = 0; numPkt < kMaxQueuedPacketsLimit; ++numPkt)
{
NL_TEST_ASSERT(inSuite,
kAllowPacket == fakeUdpEndpoint.ProcessDequeue(fakeSrc, kOtherPort, fakeDest, kOtherPort, kFakePayload));
}
// Expect no change
NL_TEST_ASSERT(inSuite, gFilter.GetNumDroppedPackets() == 4);
NL_TEST_ASSERT(inSuite, gFilter.mNumOnDroppedCalled == 4);
NL_TEST_ASSERT(inSuite, gFilter.mNumOnLastMatchDequeuedCalled == 0);
// Dequeue all matching packets, expect allowed and one OnLastMatchDequeued on last one.
for (int numPkt = 0; numPkt < (kMaxQueuedPacketsLimit - 1); ++numPkt)
{
NL_TEST_ASSERT(inSuite,
kAllowPacket ==
fakeUdpEndpoint.ProcessDequeue(fakeSrc, kOtherPort, fakeMdnsDest, kMdnsPort, kFakePayload));
}
NL_TEST_ASSERT(inSuite, gFilter.GetNumDroppedPackets() == 4);
NL_TEST_ASSERT(inSuite, gFilter.mNumOnDroppedCalled == 4);
NL_TEST_ASSERT(inSuite, gFilter.mNumOnLastMatchDequeuedCalled == 0);
NL_TEST_ASSERT(inSuite,
kAllowPacket == fakeUdpEndpoint.ProcessDequeue(fakeSrc, kOtherPort, fakeMdnsDest, kMdnsPort, kFakePayload));
NL_TEST_ASSERT(inSuite, gFilter.GetNumDroppedPackets() == 4);
NL_TEST_ASSERT(inSuite, gFilter.mNumOnDroppedCalled == 4);
NL_TEST_ASSERT(inSuite, gFilter.mNumOnLastMatchDequeuedCalled == 1);
}
// Validate that clearing drop count works
{
gFilter.ClearNumDroppedPackets();
NL_TEST_ASSERT(inSuite, gFilter.GetNumDroppedPackets() == 0);
gFilter.mNumOnDroppedCalled = 0;
gFilter.mNumOnLastMatchDequeuedCalled = 0;
}
// Validate that all packets pass when no predicate set
{
gFilter.SetPredicate(nullptr, nullptr);
// Enqueue packets up to twice the watermark. None dropped.
for (int numPkt = 0; numPkt < (2 * kMaxQueuedPacketsLimit); ++numPkt)
{
NL_TEST_ASSERT(inSuite,
kAllowPacket ==
fakeUdpEndpoint.ProcessEnqueue(fakeSrc, kOtherPort, fakeMdnsDest, kMdnsPort, kFakePayload));
}
NL_TEST_ASSERT(inSuite, gFilter.GetNumDroppedPackets() == 0);
NL_TEST_ASSERT(inSuite, gFilter.mNumOnDroppedCalled == 0);
NL_TEST_ASSERT(inSuite, gFilter.mNumOnLastMatchDequeuedCalled == 0);
// Works even if max number of packets allowed is zero
gFilter.SetMaxQueuedPacketsLimit(0);
// Enqueue packets up to twice the watermark. None dropped.
for (int numPkt = 0; numPkt < (2 * kMaxQueuedPacketsLimit); ++numPkt)
{
NL_TEST_ASSERT(inSuite,
kAllowPacket ==
fakeUdpEndpoint.ProcessEnqueue(fakeSrc, kOtherPort, fakeMdnsDest, kMdnsPort, kFakePayload));
}
NL_TEST_ASSERT(inSuite, gFilter.GetNumDroppedPackets() == 0);
NL_TEST_ASSERT(inSuite, gFilter.mNumOnDroppedCalled == 0);
NL_TEST_ASSERT(inSuite, gFilter.mNumOnLastMatchDequeuedCalled == 0);
}
// Validate that setting max packets to zero, with a matching predicate, drops all matching packets, none of the non-matching.
{
gFilter.SetPredicate(predicate, &gFilter);
gFilter.SetMaxQueuedPacketsLimit(0);
// Enqueue packets that match filter, up to watermark. All dropped
for (int numPkt = 0; numPkt < kMaxQueuedPacketsLimit; ++numPkt)
{
NL_TEST_ASSERT(
inSuite, kDropPacket == fakeUdpEndpoint.ProcessEnqueue(fakeSrc, kOtherPort, fakeMdnsDest, kMdnsPort, kFakePayload));
}
NL_TEST_ASSERT(inSuite, gFilter.GetNumDroppedPackets() == 3);
NL_TEST_ASSERT(inSuite, gFilter.mNumOnDroppedCalled == 3);
NL_TEST_ASSERT(inSuite, gFilter.mNumOnLastMatchDequeuedCalled == 0);
// Enqueue non-filter-matching, none dropped
for (int numPkt = 0; numPkt < kMaxQueuedPacketsLimit; ++numPkt)
{
NL_TEST_ASSERT(inSuite,
kAllowPacket == fakeUdpEndpoint.ProcessDequeue(fakeSrc, kOtherPort, fakeDest, kOtherPort, kFakePayload));
}
NL_TEST_ASSERT(inSuite, gFilter.GetNumDroppedPackets() == 3);
NL_TEST_ASSERT(inSuite, gFilter.mNumOnDroppedCalled == 3);
NL_TEST_ASSERT(inSuite, gFilter.mNumOnLastMatchDequeuedCalled == 0);
}
}
const nlTest sTests[] = {
NL_TEST_DEF("TestBasicPacketFilter", TestBasicPacketFilter), //
NL_TEST_SENTINEL() //
};
int TestSuiteSetup(void * inContext)
{
CHIP_ERROR error = chip::Platform::MemoryInit();
if (error != CHIP_NO_ERROR)
return FAILURE;
return SUCCESS;
}
int TestSuiteTeardown(void * inContext)
{
chip::Platform::MemoryShutdown();
return SUCCESS;
}
} // namespace
int TestBasicPacketFilters()
{
nlTestSuite theSuite = { "TestBasicPacketFilters", sTests, &TestSuiteSetup, &TestSuiteTeardown };
nlTestRunner(&theSuite, nullptr);
return nlTestRunnerStats(&theSuite);
}
CHIP_REGISTER_TEST_SUITE(TestBasicPacketFilters)