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