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());