mirror of
https://github.com/espressif/openthread.git
synced 2026-10-05 15:27:40 +00:00
This commit addresses intermittent failures in Nexus tests 1_4_DNS_TC_1, 1_4_DNS_TC_5, 1_4_PIC_TC_1, 1_4_PIC_TC_3, and 1_4_PIC_TC_4. The issue was caused by the 'ed1' node occasionally upgrading its role from an End Device to a Router. When 'ed1' became a router, it would sometimes use its Routing Locator (RLOC) as the source address for DNS queries, whereas the verification scripts expected its Mesh Local Endpoint Identifier (MLEID), leading to packet verification failures. To resolve this, 'ed1' is now explicitly joined as a Full End Device (FED) using 'Node::kAsFed' instead of the default Full Thread Device (FTD) mode. This prevents 'ed1' from becoming a router and ensures it maintains its End Device role throughout the test, providing stable addressing for verification.
488 lines
17 KiB
C++
488 lines
17 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.
|
||
*/
|
||
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
|
||
#include "net/dhcp6_types.hpp"
|
||
#include "platform/nexus_core.hpp"
|
||
#include "platform/nexus_node.hpp"
|
||
|
||
namespace ot {
|
||
namespace Nexus {
|
||
|
||
/**
|
||
* Time to advance for a node to form a network and become leader, in milliseconds.
|
||
*/
|
||
static constexpr uint32_t kFormNetworkTime = 13 * 1000;
|
||
|
||
/**
|
||
* Time to advance for a node to join as a child and upgrade to a router, in milliseconds.
|
||
*/
|
||
static constexpr uint32_t kJoinNetworkTime = 200 * 1000;
|
||
|
||
/**
|
||
* Time to advance for the BR to perform automatic actions (DHCPv6-PD, RA), in milliseconds.
|
||
*/
|
||
static constexpr uint32_t kBrActionTime = 30 * 1000;
|
||
|
||
/**
|
||
* Time to advance for the ping response, in milliseconds.
|
||
*/
|
||
static constexpr uint32_t kPingTime = 2 * 1000;
|
||
|
||
/**
|
||
* DHCPv6-PD Server Port.
|
||
*/
|
||
static constexpr uint16_t kDhcp6ServerPort = 547;
|
||
|
||
/**
|
||
* DHCPv6-PD Client Port.
|
||
*/
|
||
static constexpr uint16_t kDhcp6ClientPort = 546;
|
||
|
||
/**
|
||
* Echo Request identifier.
|
||
*/
|
||
static constexpr uint16_t kEchoIdentifier = 0x1234;
|
||
|
||
/**
|
||
* Echo Request payload size.
|
||
*/
|
||
static constexpr uint16_t kEchoPayloadSize = 10;
|
||
|
||
/**
|
||
* Internet Server Address.
|
||
*/
|
||
static const char kInternetServerAddr[] = "2002:1234::1234";
|
||
|
||
/**
|
||
* Delegated Prefix.
|
||
*/
|
||
static const char kDelegatedPrefix[] = "2005:1234:abcd:0::/64";
|
||
|
||
/**
|
||
* Eth_1 GUA address prefix.
|
||
*/
|
||
static const char kEth1Prefix[] = "2001:db8:1::/64";
|
||
|
||
/**
|
||
* Infrastructure interface index.
|
||
*/
|
||
static constexpr uint32_t kInfraIfIndex = 1;
|
||
|
||
/**
|
||
* DHCPv6 Server Hook to simulate DHCPv6-PD server.
|
||
*/
|
||
bool HandleEth2Udp(Instance &aInstance, Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
|
||
{
|
||
Node ð2 = Node::From(&aInstance);
|
||
bool handled = false;
|
||
|
||
if (aMessageInfo.GetSockPort() == kDhcp6ServerPort)
|
||
{
|
||
Dhcp6::Header header;
|
||
SuccessOrQuit(aMessage.Read(aMessage.GetOffset(), header));
|
||
|
||
if (header.GetMsgType() == Dhcp6::kMsgTypeSolicit || header.GetMsgType() == Dhcp6::kMsgTypeRequest)
|
||
{
|
||
Message *reply = eth2.Get<MessagePool>().Allocate(Message::kTypeIp6);
|
||
VerifyOrQuit(reply != nullptr);
|
||
|
||
Dhcp6::Header replyHeader;
|
||
replyHeader.Clear();
|
||
replyHeader.SetMsgType((header.GetMsgType() == Dhcp6::kMsgTypeSolicit) ? Dhcp6::kMsgTypeAdvertise
|
||
: Dhcp6::kMsgTypeReply);
|
||
replyHeader.SetTransactionId(header.GetTransactionId());
|
||
SuccessOrQuit(reply->Append(replyHeader));
|
||
|
||
uint16_t offset = aMessage.GetOffset() + sizeof(Dhcp6::Header);
|
||
uint16_t length = aMessage.GetLength() - offset;
|
||
Dhcp6::Option option;
|
||
uint32_t iaid = 0;
|
||
|
||
while (length >= sizeof(Dhcp6::Option))
|
||
{
|
||
SuccessOrQuit(aMessage.Read(offset, option));
|
||
VerifyOrQuit(length >= option.GetSize());
|
||
if (option.GetCode() == Dhcp6::Option::kClientId)
|
||
{
|
||
SuccessOrQuit(reply->AppendBytesFromMessage(aMessage, offset, option.GetSize()));
|
||
}
|
||
else if (option.GetCode() == Dhcp6::Option::kIaPd)
|
||
{
|
||
Dhcp6::IaPdOption iaPdSolicit;
|
||
|
||
VerifyOrQuit(option.GetSize() >= sizeof(iaPdSolicit));
|
||
SuccessOrQuit(aMessage.Read(offset, iaPdSolicit));
|
||
iaid = iaPdSolicit.GetIaid();
|
||
}
|
||
offset += option.GetSize();
|
||
length -= option.GetSize();
|
||
}
|
||
|
||
// Append Server ID (ETH_2 address)
|
||
SuccessOrQuit(Dhcp6::ServerIdOption::AppendWithEui64Duid(*reply, eth2.Get<Mac::Mac>().GetExtAddress()));
|
||
|
||
if (header.GetMsgType() == Dhcp6::kMsgTypeSolicit)
|
||
{
|
||
// Append Preference
|
||
Dhcp6::PreferenceOption preference;
|
||
preference.Init();
|
||
preference.SetPreference(255);
|
||
SuccessOrQuit(reply->Append(preference));
|
||
}
|
||
|
||
Dhcp6::IaPdOption iaPd;
|
||
iaPd.Init();
|
||
iaPd.SetIaid(iaid);
|
||
iaPd.SetT1(1000);
|
||
iaPd.SetT2(2000);
|
||
SuccessOrQuit(reply->Append(iaPd));
|
||
|
||
Dhcp6::IaPrefixOption prefixOption;
|
||
prefixOption.Init();
|
||
Ip6::Prefix prefix;
|
||
SuccessOrQuit(prefix.FromString(kDelegatedPrefix));
|
||
prefixOption.SetPrefix(prefix);
|
||
prefixOption.SetPreferredLifetime(3600);
|
||
prefixOption.SetValidLifetime(7200);
|
||
SuccessOrQuit(reply->Append(prefixOption));
|
||
|
||
Dhcp6::Option::UpdateOptionLengthInMessage(*reply,
|
||
reply->GetLength() - sizeof(iaPd) - sizeof(prefixOption));
|
||
|
||
eth2.mInfraIf.SendUdp(eth2.mInfraIf.GetLinkLocalAddress(), aMessageInfo.GetPeerAddr(), kDhcp6ServerPort,
|
||
kDhcp6ClientPort, *reply);
|
||
handled = true;
|
||
}
|
||
}
|
||
|
||
return handled;
|
||
}
|
||
|
||
static bool sDropInternetPings = false;
|
||
|
||
void Br1ReceiveCallback(otMessage *aMessage, void *aContext)
|
||
{
|
||
if (sDropInternetPings)
|
||
{
|
||
Ip6::Header header;
|
||
Ip6::Address internetServer;
|
||
|
||
SuccessOrQuit(AsCoreType(aMessage).Read(0, header));
|
||
SuccessOrQuit(internetServer.FromString(kInternetServerAddr));
|
||
|
||
if (header.GetDestination() == internetServer)
|
||
{
|
||
// Drop it to simulate "no route to host"
|
||
return;
|
||
}
|
||
}
|
||
|
||
Node::HandleIp6Receive(aMessage, aContext);
|
||
}
|
||
|
||
void Test_1_4_PIC_TC_3(void)
|
||
{
|
||
/**
|
||
* 10.3. IPv6 default route advertisement
|
||
*
|
||
* 10.3.1. Purpose
|
||
* - To verify that the BR DUT:
|
||
* - Correctly advertises a default route on the Thread Network, using the zero-length route ::/0 in a Prefix TLV.
|
||
* - Does not withdraws the advertised default route when the infrastructure network disables the default route,
|
||
* as long as there's still a non-ULA prefix active on the AIL.
|
||
* - Still advertises the default route, when the infrastructure network re-enables the default route.
|
||
* - TBD: a future test update needs to validate that when only a ULA prefix is set at the AIL, the default route
|
||
* gets withdrawn and replaced by fc00::/7 route.
|
||
*
|
||
* 10.3.2. Topology
|
||
* - BR_1 (DUT) - Border Router
|
||
* - Router_1 - Thread Router Reference Device, attached to BR_1
|
||
* - ED_1 - Thread Reference Device, End Device (e.g. FED/REED) role, attached to Router_1
|
||
* - Eth_1 - Adjacent Infrastructure Link Linux Reference Device
|
||
* - Eth_2 - Adjacent Infrastructure Link SPIFF Reference Device
|
||
*/
|
||
|
||
Core nexus;
|
||
|
||
Node &br1 = nexus.CreateNode();
|
||
Node &r1 = nexus.CreateNode();
|
||
Node &ed1 = nexus.CreateNode();
|
||
Node ð1 = nexus.CreateNode();
|
||
Node ð2 = nexus.CreateNode();
|
||
|
||
br1.SetName("BR_1");
|
||
r1.SetName("ROUTER_1");
|
||
ed1.SetName("ED_1");
|
||
eth1.SetName("ETH_1");
|
||
eth2.SetName("ETH_2");
|
||
|
||
nexus.AdvanceTime(0);
|
||
|
||
SuccessOrQuit(Instance::SetGlobalLogLevel(kLogLevelNote));
|
||
|
||
Log("Step 1: Enable Eth_1 and Eth_2");
|
||
/**
|
||
* Step 1
|
||
* - Device: Eth_1, Eth_2
|
||
* - Description (PIC-10.3): Enable
|
||
* - Pass Criteria:
|
||
* - N/A
|
||
*/
|
||
eth1.Get<BorderRouter::InfraIf>().Init(kInfraIfIndex, true);
|
||
eth2.Get<BorderRouter::InfraIf>().Init(kInfraIfIndex, true);
|
||
eth1.Get<BorderRouter::RoutingManager>().Init();
|
||
eth2.Get<BorderRouter::RoutingManager>().Init();
|
||
SuccessOrQuit(eth1.Get<BorderRouter::RoutingManager>().SetEnabled(false));
|
||
SuccessOrQuit(eth2.Get<BorderRouter::RoutingManager>().SetEnabled(false));
|
||
|
||
{
|
||
Ip6::Address internetServer;
|
||
SuccessOrQuit(internetServer.FromString(kInternetServerAddr));
|
||
eth2.mInfraIf.AddAddress(internetServer);
|
||
}
|
||
|
||
Ip6::Prefix pioPrefix;
|
||
SuccessOrQuit(pioPrefix.FromString(kEth1Prefix));
|
||
eth2.mInfraIf.StartRouterAdvertisement(pioPrefix);
|
||
eth2.mInfraIf.SetUdpHook(HandleEth2Udp);
|
||
|
||
Log("Step 2: Eth_2 default configuration");
|
||
/**
|
||
* Step 2
|
||
* - Device: Eth_2
|
||
* - Description (PIC-10.3): Harness instructs device to: (only default configuration is applied)
|
||
* - Pass Criteria:
|
||
* - N/A
|
||
*/
|
||
nexus.AdvanceTime(kPingTime);
|
||
|
||
Log("Step 3: Enable BR_1, Router_1, ED_1");
|
||
/**
|
||
* Step 3
|
||
* - Device: BR_1 (DUT), Router_1, ED_1
|
||
* - Description (PIC-10.3): Enable
|
||
* - Pass Criteria:
|
||
* - N/A
|
||
*/
|
||
AllowLinkBetween(br1, r1);
|
||
AllowLinkBetween(r1, ed1);
|
||
|
||
br1.Form();
|
||
nexus.AdvanceTime(kFormNetworkTime);
|
||
|
||
br1.Get<BorderRouter::InfraIf>().Init(kInfraIfIndex, true);
|
||
br1.Get<BorderRouter::RoutingManager>().Init();
|
||
br1.Get<BorderRouter::RoutingManager>().SetDhcp6PdEnabled(true);
|
||
SuccessOrQuit(br1.Get<BorderRouter::RoutingManager>().SetEnabled(true));
|
||
|
||
br1.Get<Ip6::Ip6>().SetReceiveCallback(Br1ReceiveCallback, &br1);
|
||
|
||
r1.Join(br1);
|
||
nexus.AdvanceTime(kJoinNetworkTime);
|
||
|
||
ed1.Join(r1, Node::kAsFed);
|
||
nexus.AdvanceTime(kJoinNetworkTime);
|
||
|
||
Log("Step 4: BR_1 obtains OMR prefix via DHCPv6-PD");
|
||
/**
|
||
* Step 4
|
||
* - Device: BR_1 (DUT)
|
||
* - Description (PIC-10.3): Automatically uses DHCPv6-PD client function to obtain a delegated prefix from the
|
||
* DHCPv6 server. It configures this prefix as the OMR prefix.
|
||
* - Pass Criteria:
|
||
* - N/A
|
||
*/
|
||
nexus.AdvanceTime(kBrActionTime);
|
||
|
||
Log("Step 5: BR_1 automatically advertises a default route in its Thread Network Data");
|
||
/**
|
||
* Step 5
|
||
* - Device: BR_1 (DUT)
|
||
* - Description (PIC-10.3): Automatically advertises a default route in its Thread Network Data.
|
||
* - Pass Criteria:
|
||
* - Thread Network Data MUST contain: Prefix TLV with an OMR prefix OMR_1 that starts with
|
||
* 2005:1234:abcd:0::/56.
|
||
* - Border Router sub-TLV MUST indicate the RLOC16 of BR_1, in a P_border_router_16 field.
|
||
* - Flag R (P_default) MUST be ‘1’
|
||
* - Flag O (P_on_mesh) MUST be ‘1’
|
||
* - Thread Network Data MUST contain: Prefix TLV with the zero-length prefix ::/0
|
||
* - Has Route sub-TLV Flag NP MUST be ‘0’
|
||
*/
|
||
nexus.AdvanceTime(kBrActionTime);
|
||
|
||
Log("Step 6: ED_1 pings internet address");
|
||
/**
|
||
* Step 6
|
||
* - Device: ED_1
|
||
* - Description (PIC-10.3): Harness instructs device to send ping to destination address 2002:1234::1234 with
|
||
* its (default) OMR source address, to validate that default route works.
|
||
* - Pass Criteria:
|
||
* - Ping response received by ED_1.
|
||
*/
|
||
Ip6::Address internetServer;
|
||
SuccessOrQuit(internetServer.FromString(kInternetServerAddr));
|
||
ed1.SendEchoRequest(internetServer, kEchoIdentifier, kEchoPayloadSize);
|
||
nexus.AdvanceTime(kPingTime);
|
||
|
||
Log("Step 7: Eth_2 stops advertising a default route");
|
||
/**
|
||
* Step 7
|
||
* - Device: Eth_2
|
||
* - Description (PIC-10.3): Harnes instructs device to reconfigure ND RA daemon to stop advertising a default
|
||
* route: “Router Lifetime” field set to 0 sec (indicating it is not a default router for IPv6).
|
||
* - Pass Criteria:
|
||
* - N/A
|
||
*/
|
||
eth2.mInfraIf.StopRouterAdvertisement();
|
||
nexus.AdvanceTime(kPingTime);
|
||
|
||
for (int i = 0; i < 3; i++)
|
||
{
|
||
Ip6::Nd::RouterAdvert::TxMessage ra;
|
||
Ip6::Nd::RouterAdvert::Header header;
|
||
Ip6::Nd::Icmp6Packet packet;
|
||
|
||
header.SetToDefault();
|
||
header.SetRouterLifetime(0);
|
||
SuccessOrQuit(ra.Append(header));
|
||
SuccessOrQuit(ra.AppendPrefixInfoOption(pioPrefix, 1800, 1800,
|
||
Ip6::Nd::PrefixInfoOption::kOnLinkFlag |
|
||
Ip6::Nd::PrefixInfoOption::kAutoConfigFlag));
|
||
ra.GetAsPacket(packet);
|
||
eth2.mInfraIf.SendIcmp6Nd(Ip6::Address::GetLinkLocalAllNodesMulticast(), packet.GetBytes(), packet.GetLength());
|
||
nexus.AdvanceTime(1000);
|
||
}
|
||
nexus.AdvanceTime(200 * 1000);
|
||
|
||
Log("Step 8: BR_1 does not stop advertising a default route");
|
||
/**
|
||
* Step 8
|
||
* - Device: BR_1 (DUT)
|
||
* - Description (PIC-10.3): Does not stop advertising a default route in its Thread Network Data.
|
||
* - Pass Criteria:
|
||
* - Thread Network Data MUST contain: Prefix TLV with same OMR_1.
|
||
* - Border Router sub-TLV MUST indicate the RLOC16 of BR_1, in a P_border_router_16 field.
|
||
* - Flag R (P_default) MUST be ‘1’
|
||
* - Flag O (P_on_mesh) MUST be ‘1’
|
||
* - Thread Network Data MUST contain: Prefix TLV with the zero-length prefix ::/0
|
||
* - Has Route sub-TLV Flag NP MUST be ‘0’
|
||
*/
|
||
nexus.AdvanceTime(kBrActionTime);
|
||
|
||
Log("Step 9: ED_1 pings Eth_1");
|
||
/**
|
||
* Step 9
|
||
* - Device: ED_1
|
||
* - Description (PIC-10.3): Harness instructs device to send ping to destination Eth_1 with its (default) OMR
|
||
* source address, to validate that route to AIL still works.
|
||
* - Pass Criteria:
|
||
* - Ping response received by ED_1.
|
||
*/
|
||
Ip6::Address eth1Addr = eth1.mInfraIf.FindMatchingAddress(kEth1Prefix);
|
||
ed1.SendEchoRequest(eth1Addr, kEchoIdentifier, kEchoPayloadSize);
|
||
nexus.AdvanceTime(kPingTime);
|
||
|
||
Log("Step 10: ED_1 pings internet address (should fail)");
|
||
/**
|
||
* Step 10
|
||
* - Device: ED_1
|
||
* - Description (PIC-10.3): Harness instructs device to send ping to destination address 2002:1234::1234 with
|
||
* its (default) OMR source address, to validate that default route does not work at the moment.
|
||
* - Pass Criteria:
|
||
* - Ping request MUST NOT be sent by DUT onto the AIL.
|
||
* - Ping response MUST NOT be received by ED_1.
|
||
*/
|
||
sDropInternetPings = true;
|
||
ed1.SendEchoRequest(internetServer, kEchoIdentifier, kEchoPayloadSize);
|
||
nexus.AdvanceTime(10 * 1000);
|
||
sDropInternetPings = false;
|
||
|
||
Log("Step 11: Eth_2 starts advertising a default route again");
|
||
/**
|
||
* Step 11
|
||
* - Device: Eth_2
|
||
* - Description (PIC-10.3): Harness instructs device to reconfigure ND RA daemon to start advertising a default
|
||
* route again: “Router Lifetime” field set to 875 sec (indicating it is a default router for IPv6).
|
||
* - Pass Criteria:
|
||
* - N/A
|
||
*/
|
||
eth2.mInfraIf.StartRouterAdvertisement(pioPrefix);
|
||
nexus.AdvanceTime(kBrActionTime);
|
||
|
||
Log("Step 12: BR_1 automatically advertises a default route (same as step 5)");
|
||
/**
|
||
* Step 12
|
||
* - Device: BR_1 (DUT)
|
||
* - Description (PIC-10.3): (As in step 5)
|
||
* - Pass Criteria:
|
||
* - (As in step 5)
|
||
*/
|
||
nexus.AdvanceTime(kBrActionTime);
|
||
|
||
Log("Step 13: ED_1 pings internet address (same as step 6)");
|
||
/**
|
||
* Step 13
|
||
* - Device: ED_1
|
||
* - Description (PIC-10.3): (As in step 6)
|
||
* - Pass Criteria:
|
||
* - (As in step 6)
|
||
*/
|
||
ed1.SendEchoRequest(internetServer, kEchoIdentifier, kEchoPayloadSize);
|
||
nexus.AdvanceTime(kPingTime);
|
||
|
||
Log("Step 14: ED_1 pings Eth_1 (same as step 9)");
|
||
/**
|
||
* Step 14
|
||
* - Device: ED_1
|
||
* - Description (PIC-10.3): (As in step 9)
|
||
* - Pass Criteria:
|
||
* - (As in step 9)
|
||
*/
|
||
ed1.SendEchoRequest(eth1Addr, kEchoIdentifier, kEchoPayloadSize);
|
||
nexus.AdvanceTime(kPingTime);
|
||
|
||
nexus.AddTestVar("ETH_1_ADDR", eth1Addr.ToString().AsCString());
|
||
nexus.AddTestVar("ETH_2_ADDR", eth2.mInfraIf.GetLinkLocalAddress().ToString().AsCString());
|
||
nexus.AddTestVar("INTERNET_SERVER_ADDR", kInternetServerAddr);
|
||
|
||
nexus.SaveTestInfo("test_1_4_PIC_TC_3.json");
|
||
}
|
||
|
||
} // namespace Nexus
|
||
} // namespace ot
|
||
|
||
int main(void)
|
||
{
|
||
ot::Nexus::Test_1_4_PIC_TC_3();
|
||
printf("All tests passed\n");
|
||
return 0;
|
||
}
|