diff --git a/src/core/thread/lowpan.cpp b/src/core/thread/lowpan.cpp index b90865b87..f31ae5078 100644 --- a/src/core/thread/lowpan.cpp +++ b/src/core/thread/lowpan.cpp @@ -219,16 +219,21 @@ exit: return error; } -Error Lowpan::Compress(Message &aMessage, const Mac::Addresses &aMacAddrs, FrameBuilder &aFrameBuilder) +Error Lowpan::Compress(Message &aMessage, + const Mac::Addresses &aMacAddrs, + FrameBuilder &aFrameBuilder, + uint8_t aRecursionDepth) { Error error = kErrorNone; uint8_t headerDepth = 0xff; + VerifyOrExit(aRecursionDepth <= kMaxRecursionDepth, error = kErrorParse); + while (headerDepth > 0) { FrameBuilder frameBuilder = aFrameBuilder; - error = Compress(aMessage, aMacAddrs, aFrameBuilder, headerDepth); + error = Compress(aMessage, aMacAddrs, aFrameBuilder, headerDepth, aRecursionDepth); // We exit if `Compress()` is successful. Otherwise we reset // the `aFrameBuidler` to its earlier state (remove all @@ -246,7 +251,8 @@ exit: Error Lowpan::Compress(Message &aMessage, const Mac::Addresses &aMacAddrs, FrameBuilder &aFrameBuilder, - uint8_t &aHeaderDepth) + uint8_t &aHeaderDepth, + uint8_t aRecursionDepth) { Error error = kErrorNone; uint16_t startOffset = aMessage.GetOffset(); @@ -413,7 +419,7 @@ Error Lowpan::Compress(Message &aMessage, // For IP-in-IP the NH bit of the LOWPAN_NHC encoding MUST be set to zero. SuccessOrExit(error = aFrameBuilder.AppendUint8(kExtHdrDispatch | kExtHdrEidIp6)); - error = Compress(aMessage, aMacAddrs, aFrameBuilder); + error = Compress(aMessage, aMacAddrs, aFrameBuilder, aRecursionDepth + 1); OT_FALL_THROUGH; diff --git a/src/core/thread/lowpan.hpp b/src/core/thread/lowpan.hpp index ed12ecf6f..1e68a1517 100644 --- a/src/core/thread/lowpan.hpp +++ b/src/core/thread/lowpan.hpp @@ -180,7 +180,10 @@ public: * * @returns The size of the compressed header in bytes. */ - Error Compress(Message &aMessage, const Mac::Addresses &aMacAddrs, FrameBuilder &aFrameBuilder); + Error Compress(Message &aMessage, + const Mac::Addresses &aMacAddrs, + FrameBuilder &aFrameBuilder, + uint8_t aRecursionDepth = 0); /** * Decompresses a LOWPAN_IPHC header. @@ -312,7 +315,8 @@ private: Error Compress(Message &aMessage, const Mac::Addresses &aMacAddrs, FrameBuilder &aFrameBuilder, - uint8_t &aHeaderDepth); + uint8_t &aHeaderDepth, + uint8_t aRecursionDepth); Error CompressExtensionHeader(Message &aMessage, FrameBuilder &aFrameBuilder, uint8_t &aNextHeader); Error CompressSourceIid(const Mac::Address &aMacAddr, diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt index 0d330ed30..207299148 100644 --- a/tests/nexus/CMakeLists.txt +++ b/tests/nexus/CMakeLists.txt @@ -405,6 +405,7 @@ ot_nexus_test(fed_rx_only_link_establishment "core;nexus") ot_nexus_test(form_join "core;nexus") ot_nexus_test(history_tracker "core;nexus") ot_nexus_test(ipv6_fragmentation "core;nexus") +ot_nexus_test(ipv6_recursion "core;nexus") ot_nexus_test(ipv6_source_selection "core;nexus") ot_nexus_test(key_rotation_guard_time "core;nexus") ot_nexus_test(leader_reboot_multiple_link_request "core;nexus") diff --git a/tests/nexus/test_ipv6_recursion.cpp b/tests/nexus/test_ipv6_recursion.cpp new file mode 100644 index 000000000..67f648be7 --- /dev/null +++ b/tests/nexus/test_ipv6_recursion.cpp @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2026, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include "platform/nexus_core.hpp" +#include "platform/nexus_node.hpp" + +namespace ot { +namespace Nexus { + +void TestIPv6Recursion(void) +{ + /** + * Test IPv6 Recursion + * + * Topology: + * - Leader + * + * Description: + * The purpose of this test case is to validate that lowpan compression enforces + * the kMaxRecursionDepth limit to prevent excessive recursive stack usage. + */ + + Core nexus; + Node &leader = nexus.CreateNode(); + + leader.SetName("LEADER"); + + SuccessOrQuit(Instance::SetGlobalLogLevel(kLogLevelNote)); + + leader.Form(); + nexus.AdvanceTime(10 * 1000); + VerifyOrQuit(leader.Get().IsLeader()); + + Ip6::Address selfAddress = leader.Get().GetMeshLocalEid(); + + // Test Case 1: Recursion depth 3 (acceptable limit <= 4) + { + Log("Test Case 1: Send nested IPv6 packet with depth 3 (within limit)"); + Message *message = leader.Get().NewMessage(); + VerifyOrQuit(message != nullptr); + + // Construct 3 nested headers: + // - i = 2 (innermost): NH = kProtoNone, PayloadLen = 0 + // - i = 1: NH = kProtoIp6, PayloadLen = 40 + // - i = 0 (outermost): NH = kProtoIp6, PayloadLen = 80 + for (int i = 0; i < 3; i++) + { + Ip6::Header header; + header.InitVersionTrafficClassFlow(); + header.SetSource(selfAddress); + header.SetDestination(selfAddress); + if (i == 2) + { + header.SetNextHeader(Ip6::kProtoNone); + header.SetPayloadLength(0); + } + else + { + header.SetNextHeader(Ip6::kProtoIp6); + header.SetPayloadLength((2 - i) * sizeof(Ip6::Header)); + } + SuccessOrQuit(message->Append(header)); + } + + Error error = leader.Get().HandleDatagram(OwnedPtr(message)); + VerifyOrQuit(error != kErrorDrop); + Log("Test Case 1: Passed successfully!"); + } + + // Test Case 2: Direct call to Lowpan::Compress with depth 6 nested packet + { + Log("Test Case 2: Call Lowpan::Compress with depth 6 nested packet (should cap and fall back gracefully)"); + Message *message = leader.Get().NewMessage(); + VerifyOrQuit(message != nullptr); + + // Construct 6 nested headers (similar to Test Case 1) + for (int i = 0; i < 6; i++) + { + Ip6::Header header; + header.InitVersionTrafficClassFlow(); + header.SetSource(selfAddress); + header.SetDestination(selfAddress); + if (i == 5) + { + header.SetNextHeader(Ip6::kProtoNone); + header.SetPayloadLength(0); + } + else + { + header.SetNextHeader(Ip6::kProtoIp6); + header.SetPayloadLength((5 - i) * sizeof(Ip6::Header)); + } + SuccessOrQuit(message->Append(header)); + } + + Mac::Addresses macAddrs; + macAddrs.mSource.SetShort(leader.Get().GetShortAddress()); + macAddrs.mDestination.SetShort(leader.Get().GetShortAddress()); + + uint8_t frameBuffer[512]; + FrameBuilder frameBuilder; + frameBuilder.Init(frameBuffer, sizeof(frameBuffer)); + + // This should succeed (return kErrorNone) because it will cap recursion at depth 4 + // and emit the remaining nested headers inline as opaque payload. + Error error = leader.Get().Compress(*message, macAddrs, frameBuilder); + VerifyOrQuit(error == kErrorNone); + Log("Test Case 2: Passed successfully (returned kErrorNone without excessive stack usage)!"); + message->Free(); + } + + nexus.SaveTestInfo("test_ipv6_recursion.json"); +} + +} // namespace Nexus +} // namespace ot + +int main(void) +{ + ot::Nexus::TestIPv6Recursion(); + printf("All tests passed\n"); + return 0; +}