mirror of
https://github.com/espressif/openthread.git
synced 2026-08-02 17:17:45 +00:00
[nexus] add DBR-TC-06 test and support for Router Advertisements (#12742)
This commit introduces the 1_3_DBR_TC_6 Nexus test case to verify bi-directional reachability in a multi-BR topology with existing IPv6 infrastructure. Key changes: - Implement tests/nexus/test_1_3_DBR_TC_6.cpp and its corresponding pcap-based verification script tests/nexus/verify_1_3_DBR_TC_6.py. - Enhance the Nexus platform InfraIf class to support constructing and sending ICMPv6 Router Advertisements (RA) with PIO and RIO. - Add RouterAdvertisementStart() and RouterAdvertisementStop() to InfraIf for managed periodic unsolicited RA transmissions. - Update Core::Process() to drive the periodic RA logic in InfraIf. - Implement response logic for ICMPv6 Router Solicitations in InfraIf when RA advertising is enabled. - The 1_3_DBR_TC_6 test validates that the DUT BR correctly adopts existing OMR prefixes, advertises an external default route (::/0), and sends appropriate RAs on the infrastructure link. - Register the new test case in tests/nexus/CMakeLists.txt and tests/nexus/run_nexus_tests.sh.
This commit is contained in:
@@ -257,6 +257,7 @@ ot_nexus_test(1_2_BBR_TC_3 "cert;nexus")
|
||||
ot_nexus_test(1_3_DBR_TC_1 "cert;nexus")
|
||||
ot_nexus_test(1_3_DBR_TC_2 "cert;nexus")
|
||||
ot_nexus_test(1_3_DBR_TC_3 "cert;nexus")
|
||||
ot_nexus_test(1_3_DBR_TC_6 "cert;nexus")
|
||||
|
||||
# Misc tests
|
||||
ot_nexus_test(border_admitter "core;nexus")
|
||||
|
||||
@@ -34,10 +34,12 @@
|
||||
namespace ot {
|
||||
namespace Nexus {
|
||||
|
||||
InfraIf::InfraIf(void)
|
||||
InfraIf::InfraIf(Instance &aInstance)
|
||||
: mNode(nullptr)
|
||||
, mNodeId(0)
|
||||
, mIfIndex(0)
|
||||
, mHasRioPrefix(false)
|
||||
, mRaTimer(aInstance)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -152,12 +154,69 @@ void InfraIf::SendIcmp6Nd(const Ip6::Address &aDestAddress, const uint8_t *aBuff
|
||||
mPendingTxQueue.Enqueue(*message);
|
||||
}
|
||||
|
||||
void InfraIf::SendRouterAdvertisement(const Ip6::Address &aDestination,
|
||||
const Ip6::Prefix *aPioPrefix,
|
||||
const Ip6::Prefix *aRioPrefix)
|
||||
{
|
||||
Ip6::Nd::RouterAdvert::TxMessage ra;
|
||||
Ip6::Nd::RouterAdvert::Header header;
|
||||
Ip6::Nd::Icmp6Packet packet;
|
||||
|
||||
header.SetToDefault();
|
||||
header.SetRouterLifetime(1800);
|
||||
SuccessOrQuit(ra.Append(header));
|
||||
|
||||
if (aPioPrefix != nullptr)
|
||||
{
|
||||
SuccessOrQuit(ra.AppendPrefixInfoOption(*aPioPrefix, 1800, 1800,
|
||||
Ip6::Nd::PrefixInfoOption::kOnLinkFlag |
|
||||
Ip6::Nd::PrefixInfoOption::kAutoConfigFlag));
|
||||
}
|
||||
|
||||
if (aRioPrefix != nullptr)
|
||||
{
|
||||
SuccessOrQuit(ra.AppendRouteInfoOption(*aRioPrefix, 1800, NetworkData::kRoutePreferenceMedium));
|
||||
}
|
||||
|
||||
ra.GetAsPacket(packet);
|
||||
|
||||
SendIcmp6Nd(aDestination, packet.GetBytes(), packet.GetLength());
|
||||
}
|
||||
|
||||
void InfraIf::StartRouterAdvertisement(const Ip6::Prefix &aPioPrefix, const Ip6::Prefix *aRioPrefix)
|
||||
{
|
||||
mPioPrefix = aPioPrefix;
|
||||
if (aRioPrefix != nullptr)
|
||||
{
|
||||
mRioPrefix = *aRioPrefix;
|
||||
mHasRioPrefix = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
mHasRioPrefix = false;
|
||||
}
|
||||
|
||||
// Trigger initial RA immediately
|
||||
SendPeriodicRouterAdvertisement();
|
||||
}
|
||||
|
||||
void InfraIf::StopRouterAdvertisement(void) { mRaTimer.Stop(); }
|
||||
|
||||
void InfraIf::HandleRaTimer(void) { SendPeriodicRouterAdvertisement(); }
|
||||
|
||||
void InfraIf::SendPeriodicRouterAdvertisement(void)
|
||||
{
|
||||
const uint32_t kRaInterval = 4000; // 4 seconds in milliseconds
|
||||
|
||||
SendRouterAdvertisement(Ip6::Address::GetLinkLocalAllNodesMulticast(), &mPioPrefix,
|
||||
mHasRioPrefix ? &mRioPrefix : nullptr);
|
||||
mRaTimer.Start(kRaInterval);
|
||||
}
|
||||
|
||||
void InfraIf::ProcessIcmp6Nd(const Ip6::Address &aSrcAddress, const uint8_t *aBuffer, uint16_t aBufferLength)
|
||||
{
|
||||
Ip6::Nd::Icmp6Packet packet;
|
||||
|
||||
OT_UNUSED_VARIABLE(aSrcAddress);
|
||||
|
||||
packet.Init(aBuffer, aBufferLength);
|
||||
|
||||
VerifyOrExit(packet.GetLength() >= sizeof(Ip6::Icmp::Header));
|
||||
@@ -181,6 +240,9 @@ void InfraIf::ProcessIcmp6Nd(const Ip6::Address &aSrcAddress, const uint8_t *aBu
|
||||
}
|
||||
|
||||
case Ip6::Icmp::Header::kTypeRouterSolicit:
|
||||
HandleRouterSolicitation(aSrcAddress);
|
||||
break;
|
||||
|
||||
case Ip6::Icmp::Header::kTypeNeighborAdvert:
|
||||
case Ip6::Icmp::Header::kTypeNeighborSolicit:
|
||||
// TODO: Handle other ND messages as needed for the simulation.
|
||||
@@ -237,6 +299,16 @@ exit:
|
||||
return;
|
||||
}
|
||||
|
||||
void InfraIf::HandleRouterSolicitation(const Ip6::Address &aSrcAddress)
|
||||
{
|
||||
if (mRaTimer.IsRunning())
|
||||
{
|
||||
const Ip6::Address &dest =
|
||||
aSrcAddress.IsUnspecified() ? Ip6::Address::GetLinkLocalAllNodesMulticast() : aSrcAddress;
|
||||
SendRouterAdvertisement(dest, &mPioPrefix, mHasRioPrefix ? &mRioPrefix : nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void InfraIf::SendIp6(const Ip6::Address &aSrcAddress,
|
||||
const Ip6::Address &aDestAddress,
|
||||
const uint8_t *aBuffer,
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
#ifndef OT_NEXUS_PLATFORM_NEXUS_INFRA_IF_HPP_
|
||||
#define OT_NEXUS_PLATFORM_NEXUS_INFRA_IF_HPP_
|
||||
|
||||
#include <openthread/platform/infra_if.h>
|
||||
#include "instance/instance.hpp"
|
||||
|
||||
namespace ot {
|
||||
@@ -42,7 +41,7 @@ class InfraIf
|
||||
public:
|
||||
typedef otPlatInfraIfLinkLayerAddress LinkLayerAddress;
|
||||
|
||||
InfraIf(void);
|
||||
explicit InfraIf(Instance &aInstance);
|
||||
|
||||
void Init(Node &aNode);
|
||||
|
||||
@@ -60,6 +59,11 @@ public:
|
||||
const Heap::Array<Ip6::Address> &GetAddresses(void) const { return mAddresses; }
|
||||
|
||||
void SendIcmp6Nd(const Ip6::Address &aDestAddress, const uint8_t *aBuffer, uint16_t aBufferLength);
|
||||
void SendRouterAdvertisement(const Ip6::Address &aDestination,
|
||||
const Ip6::Prefix *aPioPrefix,
|
||||
const Ip6::Prefix *aRioPrefix);
|
||||
void StartRouterAdvertisement(const Ip6::Prefix &aPioPrefix, const Ip6::Prefix *aRioPrefix = nullptr);
|
||||
void StopRouterAdvertisement(void);
|
||||
void SendIp6(const Ip6::Address &aSrcAddress,
|
||||
const Ip6::Address &aDestAddress,
|
||||
const uint8_t *aBuffer,
|
||||
@@ -94,15 +98,27 @@ public:
|
||||
|
||||
private:
|
||||
void ProcessIcmp6Nd(const Ip6::Address &aSrcAddress, const uint8_t *aBuffer, uint16_t aBufferLength);
|
||||
void SendPeriodicRouterAdvertisement(void);
|
||||
void HandlePrefixInfoOption(const Ip6::Nd::PrefixInfoOption &aPio);
|
||||
void HandleRouterSolicitation(const Ip6::Address &aSrcAddress);
|
||||
void HandleEchoRequest(const Ip6::Header &aHeader, Message &aMessage);
|
||||
void HandleEchoReply(const Ip6::Header &aHeader, Message &aMessage);
|
||||
|
||||
void HandleRaTimer(void);
|
||||
|
||||
Node *mNode;
|
||||
uint32_t mNodeId;
|
||||
uint32_t mIfIndex;
|
||||
Heap::Array<Ip6::Address> mAddresses;
|
||||
Callback<EchoReplyHandler> mEchoReplyCallback;
|
||||
|
||||
Ip6::Prefix mPioPrefix;
|
||||
Ip6::Prefix mRioPrefix;
|
||||
bool mHasRioPrefix;
|
||||
|
||||
using RaTimer = TimerMilliIn<InfraIf, &InfraIf::HandleRaTimer>;
|
||||
|
||||
RaTimer mRaTimer;
|
||||
};
|
||||
|
||||
} // namespace Nexus
|
||||
|
||||
@@ -58,8 +58,9 @@ public:
|
||||
bool mPendingTasklet;
|
||||
|
||||
protected:
|
||||
Platform(void)
|
||||
: mPendingTasklet(false)
|
||||
explicit Platform(Instance &aInstance)
|
||||
: mInfraIf(aInstance)
|
||||
, mPendingTasklet(false)
|
||||
{
|
||||
}
|
||||
};
|
||||
@@ -153,7 +154,10 @@ public:
|
||||
Node *mNext;
|
||||
|
||||
private:
|
||||
Node(void) {}
|
||||
Node(void)
|
||||
: Platform(static_cast<Instance &>(*this))
|
||||
{
|
||||
}
|
||||
|
||||
String<32> mName;
|
||||
};
|
||||
@@ -161,6 +165,9 @@ private:
|
||||
inline Node &AsNode(otInstance *aInstance) { return Node::From(aInstance); }
|
||||
|
||||
} // namespace Nexus
|
||||
|
||||
template <> inline Nexus::InfraIf &Instance::Get(void) { return static_cast<Nexus::Node *>(this)->mInfraIf; }
|
||||
|
||||
} // namespace ot
|
||||
|
||||
#endif // OT_NEXUS_PLATFORM_NEXUS_NODE_HPP_
|
||||
|
||||
@@ -193,6 +193,7 @@ DEFAULT_TESTS=(
|
||||
"1_3_DBR_TC_1"
|
||||
"1_3_DBR_TC_2"
|
||||
"1_3_DBR_TC_3"
|
||||
"1_3_DBR_TC_6"
|
||||
)
|
||||
|
||||
# Use provided arguments or the default test list
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
* 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 "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 (RA, Network Data), in milliseconds.
|
||||
*/
|
||||
static constexpr uint32_t kBrActionTime = 30 * 1000;
|
||||
|
||||
/**
|
||||
* Time to advance for the network to stabilize, in milliseconds.
|
||||
*/
|
||||
static constexpr uint32_t kStabilizationTime = 60 * 1000;
|
||||
|
||||
/**
|
||||
* Time to advance for the ping response, in milliseconds.
|
||||
*/
|
||||
static constexpr uint32_t kPingResponseTime = 5 * 1000;
|
||||
|
||||
/**
|
||||
* Infrastructure interface index.
|
||||
*/
|
||||
static constexpr uint32_t kInfraIfIndex = 1;
|
||||
|
||||
/**
|
||||
* Echo Request identifier.
|
||||
*/
|
||||
static constexpr uint16_t kEchoIdentifier = 0x1234;
|
||||
|
||||
/**
|
||||
* Echo Request payload size.
|
||||
*/
|
||||
static constexpr uint16_t kEchoPayloadSize = 10;
|
||||
|
||||
/**
|
||||
* IPv6 GUA address for Eth_1.
|
||||
*/
|
||||
static const char kEth1Gua[] = "2001:db8:1::1";
|
||||
|
||||
/**
|
||||
* IPv6 GUA prefix GUA_1.
|
||||
*/
|
||||
static const char kGua1Prefix[] = "2001:db8:1::/64";
|
||||
|
||||
void Test_1_3_DBR_TC_6(void)
|
||||
{
|
||||
/**
|
||||
* 1.6. [1.3] [CERT] Reachability - Multiple BRs - Single Thread - Single IPv6 Infrastructure
|
||||
*
|
||||
* 1.6.1. Purpose
|
||||
* To test the following:
|
||||
* - 1. Bi-directional reachability between single Thread Network and infrastructure devices
|
||||
* - 2. Multiple BRs
|
||||
* - 3. IPv6 infrastructure is already existing
|
||||
* - 4. DUT BR adopts existing OMR prefixes and doesn't advertise PIO
|
||||
*
|
||||
* 1.6.2. Topology
|
||||
* - 1. BR 1 (DUT) - Thread Border Router
|
||||
* - 2. BR 2-Test Bed border router device operating as a Thread Border Router and the Leader
|
||||
* - 3. ED 1-Test Bed device operating as a Thread End Device, attached to BR_1
|
||||
* - 4. Eth 1-Test Bed border router device on an Adjacent Infrastructure Link
|
||||
*
|
||||
* Spec Reference | V1.3.0 Section
|
||||
* ---------------|---------------
|
||||
* Reachability | 1.3
|
||||
*/
|
||||
|
||||
Core nexus;
|
||||
|
||||
Node &br1 = nexus.CreateNode();
|
||||
Node &br2 = nexus.CreateNode();
|
||||
Node &ed1 = nexus.CreateNode();
|
||||
Node ð1 = nexus.CreateNode();
|
||||
|
||||
br1.SetName("BR_1");
|
||||
br2.SetName("BR_2");
|
||||
ed1.SetName("ED_1");
|
||||
eth1.SetName("Eth_1");
|
||||
|
||||
nexus.AdvanceTime(0);
|
||||
|
||||
Instance::SetLogLevel(kLogLevelNote);
|
||||
|
||||
Log("---------------------------------------------------------------------------------------");
|
||||
Log("Step 0: Device: Eth 1 Description (DBR-1.6): Harness configures Ethernet link with an on-link IPv6 GUA prefix "
|
||||
"GUA 1. Eth 1 is configured to multicast ND RAS.");
|
||||
|
||||
eth1.mInfraIf.Init(eth1);
|
||||
|
||||
{
|
||||
Ip6::Address eth1Gua;
|
||||
SuccessOrQuit(eth1Gua.FromString(kEth1Gua));
|
||||
eth1.mInfraIf.AddAddress(eth1Gua);
|
||||
}
|
||||
|
||||
{
|
||||
Ip6::Prefix gua1;
|
||||
SuccessOrQuit(gua1.FromString(kGua1Prefix));
|
||||
eth1.mInfraIf.StartRouterAdvertisement(gua1);
|
||||
}
|
||||
|
||||
nexus.AdvanceTime(10 * 1000);
|
||||
|
||||
Log("---------------------------------------------------------------------------------------");
|
||||
Log("Step 1: Device: Eth 1, BR 2 Description (DBR-1.6): Form topology. Wait for BR_2 to: 1. Register as border "
|
||||
"router in Thread Network Data with an OMR prefix OMR_1 2. Send multicast ND RAS");
|
||||
|
||||
br2.mInfraIf.Init(br2);
|
||||
br2.Get<BorderRouter::InfraIf>().Init(kInfraIfIndex, true);
|
||||
|
||||
br2.Get<BorderRouter::RoutingManager>().Init();
|
||||
SuccessOrQuit(br2.Get<BorderRouter::RoutingManager>().SetEnabled(true));
|
||||
|
||||
br2.Form();
|
||||
nexus.AdvanceTime(kFormNetworkTime);
|
||||
|
||||
nexus.AdvanceTime(kBrActionTime);
|
||||
|
||||
Ip6::Prefix omr1;
|
||||
SuccessOrQuit(br2.Get<BorderRouter::RoutingManager>().GetOmrPrefix(omr1));
|
||||
|
||||
br2.mInfraIf.SendRouterAdvertisement(Ip6::Address::GetLinkLocalAllNodesMulticast(), nullptr, &omr1);
|
||||
nexus.AdvanceTime(kBrActionTime);
|
||||
|
||||
Log("---------------------------------------------------------------------------------------");
|
||||
Log("Step 2: Device: BR 1 (DUT) Description (DBR-1.6): Enable: switch on.");
|
||||
|
||||
br1.mInfraIf.Init(br1);
|
||||
br1.Get<BorderRouter::InfraIf>().Init(kInfraIfIndex, true);
|
||||
|
||||
br1.AllowList(br2);
|
||||
br2.AllowList(br1);
|
||||
|
||||
br1.Join(br2);
|
||||
nexus.AdvanceTime(kJoinNetworkTime);
|
||||
|
||||
br1.Get<BorderRouter::RoutingManager>().Init();
|
||||
SuccessOrQuit(br1.Get<BorderRouter::RoutingManager>().SetEnabled(true));
|
||||
|
||||
nexus.AdvanceTime(kStabilizationTime);
|
||||
|
||||
Log("---------------------------------------------------------------------------------------");
|
||||
Log("Step 2b: Device: ED 1 Description (DBR-1.6): Harness enables device.");
|
||||
|
||||
ed1.AllowList(br1);
|
||||
br1.AllowList(ed1);
|
||||
|
||||
ed1.Join(br1, Node::kAsFed);
|
||||
nexus.AdvanceTime(kJoinNetworkTime);
|
||||
|
||||
Log("---------------------------------------------------------------------------------------");
|
||||
Log("Step 3: Device: BR 1 (DUT) Description (DBR-1.6): Automatically registers itself as a border router in the "
|
||||
"Thread Network Data.");
|
||||
|
||||
nexus.AdvanceTime(kBrActionTime);
|
||||
|
||||
Log("---------------------------------------------------------------------------------------");
|
||||
Log("Step 4: Device: BR 1 (DUT) Description (DBR-1.6): Automatically multicasts ND RAs on Adjacent Infrastructure "
|
||||
"Link.");
|
||||
|
||||
Log("---------------------------------------------------------------------------------------");
|
||||
Log("Step 5: Device: Eth_1 Description (DBR-1.6): Harness instructs the device to send an ICMPv6 Echo Request to "
|
||||
"ED 1 via BR 1 or BR 2. 1. IPv6 Source: Eth 1 GUA 2. IPv6 Destination: ED_1 OMR");
|
||||
|
||||
const Ip6::Address &ed1Omr = ed1.FindMatchingAddress(omr1.ToString().AsCString());
|
||||
Ip6::Address eth1Gua;
|
||||
|
||||
SuccessOrQuit(eth1Gua.FromString(kEth1Gua));
|
||||
|
||||
eth1.mInfraIf.SendEchoRequest(eth1Gua, ed1Omr, kEchoIdentifier, kEchoPayloadSize);
|
||||
nexus.AdvanceTime(kPingResponseTime);
|
||||
|
||||
Log("---------------------------------------------------------------------------------------");
|
||||
Log("Step 6: Device: ED_1 Description (DBR-1.6): Harness instructs the device to send an ICMPv6 Echo Request to "
|
||||
"Eth_1. 1. IPv6 Source: ED 1 OMR 2. IPv6 Destination: Eth_1 GUA");
|
||||
|
||||
ed1.SendEchoRequest(eth1Gua, kEchoIdentifier, kEchoPayloadSize, 64, &ed1Omr);
|
||||
nexus.AdvanceTime(kPingResponseTime);
|
||||
|
||||
Log("---------------------------------------------------------------------------------------");
|
||||
Log("Step 7: Device: BR 2 Description (DBR-1.6): Harness disables the device.");
|
||||
|
||||
br2.Get<Mle::Mle>().Stop();
|
||||
nexus.AdvanceTime(10 * 1000);
|
||||
nexus.AdvanceTime(kBrActionTime);
|
||||
|
||||
Log("---------------------------------------------------------------------------------------");
|
||||
Log("Step 8: Device: BR 1 (DUT) Description (DBR-1.6): Repeat Step 4");
|
||||
|
||||
{
|
||||
Ip6::Nd::RouterSolicitHeader rs;
|
||||
Ip6::Nd::Icmp6Packet packet;
|
||||
|
||||
packet.Init(reinterpret_cast<const uint8_t *>(&rs), sizeof(rs));
|
||||
eth1.mInfraIf.SendIcmp6Nd(Ip6::Address::GetLinkLocalAllRoutersMulticast(), packet.GetBytes(),
|
||||
packet.GetLength());
|
||||
}
|
||||
nexus.AdvanceTime(kBrActionTime);
|
||||
|
||||
Log("---------------------------------------------------------------------------------------");
|
||||
Log("Step 9: Device: Eth 1 Description (DBR-1.6): Repeat Step 5");
|
||||
|
||||
eth1.mInfraIf.SendEchoRequest(eth1Gua, ed1Omr, kEchoIdentifier, kEchoPayloadSize);
|
||||
nexus.AdvanceTime(kPingResponseTime);
|
||||
|
||||
Log("---------------------------------------------------------------------------------------");
|
||||
Log("Step 10: Device: ED 1 Description (DBR-1.6): Repeat Step 6");
|
||||
|
||||
ed1.SendEchoRequest(eth1Gua, kEchoIdentifier, kEchoPayloadSize, 64, &ed1Omr);
|
||||
nexus.AdvanceTime(kPingResponseTime);
|
||||
|
||||
{
|
||||
char macStr[18];
|
||||
InfraIf::LinkLayerAddress addr;
|
||||
|
||||
br1.mInfraIf.GetLinkLayerAddress(addr);
|
||||
snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x", addr.mAddress[0], addr.mAddress[1],
|
||||
addr.mAddress[2], addr.mAddress[3], addr.mAddress[4], addr.mAddress[5]);
|
||||
nexus.AddTestVar("BR1_ETH", macStr);
|
||||
|
||||
eth1.mInfraIf.GetLinkLayerAddress(addr);
|
||||
snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x", addr.mAddress[0], addr.mAddress[1],
|
||||
addr.mAddress[2], addr.mAddress[3], addr.mAddress[4], addr.mAddress[5]);
|
||||
nexus.AddTestVar("ETH1_ETH", macStr);
|
||||
}
|
||||
|
||||
nexus.AddTestVar("BR1", br1.Get<Mac::Mac>().GetExtAddress().ToString().AsCString());
|
||||
nexus.AddTestVar("BR2", br2.Get<Mac::Mac>().GetExtAddress().ToString().AsCString());
|
||||
nexus.AddTestVar("ED1", ed1.Get<Mac::Mac>().GetExtAddress().ToString().AsCString());
|
||||
nexus.AddTestVar("ETH1_GUA", kEth1Gua);
|
||||
nexus.AddTestVar("ED1_OMR", ed1Omr.ToString().AsCString());
|
||||
nexus.AddTestVar("OMR_PREFIX", omr1.ToString().AsCString());
|
||||
|
||||
nexus.SaveTestInfo("test_1_3_DBR_TC_6.json");
|
||||
}
|
||||
|
||||
} // namespace Nexus
|
||||
} // namespace ot
|
||||
|
||||
int main(void)
|
||||
{
|
||||
ot::Nexus::Test_1_3_DBR_TC_6();
|
||||
printf("All tests passed\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add the current directory to sys.path to find verify_utils
|
||||
CUR_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.append(CUR_DIR)
|
||||
|
||||
import verify_utils
|
||||
from pktverify import consts
|
||||
from pktverify.addrs import Ipv6Addr
|
||||
|
||||
|
||||
def verify(pv):
|
||||
# 1.6. [1.3] [CERT] Reachability - Multiple BRs - Single Thread - Single IPv6 Infrastructure
|
||||
#
|
||||
# 1.6.1. Purpose
|
||||
# To test the following:
|
||||
# - 1. Bi-directional reachability between single Thread Network and infrastructure devices
|
||||
# - 2. Multiple BRs
|
||||
# - 3. IPv6 infrastructure is already existing
|
||||
# - 4. DUT BR adopts existing OMR prefixes and doesn't advertise PIO
|
||||
#
|
||||
# 1.6.2. Topology
|
||||
# - 1. BR 1 (DUT) - Thread Border Router
|
||||
# - 2. BR 2-Test Bed border router device operating as a Thread Border Router and the Leader
|
||||
# - 3. ED 1-Test Bed device operating as a Thread End Device, attached to BR_1
|
||||
# - 4. Eth 1-Test Bed border router device on an Adjacent Infrastructure Link
|
||||
#
|
||||
# Spec Reference | V1.3.0 Section
|
||||
# ---------------|---------------
|
||||
# Reachability | 1.3
|
||||
|
||||
pkts = pv.pkts
|
||||
pv.summary.show()
|
||||
|
||||
BR1 = pv.vars['BR1']
|
||||
ED1 = pv.vars['ED1']
|
||||
OMR_PREFIX = Ipv6Addr(pv.vars['OMR_PREFIX'].split('/')[0])
|
||||
ETH1_GUA = Ipv6Addr(pv.vars['ETH1_GUA'])
|
||||
ED1_OMR = Ipv6Addr(pv.vars['ED1_OMR'])
|
||||
|
||||
# Step 0
|
||||
# Device: Eth 1
|
||||
# Description (DBR-1.6): Harness configures Ethernet link with an on-link IPv6 GUA prefix GUA 1.
|
||||
# Eth 1 is configured to multicast ND RAS.
|
||||
# Pass Criteria:
|
||||
# N/A
|
||||
print("Step 0: Harness configures Ethernet link with an on-link IPv6 GUA prefix GUA 1.")
|
||||
|
||||
# Step 1
|
||||
# Device: Eth 1, BR 2
|
||||
# Description (DBR-1.6): Form topology. Wait for BR_2 to: 1. Register as border router in Thread
|
||||
# Network Data with an OMR prefix OMR_1 2. Send multicast ND RAS
|
||||
# Pass Criteria:
|
||||
# N/A
|
||||
print("Step 1: Form topology. Wait for BR_2 to register as border router and send ND RAs.")
|
||||
|
||||
# Step 2
|
||||
# Device: BR 1 (DUT)
|
||||
# Description (DBR-1.6): Enable: switch on.
|
||||
# Pass Criteria:
|
||||
# N/A
|
||||
print("Step 2: Enable: switch on.")
|
||||
|
||||
# Step 3
|
||||
# Device: BR 1 (DUT)
|
||||
# Description (DBR-1.6): Automatically registers itself as a border router in the Thread Network Data.
|
||||
# Pass Criteria:
|
||||
# - The DUT MUST NOT register a new OMR Prefix in the Thread Network Data.
|
||||
# - The DUT MUST advertise an external route in the Thread Network Data as follows:
|
||||
# - Prefix: ::/0 (zero-length prefix)
|
||||
# - Has Route TLV
|
||||
# - Prf 'Medium' (00) or 'Low' (11)
|
||||
print("Step 3: BR 1 (DUT) MUST NOT register a new OMR Prefix. MUST advertise an external route ::/0.")
|
||||
|
||||
def check_step3_nwd(p):
|
||||
if not (hasattr(p, 'mle') and p.mle.cmd == consts.MLE_DATA_RESPONSE):
|
||||
return False
|
||||
|
||||
prefixes = verify_utils.as_list(p.thread_nwd.tlv.prefix)
|
||||
# Check for ::/0
|
||||
if Ipv6Addr('::') not in prefixes:
|
||||
return False
|
||||
|
||||
# Check that ONLY ONE OMR prefix exists (the one from BR2)
|
||||
omr_prefixes = [pref for pref in prefixes if pref != Ipv6Addr('::')]
|
||||
if len(omr_prefixes) != 1:
|
||||
return False
|
||||
|
||||
# Check Preference of ::/0 (Has Route TLV)
|
||||
try:
|
||||
# Find the index of :: prefix
|
||||
idx = prefixes.index(Ipv6Addr('::'))
|
||||
# Preference should be Medium (0) or Low (3 in some dissectors, or 11 binary)
|
||||
pref = verify_utils.as_list(p.thread_nwd.tlv.has_route.pref)[idx]
|
||||
if pref not in (0, 3):
|
||||
return False
|
||||
except (AttributeError, IndexError):
|
||||
pass
|
||||
|
||||
return True
|
||||
|
||||
pkts.filter_wpan_src64(BR1).\
|
||||
filter(check_step3_nwd).\
|
||||
must_next()
|
||||
|
||||
# Step 2b
|
||||
# Device: ED 1
|
||||
# Description (DBR-1.6): Harness enables device.
|
||||
# Pass Criteria:
|
||||
# - ED_1 successfully attaches to the DUT as its Parent.
|
||||
print("Step 2b: ED_1 successfully attaches to the DUT as its Parent.")
|
||||
pkts.filter_wpan_src64(ED1).\
|
||||
filter(lambda p: hasattr(p, 'mle') and p.mle.cmd == consts.MLE_PARENT_REQUEST).\
|
||||
must_next()
|
||||
pkts.filter_wpan_src64(BR1).\
|
||||
filter(lambda p: hasattr(p, 'mle') and p.mle.cmd == consts.MLE_PARENT_RESPONSE).\
|
||||
must_next()
|
||||
pkts.filter_wpan_src64(ED1).\
|
||||
filter(lambda p: hasattr(p, 'mle') and p.mle.cmd == consts.MLE_CHILD_ID_REQUEST).\
|
||||
must_next()
|
||||
pkts.filter_wpan_src64(BR1).\
|
||||
filter(lambda p: hasattr(p, 'mle') and p.mle.cmd == consts.MLE_CHILD_ID_RESPONSE).\
|
||||
must_next()
|
||||
|
||||
# Step 4
|
||||
# Device: BR 1 (DUT)
|
||||
# Description (DBR-1.6): Automatically multicasts ND RAs on Adjacent Infrastructure Link.
|
||||
# Pass Criteria:
|
||||
# - The DUT MUST multicast ND RAS:
|
||||
# - IPv6 destination MUST be ff02::1
|
||||
# - MUST NOT contain a Prefix Information Option (PIO).
|
||||
# - MUST contain a Route Information Option (RIO) with the OMR prefix OMR 1.
|
||||
print("Step 4: BR 1 (DUT) MUST multicast ND RAs: ff02::1, NO PIO, RIO with OMR_1.")
|
||||
pkts.filter_eth_src(pv.vars['BR1_ETH']).\
|
||||
filter_ipv6_dst("ff02::1").\
|
||||
filter(lambda p: p.icmpv6.type == verify_utils.ICMPV6_TYPE_ROUTER_ADVERTISEMENT).\
|
||||
filter(lambda p: OMR_PREFIX in verify_utils.get_ra_prefixes(p)[0]).\
|
||||
filter(lambda p: len(verify_utils.get_ra_prefixes(p)[1]) == 0).\
|
||||
must_next()
|
||||
|
||||
# Step 5
|
||||
# Device: Eth_1
|
||||
# Description (DBR-1.6): Harness instructs the device to send an ICMPv6 Echo Request to ED 1 via BR 1 or
|
||||
# BR 2. 1. IPv6 Source: Eth 1 GUA 2. IPv6 Destination: ED_1 OMR
|
||||
# Pass Criteria:
|
||||
# - Eth_1 receives an ICMPv6 Echo Reply from ED_1.
|
||||
# - IPv6 Source: ED_1 OMR
|
||||
# - IPv6 Destination: Eth 1 GUA
|
||||
print("Step 5: Eth_1 pings ED_1 OMR.")
|
||||
_pkt = pkts.filter_ipv6_src(ETH1_GUA).\
|
||||
filter_ipv6_dst(ED1_OMR).\
|
||||
filter_ping_request().\
|
||||
must_next()
|
||||
|
||||
pkts.filter_ipv6_src(ED1_OMR).\
|
||||
filter_ipv6_dst(ETH1_GUA).\
|
||||
filter_ping_reply(identifier=_pkt.icmpv6.echo.identifier).\
|
||||
must_next()
|
||||
|
||||
# Step 6
|
||||
# Device: ED_1
|
||||
# Description (DBR-1.6): Harness instructs the device to send an ICMPv6 Echo Request to Eth_1.
|
||||
# 1. IPv6 Source: ED 1 OMR 2. IPv6 Destination: Eth_1 GUA
|
||||
# Pass Criteria:
|
||||
# - ED_1 receives an ICMPv6 Echo Reply from Eth_1.
|
||||
# - IPv6 Source: Eth_1 GUA
|
||||
# - IPv6 Destination: ED 1 OMR
|
||||
print("Step 6: ED_1 pings Eth_1 GUA.")
|
||||
_pkt = pkts.filter_ipv6_src(ED1_OMR).\
|
||||
filter_ipv6_dst(ETH1_GUA).\
|
||||
filter_ping_request().\
|
||||
must_next()
|
||||
|
||||
pkts.filter_ipv6_src(ETH1_GUA).\
|
||||
filter_ipv6_dst(ED1_OMR).\
|
||||
filter_ping_reply(identifier=_pkt.icmpv6.echo.identifier).\
|
||||
must_next()
|
||||
|
||||
# Step 7
|
||||
# Device: BR 2
|
||||
# Description (DBR-1.6): Harness disables the device. Note: automatically, the network data of BR_2
|
||||
# including the prefix OMR 1 data will remain active for the remainder of this test procedure.
|
||||
# Pass Criteria:
|
||||
# N/A
|
||||
print("Step 7: Harness disables BR 2.")
|
||||
|
||||
# Step 8
|
||||
# Device: BR 1 (DUT)
|
||||
# Description (DBR-1.6): Repeat Step 4
|
||||
# Pass Criteria:
|
||||
# - Repeat Step 4
|
||||
print("Step 8: Repeat Step 4")
|
||||
pkts.filter_eth_src(pv.vars['BR1_ETH']).\
|
||||
filter_ipv6_dst("ff02::1").\
|
||||
filter(lambda p: p.icmpv6.type == verify_utils.ICMPV6_TYPE_ROUTER_ADVERTISEMENT).\
|
||||
filter(lambda p: OMR_PREFIX in verify_utils.get_ra_prefixes(p)[0]).\
|
||||
filter(lambda p: len(verify_utils.get_ra_prefixes(p)[1]) == 0).\
|
||||
must_next()
|
||||
|
||||
# Step 9
|
||||
# Device: Eth 1
|
||||
# Description (DBR-1.6): Repeat Step 5
|
||||
# Pass Criteria:
|
||||
# - Repeat Step 5
|
||||
print("Step 9: Repeat Step 5")
|
||||
_pkt = pkts.filter_ipv6_src(ETH1_GUA).\
|
||||
filter_ipv6_dst(ED1_OMR).\
|
||||
filter_ping_request().\
|
||||
must_next()
|
||||
|
||||
pkts.filter_ipv6_src(ED1_OMR).\
|
||||
filter_ipv6_dst(ETH1_GUA).\
|
||||
filter_ping_reply(identifier=_pkt.icmpv6.echo.identifier).\
|
||||
must_next()
|
||||
|
||||
# Step 10
|
||||
# Device: ED 1
|
||||
# Description (DBR-1.6): Repeat Step 6
|
||||
# Pass Criteria:
|
||||
# - Repeat Step 6
|
||||
print("Step 10: Repeat Step 6")
|
||||
_pkt = pkts.filter_ipv6_src(ED1_OMR).\
|
||||
filter_ipv6_dst(ETH1_GUA).\
|
||||
filter_ping_request().\
|
||||
must_next()
|
||||
|
||||
pkts.filter_ipv6_src(ETH1_GUA).\
|
||||
filter_ipv6_dst(ED1_OMR).\
|
||||
filter_ping_reply(identifier=_pkt.icmpv6.echo.identifier).\
|
||||
must_next()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
verify_utils.run_main(verify)
|
||||
Reference in New Issue
Block a user