diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt index 207299148..a042f96c6 100644 --- a/tests/nexus/CMakeLists.txt +++ b/tests/nexus/CMakeLists.txt @@ -411,6 +411,7 @@ ot_nexus_test(key_rotation_guard_time "core;nexus") ot_nexus_test(leader_reboot_multiple_link_request "core;nexus") ot_nexus_test(log_override "core;nexus") ot_nexus_test(mac_scan "core;nexus") +ot_nexus_test(mle_router_role_allowed "core;nexus") ot_nexus_test(mle_blocking_downgrade "core;nexus") ot_nexus_test(mle_msg_key_seq_jump "core;nexus") ot_nexus_test(nat64_translator "core;nexus") diff --git a/tests/nexus/test_mle_router_role_allowed.cpp b/tests/nexus/test_mle_router_role_allowed.cpp new file mode 100644 index 000000000..d69ad3736 --- /dev/null +++ b/tests/nexus/test_mle_router_role_allowed.cpp @@ -0,0 +1,238 @@ +/* + * 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 = 13 * 1000; + +/** + * Time to advance for a node to join as a child and upgrade to a router, in milliseconds. + */ +static constexpr uint32_t kAttachToRouterTime = 200 * 1000; + +/** + * Maximum interval between Router advertisements. + */ +static constexpr uint32_t kMaximumRouterAdvertisementInterval = 32 * 1000; + +/** + * Device mode to configure as a Full Thread Device (FTD). + */ +static const Mle::DeviceMode kFtdDeviceMode(Mle::DeviceMode::kModeRxOnWhenIdle | + Mle::DeviceMode::kModeFullThreadDevice | + Mle::DeviceMode::kModeFullNetworkData); + +/** + * Device mode to configure as a Minimal End Device (MED). + */ +static const Mle::DeviceMode kMedDeviceMode(Mle::DeviceMode::kModeRxOnWhenIdle | Mle::DeviceMode::kModeFullNetworkData); + +void TestMleRouterRoleAllowed(void) +{ + // This test validates that correctly-formatted transmissions occur based on whether the Router + // role is allowed. This includes Advertisements (only from REEDs and Active Routers), + // which should not occur for devices with Router role disallowed. + // This test verifies Router role disallowed on FED and MED devices, with a duplicate + // pair used to demonstrate that these transmissions begin in the correct format when + // re-configured so that the Router role becomes allowed. + + Core nexus; + + Node &leader = nexus.CreateNode(); + Node &router = nexus.CreateNode(); + Node &reed = nexus.CreateNode(); + Node &fedOnly = nexus.CreateNode(); + Node &medOnly = nexus.CreateNode(); + Node &fedAndRouter = nexus.CreateNode(); + Node &medAndRouter = nexus.CreateNode(); + + leader.SetName("Leader"); + router.SetName("Router"); + reed.SetName("REED"); + fedOnly.SetName("FED_ONLY"); + medOnly.SetName("MED_ONLY"); + fedAndRouter.SetName("FED_Router"); + medAndRouter.SetName("MED_Router"); + + SuccessOrQuit(Instance::SetGlobalLogLevel(kLogLevelInfo)); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 1: Form topology"); + // Child links are only set up with the leader and router, + // so that the REED will not become a router due to a parent request. + + AllowLinkBetween(leader, router); + AllowLinkBetween(leader, reed); + AllowLinkBetween(leader, fedOnly); + AllowLinkBetween(leader, medOnly); + AllowLinkBetween(leader, fedAndRouter); + AllowLinkBetween(leader, medAndRouter); + + AllowLinkBetween(router, reed); + AllowLinkBetween(router, fedOnly); + AllowLinkBetween(router, medOnly); + AllowLinkBetween(router, fedAndRouter); + AllowLinkBetween(router, medAndRouter); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 1.1: Leader"); + // All Leader advertisements are expected to have a router source address + + leader.Form(); + nexus.AdvanceTime(kFormNetworkTime); + VerifyOrQuit(leader.Get().IsLeader()); + VerifyOrQuit(leader.Get().IsRouterRoleAllowed()); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 1.2: Join Router"); + // All Router advertisements are expected to have a router source address + + router.Join(leader); + nexus.AdvanceTime(kAttachToRouterTime); + VerifyOrQuit(leader.Get().IsLeader()); + VerifyOrQuit(router.Get().IsRouter()); + VerifyOrQuit(router.Get().IsRouterRoleAllowed()); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 1.3: Join end devices (only) - reed, fed, & med"); + + // All REED advertisements should have a child source address + reed.Get().SetRouterUpgradeThreshold(1); + reed.Join(leader); + // Router role is allowed for the REED, but should not upgrade due to the updated threshold + VerifyOrQuit(reed.Get().IsRouterRoleAllowed()); + + fedOnly.Join(leader); + // Attach as an FTD, but not configured as router-eligible + SuccessOrQuit(fedOnly.Get().SetRouterEligible(false)); + VerifyOrQuit(!fedOnly.Get().IsRouterRoleAllowed()); + + medOnly.Join(leader, Node::kAsMed); + VerifyOrQuit(!medOnly.Get().IsRouterRoleAllowed()); + + nexus.AdvanceTime(kAttachToRouterTime); + VerifyOrQuit(leader.Get().IsLeader()); + VerifyOrQuit(router.Get().IsRouter()); + VerifyOrQuit(reed.Get().IsChild()); + VerifyOrQuit(fedOnly.Get().IsChild()); + VerifyOrQuit(medOnly.Get().IsChild()); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 1.4: Join end devices that will have router role allowed - fed & med"); + + fedAndRouter.Join(leader); + // Attach as an FTD, but not configured as router-eligible + SuccessOrQuit(fedAndRouter.Get().SetRouterEligible(false)); + VerifyOrQuit(fedAndRouter.Get().IsFullThreadDevice()); + VerifyOrQuit(!fedAndRouter.Get().IsRouterRoleAllowed()); + + medAndRouter.Join(leader, Node::kAsMed); + VerifyOrQuit(!medAndRouter.Get().IsFullThreadDevice()); + VerifyOrQuit(!medAndRouter.Get().IsRouterRoleAllowed()); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 1.5: Child attachment"); + // FED and MED nodes with router role disallowed should not advertise during this time + + nexus.AdvanceTime(kAttachToRouterTime); + VerifyOrQuit(leader.Get().IsLeader()); + VerifyOrQuit(router.Get().IsRouter()); + VerifyOrQuit(reed.Get().IsChild()); + VerifyOrQuit(fedOnly.Get().IsChild()); + VerifyOrQuit(medOnly.Get().IsChild()); + VerifyOrQuit(fedAndRouter.Get().IsChild()); + VerifyOrQuit(medAndRouter.Get().IsChild()); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 2: Set FED & MED Router Role allowed"); + + // Now router eligible so that the former FED is allowed to take the Router role + SuccessOrQuit(fedAndRouter.Get().SetRouterEligible(true)); + VerifyOrQuit(fedAndRouter.Get().IsRouterRoleAllowed()); + + // Enabling FTD mode so that the former MED is allowed to take the Router role + SuccessOrQuit(medAndRouter.Get().SetDeviceMode(kFtdDeviceMode)); + VerifyOrQuit(medAndRouter.Get().IsFullThreadDevice()); + VerifyOrQuit(medAndRouter.Get().IsRouterRoleAllowed()); + + // FED and MED nodes should advertise as Routers at least once during this time + nexus.AdvanceTime(kAttachToRouterTime); + nexus.AdvanceTime(kMaximumRouterAdvertisementInterval); + VerifyOrQuit(leader.Get().IsLeader()); + VerifyOrQuit(router.Get().IsRouter()); + VerifyOrQuit(reed.Get().IsChild()); + VerifyOrQuit(fedOnly.Get().IsChild()); + VerifyOrQuit(medOnly.Get().IsChild()); + VerifyOrQuit(fedAndRouter.Get().IsRouter()); + VerifyOrQuit(medAndRouter.Get().IsRouter()); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 3: Set FED & MED back to their original configuration"); + + // Setting the router role by configuration is expected to immediately detach + SuccessOrQuit(fedAndRouter.Get().SetRouterEligible(false)); + VerifyOrQuit(!fedAndRouter.Get().IsRouterRoleAllowed()); + + // Setting the mode by configuration is expected to immediately detach + SuccessOrQuit(medAndRouter.Get().SetDeviceMode(kMedDeviceMode)); + VerifyOrQuit(!medAndRouter.Get().IsFullThreadDevice()); + VerifyOrQuit(!medAndRouter.Get().IsRouterRoleAllowed()); + + // FED and MED nodes should attach as children and send no further advertisements + nexus.AdvanceTime(kAttachToRouterTime); + nexus.AdvanceTime(kMaximumRouterAdvertisementInterval); + + VerifyOrQuit(leader.Get().IsLeader()); + VerifyOrQuit(router.Get().IsRouter()); + VerifyOrQuit(reed.Get().IsChild()); + VerifyOrQuit(fedOnly.Get().IsChild()); + VerifyOrQuit(medOnly.Get().IsChild()); + VerifyOrQuit(fedAndRouter.Get().IsChild()); + VerifyOrQuit(medAndRouter.Get().IsChild()); + + nexus.SaveTestInfo("test_mle_router_role_allowed.json", &leader); +} + +} // namespace Nexus +} // namespace ot + +int main(void) +{ + ot::Nexus::TestMleRouterRoleAllowed(); + printf("All tests passed\n"); + return 0; +} diff --git a/tests/nexus/test_router_downgrade_on_sec_policy_change.cpp b/tests/nexus/test_router_downgrade_on_sec_policy_change.cpp index 7f26800be..9537a4730 100644 --- a/tests/nexus/test_router_downgrade_on_sec_policy_change.cpp +++ b/tests/nexus/test_router_downgrade_on_sec_policy_change.cpp @@ -37,24 +37,64 @@ 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 * Time::kOneSecondInMsec; + +/** + * Time to attach as a child and upgrade to router role (>120s). + */ +static constexpr uint32_t kAttachmentTime = 200 * Time::kOneSecondInMsec; + +/** + * Time testing that the leader maintains its state for at least 10s after a policy change. + */ +static constexpr uint32_t kLeaderHoldTime = 9 * Time::kOneSecondInMsec; + +/** + * The shortest interval that allows processing events. + */ +static constexpr uint32_t kShortestTime = 1 * Time::kOneSecondInMsec; + +/** + * Sufficient time for the security policy to be propagated and a router + * downgrade (>120s) or child timeout (>240s) to occur. + */ +static constexpr uint32_t kPolicyStabilizationTime = 600 * Time::kOneSecondInMsec; + +/** + * Device mode to configure as a Full Thread Device (FTD). + */ +static const Mle::DeviceMode kFtdDeviceMode(Mle::DeviceMode::kModeRxOnWhenIdle | + Mle::DeviceMode::kModeFullThreadDevice | + Mle::DeviceMode::kModeFullNetworkData); + +/** + * Device mode to configure as a Minimal End Device (MED). + */ +static const Mle::DeviceMode kMedDeviceMode(Mle::DeviceMode::kModeRxOnWhenIdle | Mle::DeviceMode::kModeFullNetworkData); + void TestRouterDowngradeOnSecPolicyChange(void) { - // This test verifies leader and router downgrade delay on security policy version threshold change. + // This test verifies leader and router downgrade delay on security policy version threshold change, + // and that the Router role is correctly controlled when reconfiguring a FED or MED to FTD after it has attached. // // Topology: // - // LEADER - // | - // | - // ROUTER + // FED -- LEADER -- MED + // | + // | + // ROUTER // Core nexus; Node &leader = nexus.CreateNode(); Node &router = nexus.CreateNode(); + Node &fed = nexus.CreateNode(); + Node &med = nexus.CreateNode(); - leader.Get().SetRouterSelectionJitter(10); - router.Get().SetRouterSelectionJitter(10); + // Default router selection jitter (120s) is used with the timing below nexus.AdvanceTime(0); @@ -64,20 +104,79 @@ void TestRouterDowngradeOnSecPolicyChange(void) Log("Form initial topology"); AllowLinkBetween(leader, router); + AllowLinkBetween(leader, fed); + AllowLinkBetween(leader, med); leader.SetName("LEADER"); router.SetName("ROUTER"); + fed.SetName("FED"); + med.SetName("MED"); leader.Form(); - nexus.AdvanceTime(13 * Time::kOneSecondInMsec); - VerifyOrQuit(leader.Get().IsLeader()); + nexus.AdvanceTime(kFormNetworkTime); router.Join(leader); - nexus.AdvanceTime(300 * Time::kOneSecondInMsec); + nexus.AdvanceTime(kAttachmentTime); + + fed.Join(leader); + SuccessOrQuit(fed.Get().SetRouterEligible(false)); + nexus.AdvanceTime(kAttachmentTime); + + med.Join(leader, Node::kAsMed); + nexus.AdvanceTime(kAttachmentTime); + + VerifyOrQuit(leader.Get().IsLeader()); VerifyOrQuit(router.Get().IsRouter()); + VerifyOrQuit(fed.Get().IsChild()); + VerifyOrQuit(med.Get().IsChild()); VerifyOrQuit(leader.Get().IsRouterRoleAllowed()); VerifyOrQuit(router.Get().IsRouterRoleAllowed()); + // End device nodes initially have their router role disallowed + VerifyOrQuit(!fed.Get().IsRouterRoleAllowed()); + VerifyOrQuit(!med.Get().IsRouterRoleAllowed()); + + Log("---------------------------------------------------------------------------------------"); + Log("After initial attachment, convert FED to FTD and verify that the router role is now allowed"); + + SuccessOrQuit(fed.Get().SetRouterEligible(true)); + VerifyOrQuit(fed.Get().IsRouterRoleAllowed()); + + nexus.AdvanceTime(kAttachmentTime); + + VerifyOrQuit(leader.Get().IsLeader()); + VerifyOrQuit(router.Get().IsRouter()); + VerifyOrQuit(fed.Get().IsRouter()); + VerifyOrQuit(med.Get().IsChild()); + + Log("---------------------------------------------------------------------------------------"); + Log("After initial attachment, convert MED to FTD and verify that the router role is now allowed"); + + SuccessOrQuit(med.Get().SetDeviceMode(kFtdDeviceMode)); + VerifyOrQuit(med.Get().IsRouterRoleAllowed()); + + nexus.AdvanceTime(kAttachmentTime); + + VerifyOrQuit(leader.Get().IsLeader()); + VerifyOrQuit(router.Get().IsRouter()); + VerifyOrQuit(fed.Get().IsRouter()); + VerifyOrQuit(med.Get().IsRouter()); + + Log("---------------------------------------------------------------------------------------"); + Log("Revert FED and MED to their original configurations, with router role disallowed"); + + SuccessOrQuit(fed.Get().SetRouterEligible(false)); + VerifyOrQuit(!fed.Get().IsRouterRoleAllowed()); + + SuccessOrQuit(med.Get().SetDeviceMode(kMedDeviceMode)); + VerifyOrQuit(!med.Get().IsRouterRoleAllowed()); + + nexus.AdvanceTime(kAttachmentTime); + + VerifyOrQuit(leader.Get().IsLeader()); + VerifyOrQuit(router.Get().IsRouter()); + VerifyOrQuit(fed.Get().IsChild()); + VerifyOrQuit(med.Get().IsChild()); Log("---------------------------------------------------------------------------------------"); Log("Change security policy, disable `R` bit and set version threshold to max value (7)"); @@ -101,19 +200,23 @@ void TestRouterDowngradeOnSecPolicyChange(void) leader.Get().SaveLocal(datasetInfo); } - // Wait for the dataset to propagate and be processed. - nexus.AdvanceTime(5 * Time::kOneSecondInMsec); + Log("---------------------------------------------------------------------------------------"); + Log("Leader should remain in Leader role for at least 10 seconds"); + + nexus.AdvanceTime(kLeaderHoldTime); VerifyOrQuit(leader.Get().IsLeader()); + // Don't verify the router's current role, as it may have downgraded already - // We check that security policy is propagated to and applied on - // leader and router. This means `IsRouterRoleAllowed()` should be - // false on both. + Log("---------------------------------------------------------------------------------------"); + Log("Verify propagated role changes"); + + // Active routers now have Router role disallowed VerifyOrQuit(!leader.Get().IsRouterRoleAllowed()); VerifyOrQuit(!router.Get().IsRouterRoleAllowed()); - - Log("Leader should stay as leader for at least 10 seconds"); - VerifyOrQuit(leader.Get().IsLeader()); + // End devices continue to have Router role disallowed + VerifyOrQuit(!fed.Get().IsRouterRoleAllowed()); + VerifyOrQuit(!med.Get().IsRouterRoleAllowed()); Log("---------------------------------------------------------------------------------------"); Log("Change back security policy. This should cancel the ongoing downgrade delay"); @@ -137,17 +240,29 @@ void TestRouterDowngradeOnSecPolicyChange(void) leader.Get().SaveLocal(datasetInfo); } - nexus.AdvanceTime(1 * Time::kOneSecondInMsec); + Log("---------------------------------------------------------------------------------------"); + Log("Make sure the Leader device remains in its role"); + + nexus.AdvanceTime(kShortestTime); + VerifyOrQuit(leader.Get().IsLeader()); VerifyOrQuit(leader.Get().IsRouterRoleAllowed()); - Log("Make sure both devices stay as leader and router"); + Log("---------------------------------------------------------------------------------------"); + Log("Make sure the Router device remains in or restores its role"); + + // Advance a sufficiently long time + nexus.AdvanceTime(kPolicyStabilizationTime); - nexus.AdvanceTime(600 * Time::kOneSecondInMsec); VerifyOrQuit(leader.Get().IsLeader()); VerifyOrQuit(router.Get().IsRouter()); + VerifyOrQuit(fed.Get().IsChild()); + VerifyOrQuit(med.Get().IsChild()); + VerifyOrQuit(leader.Get().IsRouterRoleAllowed()); VerifyOrQuit(router.Get().IsRouterRoleAllowed()); + VerifyOrQuit(!fed.Get().IsRouterRoleAllowed()); + VerifyOrQuit(!med.Get().IsRouterRoleAllowed()); Log("---------------------------------------------------------------------------------------"); Log("Change security policy again, disable `R` bit and set version threshold to max value (7)"); @@ -171,28 +286,36 @@ void TestRouterDowngradeOnSecPolicyChange(void) leader.Get().SaveLocal(datasetInfo); } - // Wait for the dataset to propagate and be processed. - nexus.AdvanceTime(5 * Time::kOneSecondInMsec); + Log("---------------------------------------------------------------------------------------"); + Log("Leader should remain in Leader role for at least 10 seconds"); + + nexus.AdvanceTime(kLeaderHoldTime); VerifyOrQuit(leader.Get().IsLeader()); - // We check that security policy is propagated to and applied on - // leader and router. This means `IsRouterRoleAllowed()` should be - // false on both. - VerifyOrQuit(!leader.Get().IsRouterRoleAllowed()); - VerifyOrQuit(!router.Get().IsRouterRoleAllowed()); - - Log("Leader should stay as leader for at least 10 seconds"); - VerifyOrQuit(leader.Get().IsLeader()); - + Log("---------------------------------------------------------------------------------------"); Log("Make sure both leader and router are downgraded and are now `detached`."); - nexus.AdvanceTime(150 * Time::kOneSecondInMsec); + nexus.AdvanceTime(kPolicyStabilizationTime); + VerifyOrQuit(leader.Get().IsDetached()); VerifyOrQuit(router.Get().IsDetached()); + VerifyOrQuit(fed.Get().IsDetached()); + VerifyOrQuit(med.Get().IsDetached()); VerifyOrQuit(!leader.Get().IsRouterRoleAllowed()); VerifyOrQuit(!router.Get().IsRouterRoleAllowed()); + VerifyOrQuit(!fed.Get().IsRouterRoleAllowed()); + VerifyOrQuit(!med.Get().IsRouterRoleAllowed()); + + Log("---------------------------------------------------------------------------------------"); + Log("Verify that the Router role remains disallowed when FED and MED nodes are re-configured as FTDs."); + + SuccessOrQuit(fed.Get().SetRouterEligible(true)); + VerifyOrQuit(!fed.Get().IsRouterRoleAllowed()); + + SuccessOrQuit(med.Get().SetDeviceMode(kFtdDeviceMode)); + VerifyOrQuit(!med.Get().IsRouterRoleAllowed()); } } // namespace Nexus diff --git a/tests/nexus/verify_1_1_5_3_6.py b/tests/nexus/verify_1_1_5_3_6.py index ef9d15cd0..dfb2fc635 100644 --- a/tests/nexus/verify_1_1_5_3_6.py +++ b/tests/nexus/verify_1_1_5_3_6.py @@ -66,17 +66,26 @@ def verify(pv): # - Description: Ensure topology is formed correctly. # - Pass Criteria: N/A print("Step 1: All") - pkts.filter_mle_cmd(consts.MLE_ADVERTISEMENT).\ + leader_pkt = pkts.filter_mle_cmd(consts.MLE_ADVERTISEMENT).\ filter_wpan_src64(LEADER).\ must_next() + leader_child_id = leader_pkt.mle.tlv.source_addr & 0x1FF + # The leader should only advertise as a router + assert leader_child_id == 0 router1_pkt = pkts.filter_mle_cmd(consts.MLE_ADVERTISEMENT).\ filter_wpan_src64(ROUTER_1).\ must_next() router1_id = (router1_pkt.mle.tlv.source_addr >> 10) + router1_child_id = router1_pkt.mle.tlv.source_addr & 0x1FF + # Router 1 should only advertise as a router + assert router1_child_id == 0 router2_pkt = pkts.filter_mle_cmd(consts.MLE_ADVERTISEMENT).\ filter_wpan_src64(ROUTER_2).\ must_next() router2_id = (router2_pkt.mle.tlv.source_addr >> 10) + router2_child_id = router2_pkt.mle.tlv.source_addr & 0x1FF + # Router 2 should only advertise as a router + assert router2_child_id == 0 # Step 2: Router_2 # @@ -111,13 +120,16 @@ def verify(pv): # - Description: Harness re-enables the device and waits for it to reattach and upgrade to a # router. # - Pass Criteria: - # - The DUT MUST reset the MLE Advertisement trickle timer and send an Advertisement. + # - The DUT MUST reset the MLE Advertisement trickle timer and send a Router Advertisement. print("Step 5: Router_2") # Verify Router 2 becomes router again (sends advertisement) and get its new ID router2_rejoin_pkt = pkts.filter_mle_cmd(consts.MLE_ADVERTISEMENT).\ filter_wpan_src64(ROUTER_2).\ must_next() router2_id_reattached = (router2_rejoin_pkt.mle.tlv.source_addr >> 10) + router2_child_id_reattached = router2_rejoin_pkt.mle.tlv.source_addr & 0x1FF + # Router 2 should only advertise as a router + assert router2_child_id_reattached == 0 # Verify Leader sends advertisement after Router 2 re-joins pkts.filter_mle_cmd(consts.MLE_ADVERTISEMENT).\ diff --git a/tests/nexus/verify_mle_router_role_allowed.py b/tests/nexus/verify_mle_router_role_allowed.py new file mode 100644 index 000000000..a0e16723a --- /dev/null +++ b/tests/nexus/verify_mle_router_role_allowed.py @@ -0,0 +1,145 @@ +#!/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 + + +def verify(pv: verify_utils.PacketVerifier): + # Test differing behaviors based on the Router role allowed + # + # Purpose & Description + # This test validates that correctly-formatted transmissions occur based on whether the Router + # role is allowed. This includes Advertisements (only from REEDs and Active Routers), + # which should not occur for devices with Router role disallowed. + # This test verifies Router role disallowed on FED and MED devices, with a duplicate + # pair used to demonstrate that these transmissions begin in the correct format when + # re-configured so that the Router role becomes allowed. + + pv.summary.show() + + LEADER = pv.vars['Leader'] + ROUTER = pv.vars['Router'] + REED = pv.vars['REED'] + FED_ONLY = pv.vars['FED_ONLY'] + MED_ONLY = pv.vars['MED_ONLY'] + FED_ROUTER = pv.vars['FED_Router'] + MED_ROUTER = pv.vars['MED_Router'] + + print("Node: Leader") + leader_packet_filter = pv.pkts.copy().filter_wpan_src64(LEADER) + # There was at least one advertisement from the Leader + leader_mle_advertisement_packet_filter = leader_packet_filter.copy().filter_mle_cmd(consts.MLE_ADVERTISEMENT) + leader_mle_advertisement_packets = [pkt for pkt in iter(leader_mle_advertisement_packet_filter.next, None)] + assert len(leader_mle_advertisement_packets) >= 1 + # And all advertisements had a Router source address (with child id of 0) + assert all(pkt.mle.tlv.source_addr & 0x1FF == 0 for pkt in leader_mle_advertisement_packets) + # And contain a route64 TLV + assert all( + pkt.must_verify(lambda p: (consts.ROUTE64_TLV) in p.mle.tlv.type) for pkt in leader_mle_advertisement_packets) + + print("Node: Router") + router_packet_filter = pv.pkts.copy().filter_wpan_src64(ROUTER) + # There was at least one advertisement from the Router + router_mle_advertisement_packet_filter = router_packet_filter.copy().filter_mle_cmd(consts.MLE_ADVERTISEMENT) + router_mle_advertisement_packets = [pkt for pkt in iter(router_mle_advertisement_packet_filter.next, None)] + assert len(router_mle_advertisement_packets) >= 1 + # And all advertisements had a Router source address (with child id of 0) + assert all(pkt.mle.tlv.source_addr & 0x1FF == 0 for pkt in router_mle_advertisement_packets) + # And contain a route64 TLV + assert all( + pkt.must_verify(lambda p: (consts.ROUTE64_TLV) in p.mle.tlv.type) for pkt in router_mle_advertisement_packets) + + print("Node: REED") + reed_packet_filter = pv.pkts.copy().filter_wpan_src64(REED) + reed_mle_advertisement_packet_filter = reed_packet_filter.copy().filter_mle_cmd(consts.MLE_ADVERTISEMENT) + reed_mle_advertisement_packets = [pkt for pkt in iter(reed_mle_advertisement_packet_filter.next, None)] + # There was at least one advertisement from the REED + assert len(reed_mle_advertisement_packets) >= 1 + # And all advertisements had a Child source address (with child id > 0) + assert all(pkt.mle.tlv.source_addr & 0x1FF > 0 for pkt in reed_mle_advertisement_packets) + # And not contain a route64 TLV + assert all( + pkt.must_not_verify(lambda p: (consts.ROUTE64_TLV) in p.mle.tlv.type) + for pkt in reed_mle_advertisement_packets) + + print("Node: FED only") + fed_only_packet_filter = pv.pkts.copy().filter_wpan_src64(FED_ONLY) + # When only a FED, no advertisements should be sent + fed_only_packet_filter.copy().filter_mle_cmd(consts.MLE_ADVERTISEMENT).must_not_next() + + print("Node: MED only") + med_only_packet_filter = pv.pkts.copy().filter_wpan_src64(MED_ONLY) + # When only a FED, no advertisements should be sent + med_only_packet_filter.copy().filter_mle_cmd(consts.MLE_ADVERTISEMENT).must_not_next() + + print("Node: FED and Router") + fed_and_router_packet_filter = pv.pkts.copy() + # Exactly one advertisement should have been sent when configured as a router + fed_and_router_mle_advertisement_packet_filter = fed_and_router_packet_filter.filter_mle_cmd( + consts.MLE_ADVERTISEMENT).filter_wpan_src64(FED_ROUTER) + fed_and_router_mle_advertisement_packets = [ + pkt for pkt in iter(fed_and_router_mle_advertisement_packet_filter.next, None) + ] + # There was at least one advertisement as a Router + assert len(fed_and_router_mle_advertisement_packets) >= 1, f"{len(fed_and_router_mle_advertisement_packets)}" + # And all advertisements had a Router source address (with child id of 0) + assert all(pkt.mle.tlv.source_addr & 0x1FF == 0 for pkt in fed_and_router_mle_advertisement_packets) + # And contain a route64 TLV + assert all( + pkt.must_verify(lambda p: (consts.ROUTE64_TLV) in p.mle.tlv.type) + for pkt in fed_and_router_mle_advertisement_packets) + + print("Node: MED and Router") + med_and_router_packet_filter = pv.pkts.copy() + # Exactly one advertisement should have been sent when configured as a router + med_and_router_mle_advertisement_packet_filter = med_and_router_packet_filter.filter_mle_cmd( + consts.MLE_ADVERTISEMENT).filter_wpan_src64(MED_ROUTER) + med_and_router_mle_advertisement_packets = [ + pkt for pkt in iter(med_and_router_mle_advertisement_packet_filter.next, None) + ] + # There was at least one advertisement as a Router + assert len(med_and_router_mle_advertisement_packets) >= 1, f"{len(med_and_router_mle_advertisement_packets)}" + # And all advertisements had a Router source address (with child id of 0) + assert all(pkt.mle.tlv.source_addr & 0x1FF == 0 for pkt in med_and_router_mle_advertisement_packets) + # And contain a route64 TLV + assert all( + pkt.must_verify(lambda p: (consts.ROUTE64_TLV) in p.mle.tlv.type) + for pkt in med_and_router_mle_advertisement_packets) + + +if __name__ == '__main__': + verify_utils.run_main(verify)