/* * 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 #include #include #include #include #include #include #include #include #include #include #include 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