diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt index 15559fc4e..6505c52a4 100644 --- a/tests/nexus/CMakeLists.txt +++ b/tests/nexus/CMakeLists.txt @@ -261,6 +261,7 @@ ot_nexus_test(1_3_DBR_TC_6 "cert;nexus") ot_nexus_test(1_3_DBR_TC_7A "cert;nexus") ot_nexus_test(1_3_DBR_TC_7B "cert;nexus") ot_nexus_test(1_3_DBR_TC_7C "cert;nexus") +ot_nexus_test(1_3_DBR_TC_8 "cert;nexus") # Misc tests ot_nexus_test(border_admitter "core;nexus") diff --git a/tests/nexus/run_nexus_tests.sh b/tests/nexus/run_nexus_tests.sh index 9295a70e9..f299231c4 100755 --- a/tests/nexus/run_nexus_tests.sh +++ b/tests/nexus/run_nexus_tests.sh @@ -197,6 +197,7 @@ DEFAULT_TESTS=( "1_3_DBR_TC_7A" "1_3_DBR_TC_7B" "1_3_DBR_TC_7C" + "1_3_DBR_TC_8" ) # Use provided arguments or the default test list diff --git a/tests/nexus/test_1_3_DBR_TC_8.cpp b/tests/nexus/test_1_3_DBR_TC_8.cpp new file mode 100644 index 000000000..807ad9f43 --- /dev/null +++ b/tests/nexus/test_1_3_DBR_TC_8.cpp @@ -0,0 +1,942 @@ +/* + * 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 { + +/** + * Time to advance for a node to form a network and become leader, in milliseconds. + */ +static constexpr uint32_t kFormNetworkTime = 20 * 1000; + +/** + * Time to advance for a node to join as a child and upgrade to a router, in milliseconds. + */ +static constexpr uint32_t kJoinNetworkTime = 300 * 1000; + +/** + * Time to advance for the BR to perform automatic actions (RA, Network Data), in milliseconds. + */ +static constexpr uint32_t kBrActionTime = 100 * 1000; + +/** + * Longer time to advance for network stabilization and address configuration. + */ +static constexpr uint32_t kLongActionTime = 500 * 1000; + +/** + * Time to advance for the ping response, in milliseconds. + */ +static constexpr uint32_t kPingResponseTime = 100 * 1000; + +/** + * Default Hop Limit for Echo Request. + */ +static constexpr uint8_t kDefaultHopLimit = 64; + +/** + * 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; + +/** + * Max number of seconds to wait for a condition. + */ +static constexpr uint32_t kMaxWaitTime = 120; + +/** + * OMR prefix 3 string (numerically very low to be "winning"). + */ +static constexpr char kOmr3PrefixStr[] = "fd00:3::/64"; + +/** + * OMR prefix 4 string (numerically very high to be "losing" if same preference). + */ +static constexpr char kOmr4PrefixStr[] = "fdff:4::/64"; + +/** + * Infrastructure GUA address string for Eth_1. + */ +static constexpr char kEth1GuaAddrStr[] = "2001:db8:1::1"; + +/** + * Infrastructure GUA prefix string. + */ +static constexpr char kGua1PrefixStr[] = "2001:db8:1::/64"; + +static void DumpNetworkData(Node &aNode) +{ + NetworkData::Iterator iterator = NetworkData::kIteratorInit; + NetworkData::OnMeshPrefixConfig prefixConfig; + NetworkData::ExternalRouteConfig routeConfig; + + Log("Network Data for %s (Version: %d):", aNode.GetName(), + aNode.Get().GetVersion(NetworkData::kFullSet)); + while (aNode.Get().GetNext(iterator, prefixConfig) == kErrorNone) + { + Log(" Prefix: %s (pref:%d, preferred:%s)", prefixConfig.GetPrefix().ToString().AsCString(), + prefixConfig.mPreference, ToYesNo(prefixConfig.mPreferred)); + } + + iterator = NetworkData::kIteratorInit; + while (aNode.Get().GetNext(iterator, routeConfig) == kErrorNone) + { + Log(" Route: %s (pref:%d)", routeConfig.GetPrefix().ToString().AsCString(), routeConfig.mPreference); + } +} + +static bool HasNetDataPrefix(Node &aNode, const Ip6::Prefix &aPrefix) +{ + return aNode.Get().ContainsOmrPrefix(aPrefix); +} + +static bool HasNetDataRoute(Node &aNode, const char *aPrefixStr) +{ + bool hasRoute = false; + NetworkData::Iterator iterator = NetworkData::kIteratorInit; + NetworkData::ExternalRouteConfig routeConfig; + Ip6::Prefix target; + + SuccessOrQuit(target.FromString(aPrefixStr)); + + while (aNode.Get().GetNext(iterator, routeConfig) == kErrorNone) + { + if (AsCoreType(&routeConfig.mPrefix) == target) + { + hasRoute = true; + break; + } + } + + return hasRoute; +} + +static void WaitForPrefix(Core &aNexus, Node &aNode, const Ip6::Prefix &aPrefix, bool aPresent) +{ + for (uint32_t i = 0; i < kMaxWaitTime; i++) + { + if (HasNetDataPrefix(aNode, aPrefix) == aPresent) + { + break; + } + aNexus.AdvanceTime(1000); + } + VerifyOrQuit(HasNetDataPrefix(aNode, aPrefix) == aPresent); +} + +static void WaitForRoute(Core &aNexus, Node &aNode, const char *aPrefixStr, bool aPresent) +{ + for (uint32_t i = 0; i < kMaxWaitTime; i++) + { + if (HasNetDataRoute(aNode, aPrefixStr) == aPresent) + { + break; + } + aNexus.AdvanceTime(1000); + } + VerifyOrQuit(HasNetDataRoute(aNode, aPrefixStr) == aPresent); +} + +static void PublishOmrPrefix(Node &aNode, + const Ip6::Prefix &aPrefix, + NetworkData::RoutePreference aPreference, + bool aIsPreferred) +{ + NetworkData::OnMeshPrefixConfig config; + config.Clear(); + config.GetPrefix() = aPrefix; + config.mPreference = aPreference; + config.mSlaac = true; + config.mOnMesh = true; + config.mStable = true; + config.mPreferred = aIsPreferred; + SuccessOrQuit(aNode.Get().PublishOnMeshPrefix(config, NetworkData::Publisher::kFromUser)); +} + +void Test_1_3_DBR_TC_8(void) +{ + /** + * 1.8. [1.3] [CERT] Reachability - Multiple BRs - Single Thread / Single IPv6 Infrastructure - OMR prefix selection + * + * 1.8.1. Purpose + * - To test the following: + * - Maintain bi-directional reachability between Thread devices and infrastructure devices during prefix changes + * - DUT BR should observe proper OMR prefix selection when testbed-BR advertises various other prefixes: + * - Testbed advertises a winning prefix with equal preference, DUT should withdraw or deprecate its OMR prefix + * - Testbed advertises a numerically higher, non-winning prefix with equal preference, DUT should not change + * behavior. + * - Testbed advertises a numerically higher prefix with higher preference, i.e. winning OMR prefix, DUT should + * withdraw its OMR prefix. + * - Testbed withdraws winning OMR prefix, DUT should advertise its prefix with 'Low' preference again + * - Testbed OMR prefix becomes deprecated, DUT should advertise its prefix with 'Low' preference again + * + * 1.8.2. Topology + * - Eth_1 - Adjacent Infrastructure Link Reference Device + * - BR_1 (DUT) - Border Router + * - BR_2 - Border Router Reference Device and Leader + * - ED_1 - Thread Reference Device (End Device) attached to BR_1 + * + * Spec Reference | V1.1 Section | V1.3.0 Section + * -----------------|--------------|--------------- + * Reachability | N/A | 1.3 + */ + + Core nexus; + + Node ð1 = nexus.CreateNode(); + Node &br1 = nexus.CreateNode(); + Node &br2 = nexus.CreateNode(); + Node &ed1 = nexus.CreateNode(); + + eth1.SetName("Eth_1"); + br1.SetName("BR_1"); + br2.SetName("BR_2"); + ed1.SetName("ED_1"); + + nexus.AdvanceTime(0); + + Instance::SetLogLevel(kLogLevelNote); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 0 + * - Device: Eth_1 + * - Description (DBR-1.8): + * - Harness configures Ethernet link with an on-link IPv6 GUA prefix GUA_1. Eth_1 is configured to multicast ND + * RAs. + * - Automatically configures a global address “Eth_1 GUA”. + * - Pass Criteria + * - N/A + */ + Log("Step 0: Eth_1 configured with GUA_1."); + + eth1.mInfraIf.Init(eth1); + Ip6::Address eth1Gua; + SuccessOrQuit(eth1Gua.FromString(kEth1GuaAddrStr)); + eth1.mInfraIf.AddAddress(eth1Gua); + + Ip6::Prefix gua1; + SuccessOrQuit(gua1.FromString(kGua1PrefixStr)); + eth1.mInfraIf.StartRouterAdvertisement(gua1); + + nexus.AddTestVar("ETH_1_GUA_ADDR", kEth1GuaAddrStr); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 1 + * - Device: Eth_1, BR_2, ED_1, ED_2 + * - Description (DBR-1.8): + * - Enable; BR_2 automatically configures an OMR prefix for the mesh, OMR_1. Harness records the value of OMR_1 + * and saves it for verification use. + * - Note: the OT command ‘br omrprefix’ should provide the prefix from BR_2. + * - Form topology. Wait for BR_2 to: + * - Become Leader + * - Register as border router in Thread Network Data with its OMR prefix OMR_1 + * - Send multicast ND RAs on AIL + * - Automatically advertise a route to GUA_1 in the Thread Network Data, using a Prefix TLV: + * - Prefix = ::/0 + * - Domain ID = + * - Has Route sub-TLV + * - Pass Criteria + * - N/A + */ + Log("Step 1: BR_2 becomes Leader and registers OMR_1."); + + br1.AllowList(br2); + br1.AllowList(ed1); + br2.AllowList(br1); + ed1.AllowList(br1); + + br2.Form(); + nexus.AdvanceTime(kFormNetworkTime); + + br2.Get().Init(kInfraIfIndex, true); + br2.Get().Init(); + SuccessOrQuit(br2.Get().SetEnabled(true)); + + nexus.AdvanceTime(kBrActionTime); + WaitForRoute(nexus, br2, "::/0", true); + + Ip6::Prefix omr1; + SuccessOrQuit(br2.Get().GetOmrPrefix(omr1)); + nexus.AddTestVar("OMR_1_PREFIX", omr1.ToString().AsCString()); + Log("OMR_1_PREFIX: %s", omr1.ToString().AsCString()); + DumpNetworkData(br2); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 2 + * - Device: BR_1 (DUT) + * - Description (DBR-1.8): Enable: switch on. + * - Pass Criteria + * - N/A + */ + Log("Step 2: Enable BR_1 (DUT)."); + + br1.Join(br2); + nexus.AdvanceTime(kJoinNetworkTime); + + br1.Get().Init(kInfraIfIndex, true); + br1.Get().Init(); + SuccessOrQuit(br1.Get().SetEnabled(true)); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 3 + * - Device: BR_1 (DUT) + * - Description (DBR-1.8): + * - Automatically registers itself as a border router in the Thread Network Data. + * - Pass Criteria: + * - The DUT MUST register a route to GUA_1 in the Thread Network Data as follows: + * - Prefix TLV: Prefix = ::/0 + * - Domain ID = + * - Has Route sub-TLV + * - R_preference = 00 (medium) or 11 (low) + * - DUT MUST NOT register an OMR prefix (e.g. OMR_1, or other) in Thread Network Data. + */ + Log("Step 3: BR_1 (DUT) registers route to GUA_1."); + + nexus.AdvanceTime(kBrActionTime); + DumpNetworkData(br2); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 4 + * - Device: BR_1 (DUT) + * - Description (DBR-1.8): + * - Automatically announces the route to OMR_1 on the AIL. + * - Pass Criteria: + * - The DUT MUST multicast ND RAs on the infrastructure link: + * - IPv6 destination MUST be ff02::1 + * - MUST NOT contain a Prefix Information Option (PIO) with a ULA prefix. + * - MUST contain a Route Information Option (RIO) with OMR_1. + * - "Prf" bits MUST be 00 (medium) or 11 (low) + */ + Log("Step 4: BR_1 (DUT) announces OMR_1 on AIL."); + + nexus.AdvanceTime(kBrActionTime); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 5 + * - Device: BR_2 + * - Description (DBR-1.8): + * - Harness instructs device to remove the existing OMR prefix (OMR_1) being advertised and send out new network + * data without any prefix. + * - Note: for 1.3.x cert, this could be implemented by 'br disable' or 'netdata unpublish ' on BR_2. + * - Pass Criteria: + * - N/A + */ + Log("Step 5: BR_2 removes OMR_1."); + + SuccessOrQuit(br2.Get().SetEnabled(false)); + WaitForPrefix(nexus, br2, omr1, false); + + nexus.AdvanceTime(kBrActionTime); + DumpNetworkData(br2); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 6 + * - Device: BR_1 (DUT) + * - Description (DBR-1.8): + * - Automatically chooses a new OMR prefix OMR_2 and includes it in the Network Data. + * - Pass Criteria: + * - The DUT MUST register a new OMR prefix (OMR_2) in the Thread Network Data using Prefix TLV as follows: + * - Prefix = OMR_2 + * - Border Router sub-TLV + * - P_preference = 11 (Low) + * - P_default = true + * - P_stable = true + * - P_on_mesh = true + * - P_preferred = true + * - P_slaac = true + * - P_dhcp = false + * - P_dp = false + * - OMR_2 MUST be 64 bits long and start with 0xFD. + * - OMR_2 MUST differ from OMR_1. + * - Also MUST contain a Prefix TLV as follows: + * - Prefix = ::/0 + * - Domain ID + * - Has Route TLV + * - R_preference = 00 (medium) or 11 (low) + * - Note: one possible way to check this is to verify the contents of the SVR_DATA.ntf message sent by DUT to + * Leader. + */ + Log("Step 6: BR_1 (DUT) chooses OMR_2 and registers in Network Data."); + + nexus.AdvanceTime(kBrActionTime); + + Ip6::Prefix omr2; + SuccessOrQuit(br1.Get().GetOmrPrefix(omr2)); + VerifyOrQuit(omr2 != omr1); + nexus.AddTestVar("OMR_2_PREFIX", omr2.ToString().AsCString()); + Log("OMR_2_PREFIX: %s", omr2.ToString().AsCString()); + DumpNetworkData(br2); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 7 + * - Device: BR_1 (DUT) + * - Description (DBR-1.8): + * - Automatically multicasts ND RAs on Adjacent Infrastructure Link. + * - Pass Criteria: + * - The DUT MUST multicast ND RAs on the infrastructure link: + * - IPv6 destination MUST be ff02::1 + * - MUST NOT contain a Prefix Information Option (PIO) with a ULA prefix. + * - MUST contain a Route Information Option (RIO) with OMR_2. + * - "Prf" bits MUST be 00 (medium) or 11 (low). + * - MUST NOT contain a Route Information Option (RIO) with OMR_1. + */ + Log("Step 7: BR_1 (DUT) multicasts ND RA with OMR_2, no OMR_1."); + + nexus.AdvanceTime(kBrActionTime); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 8 + * - Device: Eth_1 + * - Description (DBR-1.8): + * - Harness instructs the device to send an ICMPv6 Echo Request to ED_1 via either one of BR_1 or BR_2. + * - IPv6 Source: Eth_1 GUA + * - IPv6 Destination: ED_1 OMR address (using prefix OMR_2) + * - Pass Criteria: + * - Eth_1 receives an ICMPv6 Echo Reply from ED_1. + * - IPv6 Source: ED_1 OMR (using prefix OMR_2) + * - IPv6 Destination: Eth_1 GUA + */ + Log("Step 8: Eth_1 pings ED_1 OMR_2."); + + ed1.Join(br1, Node::kAsFed); + nexus.AdvanceTime(kJoinNetworkTime); + const Ip6::Address &ed1Omr2 = ed1.FindMatchingAddress(omr2.ToString().AsCString()); + nexus.AddTestVar("ED_1_OMR_2_ADDR", ed1Omr2.ToString().AsCString()); + + eth1.mInfraIf.SendEchoRequest(eth1Gua, ed1Omr2, kEchoIdentifier, kEchoPayloadSize); + nexus.AdvanceTime(kPingResponseTime); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 9 + * - Device: ED_1 + * - Description (DBR-1.8): + * - Harness instructs the device to send an ICMPv6 Echo Request to Eth_1. + * - IPv6 Source: ED_1 OMR address (using prefix 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 address (using prefix OMR_2) + */ + Log("Step 9: ED_1 OMR_2 pings Eth_1 GUA."); + + ed1.SendEchoRequest(eth1Gua, kEchoIdentifier, kEchoPayloadSize, kDefaultHopLimit, &ed1Omr2); + nexus.AdvanceTime(kPingResponseTime); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 10 + * - Device: BR_2 + * - Description (DBR-1.8): + * - Harness instructs BR_2 to add an OMR prefix OMR_3 numerically lower than OMR_2, but with same preference + * "Low". + * - Note: can use OT command 'netdata publish prefix' for this. + * - Pass Criteria: + * - N/A + */ + Log("Step 10: BR_2 adds OMR_3."); + + Ip6::Prefix omr3; + SuccessOrQuit(omr3.FromString(kOmr3PrefixStr)); + + PublishOmrPrefix(br2, omr3, NetworkData::kRoutePreferenceLow, true); + + nexus.AdvanceTime(kLongActionTime); + + WaitForPrefix(nexus, br2, omr3, true); + DumpNetworkData(br2); + + nexus.AddTestVar("OMR_3_PREFIX", kOmr3PrefixStr); + Log("OMR_3_PREFIX: %s", kOmr3PrefixStr); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 11 + * - Device: BR_1 (DUT) + * - Description (DBR-1.8): + * - Automatically withdraws its own OMR prefix OMR_2 from the Thread Network Data. + * - Pass Criteria: + * - The DUT MUST register new Network Data: + * - MUST NOT contain any of the OMR prefixes OMR_1, OMR_2, or OMR_3 in a Prefix TLV. + * - MUST contain a Prefix TLV: + * - Prefix = ::/0 + * - Domain ID + * - Has Route TLV + * - R_preference = 00 (medium) or 11 (low) + */ + Log("Step 11: BR_1 (DUT) withdraws OMR_2 from Network Data."); + + WaitForPrefix(nexus, br2, omr2, false); + nexus.AdvanceTime(kLongActionTime); + DumpNetworkData(br2); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 12 + * - Device: BR_1 (DUT) + * - Description (DBR-1.8): + * - Automatically multicasts ND RAs on Adjacent Infrastructure Link including OMR_2/OMR_3. + * - Note: the reason that OMR_2 is still included initially, is that OMR_2 has been created by BR_1 originally + * and as seen by BR_1, it stays in a deprecated state for at most OMR_ADDR_DEPRECATION_TIME + * - Pass Criteria: + * - The DUT MUST multicast ND RAs on the infrastructure link: + * - IPv6 destination MUST be ff02::1 + * - MUST NOT contain a Prefix Information Option (PIO) with a ULA prefix. + * - MUST NOT contain a Route Information Option (RIO) with OMR_1. + * - MUST contain a Route Information Option (RIO) with OMR_2. + * - "Prf" bits MUST be 00 (medium) or 11 (low) + * - MUST contain a Route Information Option (RIO) with OMR_3. + * - "Prf" bits MUST be 00 (medium) or 11 (low) + */ + Log("Step 12: BR_1 (DUT) multicasts ND RA with OMR_2 and OMR_3."); + + nexus.AdvanceTime(kBrActionTime); + DumpNetworkData(br1); + DumpNetworkData(br2); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 13 + * - Device: Eth_1 + * - Description (DBR-1.8): + * - Harness instructs the device to send an ICMPv6 Echo Request to ED_1 via BR_1 or BR_2. + * - IPv6 Source: Eth_1 GUA + * - IPv6 Destination: ED_1 OMR address (using prefix OMR_3) + * - Pass Criteria: + * - Eth_1 receives an ICMPv6 Echo Reply from ED_1. + * - IPv6 Source: ED_1 OMR (using prefix OMR_3) + * - IPv6 Destination: Eth_1 GUA + */ + Log("Step 13: Eth_1 pings ED_1 OMR_3."); + + const Ip6::Address &ed1Omr3 = ed1.FindMatchingAddress(kOmr3PrefixStr); + nexus.AddTestVar("ED_1_OMR_3_ADDR", ed1Omr3.ToString().AsCString()); + + eth1.mInfraIf.SendEchoRequest(eth1Gua, ed1Omr3, kEchoIdentifier, kEchoPayloadSize); + nexus.AdvanceTime(kPingResponseTime); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 14 + * - Device: ED_1 + * - Description (DBR-1.8): + * - Harness instructs the device to send an ICMPv6 Echo Request to Eth_1. + * - IPv6 Source: ED_1 OMR address (using prefix OMR_3) + * - 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 address (using prefix OMR_3) + */ + Log("Step 14: ED_1 OMR_3 pings Eth_1 GUA."); + + ed1.SendEchoRequest(eth1Gua, kEchoIdentifier, kEchoPayloadSize, kDefaultHopLimit, &ed1Omr3); + nexus.AdvanceTime(kPingResponseTime); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 15 + * - Device: BR_2 + * - Description (DBR-1.8): + * - Harness instructs device to remove existing OMR prefix (OMR_3) that is being advertised and sends out new + * network data without the prefix OMR_3. + * - Note: can use OT command 'netdata unpublish ' for this. + * - Pass Criteria: + * - N/A + */ + Log("Step 15: BR_2 removes OMR_3."); + + SuccessOrQuit(br2.Get().UnpublishPrefix(omr3)); + WaitForPrefix(nexus, br2, omr3, false); + + nexus.AdvanceTime(kLongActionTime); + DumpNetworkData(br2); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 16 + * - Device: BR_1 (DUT) + * - Description (DBR-1.8): + * - Automatically starts advertising its own OMR prefix OMR_2 again. + * - Pass Criteria: + * - The DUT MUST register its OMR prefix OMR_2 in the Thread Network Data. Flags in the Border Router sub-TLV + * MUST be: + * - P_preference = 11 (Low) + * - P_default = true + * - P_stable = true + * - P_on_mesh = true + * - P_preferred = true + * - P_slaac = true + * - P_dhcp = false + * - P_dp = false + * - OMR_2 MUST be 64 bits long and start with 0xFD. + * - OMR_2 MUST be equal to the OMR_2 values as used in previous test steps. + */ + Log("Step 16: BR_1 (DUT) re-registers OMR_2."); + + WaitForPrefix(nexus, br2, omr2, true); + + nexus.AdvanceTime(kBrActionTime); + DumpNetworkData(br2); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 17 + * - Device: BR_1 (DUT) + * - Description (DBR-1.8): + * - Automatically multicasts ND RAs on Adjacent Infrastructure Link with OMR_2. + * - Note: the reason that OMR_3 is not advertised, is that OMR_3 was withdrawn and was not originally created by + * BR_1. So BR_1 has no responsibility to advertise this deprecated prefix. + * - Pass Criteria: + * - The DUT MUST multicast ND RAs on the infrastructure link: + * - IPv6 destination MUST be ff02::1 + * - MUST NOT contain a Prefix Information Option (PIO) with a ULA prefix. + * - MUST NOT contain a Route Information Option (RIO) with OMR_1. + * - MUST contain a Route Information Option (RIO) with OMR_2. + * - "Prf" bits MUST be 00 (medium) or 11 (low) + * - MUST NOT contain a Route Information Option (RIO) with OMR_3. + */ + Log("Step 17: BR_1 (DUT) multicasts ND RA with OMR_2, no OMR_3."); + + nexus.AdvanceTime(kBrActionTime); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 18 + * - Device: BR_2 + * - Description (DBR-1.8): + * - Harness instructs the device to add new OMR prefix (OMR_4) numerically higher than OMR_2, but with same + * preference "Low". + * - Pass Criteria: + * - N/A + */ + Log("Step 18: BR_2 adds OMR_4."); + + Ip6::Prefix omr4; + SuccessOrQuit(omr4.FromString(kOmr4PrefixStr)); + + PublishOmrPrefix(br2, omr4, NetworkData::kRoutePreferenceLow, true); + + WaitForPrefix(nexus, br2, omr4, true); + + nexus.AdvanceTime(kLongActionTime); + nexus.AddTestVar("OMR_4_PREFIX", kOmr4PrefixStr); + Log("OMR_4_PREFIX: %s", kOmr4PrefixStr); + DumpNetworkData(br2); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 19 + * - Device: BR_1 (DUT) + * - Description (DBR-1.8): + * - No change in behavior for advertising its OMR prefix, OMR_2. + * - Automatically starts advertising a route for the new OMR_4 prefix. + * - Pass Criteria: + * - The DUT MUST NOT send a Network Data update with any one of the following prefixes in a Prefix TLV: + * - OMR_1, OMR_2, OMR_3, OMR_4 + * - The DUT MUST multicast ND RAs on the infrastructure link: + * - IPv6 destination MUST be ff02::1 + * - MUST NOT contain a Prefix Information Option (PIO) with a ULA prefix. + * - MUST contain a Route Information Option (RIO) with OMR_2. + * - "Prf" bits MUST be 00 (medium) or 11 (low). + * - MUST contain a Route Information Option (RIO) with OMR_4. + * - "Prf" bits MUST be 00 (medium) or 11 (low). + * - MUST NOT contain RIO with any one of OMR_1, OMR_3. + */ + Log("Step 19: BR_1 (DUT) continues OMR_2, adds OMR_4 on AIL."); + + nexus.AdvanceTime(kBrActionTime); + DumpNetworkData(br2); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 20 + * - Device: BR_2 + * - Description (DBR-1.8): + * - Harness instructs the device to update the numerically higher OMR_4 with a higher preference value 00 + * ('Medium'). + * - Pass Criteria: + * - N/A + */ + Log("Step 20: BR_2 updates OMR_4 to Medium pref."); + + PublishOmrPrefix(br2, omr4, NetworkData::kRoutePreferenceMedium, true); + + nexus.AdvanceTime(kLongActionTime); + DumpNetworkData(br2); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 21 + * - Device: BR_1 (DUT) + * - Description (DBR-1.8): Automatically withdraws its current OMR prefix OMR_2. + * - Pass Criteria: + * - The DUT MUST send a Network Data update. In the new network data: + * - the following prefixes MUST NOT be present in a Prefix TLV: OMR_1, OMR_2, OMR_3 + * - The DUT MUST multicast ND RAs on the infrastructure link: + * - IPv6 destination MUST be ff02::1 + * - MUST NOT contain a Prefix Information Option (PIO) with a ULA prefix. + * - MUST contain a Route Information Option (RIO) with OMR_2: + * - "Prf" bits MUST be 00 (medium) or 11 (low). + * - MUST contain a Route Information Option (RIO) with OMR_4: + * - "Prf" bits MUST be 00 (medium) or 11 (low). + * - MUST NOT contain a Route Information Option (RIO) with OMR_1 or OMR_3. + */ + Log("Step 21: BR_1 (DUT) withdraws OMR_2."); + + WaitForPrefix(nexus, br2, omr2, false); + + nexus.AdvanceTime(kBrActionTime); + DumpNetworkData(br2); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 22 + * - Device: BR_2 + * - Description (DBR-1.8): + * - Harness instructs the device to update the prefix OMR_4 to a deprecated state by setting P_preferred = + * 'false'. + * - Note: can use OT command 'netdata publish prefix' for this where the flags do not contain the 'p' (Preferred) + * flag. + * - Pass Criteria: + * - N/A + */ + Log("Step 22: BR_2 deprecates OMR_4."); + + PublishOmrPrefix(br2, omr4, NetworkData::kRoutePreferenceMedium, false); + + nexus.AdvanceTime(kLongActionTime); + DumpNetworkData(br2); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 23 + * - Device: BR_1 (DUT) + * - Description (DBR-1.8): Automatically starts advertising its own OMR prefix OMR_2 again. + * - Pass Criteria: + * - The DUT MUST register its OMR prefix OMR_2 in the Thread Network Data. Flags in the Border Router sub-TLV + * MUST be: + * - P_preference = 11 (Low) + * - P_default = true + * - P_stable = true + * - P_on_mesh = true + * - P_preferred = true + * - P_slaac = true + * - P_dhcp = false + * - P_dp = false + * - OMR_2 MUST be 64 bits long and start with 0xFD. + * - OMR_2 MUST be equal to the OMR_2 values as used in previous test steps. + */ + Log("Step 23: BR_1 (DUT) re-registers OMR_2."); + + WaitForPrefix(nexus, br2, omr2, true); + + nexus.AdvanceTime(kBrActionTime); + DumpNetworkData(br2); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 24 + * - Device: BR_1 (DUT) + * - Description (DBR-1.8): + * - Automatically multicasts ND RAs on Adjacent Infrastructure Link. + * - Note: even though OMR_4 is deprecated, it is still valid so it is advertised on the AIL in a RIO. + * - Pass Criteria: + * - The DUT MUST multicast ND RAs on the infrastructure link: + * - IPv6 destination MUST be ff02::1 + * - MUST NOT contain a Prefix Information Option (PIO) with a ULA prefix. + * - MUST NOT contain a Route Information Option (RIO) with OMR_1. + * - MUST contain a Route Information Option (RIO) with OMR_2. + * - "Prf" bits MUST be 00 (medium) or 11 (low) + * - MUST NOT contain a Route Information Option (RIO) with OMR_3. + * - MUST contain a Route Information Option (RIO) with OMR_4. + * - "Prf" bits MUST be 00 (medium) or 11 (low) + */ + Log("Step 24: BR_1 (DUT) multicasts ND RA with OMR_2 and OMR_4."); + + nexus.AdvanceTime(kBrActionTime); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 25 + * - Device: Eth_1 + * - Description (DBR-1.8): + * - Harness instructs the device to send an ICMPv6 Echo Request to ED_1 via BR_1. + * - IPv6 Source: Eth_1 GUA + * - IPv6 Destination: ED_1 OMR address (using prefix OMR_2) + * - Pass Criteria: + * - Eth_1 receives an ICMPv6 Echo Reply from ED_1. + * - IPv6 Source: ED_1 OMR (using prefix OMR_2) + * - IPv6 Destination: Eth_1 GUA + */ + Log("Step 25: Eth_1 pings ED_1 OMR_2."); + + eth1.mInfraIf.SendEchoRequest(eth1Gua, ed1Omr2, kEchoIdentifier, kEchoPayloadSize); + nexus.AdvanceTime(kPingResponseTime); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 26 + * - Device: ED_1 + * - Description (DBR-1.8): + * - Harness instructs the device to send an ICMPv6 Echo Request to Eth_1. + * - IPv6 Source: ED_1 OMR address (using prefix 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 address (using prefix OMR_2) + */ + Log("Step 26: ED_1 OMR_2 pings Eth_1 GUA."); + + ed1.SendEchoRequest(eth1Gua, kEchoIdentifier, kEchoPayloadSize, kDefaultHopLimit, &ed1Omr2); + nexus.AdvanceTime(kPingResponseTime); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 27 + * - Device: BR_2 + * - Description (DBR-1.8): + * - Harness disables the BR function of the device. Or if that is not possible, switch it off. + * - Note: disabling BR may use 'br disable' CLI command. + * - Note: in case of switching off BR_2, the OMR prefix OMR_4 that was advertised by BR_2 remains active for some + * time. The Leader timeout of Leader BR_2 does not happen yet during the below steps of this test. + * - Pass Criteria: + * - N/A + */ + Log("Step 27: BR_2 disabled."); + + SuccessOrQuit(br2.Get().SetEnabled(false)); + nexus.AdvanceTime(kBrActionTime); + DumpNetworkData(br2); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 28 + * - Device: Eth_1 + * - Description (DBR-1.8): + * - Harness instructs the device to send an ICMPv6 Echo Request to ED_1 via BR_1. + * - IPv6 Source: Eth_1 GUA + * - IPv6 Destination: ED_1 OMR address (using prefix OMR_2) + * - Pass Criteria: + * - Eth_1 receives an ICMPv6 Echo Reply from ED_1. + * - IPv6 Source: ED_1 OMR (using prefix OMR_2) + * - IPv6 Destination: Eth_1 GUA + */ + Log("Step 28: Eth_1 pings ED_1 OMR_2."); + + eth1.mInfraIf.SendEchoRequest(eth1Gua, ed1Omr2, kEchoIdentifier, kEchoPayloadSize); + nexus.AdvanceTime(kPingResponseTime); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 29 + * - Device: ED_1 + * - Description (DBR-1.8): + * - Harness instructs the device to send an ICMPv6 Echo Request to Eth_1. + * - IPv6 Source: ED_1 OMR address (using prefix OMR_4) + * - IPv6 Destination: Eth_1 GUA. + * - Note: ED_1 is forced here to use a source address based on prefix OMR_4, even though that prefix is + * deprecated. In OT CLI this can most likely be achieved using the command: + * - ping -I + * - Pass Criteria: + * - N/A + */ + Log("Step 29: ED_1 pings Eth_1 using OMR_4."); + + const Ip6::Address &ed1Omr4 = ed1.FindMatchingAddress(kOmr4PrefixStr); + nexus.AddTestVar("ED_1_OMR_4_ADDR", ed1Omr4.ToString().AsCString()); + ed1.SendEchoRequest(eth1Gua, kEchoIdentifier, kEchoPayloadSize, kDefaultHopLimit, &ed1Omr4); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 30 + * - Device: Eth_1 + * - Description (DBR-1.8): + * - Automatically replies to the ICMPv6 Echo Request with Echo Reply, that is sent to BR_1. + * - Pass Criteria: + * - N/A + */ + Log("Step 30: Eth_1 replies to OMR_4."); + + nexus.AdvanceTime(kPingResponseTime); + + Log("---------------------------------------------------------------------------------------"); + /** + * Step 31 + * - Device: BR_1 (DUT) + * - Description (DBR-1.8): + * - Automatically attempts to deliver the Echo Reply to a node on the mesh. + * - Pass Criteria: + * - The DUT MUST attempt to deliver the packet to a node on the mesh, either: + * - 1. BR_1 sends a multicast Address Query for ED_1 OMR_4 based address into the mesh; + * - 2. or ED_1 receives the Echo Reply from Eth_1 as follows: + * - IPv6 Source: Eth_1 GUA + * - IPv6 Destination: ED_1 OMR address (using prefix OMR_4) + */ + Log("Step 31: BR_1 delivers Echo Reply for OMR_4."); + + nexus.AdvanceTime(kPingResponseTime); + + nexus.SaveTestInfo("test_1_3_DBR_TC_8.json"); +} + +} // namespace Nexus +} // namespace ot + +int main(void) +{ + ot::Nexus::Test_1_3_DBR_TC_8(); + printf("All tests passed\n"); + return 0; +} diff --git a/tests/nexus/verify_1_3_DBR_TC_7A.py b/tests/nexus/verify_1_3_DBR_TC_7A.py index 6bc7428a7..8a1248309 100644 --- a/tests/nexus/verify_1_3_DBR_TC_7A.py +++ b/tests/nexus/verify_1_3_DBR_TC_7A.py @@ -56,6 +56,7 @@ ICMPV6_TYPE_ROUTER_SOLICITATION = 133 def get_val(field_values, index): vals = verify_utils.as_list(field_values) + assert index < len(vals), f"Index {index} is out of bounds for field values with length {len(vals)}" return vals[index] diff --git a/tests/nexus/verify_1_3_DBR_TC_7B.py b/tests/nexus/verify_1_3_DBR_TC_7B.py index 5753ff99f..42885cd4b 100644 --- a/tests/nexus/verify_1_3_DBR_TC_7B.py +++ b/tests/nexus/verify_1_3_DBR_TC_7B.py @@ -56,6 +56,7 @@ ICMPV6_TYPE_ROUTER_SOLICITATION = 133 def get_val(field_values, index): vals = verify_utils.as_list(field_values) + assert index < len(vals), f"Index {index} is out of bounds for field values with length {len(vals)}" return vals[index] diff --git a/tests/nexus/verify_1_3_DBR_TC_7C.py b/tests/nexus/verify_1_3_DBR_TC_7C.py index 53036ff42..81af228f7 100644 --- a/tests/nexus/verify_1_3_DBR_TC_7C.py +++ b/tests/nexus/verify_1_3_DBR_TC_7C.py @@ -55,6 +55,7 @@ ICMPV6_TYPE_ROUTER_SOLICITATION = 133 def get_val(field_values, index): vals = verify_utils.as_list(field_values) + assert index < len(vals), f"Index {index} is out of bounds for field values with length {len(vals)}" return vals[index] diff --git a/tests/nexus/verify_1_3_DBR_TC_8.py b/tests/nexus/verify_1_3_DBR_TC_8.py new file mode 100644 index 000000000..0ec38d10f --- /dev/null +++ b/tests/nexus/verify_1_3_DBR_TC_8.py @@ -0,0 +1,760 @@ +#!/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.addrs import Ipv6Addr +from pktverify import consts +from pktverify.null_field import nullField + +# Protocol Constants +ULA_PREFIX_START_BYTE = 0xfd +BR_PREFERENCE_LOW = 3 +BR_PREFERENCE_MEDIUM = 0 +BR_FLAG_R_TRUE = 1 +BR_FLAG_O_TRUE = 1 +BR_FLAG_P_TRUE = 1 +BR_FLAG_P_FALSE = 0 +BR_FLAG_S_TRUE = 1 +BR_FLAG_D_FALSE = 0 +BR_FLAG_DP_FALSE = 0 + + +def check_nwd_has_route(packet, prefix): + """Checks if Network Data has an External Route with the given prefix.""" + try: + if not hasattr(packet, 'thread_nwd'): + return False + types = verify_utils.as_list(packet.thread_nwd.tlv.type) + prefixes = verify_utils.as_list(packet.thread_nwd.tlv.prefix) + except (AttributeError, IndexError): + return False + + prefix_idx = 0 + is_target = False + + for t in types: + if t == consts.NWD_PREFIX_TLV: + if prefix_idx < len(prefixes): + current_prefix = prefixes[prefix_idx] + prefix_idx += 1 + if current_prefix: + is_target = (Ipv6Addr(current_prefix) == Ipv6Addr(prefix)) + else: + is_target = False + elif t == consts.NWD_HAS_ROUTER_TLV: + if is_target: + return True + elif t in (consts.NWD_COMMISSIONING_DATA_TLV, consts.NWD_SERVICE_TLV): + is_target = False + + return False + + +def check_ra_rio(packet, prefix, not_prefixes=None): + """Checks if an ICMPv6 RA contains a Route Information Option (RIO) with the given prefix.""" + rio_prefixes, _ = verify_utils.get_ra_prefixes(packet) + target_addr = Ipv6Addr(prefix) + found = any(Ipv6Addr(p) == target_addr for p in rio_prefixes) + if not found: + return False + if not_prefixes: + for np in not_prefixes: + if any(Ipv6Addr(p) == Ipv6Addr(np) for p in rio_prefixes): + return False + return True + + +def check_nwd_contains_prefix(packet, prefix): + """Simple check if Network Data contains the given prefix in a Prefix TLV.""" + try: + if not hasattr(packet, 'thread_nwd') or not hasattr(packet.thread_nwd.tlv, 'prefix'): + return False + prefixes = verify_utils.as_list(packet.thread_nwd.tlv.prefix) + target_addr = Ipv6Addr(prefix) + return any(p and Ipv6Addr(p) == target_addr for p in prefixes) + except (AttributeError, IndexError): + return False + + +def verify(pv): + pkts = pv.pkts + + BR_1 = pv.vars['BR_1'] + ED_1 = pv.vars['ED_1'] + ETH_1_GUA = pv.vars['ETH_1_GUA_ADDR'] + + # Strip /len from prefixes for Ipv6Addr matching + OMR_1_PREFIX = pv.vars['OMR_1_PREFIX'].split('/')[0] + OMR_2_PREFIX = pv.vars['OMR_2_PREFIX'].split('/')[0] + OMR_3_PREFIX = pv.vars['OMR_3_PREFIX'].split('/')[0] + OMR_4_PREFIX = pv.vars['OMR_4_PREFIX'].split('/')[0] + + ED_1_OMR_2 = pv.vars['ED_1_OMR_2_ADDR'] + ED_1_OMR_3 = pv.vars['ED_1_OMR_3_ADDR'] + ED_1_OMR_4 = pv.vars['ED_1_OMR_4_ADDR'] + + # Step 0 + # - Device: Eth_1 + # - Description (DBR-1.8): + # - Harness configures Ethernet link with an on-link IPv6 GUA prefix GUA_1. Eth_1 is configured to multicast ND + # RAs. + # - Automatically configures a global address “Eth_1 GUA”. + # - Pass Criteria + # - N/A + print("Step 0: Eth_1 configured with GUA_1.") + + # Step 1 + # - Device: Eth_1, BR_2, ED_1, ED_2 + # - Description (DBR-1.8): + # - Enable; BR_2 automatically configures an OMR prefix for the mesh, OMR_1. Harness records the value of OMR_1 + # and saves it for verification use. + # - Note: the OT command ‘br omrprefix’ should provide the prefix from BR_2. + # - Form topology. Wait for BR_2 to: + # - Become Leader + # - Register as border router in Thread Network Data with its OMR prefix OMR_1 + # - Send multicast ND RAs on AIL + # - Automatically advertise a route to GUA_1 in the Thread Network Data, using a Prefix TLV: + # - Prefix = ::/0 + # - Domain ID = + # - Has Route sub-TLV + # - Pass Criteria + # - N/A + print("Step 1: BR_2 becomes Leader and registers OMR_1.") + + # Step 2 + # - Device: BR_1 (DUT) + # - Description (DBR-1.8): Enable: switch on. + # - Pass Criteria + # - N/A + print("Step 2: Enable BR_1 (DUT).") + + # Step 3 + # - Device: BR_1 (DUT) + # - Description (DBR-1.8): + # - Automatically registers itself as a border router in the Thread Network Data. + # - Pass Criteria: + # - The DUT MUST register a route to GUA_1 in the Thread Network Data as follows: + # - Prefix TLV: Prefix = ::/0 + # - Domain ID = + # - Has Route sub-TLV + # - R_preference = 00 (medium) or 11 (low) + # - DUT MUST NOT register an OMR prefix (e.g. OMR_1, or other) in Thread Network Data. + print("Step 3: BR_1 (DUT) registers route to GUA_1.") + pkts.filter_wpan_src64(BR_1).\ + filter(lambda p: hasattr(p, 'mle') and p.mle.cmd == consts.MLE_DATA_RESPONSE).\ + filter(lambda p: check_nwd_has_route(p, "::")).\ + must_next() + + # Step 4 + # - Device: BR_1 (DUT) + # - Description (DBR-1.8): + # - Automatically announces the route to OMR_1 on the AIL. + # - Pass Criteria: + # - The DUT MUST multicast ND RAs on the infrastructure link: + # - IPv6 destination MUST be ff02::1 + # - MUST NOT contain a Prefix Information Option (PIO) with a ULA prefix. + # - MUST contain a Route Information Option (RIO) with OMR_1. + # - "Prf" bits MUST be 00 (medium) or 11 (low) + print("Step 4: BR_1 (DUT) announces OMR_1 on AIL.") + pkts.filter_eth_src(pv.vars['BR_1_ETH']).\ + filter_ipv6_dst("ff02::1").\ + filter(lambda p: check_ra_rio(p, OMR_1_PREFIX)).\ + filter(lambda p: not any(Ipv6Addr(pref)[0] == ULA_PREFIX_START_BYTE + for pref in verify_utils.get_ra_prefixes(p)[1])).\ + must_next() + + # Step 5 + # - Device: BR_2 + # - Description (DBR-1.8): + # - Harness instructs device to remove the existing OMR prefix (OMR_1) being advertised and send out new network + # data without any prefix. + # - Note: for 1.3.x cert, this could be implemented by 'br disable' or 'netdata unpublish ' on BR_2. + # - Pass Criteria: + # - N/A + print("Step 5: BR_2 removes OMR_1.") + + # Step 6 + # - Device: BR_1 (DUT) + # - Description (DBR-1.8): + # - Automatically chooses a new OMR prefix OMR_2 and includes it in the Network Data. + # - Pass Criteria: + # - The DUT MUST register a new OMR prefix (OMR_2) in the Thread Network Data using Prefix TLV as follows: + # - Prefix = OMR_2 + # - Border Router sub-TLV + # - P_preference = 11 (Low) + # - P_default = true + # - P_stable = true + # - P_on_mesh = true + # - P_preferred = true + # - P_slaac = true + # - P_dhcp = false + # - P_dp = false + # - OMR_2 MUST be 64 bits long and start with 0xFD. + # - OMR_2 MUST differ from OMR_1. + # - Also MUST contain a Prefix TLV as follows: + # - Prefix = ::/0 + # - Domain ID + # - Has Route TLV + # - R_preference = 00 (medium) or 11 (low) + # - Note: one possible way to check this is to verify the contents of the SVR_DATA.ntf message sent by DUT to + # Leader. + print("Step 6: BR_1 (DUT) chooses OMR_2 and registers in Network Data.") + if Ipv6Addr(OMR_2_PREFIX) == Ipv6Addr(OMR_1_PREFIX): + raise verify_utils.VerificationError(f"OMR_2 ({OMR_2_PREFIX}) must differ from OMR_1 ({OMR_1_PREFIX})") + + # OMR_2 MUST be 64 bits long and start with 0xFD. + omr2_addr = Ipv6Addr(OMR_2_PREFIX) + if omr2_addr[0] != 0xfd: + raise verify_utils.VerificationError(f"OMR_2 ({OMR_2_PREFIX}) must start with 0xFD") + if int(pv.vars['OMR_2_PREFIX'].split('/')[-1]) != 64: + raise verify_utils.VerificationError(f"OMR_2 ({pv.vars['OMR_2_PREFIX']}) must be 64 bits long") + + pkts.filter_wpan_src64(BR_1).\ + filter(lambda p: hasattr(p, 'mle') and p.mle.cmd == consts.MLE_DATA_RESPONSE).\ + filter(lambda p: verify_utils.check_nwd_prefix_flags(p, + OMR_2_PREFIX, + stable=1, + pref=BR_PREFERENCE_LOW, + r=BR_FLAG_R_TRUE, + o=BR_FLAG_O_TRUE, + p=BR_FLAG_P_TRUE, + s=BR_FLAG_S_TRUE, + d=BR_FLAG_D_FALSE, + dp=BR_FLAG_DP_FALSE)).\ + filter(lambda p: check_nwd_has_route(p, "::")).\ + must_next() + + # Step 7 + # - Device: BR_1 (DUT) + # - Description (DBR-1.8): + # - Automatically multicasts ND RAs on Adjacent Infrastructure Link. + # - Pass Criteria: + # - The DUT MUST multicast ND RAs on the infrastructure link: + # - IPv6 destination MUST be ff02::1 + # - MUST NOT contain a Prefix Information Option (PIO) with a ULA prefix. + # - MUST contain a Route Information Option (RIO) with OMR_2. + # - "Prf" bits MUST be 00 (medium) or 11 (low). + # - MUST NOT contain a Route Information Option (RIO) with OMR_1. + print("Step 7: BR_1 (DUT) multicasts ND RA with OMR_2, no OMR_1.") + pkts.filter_eth_src(pv.vars['BR_1_ETH']).\ + filter_ipv6_dst("ff02::1").\ + filter(lambda p: check_ra_rio(p, OMR_2_PREFIX, not_prefixes=[OMR_1_PREFIX])).\ + must_next() + + # Step 8 + # - Device: Eth_1 + # - Description (DBR-1.8): + # - Harness instructs the device to send an ICMPv6 Echo Request to ED_1 via either one of BR_1 or BR_2. + # - IPv6 Source: Eth_1 GUA + # - IPv6 Destination: ED_1 OMR address (using prefix OMR_2) + # - Pass Criteria: + # - Eth_1 receives an ICMPv6 Echo Reply from ED_1. + # - IPv6 Source: ED_1 OMR (using prefix OMR_2) + # - IPv6 Destination: Eth_1 GUA + print("Step 8: Eth_1 pings ED_1 OMR_2.") + _pkt = pkts.filter_eth_src(pv.vars['Eth_1_ETH']).\ + filter_ipv6_src(ETH_1_GUA).\ + filter_ipv6_dst(ED_1_OMR_2).\ + filter_ping_request().\ + must_next() + pkts.filter(lambda p: p.eth.dst == pv.vars['Eth_1_ETH']).\ + filter_ipv6_src(ED_1_OMR_2).\ + filter_ipv6_dst(ETH_1_GUA).\ + filter_ping_reply(identifier=_pkt.icmpv6.echo.identifier).\ + must_next() + + # Step 9 + # - Device: ED_1 + # - Description (DBR-1.8): + # - Harness instructs the device to send an ICMPv6 Echo Request to Eth_1. + # - IPv6 Source: ED_1 OMR address (using prefix 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 address (using prefix OMR_2) + print("Step 9: ED_1 OMR_2 pings Eth_1 GUA.") + _pkt = pkts.filter_ipv6_src(ED_1_OMR_2).\ + filter_ipv6_dst(ETH_1_GUA).\ + filter_ping_request().\ + must_next() + pkts.filter(lambda p: p.ipv6.src == ETH_1_GUA).\ + filter_ipv6_dst(ED_1_OMR_2).\ + filter_ping_reply(identifier=_pkt.icmpv6.echo.identifier).\ + must_next() + + # Step 10 + # - Device: BR_2 + # - Description (DBR-1.8): + # - Harness instructs BR_2 to add an OMR prefix OMR_3 numerically lower than OMR_2, but with same preference + # "Low". + # - Note: can use OT command 'netdata publish prefix' for this. + # - Pass Criteria: + # - N/A + print("Step 10: BR_2 adds OMR_3.") + # Check if OMR_3 is present in ANY node's Network Data (MLE) or BR_1's RAs. + # Search from beginning of pcap as it might appear early due to propagation. + _pkt_step10 = pkts.copy().filter(lambda p: (hasattr(p, 'thread_nwd') and\ + verify_utils.check_nwd_prefix_flags(p, OMR_3_PREFIX)) or\ + check_ra_rio(p, OMR_3_PREFIX)).\ + must_next() + + # Step 11 + # - Device: BR_1 (DUT) + # - Description (DBR-1.8): + # - Automatically withdraws its own OMR prefix OMR_2 from the Thread Network Data. + # - Pass Criteria: + # - The DUT MUST register new Network Data: + # - MUST NOT contain any of the OMR prefixes OMR_1, OMR_2, or OMR_3 in a Prefix TLV. + # - MUST contain a Prefix TLV: + # - Prefix = ::/0 + # - Domain ID + # - Has Route TLV + # - R_preference = 00 (medium) or 11 (low) + print("Step 11: BR_1 (DUT) withdraws OMR_2 from Network Data.") + # Step 11 must happen after Step 10. + pkts.index = (_pkt_step10.number, _pkt_step10.number) + pkts.filter(lambda p: hasattr(p, 'thread_nwd') and not check_nwd_contains_prefix(p, OMR_2_PREFIX)).must_next() + + # Step 12 + # - Device: BR_1 (DUT) + # - Description (DBR-1.8): + # - Automatically multicasts ND RAs on Adjacent Infrastructure Link including OMR_2/OMR_3. + # - Note: the reason that OMR_2 is still included initially, is that OMR_2 has been created by BR_1 originally + # and as seen by BR_1, it stays in a deprecated state for at most OMR_ADDR_DEPRECATION_TIME + # - Pass Criteria: + # - The DUT MUST multicast ND RAs on the infrastructure link: + # - IPv6 destination MUST be ff02::1 + # - MUST NOT contain a Prefix Information Option (PIO) with a ULA prefix. + # - MUST NOT contain a Route Information Option (RIO) with OMR_1. + # - MUST contain a Route Information Option (RIO) with OMR_2. + # - "Prf" bits MUST be 00 (medium) or 11 (low) + # - MUST contain a Route Information Option (RIO) with OMR_3. + # - "Prf" bits MUST be 00 (medium) or 11 (low) + print("Step 12: BR_1 (DUT) multicasts ND RA with OMR_2 and OMR_3.") + pkts.filter_eth_src(pv.vars['BR_1_ETH']).\ + filter_ipv6_dst("ff02::1").\ + filter(lambda p: check_ra_rio(p, OMR_2_PREFIX)).\ + filter(lambda p: check_ra_rio(p, OMR_3_PREFIX)).\ + must_next() + + # Step 13 + # - Device: Eth_1 + # - Description (DBR-1.8): + # - Harness instructs the device to send an ICMPv6 Echo Request to ED_1 via BR_1 or BR_2. + # - IPv6 Source: Eth_1 GUA + # - IPv6 Destination: ED_1 OMR address (using prefix OMR_3) + # - Pass Criteria: + # - Eth_1 receives an ICMPv6 Echo Reply from ED_1. + # - IPv6 Source: ED_1 OMR (using prefix OMR_3) + # - IPv6 Destination: Eth_1 GUA + print("Step 13: Eth_1 pings ED_1 OMR_3.") + _pkt = pkts.filter_eth_src(pv.vars['Eth_1_ETH']).\ + filter_ipv6_src(ETH_1_GUA).\ + filter_ipv6_dst(ED_1_OMR_3).\ + filter_ping_request().\ + must_next() + pkts.filter(lambda p: p.eth.dst == pv.vars['Eth_1_ETH']).\ + filter_ipv6_src(ED_1_OMR_3).\ + filter_ipv6_dst(ETH_1_GUA).\ + filter_ping_reply(identifier=_pkt.icmpv6.echo.identifier).\ + must_next() + + # Step 14 + # - Device: ED_1 + # - Description (DBR-1.8): + # - Harness instructs the device to send an ICMPv6 Echo Request to Eth_1. + # - IPv6 Source: ED_1 OMR address (using prefix OMR_3) + # - 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 address (using prefix OMR_3) + print("Step 14: ED_1 OMR_3 pings Eth_1 GUA.") + _pkt = pkts.filter_ipv6_src(ED_1_OMR_3).\ + filter_ipv6_dst(ETH_1_GUA).\ + filter_ping_request().\ + must_next() + pkts.filter(lambda p: p.ipv6.src == ETH_1_GUA).\ + filter_ipv6_dst(ED_1_OMR_3).\ + filter_ping_reply(identifier=_pkt.icmpv6.echo.identifier).\ + must_next() + + # Step 15 + # - Device: BR_2 + # - Description (DBR-1.8): + # - Harness instructs device to remove existing OMR prefix (OMR_3) that is being advertised and sends out new + # network data without the prefix OMR_3. + # - Note: can use OT command 'netdata unpublish ' for this. + # - Pass Criteria: + # - N/A + print("Step 15: BR_2 removes OMR_3.") + + # Step 16 + # - Device: BR_1 (DUT) + # - Description (DBR-1.8): + # - Automatically starts advertising its own OMR prefix OMR_2 again. + # - Pass Criteria: + # - The DUT MUST register its OMR prefix OMR_2 in the Thread Network Data. Flags in the Border Router sub-TLV + # MUST be: + # - P_preference = 11 (Low) + # - P_default = true + # - P_stable = true + # - P_on_mesh = true + # - P_preferred = true + # - P_slaac = true + # - P_dhcp = false + # - P_dp = false + # - OMR_2 MUST be 64 bits long and start with 0xFD. + # - OMR_2 MUST be equal to the OMR_2 values as used in previous test steps. + print("Step 16: BR_1 (DUT) re-registers OMR_2.") + # OMR_2 MUST be 64 bits long and start with 0xFD. + omr2_addr = Ipv6Addr(OMR_2_PREFIX) + if omr2_addr[0] != 0xfd: + raise verify_utils.VerificationError(f"OMR_2 ({OMR_2_PREFIX}) must start with 0xFD") + if int(pv.vars['OMR_2_PREFIX'].split('/')[-1]) != 64: + raise verify_utils.VerificationError(f"OMR_2 ({pv.vars['OMR_2_PREFIX']}) must be 64 bits long") + + pkts.filter(lambda p: verify_utils.check_nwd_prefix_flags(p, + OMR_2_PREFIX, + stable=1, + pref=BR_PREFERENCE_LOW, + r=BR_FLAG_R_TRUE, + o=BR_FLAG_O_TRUE, + p=BR_FLAG_P_TRUE, + s=BR_FLAG_S_TRUE, + d=BR_FLAG_D_FALSE, + dp=BR_FLAG_DP_FALSE)).\ + must_next() + + # Step 17 + # - Device: BR_1 (DUT) + # - Description (DBR-1.8): + # - Automatically multicasts ND RAs on Adjacent Infrastructure Link with OMR_2. + # - Note: the reason that OMR_3 is not advertised, is that OMR_3 was withdrawn and was not originally created by + # BR_1. So BR_1 has no responsibility to advertise this deprecated prefix. + # - Pass Criteria: + # - The DUT MUST multicast ND RAs on the infrastructure link: + # - IPv6 destination MUST be ff02::1 + # - MUST NOT contain a Prefix Information Option (PIO) with a ULA prefix. + # - MUST NOT contain a Route Information Option (RIO) with OMR_1. + # - MUST contain a Route Information Option (RIO) with OMR_2. + # - "Prf" bits MUST be 00 (medium) or 11 (low) + # - MUST NOT contain a Route Information Option (RIO) with OMR_3. + print("Step 17: BR_1 (DUT) multicasts ND RA with OMR_2, no OMR_3.") + pkts.filter_eth_src(pv.vars['BR_1_ETH']).\ + filter_ipv6_dst("ff02::1").\ + filter(lambda p: check_ra_rio(p, OMR_2_PREFIX)).\ + filter(lambda p: not check_ra_rio(p, OMR_3_PREFIX)).\ + must_next() + + # Step 18 + # - Device: BR_2 + # - Description (DBR-1.8): + # - Harness instructs the device to add new OMR prefix (OMR_4) numerically higher than OMR_2, but with same + # preference "Low". + # - Pass Criteria: + # - N/A + print("Step 18: BR_2 adds OMR_4.") + pkts.filter(lambda p: check_nwd_contains_prefix(p, OMR_4_PREFIX)).\ + must_next() + + # Step 19 + # - Device: BR_1 (DUT) + # - Description (DBR-1.8): + # - No change in behavior for advertising its OMR prefix, OMR_2. + # - Automatically starts advertising a route for the new OMR_4 prefix. + # - Pass Criteria: + # - The DUT MUST NOT send a Network Data update with any one of the following prefixes in a Prefix TLV: + # - OMR_1, OMR_2, OMR_3, OMR_4 + # - The DUT MUST multicast ND RAs on the infrastructure link: + # - IPv6 destination MUST be ff02::1 + # - MUST NOT contain a Prefix Information Option (PIO) with a ULA prefix. + # - MUST contain a Route Information Option (RIO) with OMR_2. + # - "Prf" bits MUST be 00 (medium) or 11 (low). + # - MUST contain a Route Information Option (RIO) with OMR_4. + # - "Prf" bits MUST be 00 (medium) or 11 (low). + # - MUST NOT contain RIO with any one of OMR_1, OMR_3. + print("Step 19: BR_1 (DUT) continues OMR_2, adds OMR_4 on AIL.") + pkts.filter_eth_src(pv.vars['BR_1_ETH']).\ + filter_ipv6_dst("ff02::1").\ + filter(lambda p: check_ra_rio(p, OMR_2_PREFIX)).\ + filter(lambda p: check_ra_rio(p, OMR_4_PREFIX)).\ + must_next() + + # Step 20 + # - Device: BR_2 + # - Description (DBR-1.8): + # - Harness instructs the device to update the numerically higher OMR_4 with a higher preference value 00 + # ('Medium'). + # - Pass Criteria: + # - N/A + print("Step 20: BR_2 updates OMR_4 to Medium pref.") + pkts.filter(lambda p: verify_utils.check_nwd_prefix_flags(p, OMR_4_PREFIX, pref=BR_PREFERENCE_MEDIUM)).\ + must_next() + + # Step 21 + # - Device: BR_1 (DUT) + # - Description (DBR-1.8): Automatically withdraws its current OMR prefix OMR_2. + # - Pass Criteria: + # - The DUT MUST send a Network Data update. In the new network data: + # - the following prefixes MUST NOT be present in a Prefix TLV: OMR_1, OMR_2, OMR_3 + # - The DUT MUST multicast ND RAs on the infrastructure link: + # - IPv6 destination MUST be ff02::1 + # - MUST NOT contain a Prefix Information Option (PIO) with a ULA prefix. + # - MUST contain a Route Information Option (RIO) with OMR_2: + # - "Prf" bits MUST be 00 (medium) or 11 (low). + # - MUST contain a Route Information Option (RIO) with OMR_4: + # - "Prf" bits MUST be 00 (medium) or 11 (low). + # - MUST NOT contain a Route Information Option (RIO) with OMR_1 or OMR_3. + print("Step 21: BR_1 (DUT) withdraws OMR_2.") + pkts.filter(lambda p: not check_nwd_contains_prefix(p, OMR_2_PREFIX)).\ + must_next() + + # Step 22 + # - Device: BR_2 + # - Description (DBR-1.8): + # - Harness instructs the device to update the prefix OMR_4 to a deprecated state by setting P_preferred = + # 'false'. + # - Note: can use OT command 'netdata publish prefix' for this where the flags do not contain the 'p' (Preferred) + # flag. + # - Pass Criteria: + # - N/A + print("Step 22: BR_2 deprecates OMR_4.") + pkts.filter(lambda p: verify_utils.check_nwd_prefix_flags(p, OMR_4_PREFIX, p=BR_FLAG_P_FALSE)).\ + must_next() + + # Step 23 + # - Device: BR_1 (DUT) + # - Description (DBR-1.8): Automatically starts advertising its own OMR prefix OMR_2 again. + # - Pass Criteria: + # - The DUT MUST register its OMR prefix OMR_2 in the Thread Network Data. Flags in the Border Router sub-TLV + # MUST be: + # - P_preference = 11 (Low) + # - P_default = true + # - P_stable = true + # - P_on_mesh = true + # - P_preferred = true + # - P_slaac = true + # - P_dhcp = false + # - P_dp = false + # - OMR_2 MUST be 64 bits long and start with 0xFD. + # - OMR_2 MUST be equal to the OMR_2 values as used in previous test steps. + print("Step 23: BR_1 (DUT) re-registers OMR_2.") + # OMR_2 MUST be 64 bits long and start with 0xFD. + omr2_addr = Ipv6Addr(OMR_2_PREFIX) + if omr2_addr[0] != 0xfd: + raise verify_utils.VerificationError(f"OMR_2 ({OMR_2_PREFIX}) must start with 0xFD") + if int(pv.vars['OMR_2_PREFIX'].split('/')[-1]) != 64: + raise verify_utils.VerificationError(f"OMR_2 ({pv.vars['OMR_2_PREFIX']}) must be 64 bits long") + + pkts.filter(lambda p: verify_utils.check_nwd_prefix_flags(p, + OMR_2_PREFIX, + stable=1, + pref=BR_PREFERENCE_LOW, + r=BR_FLAG_R_TRUE, + o=BR_FLAG_O_TRUE, + p=BR_FLAG_P_TRUE, + s=BR_FLAG_S_TRUE, + d=BR_FLAG_D_FALSE, + dp=BR_FLAG_DP_FALSE)).\ + must_next() + + # Step 24 + # - Device: BR_1 (DUT) + # - Description (DBR-1.8): + # - Automatically multicasts ND RAs on Adjacent Infrastructure Link. + # - Note: even though OMR_4 is deprecated, it is still valid so it is advertised on the AIL in a RIO. + # - Pass Criteria: + # - The DUT MUST multicast ND RAs on the infrastructure link: + # - IPv6 destination MUST be ff02::1 + # - MUST NOT contain a Prefix Information Option (PIO) with a ULA prefix. + # - MUST NOT contain a Route Information Option (RIO) with OMR_1. + # - MUST contain a Route Information Option (RIO) with OMR_2. + # - "Prf" bits MUST be 00 (medium) or 11 (low) + # - MUST NOT contain a Route Information Option (RIO) with OMR_3. + # - MUST contain a Route Information Option (RIO) with OMR_4. + # - "Prf" bits MUST be 00 (medium) or 11 (low) + print("Step 24: BR_1 (DUT) multicasts ND RA with OMR_2 and OMR_4.") + pkts.filter_eth_src(pv.vars['BR_1_ETH']).\ + filter_ipv6_dst("ff02::1").\ + filter(lambda p: check_ra_rio(p, OMR_2_PREFIX)).\ + filter(lambda p: check_ra_rio(p, OMR_4_PREFIX)).\ + must_next() + + # Step 25 + # - Device: Eth_1 + # - Description (DBR-1.8): + # - Harness instructs the device to send an ICMPv6 Echo Request to ED_1 via BR_1. + # - IPv6 Source: Eth_1 GUA + # - IPv6 Destination: ED_1 OMR address (using prefix OMR_2) + # - Pass Criteria: + # - Eth_1 receives an ICMPv6 Echo Reply from ED_1. + # - IPv6 Source: ED_1 OMR (using prefix OMR_2) + # - IPv6 Destination: Eth_1 GUA + print("Step 25: Eth_1 pings ED_1 OMR_2.") + _pkt = pkts.filter_eth_src(pv.vars['Eth_1_ETH']).\ + filter_ipv6_src(ETH_1_GUA).\ + filter_ipv6_dst(ED_1_OMR_2).\ + filter_ping_request().\ + must_next() + pkts.filter(lambda p: p.eth.dst == pv.vars['Eth_1_ETH']).\ + filter_ipv6_src(ED_1_OMR_2).\ + filter_ipv6_dst(ETH_1_GUA).\ + filter_ping_reply(identifier=_pkt.icmpv6.echo.identifier).\ + must_next() + + # Step 26 + # - Device: ED_1 + # - Description (DBR-1.8): + # - Harness instructs the device to send an ICMPv6 Echo Request to Eth_1. + # - IPv6 Source: ED_1 OMR address (using prefix 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 address (using prefix OMR_2) + print("Step 26: ED_1 OMR_2 pings Eth_1 GUA.") + _pkt = pkts.filter_ipv6_src(ED_1_OMR_2).\ + filter_ipv6_dst(ETH_1_GUA).\ + filter_ping_request().\ + must_next() + pkts.filter(lambda p: p.ipv6.src == ETH_1_GUA).\ + filter_ipv6_dst(ED_1_OMR_2).\ + filter_ping_reply(identifier=_pkt.icmpv6.echo.identifier).\ + must_next() + + # Step 27 + # - Device: BR_2 + # - Description (DBR-1.8): + # - Harness disables the BR function of the device. Or if that is not possible, switch it off. + # - Note: disabling BR may use 'br disable' CLI command. + # - Note: in case of switching off BR_2, the OMR prefix OMR_4 that was advertised by BR_2 remains active for some + # time. The Leader timeout of Leader BR_2 does not happen yet during the below steps of this test. + # - Pass Criteria: + # - N/A + print("Step 27: BR_2 disabled.") + + # Step 28 + # - Device: Eth_1 + # - Description (DBR-1.8): + # - Harness instructs the device to send an ICMPv6 Echo Request to ED_1 via BR_1. + # - IPv6 Source: Eth_1 GUA + # - IPv6 Destination: ED_1 OMR address (using prefix OMR_2) + # - Pass Criteria: + # - Eth_1 receives an ICMPv6 Echo Reply from ED_1. + # - IPv6 Source: ED_1 OMR (using prefix OMR_2) + # - IPv6 Destination: Eth_1 GUA + print("Step 28: Eth_1 pings ED_1 OMR_2.") + _pkt = pkts.filter_eth_src(pv.vars['Eth_1_ETH']).\ + filter_ipv6_src(ETH_1_GUA).\ + filter_ipv6_dst(ED_1_OMR_2).\ + filter_ping_request().\ + must_next() + pkts.filter(lambda p: p.eth.dst == pv.vars['Eth_1_ETH']).\ + filter_ipv6_src(ED_1_OMR_2).\ + filter_ipv6_dst(ETH_1_GUA).\ + filter_ping_reply(identifier=_pkt.icmpv6.echo.identifier).\ + must_next() + + # Step 29 + # - Device: ED_1 + # - Description (DBR-1.8): + # - Harness instructs the device to send an ICMPv6 Echo Request to Eth_1. + # - IPv6 Source: ED_1 OMR address (using prefix OMR_4) + # - IPv6 Destination: Eth_1 GUA. + # - Note: ED_1 is forced here to use a source address based on prefix OMR_4, even though that prefix is + # deprecated. In OT CLI this can most likely be achieved using the command: + # - ping -I + # - Pass Criteria: + # - N/A + print("Step 29: ED_1 pings Eth_1 using OMR_4.") + _pkt = pkts.filter_ipv6_src(ED_1_OMR_4).\ + filter_ipv6_dst(ETH_1_GUA).\ + filter_ping_request().\ + must_next() + + # Step 30 + # - Device: Eth_1 + # - Description (DBR-1.8): + # - Automatically replies to the ICMPv6 Echo Request with Echo Reply, that is sent to BR_1. + # - Pass Criteria: + # Step 30: Eth_1 replies to OMR_4. + print("Step 30: Eth_1 replies to OMR_4.") + _pkt_step30 = pkts.filter_ping_reply(identifier=_pkt.icmpv6.echo.identifier).filter(lambda p: ( + hasattr(p, 'eth') and p.eth.src == pv.vars['Eth_1_ETH'] and \ + hasattr(p, 'ipv6') and \ + getattr(p.ipv6, 'dst', None) and Ipv6Addr(p.ipv6.dst)[:8] == Ipv6Addr(OMR_4_PREFIX)[:8] and \ + getattr(p.ipv6, 'src', None) and Ipv6Addr(p.ipv6.src) == ETH_1_GUA + )).must_next() + + # Step 31 + # - Device: BR_1 (DUT) + # - Description (DBR-1.8): + # - Automatically attempts to deliver the Echo Reply to a node on the mesh. + # - Pass Criteria: + # - The DUT MUST attempt to deliver the packet to a node on the mesh, either: + # - 1. BR_1 sends a multicast Address Query for ED_1 OMR_4 based address into the mesh; + # - 2. or ED_1 receives the Echo Reply from Eth_1 as follows: + # - IPv6 Source: Eth_1 GUA + # - IPv6 Destination: ED_1 OMR address (using prefix OMR_4) + print("Step 31: BR_1 delivers Echo Reply for OMR_4.") + + def check_step_31(p): + is_aq = (hasattr(p, 'thread_address') and \ + hasattr(p.thread_address, 'tlv') and \ + getattr(p.thread_address.tlv, 'target_eid', None) and \ + Ipv6Addr(p.thread_address.tlv.target_eid)[:8] == Ipv6Addr(OMR_4_PREFIX)[:8]) + + # We allow matching by IID as well because 6LoWPAN context IDs might + # not be correctly mapped in the verifier's Wireshark preferences. + is_er = (hasattr(p, 'wpan') and \ + hasattr(p, 'ipv6') and \ + getattr(p.ipv6, 'dst', None) and \ + (Ipv6Addr(p.ipv6.dst) == ED_1_OMR_4 or \ + Ipv6Addr(p.ipv6.dst)[8:] == Ipv6Addr(ED_1_OMR_4)[8:]) and\ + hasattr(p, 'icmpv6') and p.icmpv6.type == consts.ICMPV6_TYPE_ECHO_REPLY) + + return is_aq or (is_er and p.wpan.src64 == BR_1 and p.icmpv6.echo.identifier == _pkt.icmpv6.echo.identifier) + + pkts.filter(check_step_31).must_next() + # Note: Step 31 must be observed. Even though BR_2 is disabled, the route to OMR_4 + # prefix remains in Network Data, and BR_1 should still be able to forward the + # Echo Reply using the EID-to-RLOC cache. + + +if __name__ == '__main__': + verify_utils.run_main(verify)