From 82e816b82f575283f0f3ff3c7b46ebeb87c258e0 Mon Sep 17 00:00:00 2001 From: Yang Sun Date: Tue, 7 Feb 2023 13:10:38 +0800 Subject: [PATCH] [mle] retransmit link request after router reset (#8704) According to Thread 1.3.0 Section 4.7.1.3, router synchronization after reset requires the multicast link request message to be retransmitted until a response is received, or the maximum transmission limit has been reached. If the router was a leader or had more than 5 children prior to reset, it MUST be retransmitted as a critical message. Each multicast retransmission of an MLE Message on a TL1 or TL2 Link must be delayed by MLE_MULTICAST_RETRANSMISSION_DELAY multiplied by a random value between 0.9 and 1.1 with a resolution of at least 1 ms. This commit implements the above requirement. With this requirement, in the cases when a router is reset and there is no other router to respond to the link request then it takes longer time for the router to join thread network. Thus some test cases are modified to allow longer router sync time in such cases. --- src/core/thread/mle.cpp | 26 +++ src/core/thread/mle.hpp | 7 +- src/core/thread/mle_router.cpp | 23 ++- src/core/thread/mle_router.hpp | 4 + src/core/thread/mle_types.hpp | 7 + tests/scripts/expect/_common.exp | 2 +- .../thread-cert/Cert_5_3_06_RouterIdMask.py | 2 +- .../thread-cert/Cert_5_5_02_LeaderReboot.py | 2 +- .../Cert_5_5_03_SplitMergeChildren.py | 2 +- .../Cert_5_5_04_SplitMergeRouters.py | 2 +- .../Cert_5_5_07_SplitMergeThreeWay.py | 2 +- .../Cert_7_1_06_BorderRouterAsLeader.py | 2 +- .../Cert_9_2_15_PendingPartition.py | 2 +- .../Cert_9_2_16_ActivePendingPartition.py | 2 +- tests/scripts/thread-cert/config.py | 8 + tests/scripts/thread-cert/test_detach.py | 7 +- .../thread-cert/test_history_tracker.py | 2 +- ...est_leader_reboot_multiple_link_request.py | 155 +++++++++++++++ ...est_router_reboot_multiple_link_request.py | 176 ++++++++++++++++++ tests/scripts/thread-cert/test_set_mliid.py | 2 +- .../v1_2_test_backbone_router_service.py | 4 +- .../thread-cert/v1_2_test_csl_transmission.py | 4 +- ..._2_test_multicast_listener_registration.py | 1 + .../test-017-parent-reset-child-recovery.py | 2 +- .../toranj/ncp/test-027-child-mode-change.py | 3 +- .../test-600-channel-manager-properties.py | 2 +- tests/unit/test_routing_manager.cpp | 18 +- tests/unit/test_srp_server.cpp | 14 +- 28 files changed, 451 insertions(+), 32 deletions(-) create mode 100755 tests/scripts/thread-cert/test_leader_reboot_multiple_link_request.py create mode 100755 tests/scripts/thread-cert/test_router_reboot_multiple_link_request.py diff --git a/src/core/thread/mle.cpp b/src/core/thread/mle.cpp index 0dc90cda4..80902ea16 100644 --- a/src/core/thread/mle.cpp +++ b/src/core/thread/mle.cpp @@ -83,6 +83,9 @@ Mle::Mle(Instance &aInstance) , mAttachTimer(aInstance) , mDelayedResponseTimer(aInstance) , mMessageTransmissionTimer(aInstance) +#if OPENTHREAD_FTD + , mWasLeader(false) +#endif , mAttachMode(kAnyPartition) , mChildUpdateAttempts(0) , mChildUpdateRequestState(kChildUpdateRequestNone) @@ -435,6 +438,8 @@ void Mle::Restore(void) Get().SetPreviousPartitionId(networkInfo.GetPreviousPartitionId()); Get().Restore(); } + + mWasLeader = networkInfo.GetRole() == kRoleLeader; #endif // Successfully restored the network information from non-volatile settings after boot. @@ -1885,6 +1890,14 @@ void Mle::ScheduleMessageTransmissionTimer(void) { uint32_t interval = 0; +#if OPENTHREAD_FTD + if (mRole == kRoleDetached && mLinkRequestAttempts > 0) + { + ExitNow(interval = Random::NonCrypto::GetUint32InRange(kMulticastTransmissionDelayMin, + kMulticastTransmissionDelayMax)); + } +#endif + switch (mChildUpdateRequestState) { case kChildUpdateRequestNone: @@ -1940,6 +1953,19 @@ void Mle::HandleMessageTransmissionTimer(void) // - Retransmission of "Child Update Request", // - Retransmission of "Data Request" on a child, // - Sending periodic keep-alive "Child Update Request" messages on a non-sleepy (rx-on) child. + // - Retransmission of "Link Request" after router reset + +#if OPENTHREAD_FTD + // Retransmit multicast link request if no response has been received + // and maximum transmission limit has not been reached. + if (mRole == kRoleDetached && mLinkRequestAttempts > 0) + { + IgnoreError(Get().SendLinkRequest(nullptr)); + mLinkRequestAttempts--; + ScheduleMessageTransmissionTimer(); + ExitNow(); + } +#endif switch (mChildUpdateRequestState) { diff --git a/src/core/thread/mle.hpp b/src/core/thread/mle.hpp index dafbac82f..c23d67a11 100644 --- a/src/core/thread/mle.hpp +++ b/src/core/thread/mle.hpp @@ -1706,6 +1706,8 @@ protected: #endif + void ScheduleMessageTransmissionTimer(void); + private: // Declare early so we can use in as `TimerMilli` callbacks. void HandleAttachTimer(void); @@ -1733,6 +1735,10 @@ protected: AttachTimer mAttachTimer; ///< The timer for driving the attach process. DelayTimer mDelayedResponseTimer; ///< The timer to delay MLE responses. MsgTxTimer mMessageTransmissionTimer; ///< The timer for (re-)sending of MLE messages (e.g. Child Update). +#if OPENTHREAD_FTD + uint8_t mLinkRequestAttempts; ///< Number of remaining link requests to send after reset. + bool mWasLeader; ///< Indicating if device was leader before reset. +#endif private: static constexpr uint8_t kMleHopLimit = 255; @@ -1924,7 +1930,6 @@ private: void SendDelayedResponse(TxMessage &aMessage, const DelayedResponseMetadata &aMetadata); static void HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo); void HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo); - void ScheduleMessageTransmissionTimer(void); void ReestablishLinkWithNeighbor(Neighbor &aNeighbor); static void HandleDetachGracefullyTimer(Timer &aTimer); void HandleDetachGracefullyTimer(void); diff --git a/src/core/thread/mle_router.cpp b/src/core/thread/mle_router.cpp index a0d1fa055..bf4d7fdf0 100644 --- a/src/core/thread/mle_router.cpp +++ b/src/core/thread/mle_router.cpp @@ -184,6 +184,20 @@ exit: return error; } +// If the router was a leader or had more than 5 children prior to reset, +// the multicast link request is retransmitted as a critical message. +void MleRouter::SetLinkRequestTransmissionCounter(void) +{ + uint16_t numOfChildren = mChildTable.GetNumChildren(Child::kInStateValidOrRestoring); + + mLinkRequestAttempts = kMaxTransmissionCount; + + if (mWasLeader || numOfChildren >= kMinCriticalChildrenCount) + { + mLinkRequestAttempts = kMaxCriticalTransmissionCount; + } +} + Error MleRouter::BecomeRouter(ThreadStatusTlv::Status aStatus) { Error error = kErrorNone; @@ -201,7 +215,10 @@ Error MleRouter::BecomeRouter(ThreadStatusTlv::Status aStatus) switch (mRole) { case kRoleDetached: + SetLinkRequestTransmissionCounter(); SuccessOrExit(error = SendLinkRequest(nullptr)); + mLinkRequestAttempts--; + ScheduleMessageTransmissionTimer(); Get().RegisterReceiver(TimeTicker::kMleRouter); break; @@ -865,7 +882,8 @@ Error MleRouter::HandleLinkAccept(RxInfo &aRxInfo, bool aRequest) break; case Neighbor::kStateInvalid: - VerifyOrExit((mChallengeTimeout > 0) && (response == mChallenge), error = kErrorSecurity); + VerifyOrExit((mLinkRequestAttempts > 0 || mChallengeTimeout > 0) && (response == mChallenge), + error = kErrorSecurity); OT_FALL_THROUGH; @@ -930,6 +948,7 @@ Error MleRouter::HandleLinkAccept(RxInfo &aRxInfo, bool aRequest) SetStateRouter(GetRloc16()); } + mLinkRequestAttempts = 0; // completed router sync after reset, no more link request to retransmit mRetrieveNewNetworkData = true; IgnoreError(SendDataRequest(aRxInfo.mMessageInfo.GetPeerAddr(), kDataRequestTlvs)); @@ -1541,7 +1560,7 @@ void MleRouter::HandleTimeTick(void) switch (mRole) { case kRoleDetached: - if (mChallengeTimeout == 0) + if (mChallengeTimeout == 0 && mLinkRequestAttempts == 0) { IgnoreError(BecomeDetached()); ExitNow(); diff --git a/src/core/thread/mle_router.hpp b/src/core/thread/mle_router.hpp index 4bda1d240..1d34380be 100644 --- a/src/core/thread/mle_router.hpp +++ b/src/core/thread/mle_router.hpp @@ -556,6 +556,8 @@ private: static constexpr uint8_t kChildRouterLinks = OPENTHREAD_CONFIG_MLE_CHILD_ROUTER_LINKS; static constexpr uint8_t kMaxChildIpAddresses = OPENTHREAD_CONFIG_MLE_IP_ADDRS_PER_CHILD; + static constexpr uint8_t kMinCriticalChildrenCount = 6; + void HandleDetachStart(void); void HandleChildStart(AttachMode aMode); void HandleLinkRequest(RxInfo &aRxInfo); @@ -627,6 +629,8 @@ private: void HandleAdvertiseTrickleTimer(void); void HandleTimeTick(void); + void SetLinkRequestTransmissionCounter(void); + TrickleTimer mAdvertiseTrickleTimer; ChildTable mChildTable; diff --git a/src/core/thread/mle_types.hpp b/src/core/thread/mle_types.hpp index f396c0879..076f83379 100644 --- a/src/core/thread/mle_types.hpp +++ b/src/core/thread/mle_types.hpp @@ -100,6 +100,13 @@ constexpr uint32_t kMaxResponseDelay = 1000; ///< Max response del constexpr uint32_t kChildIdRequestTimeout = 5000; ///< Max delay to rx a Child ID Request (in msec) constexpr uint32_t kLinkRequestTimeout = 2000; ///< Max delay to rx a Link Accept constexpr uint8_t kMulticastLinkRequestDelay = 5; ///< Max delay for sending a mcast Link Request (in sec) +constexpr uint8_t kMaxCriticalTransmissionCount = 6; ///< Max number of times an critical MLE message may be transmitted + +constexpr uint32_t kMulticastTransmissionDelay = 5000; ///< Delay for retransmitting a multicast packet (in msec) +constexpr uint32_t kMulticastTransmissionDelayMin = + kMulticastTransmissionDelay * 9 / 10; ///< Min delay for retransmitting a multicast packet (in msec) +constexpr uint32_t kMulticastTransmissionDelayMax = + kMulticastTransmissionDelay * 11 / 10; ///< Max delay for retransmitting a multicast packet (in msec) constexpr uint32_t kMinTimeoutKeepAlive = (((kMaxChildKeepAliveAttempts + 1) * kUnicastRetransmissionDelay) / 1000); constexpr uint32_t kMinPollPeriod = OPENTHREAD_CONFIG_MAC_MINIMUM_POLL_PERIOD; diff --git a/tests/scripts/expect/_common.exp b/tests/scripts/expect/_common.exp index 3cdb89b46..263ed687d 100644 --- a/tests/scripts/expect/_common.exp +++ b/tests/scripts/expect/_common.exp @@ -37,7 +37,7 @@ proc skip_on_macos {} { proc wait_for {command success {failure {[\r\n]FAILURE_NOT_EXPECTED[\r\n]}}} { set timeout 1 - for {set i 0} {$i < 20} {incr i} { + for {set i 0} {$i < 40} {incr i} { if {$command != ""} { send "$command\n" } diff --git a/tests/scripts/thread-cert/Cert_5_3_06_RouterIdMask.py b/tests/scripts/thread-cert/Cert_5_3_06_RouterIdMask.py index 1f57e9f46..2f227f9f7 100755 --- a/tests/scripts/thread-cert/Cert_5_3_06_RouterIdMask.py +++ b/tests/scripts/thread-cert/Cert_5_3_06_RouterIdMask.py @@ -114,7 +114,7 @@ class Cert_5_3_6_RouterIdMask(thread_cert.TestCase): # 5 self.nodes[ROUTER2].start() - self.simulator.go(config.ROUTER_STARTUP_DELAY) + self.simulator.go(config.ROUTER_RESET_DELAY) self.assertEqual(self.nodes[ROUTER2].get_state(), 'router') self.simulator.go(config.MAX_ADVERTISEMENT_INTERVAL) diff --git a/tests/scripts/thread-cert/Cert_5_5_02_LeaderReboot.py b/tests/scripts/thread-cert/Cert_5_5_02_LeaderReboot.py index 6aff3cfce..bdbf3c5b8 100755 --- a/tests/scripts/thread-cert/Cert_5_5_02_LeaderReboot.py +++ b/tests/scripts/thread-cert/Cert_5_5_02_LeaderReboot.py @@ -84,7 +84,7 @@ class Cert_5_5_2_LeaderReboot(thread_cert.TestCase): self.assertEqual(self.nodes[ROUTER].get_state(), 'leader') self.nodes[LEADER].start() - self.simulator.go(config.ROUTER_STARTUP_DELAY) + self.simulator.go(config.LEADER_RESET_DELAY) self.assertEqual(self.nodes[LEADER].get_state(), 'router') addrs = self.nodes[ED].get_addrs() diff --git a/tests/scripts/thread-cert/Cert_5_5_03_SplitMergeChildren.py b/tests/scripts/thread-cert/Cert_5_5_03_SplitMergeChildren.py index 92040d013..06f77f65d 100755 --- a/tests/scripts/thread-cert/Cert_5_5_03_SplitMergeChildren.py +++ b/tests/scripts/thread-cert/Cert_5_5_03_SplitMergeChildren.py @@ -114,7 +114,7 @@ class Cert_5_5_3_SplitMergeChildren(thread_cert.TestCase): self.assertEqual(self.nodes[ROUTER2].get_state(), 'leader') self.nodes[LEADER].start() - self.simulator.go(config.ROUTER_STARTUP_DELAY) + self.simulator.go(config.LEADER_RESET_DELAY) self.assertEqual(self.nodes[LEADER].get_state(), 'router') self.simulator.go(30) diff --git a/tests/scripts/thread-cert/Cert_5_5_04_SplitMergeRouters.py b/tests/scripts/thread-cert/Cert_5_5_04_SplitMergeRouters.py index 8c08d1ecf..2bcc87652 100755 --- a/tests/scripts/thread-cert/Cert_5_5_04_SplitMergeRouters.py +++ b/tests/scripts/thread-cert/Cert_5_5_04_SplitMergeRouters.py @@ -102,7 +102,7 @@ class Cert_5_5_4_SplitMergeRouters(thread_cert.TestCase): self.simulator.go(150) self.nodes[LEADER].start() - self.simulator.go(50) + self.simulator.go(50 + config.LEADER_RESET_DELAY) self.assertEqual(self.nodes[LEADER].get_state(), 'router') diff --git a/tests/scripts/thread-cert/Cert_5_5_07_SplitMergeThreeWay.py b/tests/scripts/thread-cert/Cert_5_5_07_SplitMergeThreeWay.py index 3b6e201ff..fbb9dee48 100755 --- a/tests/scripts/thread-cert/Cert_5_5_07_SplitMergeThreeWay.py +++ b/tests/scripts/thread-cert/Cert_5_5_07_SplitMergeThreeWay.py @@ -93,7 +93,7 @@ class Cert_5_5_7_SplitMergeThreeWay(thread_cert.TestCase): self.simulator.go(140) self.nodes[LEADER1].start() - self.simulator.go(30) + self.simulator.go(30 + config.LEADER_RESET_DELAY) addrs = self.nodes[LEADER1].get_addrs() for addr in addrs: diff --git a/tests/scripts/thread-cert/Cert_7_1_06_BorderRouterAsLeader.py b/tests/scripts/thread-cert/Cert_7_1_06_BorderRouterAsLeader.py index f6053cb6b..36e0f9c97 100755 --- a/tests/scripts/thread-cert/Cert_7_1_06_BorderRouterAsLeader.py +++ b/tests/scripts/thread-cert/Cert_7_1_06_BorderRouterAsLeader.py @@ -143,7 +143,7 @@ class Cert_7_1_6_BorderRouterAsLeader(thread_cert.TestCase): self.simulator.go(720) self.nodes[ROUTER_1].start() - self.simulator.go(config.ROUTER_STARTUP_DELAY) + self.simulator.go(config.ROUTER_RESET_DELAY) self.assertEqual(self.nodes[ROUTER_1].get_state(), 'router') self.collect_rloc16s() diff --git a/tests/scripts/thread-cert/Cert_9_2_15_PendingPartition.py b/tests/scripts/thread-cert/Cert_9_2_15_PendingPartition.py index 13d3e2bec..a0e2bccda 100755 --- a/tests/scripts/thread-cert/Cert_9_2_15_PendingPartition.py +++ b/tests/scripts/thread-cert/Cert_9_2_15_PendingPartition.py @@ -138,7 +138,7 @@ class Cert_9_2_15_PendingPartition(thread_cert.TestCase): self.simulator.go(100) self.nodes[ROUTER2].start() - self.simulator.go(config.ROUTER_STARTUP_DELAY) + self.simulator.go(config.ROUTER_RESET_DELAY) self.assertEqual(self.nodes[ROUTER2].get_state(), 'router') self.simulator.go(100) diff --git a/tests/scripts/thread-cert/Cert_9_2_16_ActivePendingPartition.py b/tests/scripts/thread-cert/Cert_9_2_16_ActivePendingPartition.py index e257f28d3..8515659d5 100755 --- a/tests/scripts/thread-cert/Cert_9_2_16_ActivePendingPartition.py +++ b/tests/scripts/thread-cert/Cert_9_2_16_ActivePendingPartition.py @@ -142,7 +142,7 @@ class Cert_9_2_16_ActivePendingPartition(thread_cert.TestCase): self.simulator.go(100) self.nodes[ROUTER2].start() - self.simulator.go(config.ROUTER_STARTUP_DELAY) + self.simulator.go(config.ROUTER_RESET_DELAY) self.assertEqual(self.nodes[ROUTER2].get_state(), 'router') self.assertEqual(self.nodes[COMMISSIONER].get_network_name(), NETWORK_NAME_FINAL) diff --git a/tests/scripts/thread-cert/config.py b/tests/scripts/thread-cert/config.py index 3878d4e2f..cf04cbe74 100755 --- a/tests/scripts/thread-cert/config.py +++ b/tests/scripts/thread-cert/config.py @@ -127,6 +127,7 @@ PANID = 0xface LEADER_STARTUP_DELAY = 12 ROUTER_STARTUP_DELAY = 10 +ED_STARTUP_DELAY = 5 BORDER_ROUTER_STARTUP_DELAY = 20 MAX_NEIGHBOR_AGE = 100 INFINITE_COST_TIMEOUT = 90 @@ -153,6 +154,13 @@ PACKET_VERIFICATION_NONE = 0 PACKET_VERIFICATION_DEFAULT = 1 PACKET_VERIFICATION_TREL = 2 +# After leader reset it may retransmit link request 6 times with max 5.5s interval +LEADER_RESET_DELAY = 41 +# After router reset it may retransmit link request 3 times with max 5.5s interval +ROUTER_RESET_DELAY = 23 +MLE_MAX_CRITICAL_TRANSMISSION_COUNT = 6 +MLE_MAX_TRANSMISSION_COUNT = 3 + def create_default_network_data_prefix_sub_tlvs_factories(): return { diff --git a/tests/scripts/thread-cert/test_detach.py b/tests/scripts/thread-cert/test_detach.py index 2c1693d4c..954df9844 100755 --- a/tests/scripts/thread-cert/test_detach.py +++ b/tests/scripts/thread-cert/test_detach.py @@ -145,10 +145,13 @@ class TestDetach(thread_cert.TestCase): self.assertEqual(leader.get_state(), 'disabled') leader.start() - self.simulator.go(config.LEADER_STARTUP_DELAY) + # leader didn't become leader after the last start(), so it re-syncs in a non-critical manner thus taking ROUTER_RESET_DELAY to recover + self.simulator.go(config.ROUTER_RESET_DELAY / 2) + self.assertEqual(leader.get_state(), 'detached') + self.simulator.go(config.ROUTER_RESET_DELAY / 2) self.assertEqual(leader.get_state(), 'leader') router1.start() - self.simulator.go(config.ROUTER_STARTUP_DELAY) + self.simulator.go(config.ROUTER_RESET_DELAY) self.assertEqual(router1.get_state(), 'router') leader.thread_stop() diff --git a/tests/scripts/thread-cert/test_history_tracker.py b/tests/scripts/thread-cert/test_history_tracker.py index 74bbc1b04..2424f2a0f 100755 --- a/tests/scripts/thread-cert/test_history_tracker.py +++ b/tests/scripts/thread-cert/test_history_tracker.py @@ -128,7 +128,7 @@ class TestHistoryTracker(thread_cert.TestCase): # Start leader and child leader.start() - self.simulator.go(SHORT_WAIT * 2) + self.simulator.go(config.LEADER_RESET_DELAY) self.assertEqual(leader.get_state(), 'leader') child.start() diff --git a/tests/scripts/thread-cert/test_leader_reboot_multiple_link_request.py b/tests/scripts/thread-cert/test_leader_reboot_multiple_link_request.py new file mode 100755 index 000000000..ad9f00e26 --- /dev/null +++ b/tests/scripts/thread-cert/test_leader_reboot_multiple_link_request.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2023, 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 copy +import unittest + +import command +import config +import mle +import thread_cert +from pktverify.consts import MLE_PARENT_REQUEST, MLE_LINK_REQUEST, MLE_LINK_ACCEPT, MLE_LINK_ACCEPT_AND_REQUEST, SOURCE_ADDRESS_TLV, CHALLENGE_TLV, RESPONSE_TLV, LINK_LAYER_FRAME_COUNTER_TLV, ROUTE64_TLV, ADDRESS16_TLV, LEADER_DATA_TLV, TLV_REQUEST_TLV, VERSION_TLV +from pktverify.packet_verifier import PacketVerifier +from pktverify.null_field import nullField + +DUT_LEADER = 1 +DUT_ROUTER1 = 2 + +# Test Purpose and Description: +# ----------------------------- +# The purpose of this test case is to show that when the Leader is rebooted, it sends MLE_MAX_CRITICAL_TRANSMISSION_COUNT MLE link request packets if no response is received. +# +# Test Topology: +# ------------- +# Leader +# | +# Router +# +# DUT Types: +# ---------- +# Leader +# Router + + +class Test_LeaderRebootMultipleLinkRequest(thread_cert.TestCase): + #USE_MESSAGE_FACTORY = False + + TOPOLOGY = { + DUT_LEADER: { + 'name': 'LEADER', + 'mode': 'rdn', + 'allowlist': [DUT_ROUTER1] + }, + DUT_ROUTER1: { + 'name': 'ROUTER', + 'mode': 'rdn', + 'allowlist': [DUT_LEADER] + }, + } + + def _setUpLeader(self): + self.nodes[DUT_LEADER].add_allowlist(self.nodes[DUT_ROUTER1].get_addr64()) + self.nodes[DUT_LEADER].enable_allowlist() + + def test(self): + self.nodes[DUT_LEADER].start() + self.simulator.go(config.LEADER_STARTUP_DELAY) + self.assertEqual(self.nodes[DUT_LEADER].get_state(), 'leader') + + self.nodes[DUT_ROUTER1].start() + self.simulator.go(config.ROUTER_STARTUP_DELAY) + self.assertEqual(self.nodes[DUT_ROUTER1].get_state(), 'router') + + leader_rloc = self.nodes[DUT_LEADER].get_ip6_address(config.ADDRESS_TYPE.RLOC) + + leader_rloc16 = self.nodes[DUT_LEADER].get_addr16() + self.nodes[DUT_LEADER].reset() + self.assertFalse(self.nodes[DUT_ROUTER1].ping(leader_rloc)) + self._setUpLeader() + + # Router1 will not reply to leader's link request + self.nodes[DUT_ROUTER1].clear_allowlist() + + self.nodes[DUT_LEADER].start() + + self.simulator.go(config.LEADER_RESET_DELAY) + + def verify(self, pv): + pkts = pv.pkts + pv.summary.show() + + LEADER = pv.vars['LEADER'] + ROUTER = pv.vars['ROUTER'] + + # Verify topology is formed correctly. + pv.verify_attached('ROUTER', 'LEADER') + + # The DUT MUST send properly formatted MLE Advertisements with + # an IP Hop Limit of 255 to the Link-Local All Nodes multicast + # address (FF02::1). + # The following TLVs MUST be present in the MLE Advertisements: + # - Leader Data TLV + # - Route64 TLV + # - Source Address TLV + with pkts.save_index(): + pkts.filter_wpan_src64(LEADER).\ + filter_mle_advertisement('Leader').\ + must_next() + pkts.filter_wpan_src64(ROUTER).\ + filter_mle_advertisement('Router').\ + must_next() + + pkts.filter_ping_request().\ + filter_wpan_src64(ROUTER).\ + must_next() + + # The Leader MUST send MLE_MAX_CRITICAL_TRANSMISSION_COUNT multicast Link Request + # The following TLVs MUST be present in the Link Request: + # - Challenge TLV + # - Version TLV + # - TLV Request TLV: Address16 TLV, Route64 TLV + for i in range(0, config.MLE_MAX_CRITICAL_TRANSMISSION_COUNT): + pkts.filter_wpan_src64(LEADER).\ + filter_LLARMA().\ + filter_mle_cmd(MLE_LINK_REQUEST).\ + filter(lambda p: { + CHALLENGE_TLV, + VERSION_TLV, + TLV_REQUEST_TLV, + ADDRESS16_TLV, + ROUTE64_TLV + } <= set(p.mle.tlv.type) and\ + p.mle.tlv.addr16 is nullField and\ + p.mle.tlv.route64.id_mask is nullField + ).\ + must_next() + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/scripts/thread-cert/test_router_reboot_multiple_link_request.py b/tests/scripts/thread-cert/test_router_reboot_multiple_link_request.py new file mode 100755 index 000000000..6773b65a7 --- /dev/null +++ b/tests/scripts/thread-cert/test_router_reboot_multiple_link_request.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2023, 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 copy +import unittest + +import command +import config +import mle +import thread_cert +from pktverify.consts import MLE_PARENT_REQUEST, MLE_LINK_REQUEST, MLE_LINK_ACCEPT, MLE_LINK_ACCEPT_AND_REQUEST, SOURCE_ADDRESS_TLV, CHALLENGE_TLV, RESPONSE_TLV, LINK_LAYER_FRAME_COUNTER_TLV, ROUTE64_TLV, ADDRESS16_TLV, LEADER_DATA_TLV, TLV_REQUEST_TLV, VERSION_TLV +from pktverify.packet_verifier import PacketVerifier +from pktverify.null_field import nullField + +LEADER = 1 +DUT_ROUTER = 2 +MED1 = 3 +MED2 = 4 +MED3 = 5 +MED4 = 6 +MED5 = 7 +MED6 = 8 + +# Test Purpose and Description: +# ----------------------------- +# The purpose of this test case is to show that when a router with > 5 children is rebooted, it sends MLE_MAX_CRITICAL_TRANSMISSION_COUNT MLE link request packets if no response is received. +# +# Test Topology: +# ------------- +# Leader +# | +# Router ------------------------+ +# | | | | | | +# MED1 MED2 MED3 MED4 MED5 MED6 +# +# DUT Types: +# ---------- +# Router + + +class Test_LeaderRebootMultipleLinkRequest(thread_cert.TestCase): + #USE_MESSAGE_FACTORY = False + + TOPOLOGY = { + LEADER: { + 'name': 'LEADER', + 'mode': 'rdn', + 'allowlist': [DUT_ROUTER] + }, + DUT_ROUTER: { + 'name': 'ROUTER', + 'mode': 'rdn', + 'allowlist': [LEADER, MED1, MED2, MED3, MED4, MED5, MED6] + }, + MED1: { + 'name': 'MED1', + 'mode': 'rn', + 'allowlist': [DUT_ROUTER] + }, + MED2: { + 'name': 'MED2', + 'mode': 'rn', + 'allowlist': [DUT_ROUTER] + }, + MED3: { + 'name': 'MED3', + 'mode': 'rn', + 'allowlist': [DUT_ROUTER] + }, + MED4: { + 'name': 'MED4', + 'mode': 'rn', + 'allowlist': [DUT_ROUTER] + }, + MED5: { + 'name': 'MED5', + 'mode': 'rn', + 'allowlist': [DUT_ROUTER] + }, + MED6: { + 'name': 'MED6', + 'mode': 'rn', + 'allowlist': [DUT_ROUTER] + }, + } + + def test(self): + self.nodes[LEADER].start() + self.simulator.go(config.LEADER_STARTUP_DELAY) + self.assertEqual(self.nodes[LEADER].get_state(), 'leader') + + self.nodes[DUT_ROUTER].start() + self.simulator.go(config.ROUTER_STARTUP_DELAY) + self.assertEqual(self.nodes[DUT_ROUTER].get_state(), 'router') + + for medid in range(MED1, MED6 + 1): + self.nodes[medid].start() + self.simulator.go(config.ED_STARTUP_DELAY) + self.assertEqual(self.nodes[medid].get_state(), 'child') + + self.simulator.go(config.MAX_ADVERTISEMENT_INTERVAL) + + self.nodes[DUT_ROUTER].reset() + # Leader will not reply to router's link request + self.nodes[LEADER].clear_allowlist() + + self.nodes[DUT_ROUTER].start() + + self.simulator.go(config.LEADER_RESET_DELAY) + + def verify(self, pv): + pkts = pv.pkts + pv.summary.show() + + LEADER = pv.vars['LEADER'] + ROUTER = pv.vars['ROUTER'] + + # Verify topology is formed correctly. + pv.verify_attached('ROUTER', 'LEADER') + for i in range(1, 7): + pv.verify_attached('MED%d' % i, 'ROUTER', 'MTD') + + pkts.filter_wpan_src64(ROUTER).\ + filter_mle_advertisement('Router').\ + must_next() + + # The router MUST send MLE_MAX_CRITICAL_TRANSMISSION_COUNT multicast Link Request + # The following TLVs MUST be present in the Link Request: + # - Challenge TLV + # - Version TLV + # - TLV Request TLV: Address16 TLV, Route64 TLV + for i in range(0, config.MLE_MAX_CRITICAL_TRANSMISSION_COUNT): + pkts.filter_wpan_src64(ROUTER).\ + filter_LLARMA().\ + filter_mle_cmd(MLE_LINK_REQUEST).\ + filter(lambda p: { + CHALLENGE_TLV, + VERSION_TLV, + TLV_REQUEST_TLV, + ADDRESS16_TLV, + ROUTE64_TLV + } <= set(p.mle.tlv.type) and\ + p.mle.tlv.addr16 is nullField and\ + p.mle.tlv.route64.id_mask is nullField + ).\ + must_next() + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/scripts/thread-cert/test_set_mliid.py b/tests/scripts/thread-cert/test_set_mliid.py index 760f0da6c..292b6aea8 100755 --- a/tests/scripts/thread-cert/test_set_mliid.py +++ b/tests/scripts/thread-cert/test_set_mliid.py @@ -63,7 +63,7 @@ class Test_SetMlIid(thread_cert.TestCase): self.nodes[LEADER].reset() self.nodes[LEADER].start() - self.simulator.go(config.LEADER_STARTUP_DELAY) + self.simulator.go(config.LEADER_RESET_DELAY) self.assertEqual(self.nodes[LEADER].get_state(), 'leader') # Ensure ML-IID is persistent after reset. diff --git a/tests/scripts/thread-cert/v1_2_test_backbone_router_service.py b/tests/scripts/thread-cert/v1_2_test_backbone_router_service.py index 1a9d54df8..36d52640e 100755 --- a/tests/scripts/thread-cert/v1_2_test_backbone_router_service.py +++ b/tests/scripts/thread-cert/v1_2_test_backbone_router_service.py @@ -144,7 +144,7 @@ class TestBackboneRouterService(thread_cert.TestCase): self.nodes[BBR_1].set_domain_prefix(config.DOMAIN_PREFIX) self.nodes[BBR_1].enable_backbone_router() self.nodes[BBR_1].start() - WAIT_TIME = WAIT_ATTACH + ROUTER_SELECTION_JITTER + WAIT_TIME = config.ROUTER_RESET_DELAY self.simulator.go(WAIT_TIME) self.assertEqual(self.nodes[BBR_1].get_state(), 'router') WAIT_TIME = BBR_REGISTRATION_JITTER + WAIT_REDUNDANCE @@ -229,7 +229,7 @@ class TestBackboneRouterService(thread_cert.TestCase): self.nodes[BBR_2].enable_backbone_router() self.nodes[BBR_2].interface_up() self.nodes[BBR_2].thread_start() - WAIT_TIME = WAIT_ATTACH + ROUTER_SELECTION_JITTER + WAIT_TIME = config.ROUTER_RESET_DELAY self.simulator.go(WAIT_TIME) self.assertEqual(self.nodes[BBR_2].get_state(), 'router') WAIT_TIME = BBR_REGISTRATION_JITTER + WAIT_REDUNDANCE diff --git a/tests/scripts/thread-cert/v1_2_test_csl_transmission.py b/tests/scripts/thread-cert/v1_2_test_csl_transmission.py index 80e8204f5..a799922dd 100755 --- a/tests/scripts/thread-cert/v1_2_test_csl_transmission.py +++ b/tests/scripts/thread-cert/v1_2_test_csl_transmission.py @@ -125,7 +125,7 @@ class SSED_CslTransmission(thread_cert.TestCase): # Check if SSED is able to resynchronize with the parent after it is gone longer than the timeout self.nodes[LEADER].start() - self.simulator.go(config.LEADER_STARTUP_DELAY) + self.simulator.go(config.LEADER_RESET_DELAY) self.nodes[SSED_1].set_csl_timeout(8) self.nodes[SSED_1].set_timeout(10) self.simulator.go(2) @@ -133,7 +133,7 @@ class SSED_CslTransmission(thread_cert.TestCase): self.simulator.go(25) self.flush_all() self.nodes[LEADER].start() - self.simulator.go(config.LEADER_STARTUP_DELAY) + self.simulator.go(config.LEADER_RESET_DELAY) self.assertEqual(self.nodes[LEADER].get_state(), 'leader') self.simulator.go(5) self.assertEqual(self.nodes[SSED_1].get_state(), 'child') diff --git a/tests/scripts/thread-cert/v1_2_test_multicast_listener_registration.py b/tests/scripts/thread-cert/v1_2_test_multicast_listener_registration.py index a424e99f4..c6d369e45 100755 --- a/tests/scripts/thread-cert/v1_2_test_multicast_listener_registration.py +++ b/tests/scripts/thread-cert/v1_2_test_multicast_listener_registration.py @@ -838,6 +838,7 @@ class TestMulticastListenerRegistration(thread_cert.TestCase): # Turn off Router 1.1 and turn on Router 1.2 self.nodes[ROUTER_1_1].stop() self.nodes[ROUTER_1_2].start() + self.simulator.go(config.ROUTER_RESET_DELAY) for id in [FED_1, MED_1, SED_1]: self.simulator.go(config.DEFAULT_CHILD_TIMEOUT + WAIT_REDUNDANCE) diff --git a/tests/toranj/ncp/test-017-parent-reset-child-recovery.py b/tests/toranj/ncp/test-017-parent-reset-child-recovery.py index ede43dccf..e6ff9342a 100644 --- a/tests/toranj/ncp/test-017-parent-reset-child-recovery.py +++ b/tests/toranj/ncp/test-017-parent-reset-child-recovery.py @@ -148,7 +148,7 @@ def check_parent_is_associated(): verify(parent.is_associated()) -wpan.verify_within(check_parent_is_associated, 10) +wpan.verify_within(check_parent_is_associated, 40) # Verify that all the children are recovered and present in the parent's # child table again. diff --git a/tests/toranj/ncp/test-027-child-mode-change.py b/tests/toranj/ncp/test-027-child-mode-change.py index c3326bcb5..8301cd8aa 100644 --- a/tests/toranj/ncp/test-027-child-mode-change.py +++ b/tests/toranj/ncp/test-027-child-mode-change.py @@ -97,6 +97,7 @@ children = [child1, child2] # Test implementation WAIT_INTERVAL = 6 +LEADER_RESET_DELAY = 38 # Thread Mode for end-device and sleepy end-device DEVICE_MODE_SLEEPY_END_DEVICE = (wpan.THREAD_MODE_FLAG_FULL_NETWORK_DATA) @@ -144,7 +145,7 @@ wpan.verify_within(check_child_table, WAIT_INTERVAL) # Reset parent and verify all children are recovered parent.reset() -wpan.verify_within(check_child_table, WAIT_INTERVAL) +wpan.verify_within(check_child_table, WAIT_INTERVAL + LEADER_RESET_DELAY) # ----------------------------------------------------------------------------------------------------------------------- # Test finished diff --git a/tests/toranj/ncp/test-600-channel-manager-properties.py b/tests/toranj/ncp/test-600-channel-manager-properties.py index a222d09b1..c99ec3863 100644 --- a/tests/toranj/ncp/test-600-channel-manager-properties.py +++ b/tests/toranj/ncp/test-600-channel-manager-properties.py @@ -101,7 +101,7 @@ verify(node.get(wpan.WPAN_CHANNEL_MANAGER_AUTO_SELECT_ENABLED) == 'true') node.reset() start_time = time.time() -wait_time = 20 +wait_time = 50 while node.get(wpan.WPAN_STATE) != wpan.STATE_ASSOCIATED: if time.time() - start_time > wait_time: diff --git a/tests/unit/test_routing_manager.cpp b/tests/unit/test_routing_manager.cpp index 008d13643..a463c1328 100644 --- a/tests/unit/test_routing_manager.cpp +++ b/tests/unit/test_routing_manager.cpp @@ -820,7 +820,7 @@ void VerifyPrefixTable(const OnLinkPrefix *aOnLinkPrefixes, VerifyOrQuit(routePrefixCount == aNumRoutePrefixes); } -void InitTest(bool aEnablBorderRouting = false) +void InitTest(bool aEnablBorderRouting = false, bool aAfterReset = false) { //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Initialize OT instance. @@ -828,6 +828,12 @@ void InitTest(bool aEnablBorderRouting = false) sNow = 0; sInstance = static_cast(testInitInstance()); + uint32_t delay = 10000; + if (aAfterReset) + { + delay += 26000; // leader reset sync delay + } + memset(&sRadioTxFrame, 0, sizeof(sRadioTxFrame)); sRadioTxFrame.mPsdu = sRadioTxFramePsdu; @@ -846,7 +852,7 @@ void InitTest(bool aEnablBorderRouting = false) //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Ensure device starts as leader. - AdvanceTime(10000); + AdvanceTime(delay); VerifyOrQuit(otThreadGetDeviceRole(sInstance) == OT_DEVICE_ROLE_LEADER); @@ -2543,7 +2549,7 @@ void TestSavedOnLinkPrefixes(void) testFreeInstance(sInstance); - InitTest(/* aEnablBorderRouting */ true); + InitTest(/* aEnablBorderRouting */ true, /* aAfterReset */ true); SuccessOrQuit(sInstance->Get().SetEnabled(true)); @@ -2581,7 +2587,7 @@ void TestSavedOnLinkPrefixes(void) testFreeInstance(sInstance); - InitTest(/* aEnablBorderRouting */ true); + InitTest(/* aEnablBorderRouting */ true, /* aAfterReset */ true); sExpectedPio = kPioAdvertisingLocalOnLink; @@ -2627,7 +2633,7 @@ void TestSavedOnLinkPrefixes(void) testFreeInstance(sInstance); - InitTest(); + InitTest(/* aEnablBorderRouting */ false, /* aAfterReset */ true); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Start Routing Manager. @@ -2678,7 +2684,7 @@ void TestSavedOnLinkPrefixes(void) Log("Disabling and re-enabling OT Instance again"); testFreeInstance(sInstance); - InitTest(); + InitTest(/* aEnablBorderRouting */ false, /* aAfterReset */ true); SuccessOrQuit(sInstance->Get().SetEnabled(true)); AdvanceTime(100); diff --git a/tests/unit/test_srp_server.cpp b/tests/unit/test_srp_server.cpp index 75941fba9..0209a8a9e 100644 --- a/tests/unit/test_srp_server.cpp +++ b/tests/unit/test_srp_server.cpp @@ -208,6 +208,14 @@ void InitTest(void) VerifyOrQuit(otThreadGetDeviceRole(sInstance) == OT_DEVICE_ROLE_LEADER); } +void FinalizeTest(void) +{ + SuccessOrQuit(otIp6SetEnabled(sInstance, false)); + SuccessOrQuit(otThreadSetEnabled(sInstance, false)); + SuccessOrQuit(otInstanceErasePersistentInfo(sInstance)); + testFreeInstance(sInstance); +} + //--------------------------------------------------------------------------------------------------------------------- enum UpdateHandlerMode @@ -458,7 +466,7 @@ void TestSrpServerBase(void) // Finalize OT instance and validate all heap allocations are freed. Log("Finalizing OT instance"); - testFreeInstance(sInstance); + FinalizeTest(); VerifyOrQuit(sHeapAllocatedPtrs.IsEmpty()); @@ -570,7 +578,7 @@ void TestSrpServerReject(void) // Finalize OT instance and validate all heap allocations are freed. Log("Finalizing OT instance"); - testFreeInstance(sInstance); + FinalizeTest(); VerifyOrQuit(sHeapAllocatedPtrs.IsEmpty()); @@ -682,7 +690,7 @@ void TestSrpServerIgnore(void) // Finalize OT instance and validate all heap allocations are freed. Log("Finalizing OT instance"); - testFreeInstance(sInstance); + FinalizeTest(); VerifyOrQuit(sHeapAllocatedPtrs.IsEmpty());