blob: 162fe5272d4b94c5839a218ebe7f718925f9b4fb [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 <gtest/gtest.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/logging/CHIPLogging.h>
#include <system/SystemPacketBuffer.h>
namespace {
using namespace chip;
using namespace chip::Inet;
class TestBasicPacketFilters : public ::testing::Test
{
public:
static void SetUpTestSuite() { ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); }
static void TearDownTestSuite() { chip::Platform::MemoryShutdown(); }
};
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
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;
TEST_F(TestBasicPacketFilters, TestBasicPacketFilter)
{
constexpr uint16_t kMdnsPort = 5353u;
// 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 expectedEndpoint = &gFakeEndpointForPointer;
// Ensure we get called with context and expected endpoint pointer
EXPECT_EQ(context, &gFilter);
EXPECT_EQ(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 };
EXPECT_TRUE(IPAddress::FromString("fe80::aaaa:bbbb:cccc:dddd", fakeSrc));
EXPECT_TRUE(IPAddress::FromString("fe80::0000:1111:2222:3333", fakeDest));
EXPECT_TRUE(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)
{
EXPECT_EQ(kAllowPacket, fakeUdpEndpoint.ProcessEnqueue(fakeSrc, kOtherPort, fakeDest, kOtherPort, kFakePayload));
}
EXPECT_EQ(gFilter.GetNumDroppedPackets(), 0u);
// Dequeue all packets
for (int numPkt = 0; numPkt < (kMaxQueuedPacketsLimit + 1); ++numPkt)
{
EXPECT_EQ(kAllowPacket, fakeUdpEndpoint.ProcessDequeue(fakeSrc, kOtherPort, fakeDest, kOtherPort, kFakePayload));
}
EXPECT_EQ(gFilter.GetNumDroppedPackets(), 0u);
// OnDroped/OnLastMatchDequeued only ever called for matching packets, never for non-matching
EXPECT_EQ(gFilter.mNumOnDroppedCalled, 0);
EXPECT_EQ(gFilter.mNumOnLastMatchDequeuedCalled, 0);
}
{
// Enqueue packets that match filter, up to watermark. None dropped
for (int numPkt = 0; numPkt < kMaxQueuedPacketsLimit; ++numPkt)
{
EXPECT_EQ(kAllowPacket, fakeUdpEndpoint.ProcessEnqueue(fakeSrc, kOtherPort, fakeMdnsDest, kMdnsPort, kFakePayload));
}
EXPECT_EQ(gFilter.GetNumDroppedPackets(), 0u);
EXPECT_EQ(gFilter.mNumOnDroppedCalled, 0);
EXPECT_EQ(gFilter.mNumOnLastMatchDequeuedCalled, 0);
// Enqueue packets that match filter, beyond watermark: all dropped.
for (int numPkt = 0; numPkt < 2; ++numPkt)
{
EXPECT_EQ(kDropPacket, fakeUdpEndpoint.ProcessEnqueue(fakeSrc, kOtherPort, fakeMdnsDest, kMdnsPort, kFakePayload));
}
EXPECT_EQ(gFilter.GetNumDroppedPackets(), 2u);
EXPECT_EQ(gFilter.mNumOnDroppedCalled, 2);
EXPECT_EQ(gFilter.mNumOnLastMatchDequeuedCalled, 0);
// Dequeue 2 packets that were enqueued, matching filter
for (int numPkt = 0; numPkt < 2; ++numPkt)
{
EXPECT_EQ(kAllowPacket, fakeUdpEndpoint.ProcessDequeue(fakeSrc, kOtherPort, fakeMdnsDest, kMdnsPort, kFakePayload));
}
// Number of dropped packets didn't change
EXPECT_EQ(gFilter.GetNumDroppedPackets(), 2u);
EXPECT_EQ(gFilter.mNumOnDroppedCalled, 2);
EXPECT_EQ(gFilter.mNumOnLastMatchDequeuedCalled, 0);
// Enqueue packets that match filter, up to watermark again. None dropped.
for (int numPkt = 0; numPkt < 2; ++numPkt)
{
EXPECT_EQ(kAllowPacket, fakeUdpEndpoint.ProcessEnqueue(fakeSrc, kOtherPort, fakeMdnsDest, kMdnsPort, kFakePayload));
}
// No change from prior state
EXPECT_EQ(gFilter.GetNumDroppedPackets(), 2u);
EXPECT_EQ(gFilter.mNumOnDroppedCalled, 2);
EXPECT_EQ(gFilter.mNumOnLastMatchDequeuedCalled, 0);
// Enqueue two more packets, expect drop
for (int numPkt = 0; numPkt < 2; ++numPkt)
{
EXPECT_EQ(kDropPacket, fakeUdpEndpoint.ProcessEnqueue(fakeSrc, kOtherPort, fakeMdnsDest, kMdnsPort, kFakePayload));
}
// Expect two more dropped total
EXPECT_EQ(gFilter.GetNumDroppedPackets(), 4u);
EXPECT_EQ(gFilter.mNumOnDroppedCalled, 4);
EXPECT_EQ(gFilter.mNumOnLastMatchDequeuedCalled, 0);
// Enqueue non-matching packet, expect allowed.
for (int numPkt = 0; numPkt < kMaxQueuedPacketsLimit; ++numPkt)
{
EXPECT_EQ(kAllowPacket, fakeUdpEndpoint.ProcessEnqueue(fakeSrc, kOtherPort, fakeDest, kOtherPort, kFakePayload));
}
// Expect no more dropepd
EXPECT_EQ(gFilter.GetNumDroppedPackets(), 4u);
EXPECT_EQ(gFilter.mNumOnDroppedCalled, 4);
EXPECT_EQ(gFilter.mNumOnLastMatchDequeuedCalled, 0);
// Dequeue non-matching packet, expect allowed.
for (int numPkt = 0; numPkt < kMaxQueuedPacketsLimit; ++numPkt)
{
EXPECT_EQ(kAllowPacket, fakeUdpEndpoint.ProcessDequeue(fakeSrc, kOtherPort, fakeDest, kOtherPort, kFakePayload));
}
// Expect no change
EXPECT_EQ(gFilter.GetNumDroppedPackets(), 4u);
EXPECT_EQ(gFilter.mNumOnDroppedCalled, 4);
EXPECT_EQ(gFilter.mNumOnLastMatchDequeuedCalled, 0);
// Dequeue all matching packets, expect allowed and one OnLastMatchDequeued on last one.
for (int numPkt = 0; numPkt < (kMaxQueuedPacketsLimit - 1); ++numPkt)
{
EXPECT_EQ(kAllowPacket, fakeUdpEndpoint.ProcessDequeue(fakeSrc, kOtherPort, fakeMdnsDest, kMdnsPort, kFakePayload));
}
EXPECT_EQ(gFilter.GetNumDroppedPackets(), 4u);
EXPECT_EQ(gFilter.mNumOnDroppedCalled, 4);
EXPECT_EQ(gFilter.mNumOnLastMatchDequeuedCalled, 0);
EXPECT_EQ(kAllowPacket, fakeUdpEndpoint.ProcessDequeue(fakeSrc, kOtherPort, fakeMdnsDest, kMdnsPort, kFakePayload));
EXPECT_EQ(gFilter.GetNumDroppedPackets(), 4u);
EXPECT_EQ(gFilter.mNumOnDroppedCalled, 4);
EXPECT_EQ(gFilter.mNumOnLastMatchDequeuedCalled, 1);
}
// Validate that clearing drop count works
{
gFilter.ClearNumDroppedPackets();
EXPECT_EQ(gFilter.GetNumDroppedPackets(), 0u);
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)
{
EXPECT_EQ(kAllowPacket, fakeUdpEndpoint.ProcessEnqueue(fakeSrc, kOtherPort, fakeMdnsDest, kMdnsPort, kFakePayload));
}
EXPECT_EQ(gFilter.GetNumDroppedPackets(), 0u);
EXPECT_EQ(gFilter.mNumOnDroppedCalled, 0);
EXPECT_EQ(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)
{
EXPECT_EQ(kAllowPacket, fakeUdpEndpoint.ProcessEnqueue(fakeSrc, kOtherPort, fakeMdnsDest, kMdnsPort, kFakePayload));
}
EXPECT_EQ(gFilter.GetNumDroppedPackets(), 0u);
EXPECT_EQ(gFilter.mNumOnDroppedCalled, 0);
EXPECT_EQ(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)
{
EXPECT_EQ(kDropPacket, fakeUdpEndpoint.ProcessEnqueue(fakeSrc, kOtherPort, fakeMdnsDest, kMdnsPort, kFakePayload));
}
EXPECT_EQ(gFilter.GetNumDroppedPackets(), 3u);
EXPECT_EQ(gFilter.mNumOnDroppedCalled, 3);
EXPECT_EQ(gFilter.mNumOnLastMatchDequeuedCalled, 0);
// Enqueue non-filter-matching, none dropped
for (int numPkt = 0; numPkt < kMaxQueuedPacketsLimit; ++numPkt)
{
EXPECT_EQ(kAllowPacket, fakeUdpEndpoint.ProcessDequeue(fakeSrc, kOtherPort, fakeDest, kOtherPort, kFakePayload));
}
EXPECT_EQ(gFilter.GetNumDroppedPackets(), 3u);
EXPECT_EQ(gFilter.mNumOnDroppedCalled, 3);
EXPECT_EQ(gFilter.mNumOnLastMatchDequeuedCalled, 0);
}
}
} // namespace