[nexus] add helpers for sending and validating ICMPv6 echo exchange (#12408)

This commit adds helper methods in Nexus to simplify ICMPv6 echo
exchanges:
- `Node::SendEchoRequest()`: sends an ICMPv6 Echo Request with
  configurable parameters such as payload size and hop limit.
- `Core::SendAndVerifyEchoRequest()`: sends an Echo Request and
  validates the matching Echo Reply within a timeout.

This commit also update various certification tests to use these
helpers, removing duplicate local utility functions.
This commit is contained in:
Abtin Keshavarzian
2026-02-10 16:57:48 -08:00
committed by GitHub
parent 348b2f671e
commit b347195b15
12 changed files with 132 additions and 231 deletions
+2
View File
@@ -51,6 +51,8 @@ Message *Icmp::NewMessage(void) { return Get<Ip6>().NewMessage(sizeof(Header));
Error Icmp::RegisterHandler(Handler &aHandler) { return mHandlers.Add(aHandler); }
Error Icmp::UnregisterHandler(Handler &aHandler) { return mHandlers.Remove(aHandler); }
Error Icmp::SendEchoRequest(Message &aMessage, const MessageInfo &aMessageInfo, uint16_t aIdentifier)
{
Error error = kErrorNone;
+10
View File
@@ -236,6 +236,16 @@ public:
*/
Error RegisterHandler(Handler &aHandler);
/**
* Unregisters an ICMPv6 handler.
*
* @param[in] aHandler The ICMPv6 handler.
*
* @retval kErrorNone The handler was successfully removed from the list.
* @retval kErrorNotFound Could not find the handler in the list.
*/
Error UnregisterHandler(Handler &aHandler);
/**
* Sends an ICMPv6 Echo Request message.
*
+53
View File
@@ -415,5 +415,58 @@ void Core::ProcessTrel(Node &aNode)
#endif // OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
//---------------------------------------------------------------------------------------------------------------------
Core::IcmpEchoResponseContext::IcmpEchoResponseContext(Node &aNode, uint16_t aIdentifier)
: mNode(aNode)
, mIdentifier(aIdentifier)
, mResponseReceived(false)
{
}
void Core::HandleIcmpResponse(void *aContext,
otMessage *aMessage,
const otMessageInfo *aMessageInfo,
const otIcmp6Header *aIcmpHeader)
{
OT_UNUSED_VARIABLE(aMessage);
IcmpEchoResponseContext *context = static_cast<IcmpEchoResponseContext *>(aContext);
const Ip6::Icmp::Header *header = AsCoreTypePtr(aIcmpHeader);
const Ip6::MessageInfo *messageInfo = AsCoreTypePtr(aMessageInfo);
VerifyOrQuit(context != nullptr);
VerifyOrQuit(header != nullptr);
VerifyOrQuit(messageInfo != nullptr);
if ((header->GetType() == Ip6::Icmp::Header::kTypeEchoReply) && (header->GetId() == context->mIdentifier))
{
context->mResponseReceived = true;
Log("Received Echo Reply on Node %u (%s) from %s", context->mNode.GetId(), context->mNode.GetName(),
messageInfo->GetPeerAddr().ToString().AsCString());
}
}
void Core::SendAndVerifyEchoRequest(Node &aSender,
const Ip6::Address &aDestination,
uint16_t aPayloadSize,
uint8_t aHopLimit,
uint32_t aResponseTimeout)
{
static constexpr uint16_t kIdentifier = 0x1234;
IcmpEchoResponseContext icmpContext(aSender, kIdentifier);
Ip6::Icmp::Handler icmpHandler(HandleIcmpResponse, &icmpContext);
SuccessOrQuit(aSender.Get<Ip6::Icmp>().RegisterHandler(icmpHandler));
aSender.SendEchoRequest(aDestination, kIdentifier, aPayloadSize, aHopLimit);
AdvanceTime(aResponseTimeout);
VerifyOrQuit(icmpContext.mResponseReceived);
SuccessOrQuit(aSender.Get<Ip6::Icmp>().UnregisterHandler(icmpHandler));
}
} // namespace Nexus
} // namespace ot
+22
View File
@@ -56,7 +56,15 @@ public:
TimeMilli GetNow(void) { return mNow; }
void AdvanceTime(uint32_t aDuration);
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Test specific helper methods
void SaveTestInfo(const char *aFilename);
void SendAndVerifyEchoRequest(Node &aSender,
const Ip6::Address &aDestination,
uint16_t aPayloadSize = 0,
uint8_t aHopLimit = 64,
uint32_t aResponseTimeout = 1000);
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Used by platform implementation
@@ -77,6 +85,15 @@ private:
kSendAckFramePending,
};
struct IcmpEchoResponseContext
{
IcmpEchoResponseContext(Node &aNode, uint16_t aIdentifier);
Node &mNode;
uint16_t mIdentifier;
bool mResponseReceived;
};
void Process(Node &aNode);
void ProcessRadio(Node &aNode);
void ProcessMdns(Node &aNode);
@@ -84,6 +101,11 @@ private:
void ProcessTrel(Node &aNode);
#endif
static void HandleIcmpResponse(void *aContext,
otMessage *aMessage,
const otMessageInfo *aMessageInfo,
const otIcmp6Header *aIcmpHeader);
static Core *sCore;
static bool sInUse;
+22
View File
@@ -103,6 +103,28 @@ void Node::AllowList(Node &aNode)
void Node::UnallowList(Node &aNode) { Get<Mac::Filter>().RemoveAddress(aNode.Get<Mac::Mac>().GetExtAddress()); }
void Node::SendEchoRequest(const Ip6::Address &aDestination,
uint16_t aIdentifier,
uint16_t aPayloadSize,
uint8_t aHopLimit)
{
Message *message;
Ip6::MessageInfo messageInfo;
message = Get<Ip6::Icmp>().NewMessage();
VerifyOrQuit(message != nullptr);
SuccessOrQuit(message->SetLength(aPayloadSize));
messageInfo.SetPeerAddr(aDestination);
messageInfo.SetHopLimit(aHopLimit);
Log("Sending Echo Request from Node %lu (%s) to %s (payload-size:%u)", ToUlong(GetId()), GetName(),
aDestination.ToString().AsCString(), aPayloadSize);
SuccessOrQuit(Get<Ip6::Icmp>().SendEchoRequest(*message, messageInfo, aIdentifier));
}
void Node::SetName(const char *aPrefix, uint16_t aIndex) { mName.Clear().Append("%s %u", aPrefix, aIndex); }
#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
+4
View File
@@ -79,6 +79,10 @@ public:
void Join(Node &aNode, JoinMode aJoinMode = kAsFtd);
void AllowList(Node &aNode);
void UnallowList(Node &aNode);
void SendEchoRequest(const Ip6::Address &aDestination,
uint16_t aIdentifier,
uint16_t aPayloadSize = 0,
uint8_t aHopLimit = 64);
#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
void GetTrelSockAddr(Ip6::SockAddr &aSockAddr) const;
#endif
+4 -50
View File
@@ -46,42 +46,6 @@ static constexpr uint32_t kFormNetworkTime = 13 * 1000;
*/
static constexpr uint32_t kAttachToRouterTime = 200 * 1000;
/**
* Time to wait for ICMPv6 Echo Reply.
*/
static constexpr uint32_t kEchoResponseTime = 1000;
static void HandleEchoReply(void *aContext,
otMessage *aMessage,
const otMessageInfo *aMessageInfo,
const otIcmp6Header *aIcmpHeader)
{
OT_UNUSED_VARIABLE(aMessage);
OT_UNUSED_VARIABLE(aMessageInfo);
if (aIcmpHeader->mType == OT_ICMP6_TYPE_ECHO_REPLY)
{
*static_cast<bool *>(aContext) = true;
}
}
static void SendAndVerifyEchoRequest(Core &aNexus, Node &aSender, Node &aReceiver, bool &aReceivedEchoReply)
{
Message *message = aSender.Get<Ip6::Icmp>().NewMessage();
Ip6::MessageInfo messageInfo;
VerifyOrQuit(message != nullptr);
messageInfo.SetPeerAddr(aReceiver.Get<Mle::Mle>().GetLinkLocalAddress());
messageInfo.SetHopLimit(64);
aReceivedEchoReply = false;
SuccessOrQuit(aSender.Get<Ip6::Icmp>().SendEchoRequest(*message, messageInfo, 0x1234));
aNexus.AdvanceTime(kEchoResponseTime);
VerifyOrQuit(aReceivedEchoReply, "Echo Reply not received");
}
void Test5_1_1(void)
{
/**
@@ -177,26 +141,16 @@ void Test5_1_1(void)
* - Pass Criteria:
* - The DUT MUST respond with ICMPv6 Echo Reply
*/
bool routerReceivedEchoReply = false;
Ip6::Icmp::Handler routerIcmpHandler(HandleEchoReply, &routerReceivedEchoReply);
bool leaderReceivedEchoReply = false;
Ip6::Icmp::Handler leaderIcmpHandler(HandleEchoReply, &leaderReceivedEchoReply);
// 1. Verify Leader as DUT: Router (Reference) sends Echo Request to Leader (DUT) Link-Local address
SuccessOrQuit(router.Get<Ip6::Icmp>().RegisterHandler(routerIcmpHandler));
Log("Step 11.1: Sending Echo Request from Router to Leader Link-Local: %s",
leader.Get<Mle::Mle>().GetLinkLocalAddress().ToString().AsCString());
SendAndVerifyEchoRequest(nexus, router, leader, routerReceivedEchoReply);
Log("Leader (as DUT) responded with Echo Reply successfully");
Log("Step 11.1: Sending Echo Request from Router to Leader Link-Local");
nexus.SendAndVerifyEchoRequest(router, leader.Get<Mle::Mle>().GetLinkLocalAddress());
// 2. Verify Router as DUT: Leader (Reference) sends Echo Request to Router (DUT) Link-Local address
SuccessOrQuit(leader.Get<Ip6::Icmp>().RegisterHandler(leaderIcmpHandler));
Log("Step 11.2: Sending Echo Request from Leader to Router Link-Local: %s",
router.Get<Mle::Mle>().GetLinkLocalAddress().ToString().AsCString());
SendAndVerifyEchoRequest(nexus, leader, router, leaderReceivedEchoReply);
Log("Router (as DUT) responded with Echo Reply successfully");
Log("Step 11.2: Sending Echo Request from Leader to Router Link-Local");
nexus.SendAndVerifyEchoRequest(leader, router.Get<Mle::Mle>().GetLinkLocalAddress());
nexus.SaveTestInfo("test_5_1_1.json");
}
+2 -13
View File
@@ -64,17 +64,6 @@ static constexpr uint32_t kChildTimeoutWaitTime = (kChildTimeout + 2) * 1000;
*/
static constexpr uint32_t kEchoRequestWaitTime = 5 * 1000;
static void SendEchoRequest(Node &aSender, const Ip6::Address &aPeerAddr, uint16_t aIdentifier)
{
Message *message = aSender.Get<Ip6::Icmp>().NewMessage();
Ip6::MessageInfo messageInfo;
VerifyOrQuit(message != nullptr);
messageInfo.SetPeerAddr(aPeerAddr);
messageInfo.SetHopLimit(64);
SuccessOrQuit(aSender.Get<Ip6::Icmp>().SendEchoRequest(*message, messageInfo, aIdentifier));
}
void Test5_1_2(void)
{
/**
@@ -167,7 +156,7 @@ void Test5_1_2(void)
* Leader automatically attempts to perform address resolution by sending an Address Query Request
* - Pass Criteria: N/A
*/
SendEchoRequest(leader, med.Get<Mle::Mle>().GetMeshLocalEid(), 0x1234);
leader.SendEchoRequest(med.Get<Mle::Mle>().GetMeshLocalEid(), 0x1234);
Log("---------------------------------------------------------------------------------------");
Log("Step 4: Router_1 (DUT)");
@@ -188,7 +177,7 @@ void Test5_1_2(void)
* Leader automatically attempts to perform address resolution by sending an Address Query Request
* - Pass Criteria: N/A
*/
SendEchoRequest(leader, sed.Get<Mle::Mle>().GetMeshLocalEid(), 0x5678);
leader.SendEchoRequest(sed.Get<Mle::Mle>().GetMeshLocalEid(), 0x5678);
Log("---------------------------------------------------------------------------------------");
Log("Step 7: Router_1 (DUT)");
+2 -52
View File
@@ -46,52 +46,6 @@ static constexpr uint32_t kFormNetworkTime = 13 * 1000;
*/
static constexpr uint32_t kAttachToRouterTime = 200 * 1000;
/**
* Time to wait for ICMPv6 Echo Response.
*/
static constexpr uint32_t kEchoResponseTime = 1000;
/**
* ICMPv6 Echo Request identifier.
*/
static constexpr uint16_t kEchoIdentifier = 0x1234;
/**
* ICMPv6 Hop Limit.
*/
static constexpr uint8_t kHopLimit = 64;
static void HandleEchoReply(void *aContext,
otMessage *aMessage,
const otMessageInfo *aMessageInfo,
const otIcmp6Header *aIcmpHeader)
{
OT_UNUSED_VARIABLE(aMessage);
OT_UNUSED_VARIABLE(aMessageInfo);
if (aIcmpHeader->mType == OT_ICMP6_TYPE_ECHO_REPLY)
{
*static_cast<bool *>(aContext) = true;
}
}
static void SendAndVerifyEchoRequest(Core &aNexus, Node &aSender, Node &aReceiver, bool &aReceivedEchoReply)
{
Message *message = aSender.Get<Ip6::Icmp>().NewMessage();
Ip6::MessageInfo messageInfo;
VerifyOrQuit(message != nullptr);
messageInfo.SetPeerAddr(aReceiver.Get<Mle::Mle>().GetLinkLocalAddress());
messageInfo.SetHopLimit(kHopLimit);
aReceivedEchoReply = false;
SuccessOrQuit(aSender.Get<Ip6::Icmp>().SendEchoRequest(*message, messageInfo, kEchoIdentifier));
aNexus.AdvanceTime(kEchoResponseTime);
VerifyOrQuit(aReceivedEchoReply, "Echo Reply not received");
}
void Test5_1_6(void)
{
/**
@@ -151,11 +105,7 @@ void Test5_1_6(void)
* - Pass Criteria:
* - The DUT MUST respond with an ICMPv6 Echo Reply
*/
bool leaderReceivedEchoReply = false;
Ip6::Icmp::Handler leaderIcmpHandler(HandleEchoReply, &leaderReceivedEchoReply);
SuccessOrQuit(leader.Get<Ip6::Icmp>().RegisterHandler(leaderIcmpHandler));
SendAndVerifyEchoRequest(nexus, leader, router, leaderReceivedEchoReply);
nexus.SendAndVerifyEchoRequest(leader, router.Get<Mle::Mle>().GetLinkLocalAddress());
Log("---------------------------------------------------------------------------------------");
Log("Step 2: Harness instructs the Leader to remove the Router ID of Router_1 (the DUT)");
@@ -191,7 +141,7 @@ void Test5_1_6(void)
* - Pass Criteria:
* - The DUT MUST respond with an ICMPv6 Echo Reply
*/
SendAndVerifyEchoRequest(nexus, leader, router, leaderReceivedEchoReply);
nexus.SendAndVerifyEchoRequest(leader, router.Get<Mle::Mle>().GetLinkLocalAddress());
nexus.SaveTestInfo("test_5_1_6.json");
}
+9 -38
View File
@@ -73,14 +73,14 @@ static constexpr uint16_t kIp6HeaderSize = 40;
static constexpr uint16_t kIcmp6HeaderSize = 8;
/**
* Small ICMPv6 Echo Request datagram size in octets.
* Small ICMPv6 Echo Request payload size in octets.
*/
static constexpr uint16_t kSmallDatagramSize = 106;
static constexpr uint16_t kSmallIcmpEchoPayloadSize = 106 - kIp6HeaderSize - kIcmp6HeaderSize;
/**
* Large ICMPv6 Echo Request datagram size in octets.
*/
static constexpr uint16_t kLargeDatagramSize = 1280;
static constexpr uint16_t kLargeIcmpEchoPayloadSize = 1280 - kIp6HeaderSize - kIcmp6HeaderSize;
/**
* Time to wait for ICMPv6 Echo replies.
@@ -97,38 +97,6 @@ static constexpr uint16_t kMedEchoId = 1001;
*/
static constexpr uint16_t kSedEchoId = 2001;
static void SendEchoRequest(Node &aSender, const Ip6::Address &aPeerAddr, uint16_t aDatagramSize, uint16_t aIdentifier)
{
Error error = kErrorNone;
Message *message = aSender.Get<Ip6::Icmp>().NewMessage();
Ip6::MessageInfo messageInfo;
uint16_t payloadLength;
VerifyOrQuit(message != nullptr);
VerifyOrExit(aDatagramSize >= kIp6HeaderSize + kIcmp6HeaderSize, error = kErrorInvalidArgs);
payloadLength = aDatagramSize - kIp6HeaderSize - kIcmp6HeaderSize;
error = message->SetLength(payloadLength);
SuccessOrExit(error);
messageInfo.SetPeerAddr(aPeerAddr);
messageInfo.SetHopLimit(64);
error = aSender.Get<Ip6::Icmp>().SendEchoRequest(*message, messageInfo, aIdentifier);
SuccessOrExit(error);
message = nullptr;
exit:
if (message != nullptr)
{
message->Free();
}
SuccessOrQuit(error);
}
static void CreateNodes(Core &aNexus, Node *aNodes[], uint16_t aCount, const char *aNamePrefix)
{
for (uint16_t i = 0; i < aCount; i++)
@@ -249,8 +217,9 @@ void Test5_1_7(void)
*/
for (uint16_t i = 0; i < kNumMeds; i++)
{
SendEchoRequest(leader, meds[i]->Get<Mle::Mle>().GetMeshLocalEid(), kSmallDatagramSize, kMedEchoId + i);
leader.SendEchoRequest(meds[i]->Get<Mle::Mle>().GetMeshLocalEid(), kMedEchoId + i, kSmallIcmpEchoPayloadSize);
}
nexus.AdvanceTime(kEchoResponseWaitTime);
Log("---------------------------------------------------------------------------------------");
@@ -266,11 +235,13 @@ void Test5_1_7(void)
* - The DUT MUST properly forward ICMPv6 Echo Requests to all SED children.
* - The DUT MUST properly forward ICMPv6 Echo Replies to the Leader.
*/
SendEchoRequest(leader, seds[0]->Get<Mle::Mle>().GetMeshLocalEid(), kLargeDatagramSize, kSedEchoId);
leader.SendEchoRequest(seds[0]->Get<Mle::Mle>().GetMeshLocalEid(), kSedEchoId, kLargeIcmpEchoPayloadSize);
for (uint16_t i = 1; i < kNumSeds; i++)
{
SendEchoRequest(leader, seds[i]->Get<Mle::Mle>().GetMeshLocalEid(), kSmallDatagramSize, kSedEchoId + i);
leader.SendEchoRequest(seds[i]->Get<Mle::Mle>().GetMeshLocalEid(), kSedEchoId + i, kSmallIcmpEchoPayloadSize);
}
nexus.AdvanceTime(kEchoResponseWaitTime);
nexus.SaveTestInfo("test_5_1_7.json");
+1 -51
View File
@@ -51,52 +51,6 @@ static constexpr uint32_t kAttachToRouterTime = 200 * 1000;
*/
static constexpr uint32_t kAttachToChildTime = 10 * 1000;
/**
* Time to wait for ICMPv6 Echo Reply.
*/
static constexpr uint32_t kEchoResponseTime = 1000;
static void HandleEchoReply(void *aContext,
otMessage *aMessage,
const otMessageInfo *aMessageInfo,
const otIcmp6Header *aIcmpHeader)
{
OT_UNUSED_VARIABLE(aMessage);
OT_UNUSED_VARIABLE(aMessageInfo);
if (aIcmpHeader->mType == OT_ICMP6_TYPE_ECHO_REPLY)
{
*static_cast<bool *>(aContext) = true;
}
}
static void SendAndVerifyEchoRequest(Core &aNexus,
Node &aSender,
const Ip6::Address &aReceiverAddr,
bool &aReceivedEchoReply)
{
Message *message = aSender.Get<Ip6::Icmp>().NewMessage();
Ip6::MessageInfo messageInfo;
Error error = kErrorNone;
VerifyOrQuit(message != nullptr);
messageInfo.SetPeerAddr(aReceiverAddr);
messageInfo.SetHopLimit(64);
aReceivedEchoReply = false;
error = aSender.Get<Ip6::Icmp>().SendEchoRequest(*message, messageInfo, 0x1234);
if (error != kErrorNone)
{
message->Free();
SuccessOrQuit(error);
}
aNexus.AdvanceTime(kEchoResponseTime);
VerifyOrQuit(aReceivedEchoReply, "Echo Reply not received");
}
void Test5_2_1(void)
{
/**
@@ -256,11 +210,7 @@ void Test5_2_1(void)
* - Pass Criteria:
* - REED_1 responds with ICMPv6 Echo Reply.
*/
bool reed1ReceivedEchoReply = false;
Ip6::Icmp::Handler leaderIcmpHandler(HandleEchoReply, &reed1ReceivedEchoReply);
leader.Get<Ip6::Icmp>().RegisterHandler(leaderIcmpHandler);
SendAndVerifyEchoRequest(nexus, leader, reed1.Get<Mle::Mle>().GetMeshLocalEid(), reed1ReceivedEchoReply);
nexus.SendAndVerifyEchoRequest(leader, reed1.Get<Mle::Mle>().GetMeshLocalEid());
nexus.SaveTestInfo("test_5_2_1.json");
}
+1 -27
View File
@@ -60,26 +60,11 @@ static constexpr uint32_t kReedAdvertisementMaxJitter = 60 * 1000;
*/
static constexpr uint32_t kWaitTime = kReedAdvertisementInterval + kReedAdvertisementMaxJitter;
/**
* Time to wait for ICMPv6 Echo Response.
*/
static constexpr uint32_t kEchoResponseTime = 1000;
/**
* Number of routers in the topology besides the leader.
*/
static constexpr uint16_t kNumRouters = 15;
/**
* Hop limit for ICMPv6 Echo Request.
*/
static constexpr uint8_t kEchoHopLimit = 64;
/**
* Echo Request Identifier.
*/
static constexpr uint16_t kEchoIdentifier = 0x1234;
void Test5_2_4(void)
{
/**
@@ -281,18 +266,7 @@ void Test5_2_4(void)
* Leader.
* - Pass Criteria: The Leader MUST respond with an ICMPv6 Echo Reply.
*/
{
Message *message = med1.Get<Ip6::Icmp>().NewMessage();
Ip6::MessageInfo messageInfo;
VerifyOrQuit(message != nullptr);
messageInfo.SetPeerAddr(leader.Get<Mle::Mle>().GetMeshLocalEid());
messageInfo.SetHopLimit(kEchoHopLimit);
SuccessOrQuit(med1.Get<Ip6::Icmp>().SendEchoRequest(*message, messageInfo, kEchoIdentifier));
}
nexus.AdvanceTime(kEchoResponseTime);
nexus.SendAndVerifyEchoRequest(med1, leader.Get<Mle::Mle>().GetMeshLocalEid());
nexus.SaveTestInfo("test_5_2_4.json");
}