mirror of
https://github.com/espressif/openthread.git
synced 2026-10-13 11:17:39 +00:00
556 lines
21 KiB
C++
556 lines
21 KiB
C++
/*
|
|
* 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.
|
|
*/
|
|
|
|
/*
|
|
* Verifies IPv6 fragment reassembly buffer sizing and payload placement when fragments
|
|
* of the same datagram carry different-length unfragmentable parts.
|
|
*
|
|
* `Ip6::HandleFragment()` tracks contiguity in fragment-payload space only. It sizes
|
|
* the reassembly buffer and places fragment-payload writes using only the 40-byte base
|
|
* IPv6 header (`sizeof(Header)`), which is the only header copied into the reassembled
|
|
* datagram. Unfragmentable extension headers (like Hop-by-Hop options) are processed
|
|
* per-fragment and not carried forward. This ensures no region of the reassembled
|
|
* datagram is left unwritten when fragments carry different-length extension header
|
|
* stacks, and prevents duplicate extension header processing upon reassembly completion.
|
|
*
|
|
* This test asserts:
|
|
* case A (positive control): a normally fragmented echo (consistent 40-byte headers,
|
|
* no length mismatch) reassembles and is answered; its data carries a
|
|
* deliberate run of marker bytes which must be reflected in the reply,
|
|
* proving the reply-content (marker run) inspection used in case B executes;
|
|
* case B: fragments whose unfragmentable-part lengths differ (the final fragment
|
|
* carries an extra Hop-by-Hop extension header), constructed so that any
|
|
* unwritten region would be exposed (with checksums precomputed against
|
|
* several possible region contents), must NOT produce an echo reply, since a
|
|
* reply only appears when the reassembled datagram incorporated bytes matching
|
|
* the checksum assumption;
|
|
* case C: fragment 1 carries a Hop-by-Hop extension header; the reassembled datagram
|
|
* contains only the 40-byte base IPv6 header with NextHeader set to ICMPv6
|
|
* and delivers the reassembled echo request directly to ICMPv6 without duplicate
|
|
* extension header processing;
|
|
* case D: both fragments carry the Hop-by-Hop extension header; reassembly similarly
|
|
* completes with a 40-byte base header and direct upper-layer delivery, answering
|
|
* the echo request.
|
|
*
|
|
* Gates: OPENTHREAD_CONFIG_IP6_FRAGMENTATION_ENABLE (default 0; 1 in this nexus config),
|
|
* default static message pool (heap builds zero via calloc).
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <openthread/icmp6.h>
|
|
#include <openthread/ip6.h>
|
|
#include <openthread/message.h>
|
|
#include <openthread/udp.h>
|
|
|
|
#include "platform/nexus_core.hpp"
|
|
#include "platform/nexus_node.hpp"
|
|
|
|
#include "net/ip6.hpp"
|
|
|
|
namespace ot {
|
|
namespace Nexus {
|
|
|
|
static constexpr uint32_t kFormNetworkTime = 13 * 1000;
|
|
|
|
static constexpr uint16_t kPay1 = 200; // fragment 1 payload (echo header + data)
|
|
static constexpr uint16_t kPay2 = 8; // final fragment payload
|
|
static constexpr uint16_t kHole = 8; // hole size = HBH header length delta
|
|
static constexpr uint8_t kPadOptionSize = 6; // PadN size filling the 8-byte HBH header
|
|
static constexpr uint16_t kExtLength = sizeof(Ip6::HopByHopHeader) + kPadOptionSize;
|
|
static constexpr uint16_t kMarkerRunOffset = 16; // marker run position within control echo message
|
|
static constexpr uint8_t kHopLimit = 64;
|
|
static constexpr uint8_t kMarker = 0xC3;
|
|
static constexpr uint8_t kPayByte = 0xAA;
|
|
static constexpr uint8_t kPay2Byte = 0x55;
|
|
|
|
static bool sGotReply;
|
|
static bool sReplyHasMarkerRun;
|
|
static uint16_t sReplyLength;
|
|
|
|
static void HandleIcmp(void *, otMessage *aMessage, const otMessageInfo *, const otIcmp6Header *aHeader)
|
|
{
|
|
if (aHeader->mType != OT_ICMP6_TYPE_ECHO_REPLY)
|
|
{
|
|
return;
|
|
}
|
|
|
|
uint8_t buf[512];
|
|
uint16_t offset = otMessageGetOffset(aMessage);
|
|
uint16_t len = otMessageGetLength(aMessage) - offset;
|
|
|
|
len = (len > sizeof(buf)) ? sizeof(buf) : len;
|
|
otMessageRead(aMessage, offset, buf, len);
|
|
|
|
sGotReply = true;
|
|
sReplyLength = len;
|
|
|
|
// look for a run of kHole marker bytes (a reflected hole, or the control's seeded run)
|
|
for (uint16_t i = 0; i + kHole <= len; i++)
|
|
{
|
|
bool run = true;
|
|
|
|
for (uint16_t j = 0; j < kHole; j++)
|
|
{
|
|
if (buf[i + j] != kMarker)
|
|
{
|
|
run = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (run)
|
|
{
|
|
sReplyHasMarkerRun = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static uint32_t ChecksumAdd(uint32_t aSum, const uint8_t *aData, uint16_t aLength)
|
|
{
|
|
for (uint16_t i = 0; i < aLength; i++)
|
|
{
|
|
aSum += (i & 1) ? aData[i] : (static_cast<uint32_t>(aData[i]) << 8);
|
|
}
|
|
|
|
return aSum;
|
|
}
|
|
|
|
static uint16_t IcmpChecksum(const Ip6::Address &aSrc,
|
|
const Ip6::Address &aDst,
|
|
const uint8_t *aIcmp,
|
|
uint16_t aLen1,
|
|
const uint8_t *aHole,
|
|
uint16_t aHoleLen,
|
|
const uint8_t *aTail,
|
|
uint16_t aTailLen)
|
|
{
|
|
uint32_t sum = 0;
|
|
uint16_t total = aLen1 + aHoleLen + aTailLen;
|
|
|
|
sum = ChecksumAdd(sum, aSrc.GetBytes(), sizeof(otIp6Address));
|
|
sum = ChecksumAdd(sum, aDst.GetBytes(), sizeof(otIp6Address));
|
|
sum += total;
|
|
sum += Ip6::kProtoIcmp6;
|
|
sum = ChecksumAdd(sum, aIcmp, aLen1);
|
|
sum = ChecksumAdd(sum, aHole, aHoleLen);
|
|
sum = ChecksumAdd(sum, aTail, aTailLen);
|
|
|
|
while (sum >> 16)
|
|
{
|
|
sum = (sum & 0xffff) + (sum >> 16);
|
|
}
|
|
|
|
return static_cast<uint16_t>(~sum);
|
|
}
|
|
|
|
static Message *NewIp6Message(Node &aNode)
|
|
{
|
|
Message *message = aNode.Get<MessagePool>().Allocate(Message::kTypeIp6);
|
|
|
|
VerifyOrQuit(message != nullptr);
|
|
SuccessOrQuit(message->SetPriority(Message::kPriorityNormal));
|
|
|
|
return message;
|
|
}
|
|
|
|
static void SendPrimers(Node &aSender, const Ip6::Address &aDst, uint8_t aFill)
|
|
{
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
static uint8_t primer[600];
|
|
|
|
memset(primer, aFill, sizeof(primer));
|
|
|
|
otMessageInfo info;
|
|
otUdpSocket socket;
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
memset(&socket, 0, sizeof(socket));
|
|
info.mPeerAddr = aDst;
|
|
info.mPeerPort = 55555; // no listener on leader
|
|
|
|
SuccessOrQuit(otUdpOpen(&aSender.GetInstance(), &socket, nullptr, nullptr));
|
|
{
|
|
otMessage *m = otUdpNewMessage(&aSender.GetInstance(), nullptr);
|
|
VerifyOrQuit(m != nullptr);
|
|
SuccessOrQuit(otMessageAppend(m, primer, sizeof(primer)));
|
|
SuccessOrQuit(otUdpSend(&aSender.GetInstance(), &socket, m, &info));
|
|
}
|
|
SuccessOrQuit(otUdpClose(&aSender.GetInstance(), &socket));
|
|
}
|
|
}
|
|
|
|
static void AppendHopByHopHeader(Message &aMessage)
|
|
{
|
|
Ip6::HopByHopHeader hbhHeader;
|
|
Ip6::PadOption padOption;
|
|
|
|
hbhHeader.SetNextHeader(Ip6::kProtoFragment);
|
|
hbhHeader.SetLength(0); // one 8-byte unit in total
|
|
SuccessOrQuit(aMessage.Append(hbhHeader));
|
|
|
|
padOption.InitForPadSize(kPadOptionSize);
|
|
SuccessOrQuit(aMessage.AppendBytes(&padOption, padOption.GetSize()));
|
|
}
|
|
|
|
static void BuildAndSendFragments(Node &aSender,
|
|
Core &aNexus,
|
|
const Ip6::Address &aSrc,
|
|
const Ip6::Address &aDst,
|
|
uint32_t aFragId,
|
|
bool aWithHole,
|
|
uint8_t aGapAssume,
|
|
bool aPrimeBetween,
|
|
uint16_t aEchoSeq)
|
|
{
|
|
uint8_t icmp[kPay1];
|
|
uint8_t hole[kHole];
|
|
uint8_t tail[kPay2];
|
|
uint16_t csum;
|
|
Ip6::Icmp6Header echoHeader;
|
|
|
|
// ICMP Echo Request message: header + patterned data. The control request
|
|
// (`aWithHole` false) carries a deliberate run of kMarker bytes so that the
|
|
// reply-content inspection in `HandleIcmp()` is exercised and can be asserted.
|
|
memset(icmp, kPayByte, sizeof(icmp));
|
|
|
|
if (!aWithHole)
|
|
{
|
|
memset(&icmp[kMarkerRunOffset], kMarker, kHole);
|
|
}
|
|
|
|
echoHeader.Clear();
|
|
echoHeader.SetType(Ip6::Icmp6Header::kTypeEchoRequest);
|
|
echoHeader.SetId(0x1234);
|
|
echoHeader.SetSequence(aEchoSeq);
|
|
memcpy(icmp, &echoHeader, sizeof(echoHeader));
|
|
|
|
memset(hole, aGapAssume, sizeof(hole));
|
|
memset(tail, kPay2Byte, sizeof(tail));
|
|
|
|
if (aWithHole)
|
|
{
|
|
// checksum computed assuming the unwritten region would read back the assumed bytes
|
|
csum = IcmpChecksum(aSrc, aDst, icmp, kPay1, hole, kHole, tail, kPay2);
|
|
}
|
|
else
|
|
{
|
|
csum = IcmpChecksum(aSrc, aDst, icmp, kPay1, nullptr, 0, tail, kPay2);
|
|
}
|
|
|
|
echoHeader.SetChecksum(csum);
|
|
memcpy(icmp, &echoHeader, sizeof(echoHeader));
|
|
|
|
// ---- fragment 1: base IPv6 header only (40-byte unfragmentable part), offset 0,
|
|
// M flag set, payload = icmp[kPay1]
|
|
|
|
{
|
|
Message *message = NewIp6Message(aSender);
|
|
Ip6::Header ip6Header;
|
|
Ip6::FragmentHeader fragmentHeader;
|
|
|
|
ip6Header.InitVersionTrafficClassFlow();
|
|
ip6Header.SetPayloadLength(static_cast<uint16_t>(sizeof(Ip6::FragmentHeader) + kPay1));
|
|
ip6Header.SetNextHeader(Ip6::kProtoFragment);
|
|
ip6Header.SetHopLimit(kHopLimit);
|
|
ip6Header.SetSource(aSrc);
|
|
ip6Header.SetDestination(aDst);
|
|
SuccessOrQuit(message->Append(ip6Header));
|
|
|
|
fragmentHeader.Init();
|
|
fragmentHeader.SetNextHeader(Ip6::kProtoIcmp6);
|
|
fragmentHeader.SetOffset(0);
|
|
fragmentHeader.SetMoreFlag();
|
|
fragmentHeader.SetIdentification(aFragId);
|
|
SuccessOrQuit(message->Append(fragmentHeader));
|
|
|
|
SuccessOrQuit(message->AppendBytes(icmp, kPay1));
|
|
|
|
SuccessOrQuit(aSender.Get<Ip6::Ip6>().SendRaw(OwnedPtr<Message>(message)));
|
|
}
|
|
|
|
aNexus.AdvanceTime(400);
|
|
|
|
if (aPrimeBetween)
|
|
{
|
|
// seed the message-pool free list with a known pattern right before the reassembly growth happens
|
|
SendPrimers(aSender, aDst, aGapAssume);
|
|
aNexus.AdvanceTime(400);
|
|
}
|
|
|
|
// ---- fragment 2 (final): an optional Hop-by-Hop extension header widens the
|
|
// unfragmentable part
|
|
|
|
{
|
|
Message *message = NewIp6Message(aSender);
|
|
Ip6::Header ip6Header;
|
|
Ip6::FragmentHeader fragmentHeader;
|
|
uint16_t extLength = aWithHole ? kExtLength : 0;
|
|
|
|
ip6Header.InitVersionTrafficClassFlow();
|
|
ip6Header.SetPayloadLength(static_cast<uint16_t>(extLength + sizeof(Ip6::FragmentHeader) + kPay2));
|
|
ip6Header.SetNextHeader(aWithHole ? Ip6::kProtoHopOpts : Ip6::kProtoFragment);
|
|
ip6Header.SetHopLimit(kHopLimit);
|
|
ip6Header.SetSource(aSrc);
|
|
ip6Header.SetDestination(aDst);
|
|
SuccessOrQuit(message->Append(ip6Header));
|
|
|
|
if (aWithHole)
|
|
{
|
|
AppendHopByHopHeader(*message);
|
|
}
|
|
|
|
fragmentHeader.Init();
|
|
fragmentHeader.SetNextHeader(Ip6::kProtoIcmp6);
|
|
fragmentHeader.SetOffset(Ip6::FragmentHeader::BytesToFragmentOffset(kPay1));
|
|
fragmentHeader.ClearMoreFlag();
|
|
fragmentHeader.SetIdentification(aFragId);
|
|
SuccessOrQuit(message->Append(fragmentHeader));
|
|
|
|
SuccessOrQuit(message->AppendBytes(tail, kPay2));
|
|
|
|
SuccessOrQuit(aSender.Get<Ip6::Ip6>().SendRaw(OwnedPtr<Message>(message)));
|
|
}
|
|
}
|
|
|
|
static void BuildAndSendFragmentsWithExtHeader(Node &aSender,
|
|
Core &aNexus,
|
|
const Ip6::Address &aSrc,
|
|
const Ip6::Address &aDst,
|
|
uint32_t aFragId,
|
|
bool aFrag2WithExt,
|
|
uint16_t aEchoSeq)
|
|
{
|
|
uint8_t icmp[kPay1];
|
|
uint8_t tail[kPay2];
|
|
uint16_t csum;
|
|
Ip6::Icmp6Header echoHeader;
|
|
|
|
memset(icmp, kPayByte, sizeof(icmp));
|
|
memset(&icmp[kMarkerRunOffset], kMarker, kHole);
|
|
|
|
echoHeader.Clear();
|
|
echoHeader.SetType(Ip6::Icmp6Header::kTypeEchoRequest);
|
|
echoHeader.SetId(0x5678);
|
|
echoHeader.SetSequence(aEchoSeq);
|
|
memcpy(icmp, &echoHeader, sizeof(echoHeader));
|
|
|
|
memset(tail, kPay2Byte, sizeof(tail));
|
|
|
|
csum = IcmpChecksum(aSrc, aDst, icmp, kPay1, nullptr, 0, tail, kPay2);
|
|
echoHeader.SetChecksum(csum);
|
|
memcpy(icmp, &echoHeader, sizeof(echoHeader));
|
|
|
|
// ---- fragment 1: Hop-by-Hop extension header precedes the Fragment Header.
|
|
// Unfragmentable extension headers are processed per-fragment and not carried into reassembly.
|
|
{
|
|
Message *message = NewIp6Message(aSender);
|
|
Ip6::Header ip6Header;
|
|
Ip6::FragmentHeader fragmentHeader;
|
|
|
|
ip6Header.InitVersionTrafficClassFlow();
|
|
ip6Header.SetPayloadLength(static_cast<uint16_t>(kExtLength + sizeof(Ip6::FragmentHeader) + kPay1));
|
|
ip6Header.SetNextHeader(Ip6::kProtoHopOpts);
|
|
ip6Header.SetHopLimit(kHopLimit);
|
|
ip6Header.SetSource(aSrc);
|
|
ip6Header.SetDestination(aDst);
|
|
SuccessOrQuit(message->Append(ip6Header));
|
|
|
|
AppendHopByHopHeader(*message);
|
|
|
|
fragmentHeader.Init();
|
|
fragmentHeader.SetNextHeader(Ip6::kProtoIcmp6);
|
|
fragmentHeader.SetOffset(0);
|
|
fragmentHeader.SetMoreFlag();
|
|
fragmentHeader.SetIdentification(aFragId);
|
|
SuccessOrQuit(message->Append(fragmentHeader));
|
|
|
|
SuccessOrQuit(message->AppendBytes(icmp, kPay1));
|
|
|
|
SuccessOrQuit(aSender.Get<Ip6::Ip6>().SendRaw(OwnedPtr<Message>(message)));
|
|
}
|
|
|
|
aNexus.AdvanceTime(400);
|
|
|
|
// ---- fragment 2 (final): optionally carries the same HBH extension header.
|
|
{
|
|
Message *message = NewIp6Message(aSender);
|
|
Ip6::Header ip6Header;
|
|
Ip6::FragmentHeader fragmentHeader;
|
|
uint16_t extLength = aFrag2WithExt ? kExtLength : 0;
|
|
|
|
ip6Header.InitVersionTrafficClassFlow();
|
|
ip6Header.SetPayloadLength(static_cast<uint16_t>(extLength + sizeof(Ip6::FragmentHeader) + kPay2));
|
|
ip6Header.SetNextHeader(aFrag2WithExt ? Ip6::kProtoHopOpts : Ip6::kProtoFragment);
|
|
ip6Header.SetHopLimit(kHopLimit);
|
|
ip6Header.SetSource(aSrc);
|
|
ip6Header.SetDestination(aDst);
|
|
SuccessOrQuit(message->Append(ip6Header));
|
|
|
|
if (aFrag2WithExt)
|
|
{
|
|
AppendHopByHopHeader(*message);
|
|
}
|
|
|
|
fragmentHeader.Init();
|
|
fragmentHeader.SetNextHeader(Ip6::kProtoIcmp6);
|
|
fragmentHeader.SetOffset(Ip6::FragmentHeader::BytesToFragmentOffset(kPay1));
|
|
fragmentHeader.ClearMoreFlag();
|
|
fragmentHeader.SetIdentification(aFragId);
|
|
SuccessOrQuit(message->Append(fragmentHeader));
|
|
|
|
SuccessOrQuit(message->AppendBytes(tail, kPay2));
|
|
|
|
SuccessOrQuit(aSender.Get<Ip6::Ip6>().SendRaw(OwnedPtr<Message>(message)));
|
|
}
|
|
}
|
|
|
|
void TestFragmentReassemblyHole(void)
|
|
{
|
|
Core nexus;
|
|
Node &leader = nexus.CreateNode();
|
|
Node &child = nexus.CreateNode();
|
|
|
|
otIcmp6Handler handler;
|
|
|
|
memset(&handler, 0, sizeof(handler));
|
|
handler.mReceiveCallback = HandleIcmp;
|
|
|
|
leader.Form();
|
|
nexus.AdvanceTime(kFormNetworkTime);
|
|
VerifyOrQuit(leader.Get<Mle::Mle>().IsLeader());
|
|
|
|
child.Join(leader);
|
|
nexus.AdvanceTime(20 * 1000);
|
|
VerifyOrQuit(child.Get<Mle::Mle>().IsAttached());
|
|
|
|
SuccessOrQuit(otIcmp6RegisterHandler(&child.GetInstance(), &handler));
|
|
|
|
const Ip6::Address &src = child.Get<Mle::Mle>().GetMeshLocalEid();
|
|
const Ip6::Address &dst = leader.Get<Mle::Mle>().GetMeshLocalEid();
|
|
|
|
Log("Case A (positive control): consistent-header fragmented echo must be answered");
|
|
sGotReply = false;
|
|
sReplyHasMarkerRun = false;
|
|
BuildAndSendFragments(child, nexus, src, dst, 0x11111111, /* aWithHole */ false, 0, false, 1);
|
|
nexus.AdvanceTime(3 * 1000);
|
|
VerifyOrQuit(sGotReply);
|
|
|
|
// the reply must reflect the seeded marker run, proving the reply-content inspection works
|
|
VerifyOrQuit(sReplyHasMarkerRun);
|
|
Log("control reply received (len %u, seeded marker run reflected)", sReplyLength);
|
|
|
|
Log("Case B: final fragment carries an extra HBH header (longer unfragmentable part).");
|
|
Log("A reply arrives only when the reassembled datagram content matches one of the");
|
|
Log("checksum assumptions, i.e., only if an unwritten region was incorporated.");
|
|
|
|
struct Attempt
|
|
{
|
|
uint8_t mGapAssume;
|
|
bool mPrimeBetween;
|
|
};
|
|
|
|
// (1) seed the free list with 0xC3 right before the growth; (2) assume the region
|
|
// reuses fragment 1's own freed payload bytes; (3) assume cleared/zero bytes.
|
|
Attempt attempts[] = {{kMarker, true}, {kPayByte, false}, {0x00, false}};
|
|
bool replied = false;
|
|
uint8_t repliedVal = 0;
|
|
|
|
for (uint8_t i = 0; i < 3; i++)
|
|
{
|
|
sGotReply = false;
|
|
sReplyHasMarkerRun = false;
|
|
BuildAndSendFragments(child, nexus, src, dst, 0x22220000u + i, /* aWithHole */ true, attempts[i].mGapAssume,
|
|
attempts[i].mPrimeBetween, static_cast<uint16_t>(10 + i));
|
|
nexus.AdvanceTime(3 * 1000);
|
|
|
|
Log("attempt %u (gap assumption 0x%02x, prime-between %u): %s", i, attempts[i].mGapAssume,
|
|
attempts[i].mPrimeBetween, sGotReply ? "ECHO REPLY RECEIVED" : "no reply");
|
|
|
|
if (sGotReply)
|
|
{
|
|
replied = true;
|
|
repliedVal = attempts[i].mGapAssume;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (replied)
|
|
{
|
|
Log("mismatched-length request answered; region bytes == 0x%02x pattern", repliedVal);
|
|
Log("(reply len %u, marker run %s; unwritten region content was incorporated and reflected)", sReplyLength,
|
|
sReplyHasMarkerRun ? "present" : "absent");
|
|
}
|
|
|
|
// Corrected behavior: no unwritten region exists in the reassembled datagram, so
|
|
// none of the region-assuming checksums can match and no reply is produced for any
|
|
// attempt.
|
|
VerifyOrQuit(!replied);
|
|
VerifyOrQuit(!sReplyHasMarkerRun);
|
|
|
|
Log("Case C: Fragment 1 carries HBH extension header, Fragment 2 without HBH");
|
|
Log("Reassembled datagram contains only the 40-byte base header with NextHeader");
|
|
Log("set to ICMPv6 and delivers directly to ICMPv6 without duplicate HBH processing.");
|
|
sGotReply = false;
|
|
sReplyHasMarkerRun = false;
|
|
sReplyLength = 0;
|
|
BuildAndSendFragmentsWithExtHeader(child, nexus, src, dst, 0x33330001, /* aFrag2WithExt */ false, 20);
|
|
nexus.AdvanceTime(3 * 1000);
|
|
VerifyOrQuit(sGotReply);
|
|
VerifyOrQuit(sReplyHasMarkerRun);
|
|
VerifyOrQuit(sReplyLength == kPay1 + kPay2 - sizeof(Ip6::Icmp6Header));
|
|
Log("reply received (len %u, marker run verified)", sReplyLength);
|
|
|
|
Log("Case D: Both Fragment 1 and Fragment 2 carry HBH extension header");
|
|
Log("Reassembled datagram similarly completes with 40-byte base header and direct ICMPv6 delivery.");
|
|
sGotReply = false;
|
|
sReplyHasMarkerRun = false;
|
|
sReplyLength = 0;
|
|
BuildAndSendFragmentsWithExtHeader(child, nexus, src, dst, 0x33330002, /* aFrag2WithExt */ true, 21);
|
|
nexus.AdvanceTime(3 * 1000);
|
|
VerifyOrQuit(sGotReply);
|
|
VerifyOrQuit(sReplyHasMarkerRun);
|
|
VerifyOrQuit(sReplyLength == kPay1 + kPay2 - sizeof(Ip6::Icmp6Header));
|
|
Log("reply received (len %u, marker run verified)", sReplyLength);
|
|
|
|
Log("TestFragmentReassemblyHole passed");
|
|
}
|
|
|
|
} // namespace Nexus
|
|
} // namespace ot
|
|
|
|
int main(void)
|
|
{
|
|
ot::Nexus::TestFragmentReassemblyHole();
|
|
printf("All tests passed\n");
|
|
return 0;
|
|
}
|